e65111a7f4
Permisos (superadmin, viewer, busqueda, tareas recurrentes), bootstrap atomico, revocacion de JWT por version de sesion (028), control optimista de edicion (029), logo sin SVG + CSP, ingesta endurecida, correos escapados, borradores, paginacion de proyectos, accesibilidad de dialogos/controles e i18n. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
4.4 KiB
JavaScript
92 lines
4.4 KiB
JavaScript
import * as repo from './repository.js';
|
|
import * as activitiesRepo from '../activities/repository.js';
|
|
import * as projectsRepo from '../projects/repository.js';
|
|
import { canWriteActivity, recalcFromTasks } from '../activities/service.js';
|
|
import { canReadProject } from '../projects/service.js';
|
|
import { HttpError } from '../../utils/http.js';
|
|
import { requireEditVersion, requireUpdated } from '../../utils/editVersion.js';
|
|
import { logAudit } from '../../utils/audit.js';
|
|
|
|
async function requireActivityAccess(activityId, currentUser) {
|
|
const activity = await activitiesRepo.findById(activityId);
|
|
if (!activity) throw new HttpError(404, 'Actividad no encontrada');
|
|
const project = await projectsRepo.findById(activity.project_id);
|
|
if (!(await canWriteActivity(currentUser, project, activity))) {
|
|
throw new HttpError(403, 'No tienes permisos sobre esta actividad');
|
|
}
|
|
return activity;
|
|
}
|
|
|
|
export async function listByActivity(activityId, currentUser) {
|
|
const activity = await activitiesRepo.findById(activityId);
|
|
if (!activity) throw new HttpError(404, 'Actividad no encontrada');
|
|
const project = await projectsRepo.findById(activity.project_id);
|
|
if (!(await canReadProject(currentUser, project))) throw new HttpError(404, 'Actividad no encontrada');
|
|
return repo.findByActivity(activityId);
|
|
}
|
|
|
|
export async function listTrash(activityId, currentUser) {
|
|
await requireActivityAccess(activityId, currentUser);
|
|
return repo.findTrashByActivity(activityId);
|
|
}
|
|
|
|
export async function createTask(activityId, payload, currentUser) {
|
|
await requireActivityAccess(activityId, currentUser);
|
|
if (!payload.name) throw new HttpError(400, 'El nombre es obligatorio');
|
|
const task = await repo.create(activityId, payload);
|
|
await logAudit({ userId: currentUser.id, action: 'create', entityType: 'task', entityId: task.id });
|
|
const counts = await repo.countsByActivity(activityId);
|
|
await recalcFromTasks(activityId, counts.done, counts.total);
|
|
return task;
|
|
}
|
|
|
|
export async function updateTask(id, payload, currentUser) {
|
|
const task = await repo.findById(id);
|
|
if (!task) throw new HttpError(404, 'Tarea no encontrada');
|
|
await requireActivityAccess(task.activity_id, currentUser);
|
|
if (!payload.name) throw new HttpError(400, 'El nombre es obligatorio');
|
|
requireEditVersion(payload.editVersion);
|
|
const updated = requireUpdated(await repo.update(id, payload));
|
|
await logAudit({ userId: currentUser.id, action: 'update', entityType: 'task', entityId: id });
|
|
return updated;
|
|
}
|
|
|
|
export async function setTaskDone(id, isDone, currentUser) {
|
|
const task = await repo.findById(id);
|
|
if (!task) throw new HttpError(404, 'Tarea no encontrada');
|
|
await requireActivityAccess(task.activity_id, currentUser);
|
|
const updated = await repo.setDone(id, isDone);
|
|
await logAudit({ userId: currentUser.id, action: isDone ? 'complete' : 'reopen', entityType: 'task', entityId: id });
|
|
const counts = await repo.countsByActivity(task.activity_id);
|
|
await recalcFromTasks(task.activity_id, counts.done, counts.total);
|
|
return updated;
|
|
}
|
|
|
|
// Nuevo orden manual (arrastrar con el mouse): mismo permiso que crear/editar tareas de la actividad.
|
|
export async function reorderTasks(activityId, ids, currentUser) {
|
|
await requireActivityAccess(activityId, currentUser);
|
|
if (!Array.isArray(ids) || ids.length === 0) throw new HttpError(400, 'Falta el orden de tareas');
|
|
await repo.reorder(activityId, ids);
|
|
return repo.findByActivity(activityId);
|
|
}
|
|
|
|
export async function removeTask(id, currentUser) {
|
|
const task = await repo.findById(id);
|
|
if (!task) throw new HttpError(404, 'Tarea no encontrada');
|
|
await requireActivityAccess(task.activity_id, currentUser);
|
|
await repo.softDelete(id, currentUser.id);
|
|
await logAudit({ userId: currentUser.id, action: 'delete', entityType: 'task', entityId: id });
|
|
const counts = await repo.countsByActivity(task.activity_id);
|
|
await recalcFromTasks(task.activity_id, counts.done, counts.total);
|
|
}
|
|
|
|
export async function restoreTask(id, currentUser) {
|
|
const task = await repo.findById(id, { includeDeleted: true });
|
|
if (!task || !task.deleted_at) throw new HttpError(404, 'Tarea no encontrada en la papelera');
|
|
await requireActivityAccess(task.activity_id, currentUser);
|
|
await repo.restore(id);
|
|
await logAudit({ userId: currentUser.id, action: 'restore', entityType: 'task', entityId: id });
|
|
const counts = await repo.countsByActivity(task.activity_id);
|
|
await recalcFromTasks(task.activity_id, counts.done, counts.total);
|
|
}
|