Sistemato attività

This commit is contained in:
Lorenzo Sanesi
2026-07-26 03:19:11 +02:00
parent 8d1a3e0d18
commit de921abcd6
57 changed files with 3063 additions and 144 deletions
@@ -0,0 +1,57 @@
import { Request, Response, NextFunction, Router } from 'express';
import { HttpError } from '../../errors';
import * as notificheService from './notifiche.service';
function asyncHandler(
handler: (req: Request, res: Response, next: NextFunction) => Promise<void>,
) {
return (req: Request, res: Response, next: NextFunction): void => {
handler(req, res, next).catch(next);
};
}
function parseId(req: Request, next: NextFunction): number | undefined {
const id = Number(req.params.id);
if (!Number.isInteger(id)) {
next(new HttpError(400, "l'id deve essere un numero intero"));
return undefined;
}
return id;
}
export const notificheRouter = Router();
notificheRouter.get(
'/',
asyncHandler(async (req, res) => {
res.status(200).json(await notificheService.listNotifiche(req.auth!));
}),
);
notificheRouter.get(
'/non-lette/count',
asyncHandler(async (req, res) => {
res.status(200).json({ count: await notificheService.countNonLette(req.auth!) });
}),
);
notificheRouter.put(
'/letta-tutte',
asyncHandler(async (req, res) => {
await notificheService.segnaTutteLette(req.auth!);
res.status(204).end();
}),
);
notificheRouter.put(
'/:id/letta',
asyncHandler(async (req, res, next) => {
const id = parseId(req, next);
if (id === undefined) return;
await notificheService.segnaLetta(id, req.auth!);
res.status(204).end();
}),
);
export default notificheRouter;
@@ -0,0 +1,95 @@
import { TipoNotifica } from '@prisma/client';
import { prisma } from '../../db/prisma';
import { HttpError } from '../../errors';
import { AuthContext } from '../../middlewares/auth.types';
import { NotificaDto } from '../../types/dto';
const RUOLI_MODERAZIONE = ['admin', 'moderatore'];
function isModeratore(auth: AuthContext): boolean {
return RUOLI_MODERAZIONE.some((ruolo) => auth.roles.includes(ruolo));
}
function toDto(entity: {
id: number;
tipo: TipoNotifica;
messaggio: string;
link: string | null;
letta: boolean;
dataCreazione: Date;
}): NotificaDto {
return {
id: entity.id,
tipo: entity.tipo,
messaggio: entity.messaggio,
link: entity.link,
letta: entity.letta,
dataCreazione: entity.dataCreazione,
};
}
// Le notifiche "broadcast" (destinatarioId null) sono la coda di moderazione condivisa tra
// admin/moderatore: sono visibili solo a chi ha uno di questi ruoli, oltre alle proprie
// notifiche personali.
function whereVisibili(auth: AuthContext) {
return isModeratore(auth)
? { OR: [{ destinatarioId: auth.userId }, { destinatarioId: null }] }
: { destinatarioId: auth.userId };
}
export async function listNotifiche(auth: AuthContext): Promise<NotificaDto[]> {
const entities = await prisma.notifica.findMany({
where: whereVisibili(auth),
orderBy: { dataCreazione: 'desc' },
take: 50,
});
return entities.map(toDto);
}
export async function countNonLette(auth: AuthContext): Promise<number> {
return prisma.notifica.count({ where: { ...whereVisibili(auth), letta: false } });
}
export async function segnaLetta(id: number, auth: AuthContext): Promise<void> {
const entity = await prisma.notifica.findUnique({ where: { id } });
if (!entity) {
throw new HttpError(404, 'notifica non trovata');
}
const visibile =
entity.destinatarioId === auth.userId || (entity.destinatarioId === null && isModeratore(auth));
if (!visibile) {
throw new HttpError(403, 'non puoi accedere a questa notifica');
}
await prisma.notifica.update({ where: { id }, data: { letta: true } });
}
export async function segnaTutteLette(auth: AuthContext): Promise<void> {
await prisma.notifica.updateMany({
where: { ...whereVisibili(auth), letta: false },
data: { letta: true },
});
}
// Notifica destinata a chiunque abbia ruolo admin/moderatore (coda di moderazione condivisa,
// vedi whereVisibili): usata per segnalare nuove proposte/attività in attesa di revisione.
export async function creaBroadcastModerazione(
tipo: TipoNotifica,
messaggio: string,
link?: string,
): Promise<void> {
await prisma.notifica.create({
data: { tipo, messaggio, link: link ?? null, destinatarioId: null },
});
}
export async function creaPersonale(
tipo: TipoNotifica,
messaggio: string,
destinatarioId: string,
link?: string,
): Promise<void> {
await prisma.notifica.create({
data: { tipo, messaggio, link: link ?? null, destinatarioId },
});
}