feat: fix frontend - #17
Open
lucas-luchack wants to merge 8 commits into
Open
Conversation
Le front avait été construit à partir de maquettes complètes, en avance
sur l'API : 36 migrations couvraient le métier mais seuls ~8 domaines
étaient exposés en HTTP. Plusieurs pages appelaient donc des routes
inexistantes.
Routes
- `start/routes.ts` devient un index mince ; les routes sont déclarées
explicitement dans `start/routes/*.ts` par domaine, sans `apiOnly()`.
- Corrige `products/summary`, jusqu'ici masqué par le `products/:id` de
`router.resource('products')` enregistré avant lui : l'URL résolvait
vers `show()` avec `id = 'summary'`.
- Enregistre `fast-passes`, dont le contrôleur existait sans route.
- `router.route(path, ['PUT','PATCH'], …)` au lieu de `put` + `patch`
séparés, qui font planter le boot (nom de route auto-dérivé en double).
Coordination
- Jobs, EventJobs, Assignments, Responses, Preferences : la page
`coordination` échouait entièrement, 5 de ses 7 requêtes renvoyant 404.
- `preferenceRank` est exposé depuis la colonne pivot `rank`.
Billing
- Vouchers (nouvelle table liée à `suppliers`) et transactions en lecture.
- `GoodsController.index` expose les prix par fournisseur : ils étaient
préchargés mais jamais sérialisés, Lucid ignorant `$extras.pivot_price`.
- Les colonnes `decimal` sont converties en number : le driver les renvoie
en string, ce qui contaminerait toute arithmétique côté front.
Sessions
- `ip_address` / `user_agent` capturés à la connexion, liste et révocation
des sessions. Le provider d'`@adonisjs/auth` n'accepte pas de colonnes
arbitraires, d'où l'update direct après `accessTokens.create()`.
- `hash` et `abilities` ne sont jamais lus ni renvoyés.
Produits
- `summary()` renvoie `category`, dérivée de l'ingrédient de rang le plus
bas : un produit n'a pas de catégorie propre, seuls ses `goods` en ont.
- `minSupplierPrice` déplacé dans `#services/pricing_service`.
Tests
- `product_summary.spec.ts` verrouille l'ordre des routes et la dérivation
de catégorie. 9 tests passent.
`POST /v1/auth/signup` échouait systématiquement en 500 : `users.cas_id` était `notNullable()` sans valeur par défaut, et `NewAccountController` ne renseigne qu'`email` et `password`. Seule la factory de test remplissait la colonne, ce qui masquait le problème aux tests existants. `cas_id` n'est lu nulle part ailleurs que dans `UserTransformer` et aucune intégration CAS n'existe encore dans le projet : la colonne est un emplacement réservé pour un futur SSO. Un compte créé par inscription directe n'a donc légitimement pas d'identité CAS. La colonne devient nullable plutôt que remplie d'une valeur synthétique, qui serait indiscernable d'un vrai identifiant CAS une fois le SSO branché. L'index unique est conservé : Postgres considère les NULL comme distincts, donc les comptes hors CAS coexistent sans collision. Tests - `signup.spec.ts` : création de compte, `casId` null, jeton utilisable sur une route protégée, e-mail dupliqué et confirmation divergente. - `goods_pricing.spec.ts` : verrouille la sérialisation des prix du pivot `good_suppliers`, y compris la conversion decimal → number. 14 tests passent.
Version Preview
|
`RequestLoggerMiddleware` stockait `ctx.response.getBody()` tel quel pour chaque requête. Les réponses de `/auth/login` et `/auth/signup` contiennent le jeton d'accès fraîchement émis : celui-ci se retrouvait donc en clair dans `logs.meta.response`. `GET /v1/logs` n'est protégé que par `middleware.auth()`, sans contrôle de rôle : n'importe quel utilisateur authentifié pouvait donc lire les jetons des autres et usurper leur compte, y compris avec plus de privilèges. Deux niveaux de protection : - les routes dont la réponse est un secret par nature ne voient plus leur corps stocké du tout ; - ce qui subsiste est parcouru et les clés sensibles sont masquées. Le second niveau est une liste de refus, donc au mieux partielle : un nouvel endpoint renvoyant un secret inédit fuiterait jusqu'à ce que son nom y soit ajouté. Le premier niveau est la vraie garantie — c'est là qu'il faut déclarer une route sensible. Pagination - `GET /v1/logs` renvoyait toute la table d'un coup, plus de 500 lignes et 1,3 Mo, que le client traversait ensuite clé par clé dans son convertisseur de casse. Désormais paginé (50 par défaut, 200 au plus) et trié du plus récent au plus ancien.⚠️ Les lignes écrites avant ce correctif contiennent toujours des jetons en clair : la table demande une purge.
Ajoute un algorithme de Gale-Shapley (membre-proposant, capacités multiples) pour affecter automatiquement les membres disponibles aux postes d'une soirée selon leurs préférences, en classant les postes par points normalisés par la fréquence de participation pour éviter que les habitués dominent toujours le classement. Ajoute aussi le verrouillage manuel d'une affectation (neutre en points, jamais recalculé) et la restriction d'un poste à une liste de membres éligibles (poste sans ligne = ouvert à tous).
`GET /v1/logs` n'était protégé que par `middleware.auth()` : n'importe quel
membre authentifié pouvait lire qui avait fait quoi, depuis quelle IP, ainsi
que le corps de réponse de chaque route non authentifiante.
Nouveau `middleware.can(permission)`, première brique d'autorisation du
projet. La chaîne était déjà modélisée en base et n'est que résolue :
user → member (clé primaire partagée) → role → roles_permissions
`Member` utilise `selfAssignPrimaryKey` avec `belongsTo(User, { foreignKey:
'id' })`, donc la ligne membre d'un utilisateur est celle de même id.
Les refus sont volontairement indifférenciés : absence de ligne membre,
absence de rôle ou rôle sans la permission renvoient le même 403. Distinguer
ces cas révélerait la forme du modèle de permissions à des appelants qui
n'en ont aucune.
`log:read` est accordée à `President` et `Finance` — les deux périmètres
d'administration que l'application revendique déjà (« admin réservé
Présidence + Trésorerie »).
Deux bugs latents corrigés au passage, tous deux révélés par ce travail et
indépendants de lui :
- `Array.isArray()` est vrai pour le `ModelPaginator` de Lucid, si bien que
`case_converter_middleware` le parcourait comme un tableau ordinaire et
échouait sur ses propres champs internes. `API.md` documente pourtant un
rendu `{ data, metadata }` : rien dans l'application n'avait jamais
paginé, donc ce chemin n'avait jamais été exercé. Le paginateur est
déballé via `.all()` et la métadonnée jointe explicitement, ce qui laisse
`data` sous forme de tableau — la forme que le client attend déjà.
- `DateTime.isDateTime()` ne teste que le marqueur `isLuxonDateTime`, lequel
survit à un aller-retour JSON alors que le prototype, non. Un `DateTime`
brut stocké dans une colonne JSON se relit donc comme un objet simple qui
se déclare DateTime sans posséder `toISO`, et l'appeler faisait planter la
requête en 500. Les vraies instances sont converties, les imitations sont
parcourues comme n'importe quel objet.
`GET /v1/logs` est par ailleurs paginé (50 par défaut, 200 au plus), du plus
récent au plus ancien : il renvoyait auparavant toute la table d'un seul
tenant, plus de 500 lignes et 1,3 Mo.
45 tests passent.
…ces de postes Verrouillage - `PUT|PATCH /v1/assignments` met à jour une affectation existante. La clé composite voyage dans la query string, faute d'identifiant de substitution ; seul `locked` est modifiable. - Sans cette route, basculer un verrou imposait un DELETE suivi d'un POST, ce qui remettait `points_delta` à 0 — précisément la valeur que le moteur d'affectation rembourse lorsqu'il remplace une ligne, donc une corruption des totaux de points au lancement suivant. Un test le verrouille. - `points_delta` reste volontairement absent du validateur : c'est une écriture du moteur, pas du client. Préférences de postes - `GET` et `PUT|PATCH /v1/account/preferences` : un membre gère son propre classement. L'appelant est déduit du jeton, aucun identifiant de membre ne circule dans le chemin, donc personne ne peut classer à la place d'un autre. - La liste est ORDONNÉE : le rang découle de la position, si bien qu'un client ne peut produire ni trou, ni ex æquo, ni doublon. Une liste vide efface le classement. Un poste inconnu invalide toute la soumission, sans écriture partielle. 58 tests passent.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ajout de toutes les fonctionnalités présente dans le frontend avant le passage à AdonisJS