From 3a161e2821e69e39b55f606dcb9bf766d1cd66b9 Mon Sep 17 00:00:00 2001 From: borja Date: Tue, 14 Oct 2025 14:27:22 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20mostrar=20siempre=20la=20URL=20ICS=20us?= =?UTF-8?q?ando=20token=5Fplain=20y=20migraci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aider (openrouter/openai/gpt-5) --- apps/web/src/lib/server/calendar-tokens.ts | 11 ++++++----- .../src/routes/api/integrations/feeds/+server.ts | 9 +++++---- src/db/migrations/index.ts | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/web/src/lib/server/calendar-tokens.ts b/apps/web/src/lib/server/calendar-tokens.ts index 8eb487a..f6c272a 100644 --- a/apps/web/src/lib/server/calendar-tokens.ts +++ b/apps/web/src/lib/server/calendar-tokens.ts @@ -32,6 +32,7 @@ export async function findActiveToken( user_id: string; group_id: string | null; token_hash: string; + token_plain: string | null; created_at: string; revoked_at: string | null; last_used_at: string | null; @@ -39,14 +40,14 @@ export async function findActiveToken( const db = await getDb(); const sql = groupId ? ` - SELECT id, type, user_id, group_id, token_hash, created_at, revoked_at, last_used_at + SELECT id, type, user_id, group_id, token_hash, token_plain, created_at, revoked_at, last_used_at FROM calendar_tokens WHERE type = ? AND user_id = ? AND group_id = ? AND revoked_at IS NULL ORDER BY id DESC LIMIT 1 ` : ` - SELECT id, type, user_id, group_id, token_hash, created_at, revoked_at, last_used_at + SELECT id, type, user_id, group_id, token_hash, token_plain, created_at, revoked_at, last_used_at FROM calendar_tokens WHERE type = ? AND user_id = ? AND group_id IS NULL AND revoked_at IS NULL ORDER BY id DESC @@ -74,10 +75,10 @@ export async function createCalendarTokenUrl( const createdAt = toIsoSql(new Date()); const insert = db.prepare(` - INSERT INTO calendar_tokens (type, user_id, group_id, token_hash, created_at) - VALUES (?, ?, ?, ?, ?) + INSERT INTO calendar_tokens (type, user_id, group_id, token_hash, token_plain, created_at) + VALUES (?, ?, ?, ?, ?, ?) `); - const res = insert.run(type, userId, groupId ?? null, tokenHash, createdAt); + const res = insert.run(type, userId, groupId ?? null, tokenHash, token, createdAt); const id = Number(res.lastInsertRowid || 0); return { url: buildCalendarIcsUrl(type, token), token, id }; diff --git a/apps/web/src/routes/api/integrations/feeds/+server.ts b/apps/web/src/routes/api/integrations/feeds/+server.ts index 7ef69ad..5217b36 100644 --- a/apps/web/src/routes/api/integrations/feeds/+server.ts +++ b/apps/web/src/routes/api/integrations/feeds/+server.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from './$types'; import { getDb } from '$lib/server/db'; -import { findActiveToken, createCalendarTokenUrl } from '$lib/server/calendar-tokens'; +import { findActiveToken, createCalendarTokenUrl, buildCalendarIcsUrl } from '$lib/server/calendar-tokens'; export const GET: RequestHandler = async (event) => { // Requiere sesión @@ -29,7 +29,7 @@ export const GET: RequestHandler = async (event) => { const personalExisting = await findActiveToken('personal', userId, null); const personal = personalExisting - ? { url: null } + ? { url: personalExisting.token_plain ? buildCalendarIcsUrl('personal', personalExisting.token_plain) : null } : await (async () => { const created = await createCalendarTokenUrl('personal', userId, null); return { url: created.url }; @@ -39,7 +39,7 @@ export const GET: RequestHandler = async (event) => { const aggregateExisting = await findActiveToken('aggregate', userId, null); const aggregate = aggregateExisting - ? { url: null } + ? { url: aggregateExisting.token_plain ? buildCalendarIcsUrl('aggregate', aggregateExisting.token_plain) : null } : await (async () => { const created = await createCalendarTokenUrl('aggregate', userId, null); return { url: created.url }; @@ -50,7 +50,8 @@ export const GET: RequestHandler = async (event) => { for (const g of groups) { const ex = await findActiveToken('group', userId, g.id); if (ex) { - groupFeeds.push({ groupId: g.id, groupName: g.name ?? null, url: null }); + const url = ex.token_plain ? buildCalendarIcsUrl('group', ex.token_plain) : null; + groupFeeds.push({ groupId: g.id, groupName: g.name ?? null, url }); } else { const created = await createCalendarTokenUrl('group', userId, g.id); groupFeeds.push({ groupId: g.id, groupName: g.name ?? null, url: created.url }); diff --git a/src/db/migrations/index.ts b/src/db/migrations/index.ts index 9a60c07..9b51b87 100644 --- a/src/db/migrations/index.ts +++ b/src/db/migrations/index.ts @@ -359,5 +359,21 @@ export const migrations: Migration[] = [ db.exec(`CREATE INDEX IF NOT EXISTS idx_calendar_tokens_group ON calendar_tokens (group_id);`); db.exec(`CREATE INDEX IF NOT EXISTS idx_calendar_tokens_type ON calendar_tokens (type);`); } + }, + { + version: 12, + name: 'calendar-tokens-plain', + checksum: 'v12-calendar-tokens-plain-2025-10-14', + up: (db: Database) => { + // Añadir columna para poder mostrar siempre la URL (guardando el token en claro). + // Nota: mantenemos token_hash para validación; token_plain se usa solo para construir la URL en UI. + try { + const cols = db.query(`PRAGMA table_info(calendar_tokens)`).all() as any[]; + const hasPlain = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'token_plain'); + if (!hasPlain) { + db.exec(`ALTER TABLE calendar_tokens ADD COLUMN token_plain TEXT NULL;`); + } + } catch {} + } } ];