mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r2.4): portail public QR — signalement sans compte, suivi par jeton
- 3 routes publiques /portal (les seules @Public métier), throttlées
(@nestjs/throttler : 10 signalements/min, 60 lectures/min) : résolution
du QR, signalement (retourne un jeton de suivi opaque), suivi par
référence + jeton — pas de jeton, pas de lecture, jamais de liste
- migration r2_portail : Request.publicToken (unique)
- page /q/{réf} fidèle à l'écran 6 validé R0 (mobile d'abord) : équipement
prérempli « détecté par le QR », interrupteur personne bloquée,
suivi sans jargon Reçu → Intervention → Résolu (dérivé de l'OT lié),
rejet affiché « Sans suite : {motif} », photo annoncée (upload R3) ;
signalements gardés sur le téléphone (localStorage, 10 max)
- e2e : LA boucle produit — gardien sans compte → traitement → ✓ Résolu ;
11 tests Playwright verts, 50 tests API, 52 opérations au contrat
- générateur OpenAPI : paramètres non-« id » plus typés uuid
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
42
packages/shared/src/schemas/portail.ts
Normal file
42
packages/shared/src/schemas/portail.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { z } from 'zod';
|
||||
import { REQUEST_STATUSES } from '../exploitation';
|
||||
|
||||
/**
|
||||
* Portail public (QR cabine) — AUCUN compte, AUCUN jargon (charte §7).
|
||||
* Le suivi se fait par jeton opaque gardé sur le téléphone du gardien.
|
||||
*/
|
||||
|
||||
export const PortalAssetSchema = z.object({
|
||||
reference: z.string(),
|
||||
siteName: z.string(),
|
||||
locationName: z.string(),
|
||||
});
|
||||
export type PortalAsset = z.infer<typeof PortalAssetSchema>;
|
||||
|
||||
export const PortalRequestCreateSchema = z.object({
|
||||
assetReference: z.string().min(1).max(30),
|
||||
description: z.string().min(3).max(1000),
|
||||
isPersonTrapped: z.boolean().optional(),
|
||||
requesterName: z.string().max(120).optional(),
|
||||
});
|
||||
export type PortalRequestCreate = z.infer<typeof PortalRequestCreateSchema>;
|
||||
|
||||
/** Étapes lisibles par tous : Reçu → Intervention → Résolu. */
|
||||
export const PORTAL_STEPS = ['RECEIVED', 'IN_PROGRESS', 'RESOLVED'] as const;
|
||||
export type PortalStep = (typeof PORTAL_STEPS)[number];
|
||||
|
||||
export const PortalRequestStatusSchema = z.object({
|
||||
reference: z.string(),
|
||||
description: z.string(),
|
||||
isPersonTrapped: z.boolean(),
|
||||
status: z.enum(REQUEST_STATUSES),
|
||||
step: z.enum(PORTAL_STEPS),
|
||||
rejectionReason: z.string().nullable(),
|
||||
createdAt: z.iso.datetime(),
|
||||
});
|
||||
export type PortalRequestStatus = z.infer<typeof PortalRequestStatusSchema>;
|
||||
|
||||
export const PortalRequestCreatedSchema = PortalRequestStatusSchema.extend({
|
||||
publicToken: z.string(), // à conserver côté téléphone — seul accès au suivi
|
||||
});
|
||||
export type PortalRequestCreated = z.infer<typeof PortalRequestCreatedSchema>;
|
||||
Reference in New Issue
Block a user