feat: añadir POST /api/me/preferences con validación y upsert

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
webui
borja 2 weeks ago
parent 4a34b4b53d
commit 01e1d31f00

@ -28,3 +28,100 @@ export const GET: RequestHandler = async (event) => {
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' } headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
}); });
}; };
export const POST: RequestHandler = async (event) => {
// Requiere sesión
const userId = event.locals.userId ?? null;
if (!userId) {
return new Response('Unauthorized', { status: 401 });
}
let payload: any = null;
try {
payload = await event.request.json();
} catch {
return new Response('Bad Request', { status: 400 });
}
const freqRaw = String(payload?.freq || '').trim().toLowerCase();
const timeRaw = payload?.time == null ? null : String(payload.time).trim();
const allowed = new Set(['off', 'daily', 'weekly', 'weekdays']);
if (!allowed.has(freqRaw)) {
return new Response(JSON.stringify({ error: 'freq inválida' }), {
status: 400,
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
});
}
function normalizeTime(input: string): string | null {
const m = /^\s*(\d{1,2}):(\d{1,2})\s*$/.exec(input || '');
if (!m) return null;
const h = Number(m[1]);
const min = Number(m[2]);
if (!Number.isFinite(h) || !Number.isFinite(min)) return null;
if (h < 0 || h > 23 || min < 0 || min > 59) return null;
const hh = String(h).padStart(2, '0');
const mm = String(min).padStart(2, '0');
return `${hh}:${mm}`;
}
const db = await getDb();
let timeToSave: string | null = null;
if (freqRaw === 'off') {
// Hora opcional: si viene, validar/normalizar; si no, conservar la actual o usar '08:30'
if (timeRaw && timeRaw.length > 0) {
const norm = normalizeTime(timeRaw);
if (!norm) {
return new Response(JSON.stringify({ error: 'hora inválida' }), {
status: 400,
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
});
}
timeToSave = norm;
} else {
const row = db
.prepare(
`SELECT reminder_time AS time
FROM user_preferences
WHERE user_id = ?
LIMIT 1`
)
.get(userId) as any;
timeToSave = row?.time ? String(row.time) : '08:30';
}
} else {
// daily/weekly/weekdays: si no se especifica hora, usar '08:30'
if (!timeRaw || timeRaw.length === 0) {
timeToSave = '08:30';
} else {
const norm = normalizeTime(timeRaw);
if (!norm) {
return new Response(JSON.stringify({ error: 'hora inválida' }), {
status: 400,
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
});
}
timeToSave = norm;
}
}
// Upsert preferencia (mantener last_reminded_on intacto)
db.prepare(
`INSERT INTO user_preferences (user_id, reminder_freq, reminder_time, last_reminded_on, updated_at)
VALUES (?, ?, ?, (SELECT last_reminded_on FROM user_preferences WHERE user_id = ?), strftime('%Y-%m-%d %H:%M:%f','now'))
ON CONFLICT(user_id) DO UPDATE SET
reminder_freq = excluded.reminder_freq,
reminder_time = excluded.reminder_time,
updated_at = excluded.updated_at`
).run(userId, freqRaw, timeToSave, userId);
const responseBody = { freq: freqRaw, time: timeToSave };
return new Response(JSON.stringify(responseBody), {
status: 200,
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
});
};

@ -74,4 +74,93 @@ describe('Web API - GET /api/me/preferences', () => {
const json = await res.json(); const json = await res.json();
expect(json).toEqual({ freq: 'off', time: '08:30' }); expect(json).toEqual({ freq: 'off', time: '08:30' });
}); });
it('POST /api/me/preferences - flujo completo', async () => {
const sid = 'sid-test-pref';
const base = server!.baseUrl;
// 1) daily sin hora -> usa default '08:30'
let res = await fetch(`${base}/api/me/preferences`, {
method: 'POST',
headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` },
body: JSON.stringify({ freq: 'daily' })
});
expect(res.status).toBe(200);
let json = await res.json();
expect(json).toEqual({ freq: 'daily', time: '08:30' });
// GET refleja lo guardado
res = await fetch(`${base}/api/me/preferences`, { headers: { Cookie: `sid=${sid}` } });
expect(res.status).toBe(200);
json = await res.json();
expect(json).toEqual({ freq: 'daily', time: '08:30' });
// 2) weekly con hora '7:5' → normaliza a '07:05'
res = await fetch(`${base}/api/me/preferences`, {
method: 'POST',
headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` },
body: JSON.stringify({ freq: 'weekly', time: '7:5' })
});
expect(res.status).toBe(200);
json = await res.json();
expect(json).toEqual({ freq: 'weekly', time: '07:05' });
// 3) weekdays con hora inválida → 400
res = await fetch(`${base}/api/me/preferences`, {
method: 'POST',
headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` },
body: JSON.stringify({ freq: 'weekdays', time: '25:00' })
});
expect(res.status).toBe(400);
// 4) freq inválida → 400
res = await fetch(`${base}/api/me/preferences`, {
method: 'POST',
headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` },
body: JSON.stringify({ freq: 'foo', time: '08:00' })
});
expect(res.status).toBe(400);
// 5) off sin hora → conserva última ('07:05')
res = await fetch(`${base}/api/me/preferences`, {
method: 'POST',
headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` },
body: JSON.stringify({ freq: 'off' })
});
expect(res.status).toBe(200);
json = await res.json();
expect(json).toEqual({ freq: 'off', time: '07:05' });
// GET refleja off con hora conservada
res = await fetch(`${base}/api/me/preferences`, { headers: { Cookie: `sid=${sid}` } });
expect(res.status).toBe(200);
json = await res.json();
expect(json).toEqual({ freq: 'off', time: '07:05' });
// 6) weekdays con hora válida
res = await fetch(`${base}/api/me/preferences`, {
method: 'POST',
headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` },
body: JSON.stringify({ freq: 'weekdays', time: '18:45' })
});
expect(res.status).toBe(200);
json = await res.json();
expect(json).toEqual({ freq: 'weekdays', time: '18:45' });
// 7) daily con hora '6:7' -> normaliza '06:07'
res = await fetch(`${base}/api/me/preferences`, {
method: 'POST',
headers: { 'content-type': 'application/json', Cookie: `sid=${sid}` },
body: JSON.stringify({ freq: 'daily', time: '6:7' })
});
expect(res.status).toBe(200);
json = await res.json();
expect(json).toEqual({ freq: 'daily', time: '06:07' });
// GET final
res = await fetch(`${base}/api/me/preferences`, { headers: { Cookie: `sid=${sid}` } });
expect(res.status).toBe(200);
json = await res.json();
expect(json).toEqual({ freq: 'daily', time: '06:07' });
});
}); });

Loading…
Cancel
Save