/chat-conversation-members/mine pouvait être capté par le routeur cœur
comme un findOne avec id = "mine", selon l'ordre d'enregistrement des
routes — et les deux répondent 403 sans jeton, ce qui rendait le
diagnostic impossible. L'endpoint devient /api/my-chat-conversations.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La messagerie affichait des conversations vides : ni nom, ni
interlocuteur, ni message. Le front peuplait `conversation` et ses
`messages` via l'API REST, dont le sanitizer retire toute relation vers
un content-type que le rôle ne peut pas lire — or ni
`chat-conversation.find` ni `chat-message.find` ne sont accordées.
Les accorder aurait suffi, mais aurait ouvert la lecture de TOUTES les
conversations et de TOUS les messages privés à n'importe quel compte
connecté. On ajoute donc une action dédiée :
- GET /api/chat-conversation-members/mine — conversations du seul
appelant, avec interlocuteurs et messages, renvoyées via ctx.send
- mot de passe et jetons retirés des utilisateurs peuplés
- permission `mine` déclarée dans permissions-sync, avec la raison pour
laquelle les deux `find` restent volontairement absentes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le controller forçait `state = "pending_user_approval"` quel que soit le
sens — commentaire à l'appui : « On force le statut en attente peu
importe le type d'invité ». Une candidature déposée par un utilisateur
ressortait donc en invitation, et il recevait un email « vous avez été
invité » pour une chorale qu'il venait lui-même de demander à rejoindre.
- une adhésion créée par un utilisateur pour lui-même est une
candidature : `pending_admin_approval`, à valider par un administrateur
- rôle imposé à `member` dans ce cas : sans cela, une candidature pouvait
se déclarer `owner` de la chorale visée
- aucun email d'invitation n'est envoyé pour une candidature
- l'invitation, elle, garde son comportement : `pending_user_approval`,
token pour un email externe, notification pour un compte existant
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le créateur d'une chorale n'avait aucune adhésion : la chorale
n'apparaissait pas dans « Mes chorales » et son créateur n'en était pas
propriétaire — il n'avait donc aucun droit sur ce qu'il venait de créer.
Seule la recherche la montrait.
Le controller crée désormais une choral-membership `owner` / `active`
après la création, et journalise l'anomalie si l'id de la chorale n'est
pas exploitable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un corps brut absent et un secret de signature erroné produisent des
messages Stripe proches, alors que les correctifs sont opposés. Le log
d'échec indique désormais si le corps brut est présent et sa taille, si
l'en-tête stripe-signature est là, et si STRIPE_WEBHOOK_SECRET est défini
(sa longueur, jamais sa valeur).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un abonnement premium souscrit en test aboutissait chez Stripe sans
qu'aucune commande ni aucun droit n'apparaisse en base — et sans la
moindre trace côté serveur.
- `metadata.userId` absent faisait sortir le handler en silence avec un
200 : c'est désormais journalisé en erreur, avec l'id de session Stripe
- l'utilisateur est résolu une fois par `documentId` (ce que le front
envoie) : la relation `user` d'une commande attend un id numérique,
elle recevait un documentId
- un utilisateur introuvable est journalisé au lieu d'échouer plus loin
- les types d'événements ignorés sont tracés, pour distinguer « Stripe
n'appelle pas » de « Stripe appelle sans checkout.session.completed »
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- webhook : crédite maxChoirs selon le plan (pro=1, premium=5), écrit
uniquement des champs existants du schéma (isPremium supprimé,
subscriptionId -> stripeSubscriptionId)
- schéma user : ajout stripeCustomerId / stripeSubscriptionId (private)
- controller choral.create : contrôle du crédit côté serveur + décrément
après création
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>