Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@ PORT=3001
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/aoweb
TOKEN_AUTH=changeme
CORS_ORIGIN=http://localhost:3000

# Game Data Admin Configuration (World Builder & Content Management)
GAME_DATA_ADMIN_EMAIL=admin@aoweb.app
GAME_DATA_ADMIN_ACCOUNT_ID=
GAME_DATA_ADMIN_PROXY_TOKEN=secret-proxy-token

# Amazon SES v2 Configuration (Password Reset & System Emails)
SES_REGION=us-east-1
SES_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
SES_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
SES_FROM_EMAIL=soporte@aoweb.app
SES_FROM_NAME=AOWeb
27 changes: 27 additions & 0 deletions api/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -627,3 +627,30 @@ CREATE INDEX IF NOT EXISTS idx_game_map_tile_overrides_map
ON game_map_tile_overrides(map_num, status);
CREATE INDEX IF NOT EXISTS idx_game_uploaded_graphics_created_at
ON game_uploaded_graphics(created_at DESC);

-- Permisos granulares de edicion de mapa por cuenta.
-- map_num = 0 indica permiso de edicion global sobre mapas no protegidos.
CREATE TABLE IF NOT EXISTS game_map_permissions (
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
map_num INTEGER NOT NULL CHECK (map_num >= 0),
granted_by UUID REFERENCES accounts(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (account_id, map_num)
);

CREATE INDEX IF NOT EXISTS idx_game_map_permissions_account_map
ON game_map_permissions(account_id, map_num);

-- Entradas de paleta dinamicas por mapa (permite asignar graficos subidos a tiles reusables).
CREATE TABLE IF NOT EXISTS game_map_palette_overrides (
map_num INTEGER NOT NULL CHECK (map_num > 0),
palette_id INTEGER NOT NULL CHECK (palette_id > 0),
graphics INTEGER[] NOT NULL,
blocked BOOLEAN NOT NULL DEFAULT FALSE,
updated_by_account_id UUID REFERENCES accounts(id) ON DELETE SET NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (map_num, palette_id)
);

CREATE INDEX IF NOT EXISTS idx_game_map_palette_overrides_map
ON game_map_palette_overrides(map_num);
55 changes: 35 additions & 20 deletions api/src/lib/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,30 +101,45 @@ function buildPasswordResetText({ displayName, resetUrl }: PasswordResetEmailInp
}

export async function sendPasswordResetEmail(input: PasswordResetEmailInput): Promise<void> {
const client = getSesClient();
let client: SESv2Client;
try {
client = getSesClient();
} catch (configError) {
console.error("[Email/SES] Error de configuracion al intentar enviar email de recuperacion:", configError);
throw new Error("No se pudo enviar el email de recuperacion. Intenta de nuevo.");
}

await client.send(new SendEmailCommand({
FromEmailAddress: `${config.sesFromName} <${config.sesFromEmail}>`,
Destination: {
ToAddresses: [input.to],
},
Content: {
Simple: {
Subject: {
Data: "AOWeb | Recuperacion de contraseña",
Charset: "UTF-8",
},
Body: {
Html: {
Data: buildPasswordResetHtml(input),
try {
await client.send(new SendEmailCommand({
FromEmailAddress: `${config.sesFromName} <${config.sesFromEmail}>`,
Destination: {
ToAddresses: [input.to],
},
Content: {
Simple: {
Subject: {
Data: "AOWeb | Recuperacion de contraseña",
Charset: "UTF-8",
},
Text: {
Data: buildPasswordResetText(input),
Charset: "UTF-8",
Body: {
Html: {
Data: buildPasswordResetHtml(input),
Charset: "UTF-8",
},
Text: {
Data: buildPasswordResetText(input),
Charset: "UTF-8",
},
},
},
},
},
}));
}));
} catch (awsError: any) {
console.error(`[Email/SES] Error de envio AWS SES al destinatario ${input.to}:`, {
name: awsError?.name,
message: awsError?.message,
code: awsError?.code || awsError?.$metadata?.httpStatusCode,
});
throw new Error("No se pudo enviar el email de recuperacion. Intenta de nuevo.");
}
}
Loading