You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			113 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			113 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
| import type { RequestHandler } from './$types';
 | |
| import { getDb } from '$lib/server/db';
 | |
| 
 | |
| function isValidYmd(input: string): boolean {
 | |
|   const m = /^\s*(\d{4})-(\d{2})-(\d{2})\s*$/.exec(input || '');
 | |
|   if (!m) return false;
 | |
|   const y = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);
 | |
|   if (!Number.isFinite(y) || !Number.isFinite(mo) || !Number.isFinite(d)) return false;
 | |
|   if (mo < 1 || mo > 12 || d < 1 || d > 31) return false;
 | |
|   const dt = new Date(`${String(y).padStart(4, '0')}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}T00:00:00Z`);
 | |
|   // Comprobar que el Date resultante coincide (evita 2025-02-31)
 | |
|   return dt.getUTCFullYear() === y && (dt.getUTCMonth() + 1) === mo && dt.getUTCDate() === d;
 | |
| }
 | |
| 
 | |
| export const PATCH: RequestHandler = async (event) => {
 | |
|   const userId = event.locals.userId ?? null;
 | |
|   if (!userId) {
 | |
|     return new Response('Unauthorized', { status: 401 });
 | |
|   }
 | |
| 
 | |
|   const idStr = event.params.id || '';
 | |
|   const taskId = parseInt(idStr, 10);
 | |
|   if (!Number.isFinite(taskId) || taskId <= 0) {
 | |
|     return new Response('Bad Request', { status: 400 });
 | |
|   }
 | |
| 
 | |
|   let payload: any = null;
 | |
|   try {
 | |
|     payload = await event.request.json();
 | |
|   } catch {
 | |
|     return new Response('Bad Request', { status: 400 });
 | |
|   }
 | |
| 
 | |
|   const due_date_raw = payload?.due_date;
 | |
|   if (due_date_raw !== null && due_date_raw !== undefined && typeof due_date_raw !== 'string') {
 | |
|     return new Response('Bad Request', { status: 400 });
 | |
|   }
 | |
|   const due_date =
 | |
|     due_date_raw == null || String(due_date_raw).trim() === ''
 | |
|       ? null
 | |
|       : String(due_date_raw).trim();
 | |
| 
 | |
|   if (due_date !== null && !isValidYmd(due_date)) {
 | |
|     return new Response(JSON.stringify({ error: 'invalid_due_date' }), {
 | |
|       status: 400,
 | |
|       headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   const db = await getDb();
 | |
| 
 | |
|   // Cargar tarea y validar abierta
 | |
|   const task = db
 | |
|     .prepare(
 | |
|       `SELECT id, description, due_date, group_id, COALESCE(completed, 0) AS completed, completed_at, display_code
 | |
|        FROM tasks
 | |
|        WHERE id = ?`
 | |
|     )
 | |
|     .get(taskId) as any;
 | |
| 
 | |
|   if (!task) {
 | |
|     return new Response(JSON.stringify({ status: 'not_found' }), {
 | |
|       status: 404,
 | |
|       headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
 | |
|     });
 | |
|   }
 | |
|   if (Number(task.completed) !== 0 || task.completed_at) {
 | |
|     return new Response(JSON.stringify({ status: 'completed' }), {
 | |
|       status: 400,
 | |
|       headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   // Gating: grupo permitido + usuario miembro activo (si tiene group_id)
 | |
|   const groupId: string | null = task.group_id ? String(task.group_id) : null;
 | |
|   if (groupId) {
 | |
|     const allowed = db
 | |
|       .prepare(`SELECT 1 FROM allowed_groups WHERE group_id = ? AND status = 'allowed' LIMIT 1`)
 | |
|       .get(groupId);
 | |
|     const active = db
 | |
|       .prepare(
 | |
|         `SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ? AND is_active = 1 LIMIT 1`
 | |
|       )
 | |
|       .get(groupId, userId);
 | |
| 
 | |
|     if (!allowed || !active) {
 | |
|       return new Response('Forbidden', { status: 403 });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Aplicar actualización
 | |
|   db.prepare(`UPDATE tasks SET due_date = ? WHERE id = ?`).run(due_date, taskId);
 | |
| 
 | |
|   const updated = db
 | |
|     .prepare(`SELECT id, description, due_date, display_code FROM tasks WHERE id = ?`)
 | |
|     .get(taskId) as any;
 | |
| 
 | |
|   const body = {
 | |
|     status: 'updated',
 | |
|     task: {
 | |
|       id: Number(updated.id),
 | |
|       description: String(updated.description || ''),
 | |
|       due_date: updated.due_date ? String(updated.due_date) : null,
 | |
|       display_code: updated.display_code != null ? Number(updated.display_code) : null
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   return new Response(JSON.stringify(body), {
 | |
|     status: 200,
 | |
|     headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }
 | |
|   });
 | |
| };
 |