fix: version 2.5.0 -- compartir proyectos con permisos reales (Lectura/Escritura)
Bug de seguridad real corregido: cualquier usuario autenticado veia TODOS los proyectos (listado, detalle, actividades, tareas, comentarios, adjuntos, KPIs del panel), sin importar si era miembro. Ahora nadie ve un proyecto ajeno sin que su dueno se lo comparta -- los roles de administracion (superadmin/admin/it_manager) mantienen visibilidad total para soporte. Nuevo modelo de permisos por proyecto, simple y no granular: - Solo el dueno original puede compartir un proyecto (agregar/quitar miembros, cambiar su nivel) -- ni un admin del proyecto ni un rol global pueden hacerlo por el. - Dos niveles: Lectura (solo ver) y Escritura (crear/editar/eliminar actividades y tareas -- nunca el proyecto en si: no lo borra, no edita sus datos, no crea proyectos nuevos). Actividades y tareas pasan a borrado logico (antes se borraban fisico sin poder recuperar) -- nueva papelera por proyecto con restaurar. Incluye tambien el filtro "Usuario" en Proyectos (probado en dev en un paso anterior), que va de la mano con este cambio de visibilidad. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,38 +1,51 @@
|
||||
import { pool } from '../../config/db.js';
|
||||
|
||||
export async function summary() {
|
||||
// memberUserId: se pasa solo para roles sin visibilidad total (ver dashboard/controller.js),
|
||||
// restringe cada KPI a proyectos donde el usuario es miembro -- mismo criterio que
|
||||
// projects/repository.js#findAll, para no mostrar conteos de proyectos que ni siquiera puede ver.
|
||||
export async function summary(memberUserId) {
|
||||
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
||||
const memberParams = memberUserId ? [memberUserId] : [];
|
||||
|
||||
const [[activeProjects]] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL`
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
const [[overdueProjects]] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL
|
||||
AND p.estimated_end_date IS NOT NULL AND p.estimated_end_date < CURDATE()`
|
||||
AND p.estimated_end_date IS NOT NULL AND p.estimated_end_date < CURDATE() ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
const [[dueSoonProjects]] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL AND p.estimated_end_date IS NOT NULL
|
||||
AND p.estimated_end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)`
|
||||
AND p.estimated_end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY) ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
const [[pendingActivities]] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
|
||||
JOIN projects p ON p.id = a.project_id
|
||||
WHERE st.is_closed = 0 AND p.deleted_at IS NULL`
|
||||
WHERE st.is_closed = 0 AND p.deleted_at IS NULL ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
const [[overdueActivities]] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
|
||||
JOIN projects p ON p.id = a.project_id
|
||||
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.due_date IS NOT NULL AND a.due_date < CURDATE()`
|
||||
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.due_date IS NOT NULL AND a.due_date < CURDATE() ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
const [[todayActivities]] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id
|
||||
JOIN projects p ON p.id = a.project_id
|
||||
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.due_date = CURDATE()`
|
||||
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.due_date = CURDATE() ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
const [[avgProgress]] = await pool.query(
|
||||
`SELECT AVG(progress_pct) AS avg_pct FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL`
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -46,34 +59,41 @@ export async function summary() {
|
||||
};
|
||||
}
|
||||
|
||||
export async function myPendingActivities(userId) {
|
||||
export async function myPendingActivities(userId, memberUserId) {
|
||||
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
||||
const params = memberUserId ? [userId, memberUserId] : [userId];
|
||||
const [rows] = await pool.query(
|
||||
`SELECT a.id, a.code, a.name, a.due_date, a.progress_pct, p.id AS project_id, p.name AS project_name
|
||||
FROM activities a JOIN activity_statuses st ON st.id = a.status_id JOIN projects p ON p.id = a.project_id
|
||||
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.responsible_user_id = ?
|
||||
WHERE st.is_closed = 0 AND p.deleted_at IS NULL AND a.responsible_user_id = ? ${memberClause}
|
||||
ORDER BY (a.due_date IS NULL), a.due_date LIMIT 20`,
|
||||
[userId]
|
||||
params
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function calendarActivities(monthStart, monthEnd) {
|
||||
export async function calendarActivities(monthStart, monthEnd, memberUserId) {
|
||||
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
||||
const params = memberUserId ? [monthStart, monthEnd, memberUserId] : [monthStart, monthEnd];
|
||||
const [rows] = await pool.query(
|
||||
`SELECT a.id, a.code, a.name, a.due_date, a.is_milestone, a.progress_pct,
|
||||
st.is_closed AS status_is_closed, p.id AS project_id, p.name AS project_name
|
||||
FROM activities a JOIN activity_statuses st ON st.id = a.status_id JOIN projects p ON p.id = a.project_id
|
||||
WHERE a.due_date BETWEEN ? AND ? AND p.deleted_at IS NULL
|
||||
WHERE a.due_date BETWEEN ? AND ? AND p.deleted_at IS NULL ${memberClause}
|
||||
ORDER BY a.due_date`,
|
||||
[monthStart, monthEnd]
|
||||
params
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function projectsByStatus() {
|
||||
export async function projectsByStatus(memberUserId) {
|
||||
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
||||
const params = memberUserId ? [memberUserId] : [];
|
||||
const [rows] = await pool.query(
|
||||
`SELECT ps.name, ps.color, COUNT(p.id) AS total
|
||||
FROM project_statuses ps LEFT JOIN projects p ON p.status_id = ps.id AND p.deleted_at IS NULL
|
||||
GROUP BY ps.id ORDER BY ps.sort_order`
|
||||
FROM project_statuses ps LEFT JOIN projects p ON p.status_id = ps.id AND p.deleted_at IS NULL ${memberClause}
|
||||
GROUP BY ps.id ORDER BY ps.sort_order`,
|
||||
params
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user