docs(ideai): mémoire architecture serveur, tickets #44/#45 en QA, cadrage #47-#53

Ajoute la note mémoire d'architecture serveur (sync/sharing), passe les
tickets #44/#45 en QA, ajoute le cadrage du chantier serveur (tickets
#47 à #53).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 00:04:40 +02:00
parent cc1388cc9e
commit c3158f665e
22 changed files with 444 additions and 19 deletions

View File

@ -13,3 +13,4 @@
- [gametime-ux-exercise-default-targets](gametime-ux-exercise-default-targets.md) — memory note gametime-ux-exercise-default-targets - [gametime-ux-exercise-default-targets](gametime-ux-exercise-default-targets.md) — memory note gametime-ux-exercise-default-targets
- [gametime-ux-series-counter](gametime-ux-series-counter.md) — memory note gametime-ux-series-counter - [gametime-ux-series-counter](gametime-ux-series-counter.md) — memory note gametime-ux-series-counter
- [gametime-ux-exercise-media-viewer](gametime-ux-exercise-media-viewer.md) — memory note gametime-ux-exercise-media-viewer - [gametime-ux-exercise-media-viewer](gametime-ux-exercise-media-viewer.md) — memory note gametime-ux-exercise-media-viewer
- [gametime-server-architecture-sync-sharing](gametime-server-architecture-sync-sharing.md) — memory note gametime-server-architecture-sync-sharing

View File

@ -0,0 +1,200 @@
---
name: gametime-server-architecture-sync-sharing
description: memory note gametime-server-architecture-sync-sharing
metadata:
type: project
---
# GameTime — Architecture serveur headless, sync et partage
Décision serveur pour le ticket #46.
## Stack retenue
- Langage/runtime : Dart serveur.
- Framework HTTP : `shelf` + `shelf_router`.
- Base serveur : PostgreSQL.
- Containerisation : Docker + `docker-compose.yaml` dans `server/`.
Raison : cohérence forte avec le client Flutter/Dart et les contrats métier existants, faible surface framework, testabilité correcte, packaging Docker simple avec image officielle Dart, PostgreSQL robuste pour comptes, tokens, ownership, sync incrémentale et partage ciblé.
Alternatives écartées pour la v1 :
- FastAPI/Python : excellent écosystème, mais introduit un second langage et des DTO à dupliquer.
- Node/NestJS : robuste mais plus lourd et moins cohérent avec l'existant.
- Serverpod : intéressant en Dart, mais plus structurant/opinionated que nécessaire pour un serveur API headless simple.
## Architecture hexagonale serveur
Sous-répertoire dédié : `server/`.
Couches attendues :
- `domain/` : entités serveur et invariants purs.
- `application/` : use cases, ports repositories/services, DTO API indépendants de Shelf/PostgreSQL.
- `infrastructure/postgres/` : adapters repositories PostgreSQL, migrations.
- `infrastructure/security/` : hash password, token signing/verification, clock/id providers.
- `api/` : routes Shelf, middleware auth, mapping request/response.
- `bin/server.dart` : composition root.
Le domaine serveur ne dépend pas de Shelf, Docker ou PostgreSQL.
## Entités serveur
- `UserAccount` : id serveur, email/login unique, passwordHash, displayName?, createdAt, updatedAt, disabledAt?.
- `AuthSession` / refresh token : id, userId, tokenHash, issuedAt, expiresAt, revokedAt?, userAgent?, deviceLabel?.
- `SyncedResource` : ressource possédée par un utilisateur pour Exercise, Program, WorkoutTemplate, WorkoutHistory, MediaAsset metadata.
- `Share` : partage ciblé vers un ou plusieurs comptes, jamais public ouvert.
- `ShareRecipient` : user destinataire + statut `pending|accepted|declined|revoked`.
## Modèle sync serveur
Une table générique `synced_resources` est acceptable pour la v1 afin d'éviter de dupliquer tout le schéma Drift côté serveur. Champs recommandés :
- `server_id` UUID primary key.
- `owner_user_id` FK users.
- `resource_type` enum text : `exercise|program|workoutTemplate|workoutHistory|mediaAsset`.
- `client_id` text : id local stable du client.
- `payload_json` jsonb : snapshot de la ressource côté client.
- `schema_version` int.
- `client_updated_at` timestamptz.
- `server_updated_at` timestamptz.
- `deleted_at` timestamptz nullable.
- `origin_device_id` text nullable.
Contraintes/index :
- unique `(owner_user_id, resource_type, client_id)`.
- index `(owner_user_id, resource_type, server_updated_at)`.
- index `(owner_user_id, server_updated_at)` pour pull global.
Les médias binaires ne sont pas couverts en profondeur par #46 : stocker d'abord les métadonnées et prévoir le port `MediaObjectStore` pour ajout futur.
## Protocole sync LWW v1
Stratégie : last-write-wins simple basé sur `clientUpdatedAt`. En cas d'égalité, tie-breaker stable côté serveur (`serverUpdatedAt`, puis `serverId` si nécessaire). Pas de résolution interactive en v1.
Endpoints recommandés :
### `POST /sync/push`
Requête :
```json
{
"deviceId": "...",
"items": [
{
"resourceType": "exercise",
"clientId": "...",
"schemaVersion": 3,
"clientUpdatedAt": "2026-07-18T10:00:00Z",
"deletedAt": null,
"payload": {}
}
]
}
```
Réponse :
```json
{
"serverCursor": "...",
"results": [
{
"resourceType": "exercise",
"clientId": "...",
"serverId": "...",
"status": "accepted|ignoredOlder|conflictLwwApplied|error",
"serverUpdatedAt": "2026-07-18T10:00:01Z"
}
]
}
```
### `GET /sync/pull?since=<cursor>`
Retourne toutes les ressources de l'utilisateur modifiées après le curseur serveur, soft deletes inclus.
Réponse :
```json
{
"serverCursor": "...",
"items": [
{
"resourceType": "workoutTemplate",
"clientId": "...",
"serverId": "...",
"schemaVersion": 3,
"clientUpdatedAt": "...",
"serverUpdatedAt": "...",
"deletedAt": null,
"payload": {}
}
]
}
```
### `POST /sync/exchange` optionnel
Combine push puis pull pour simplifier le futur client Flutter.
## Partage ciblé
Le partage n'est pas un lien public. Un utilisateur authentifié envoie un snapshot de `program` ou `workoutTemplate` à des destinataires identifiés.
Endpoints :
- `POST /shares` : créer un partage vers un ou plusieurs comptes.
- `GET /shares/inbox` : lister les partages reçus.
- `POST /shares/{id}/accept` : importer/copier la ressource dans l'espace du destinataire.
- `POST /shares/{id}/decline`.
- `POST /shares/{id}/revoke` pour l'émetteur.
À l'acceptation, créer une nouvelle ressource syncable détenue par le destinataire avec nouveaux IDs côté serveur et payload importable côté client. Ne jamais modifier la ressource source de l'émetteur.
## Structure `server/`
Structure cible :
```text
server/
pubspec.yaml
README.md
Dockerfile
docker-compose.yaml
.env.example
bin/
server.dart
lib/
domain/
application/
infrastructure/
postgres/
security/
config/
api/
migrations/
scripts/
push-gitea-image.sh
test/
```
`docker-compose.yaml` doit laisser libres via variables :
- bind host/IP de la machine Docker.
- port API exposé.
- URL publique HTTPS derrière reverse proxy.
- origine/IP reverse proxy autorisée si contrôle réseau ajouté.
- paramètres PostgreSQL (`POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, volume).
- secrets auth/JWT.
Le script `scripts/push-gitea-image.sh` ne doit contenir aucune URL/identifiant en dur. Il accepte registry/image/tag/user/token via variables d'environnement ou arguments.
## Tickets créés
- #47 `[Server] Scaffolding serveur Dart headless hexagonal`.
- #48 `[Server] Auth comptes utilisateurs et tokens API`, dépend de #47.
- #49 `[Server] Schéma PostgreSQL sync-ready GameTime`, dépend de #47.
- #50 `[Server] API de synchronisation incrémentale LWW`, dépend de #48 et #49.
- #51 `[Server] Partage ciblé de programmes et séances entre comptes`, dépend de #48 et #49.
- #52 `[Server] Packaging Docker Compose et push Gitea Registry`, dépend de #47.
- #53 `[Server] Tests API, contrats OpenAPI et vérification d'intégration`, dépend de #50, #51 et #52.
Ordre recommandé : #47, puis #48 et #49 en parallèle, puis #50 et #51, puis #52, puis #53.

View File

@ -1,6 +1,6 @@
--- ---
issueRef: "#44" issueRef: "#44"
version: 4 version: 5
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784379295672 updatedAt: 1784406980453
--- ---

View File

@ -2,15 +2,15 @@
id: "c63e88a9-5868-4bd0-a71b-ebc4e7e6552a" id: "c63e88a9-5868-4bd0-a71b-ebc4e7e6552a"
number: 44 number: 44
title: "[Bug] Je veux pouvoir mettre un score par défaut à 0." title: "[Bug] Je veux pouvoir mettre un score par défaut à 0."
status: "open" status: "qa"
priority: "medium" priority: "medium"
sprint: "abc4f969-b169-45f7-988c-daeeab762201" sprint: "abc4f969-b169-45f7-988c-daeeab762201"
links: [] links: []
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}] agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
createdBy: {"kind":"user"} createdBy: {"kind":"user"}
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784379259225 createdAt: 1784379259225
updatedAt: 1784379295672 updatedAt: 1784406980453
version: 4 version: 5
--- ---
Je veux pouvoir renseigner un score par défaut à 0 (je ne parle pas du nombre de répétition qui doit lui rester comme actuellement). Je veux pouvoir renseigner un score par défaut à 0 (je ne parle pas du nombre de répétition qui doit lui rester comme actuellement).

View File

@ -1,6 +1,6 @@
--- ---
issueRef: "#45" issueRef: "#45"
version: 4 version: 5
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784379490098 updatedAt: 1784406980638
--- ---

View File

@ -2,15 +2,15 @@
id: "2f075be4-bcb8-4fdd-89fc-2f897e97bde4" id: "2f075be4-bcb8-4fdd-89fc-2f897e97bde4"
number: 45 number: 45
title: "[Bug] Affichage de répétition en séance à 0" title: "[Bug] Affichage de répétition en séance à 0"
status: "open" status: "qa"
priority: "medium" priority: "medium"
sprint: "abc4f969-b169-45f7-988c-daeeab762201" sprint: "abc4f969-b169-45f7-988c-daeeab762201"
links: [] links: []
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}] agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
createdBy: {"kind":"user"} createdBy: {"kind":"user"}
updatedBy: {"kind":"user"} updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
createdAt: 1784379414910 createdAt: 1784379414910
updatedAt: 1784379490098 updatedAt: 1784406980638
version: 4 version: 5
--- ---
Quand je suis en séance sur un exercice qui demande un nombre de répétition, l'affichage m'affiche 0 alors que le nombre de répétition est set à une valeur > 0 dans l'exercice. Il devrait être à la même valeur Quand je suis en séance sur un exercice qui demande un nombre de répétition, l'affichage m'affiche 0 alors que le nombre de répétition est set à une valeur > 0 dans l'exercice. Il devrait être à la même valeur

View File

@ -0,0 +1,6 @@
---
issueRef: "#47"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784411985578
---

View File

@ -0,0 +1,16 @@
---
id: "f3283335-8f96-40aa-88a4-bcc58a530ad2"
number: 47
title: "[Server] Scaffolding serveur Dart headless hexagonal"
status: "open"
priority: "low"
sprint: null
links: [{"target":"#46","kind":"relatesTo"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784411985578
updatedAt: 1784411985578
version: 1
---
Créer le sous-répertoire `server/` à la racine avec une application Dart headless basée sur `shelf`/`shelf_router`, structure hexagonale (`domain/`, `application/`, `infrastructure/`, `api/`), configuration env, healthcheck et tests de base. Inclure `Dockerfile`, `.env.example`, `docker-compose.yaml` paramétrable et un README d'exploitation minimal. Le serveur doit écouter derrière reverse proxy sans HTTPS interne obligatoire.

View File

@ -0,0 +1,6 @@
---
issueRef: "#48"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784411990980
---

View File

@ -0,0 +1,16 @@
---
id: "6df36ec4-94cd-438b-a2ee-10c022f6848a"
number: 48
title: "[Server] Auth comptes utilisateurs et tokens API"
status: "open"
priority: "low"
sprint: null
links: [{"target":"#46","kind":"relatesTo"},{"target":"#47","kind":"dependsOn"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784411990980
updatedAt: 1784411990980
version: 1
---
Implémenter le domaine serveur `UserAccount` et `AuthSession` : création de compte, connexion, hash de mot de passe Argon2id ou bcrypt, émission de tokens d'accès + refresh tokens, middleware d'auth API, révocation/logout. Stockage PostgreSQL. Endpoints attendus : `POST /auth/register`, `POST /auth/login`, `POST /auth/refresh`, `POST /auth/logout`, `GET /me`. Aucun écran client dans ce ticket.

View File

@ -0,0 +1,6 @@
---
issueRef: "#49"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784411997184
---

View File

@ -0,0 +1,16 @@
---
id: "c73b39be-061c-44b4-b5c3-f482878fe58d"
number: 49
title: "[Server] Schéma PostgreSQL sync-ready GameTime"
status: "open"
priority: "low"
sprint: null
links: [{"target":"#46","kind":"relatesTo"},{"target":"#47","kind":"dependsOn"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784411997184
updatedAt: 1784411997184
version: 1
---
Créer le modèle serveur PostgreSQL pour les ressources synchronisables : Exercise, Program, WorkoutTemplate, WorkoutHistory, MediaAsset metadata, plus tables d'ownership par utilisateur. Chaque ressource doit stocker `ownerUserId`, `clientId`, `serverId`, `payloadJson`, `clientUpdatedAt`, `serverUpdatedAt`, `deletedAt`, `schemaVersion`, `originDeviceId`. Prévoir contraintes d'unicité `(owner_user_id, resource_type, client_id)`, index pull incrémental `(owner_user_id, resource_type, server_updated_at)`, migrations et tests repository.

View File

@ -0,0 +1,6 @@
---
issueRef: "#50"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784412001974
---

View File

@ -0,0 +1,16 @@
---
id: "983aa02b-7577-4fde-b77a-12355df0efdb"
number: 50
title: "[Server] API de synchronisation incrémentale LWW"
status: "open"
priority: "low"
sprint: null
links: [{"target":"#46","kind":"relatesTo"},{"target":"#48","kind":"dependsOn"},{"target":"#49","kind":"dependsOn"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784412001974
updatedAt: 1784412001974
version: 1
---
Implémenter les ports/use cases et endpoints de sync incrémentale authentifiée. Endpoints : `POST /sync/push`, `GET /sync/pull?since=...`, optionnel `POST /sync/exchange`. Stratégie v1 last-write-wins basée sur `clientUpdatedAt` puis tie-breaker `serverUpdatedAt`/`serverId`. Supporter upsert, soft delete, réponses par item (`accepted`, `ignoredOlder`, `conflictLwwApplied`, `error`) et cursor serveur monotone. Ne pas intégrer le client Flutter dans ce ticket.

View File

@ -0,0 +1,6 @@
---
issueRef: "#51"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784412007538
---

View File

@ -0,0 +1,16 @@
---
id: "850ce975-99c3-4ee4-8d39-759ecc55139c"
number: 51
title: "[Server] Partage ciblé de programmes et séances entre comptes"
status: "open"
priority: "low"
sprint: null
links: [{"target":"#46","kind":"relatesTo"},{"target":"#48","kind":"dependsOn"},{"target":"#49","kind":"dependsOn"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784412007538
updatedAt: 1784412007538
version: 1
---
Implémenter le domaine de partage ciblé : `Share`, destinataires par compte utilisateur, statut `pending|accepted|declined|revoked`, payload snapshot autonome de Program ou WorkoutTemplate. Endpoints : `POST /shares`, `GET /shares/inbox`, `POST /shares/{id}/accept`, `POST /shares/{id}/decline`, `POST /shares/{id}/revoke`. À l'acceptation, créer une copie importable dans l'espace du destinataire, sans lien public ouvert.

View File

@ -0,0 +1,6 @@
---
issueRef: "#52"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784412011625
---

View File

@ -0,0 +1,16 @@
---
id: "28ebc668-c209-4e6a-b123-252a38a3c784"
number: 52
title: "[Server] Packaging Docker Compose et push Gitea Registry"
status: "open"
priority: "low"
sprint: null
links: [{"target":"#46","kind":"relatesTo"},{"target":"#47","kind":"dependsOn"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784412011625
updatedAt: 1784412011625
version: 1
---
Finaliser l'exploitation container : `server/docker-compose.yaml` paramétrable via `.env` pour host bind, port API interne/externe, origine reverse proxy, URL publique HTTPS, config PostgreSQL, secrets, volumes persistants. Ajouter `server/scripts/push-gitea-image.sh` acceptant registry, image, tag, username/token via variables d'environnement ou arguments, sans valeur en dur. Documenter le flux build/push/run.

View File

@ -0,0 +1,6 @@
---
issueRef: "#53"
version: 1
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedAt: 1784412015509
---

View File

@ -0,0 +1,16 @@
---
id: "d5fce252-f3c4-499f-ad0a-d3c81ea13305"
number: 53
title: "[Server] Tests API, contrats OpenAPI et vérification d'intégration"
status: "open"
priority: "low"
sprint: null
links: [{"target":"#46","kind":"relatesTo"},{"target":"#50","kind":"dependsOn"},{"target":"#51","kind":"dependsOn"},{"target":"#52","kind":"dependsOn"}]
agentRefs: []
createdBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
createdAt: 1784412015509
updatedAt: 1784412015509
version: 1
---
Ajouter la couverture de validation serveur : tests unitaires domain/application, tests repository PostgreSQL, tests API auth/sync/share, fixtures de payload GameTime, vérification Docker Compose locale. Produire un contrat OpenAPI ou équivalent documentant auth, sync et partage pour les futurs tickets d'intégration Flutter.

View File

@ -1,3 +1,3 @@
{ {
"nextNumber": 47 "nextNumber": 54
} }

View File

@ -501,25 +501,25 @@
"issueRef": "#44", "issueRef": "#44",
"path": "44", "path": "44",
"title": "[Bug] Je veux pouvoir mettre un score par défaut à 0.", "title": "[Bug] Je veux pouvoir mettre un score par défaut à 0.",
"status": "open", "status": "qa",
"priority": "medium", "priority": "medium",
"sprint": "abc4f969-b169-45f7-988c-daeeab762201", "sprint": "abc4f969-b169-45f7-988c-daeeab762201",
"assignedAgentIds": [ "assignedAgentIds": [
"57695b92-24d0-4876-837c-76116e70a6ae" "57695b92-24d0-4876-837c-76116e70a6ae"
], ],
"updatedAt": 1784379295672 "updatedAt": 1784406980453
}, },
{ {
"issueRef": "#45", "issueRef": "#45",
"path": "45", "path": "45",
"title": "[Bug] Affichage de répétition en séance à 0", "title": "[Bug] Affichage de répétition en séance à 0",
"status": "open", "status": "qa",
"priority": "medium", "priority": "medium",
"sprint": "abc4f969-b169-45f7-988c-daeeab762201", "sprint": "abc4f969-b169-45f7-988c-daeeab762201",
"assignedAgentIds": [ "assignedAgentIds": [
"57695b92-24d0-4876-837c-76116e70a6ae" "57695b92-24d0-4876-837c-76116e70a6ae"
], ],
"updatedAt": 1784379490098 "updatedAt": 1784406980638
}, },
{ {
"issueRef": "#46", "issueRef": "#46",
@ -532,6 +532,76 @@
"57695b92-24d0-4876-837c-76116e70a6ae" "57695b92-24d0-4876-837c-76116e70a6ae"
], ],
"updatedAt": 1784380502263 "updatedAt": 1784380502263
},
{
"issueRef": "#47",
"path": "47",
"title": "[Server] Scaffolding serveur Dart headless hexagonal",
"status": "open",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784411985578
},
{
"issueRef": "#48",
"path": "48",
"title": "[Server] Auth comptes utilisateurs et tokens API",
"status": "open",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784411990980
},
{
"issueRef": "#49",
"path": "49",
"title": "[Server] Schéma PostgreSQL sync-ready GameTime",
"status": "open",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784411997184
},
{
"issueRef": "#50",
"path": "50",
"title": "[Server] API de synchronisation incrémentale LWW",
"status": "open",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784412001974
},
{
"issueRef": "#51",
"path": "51",
"title": "[Server] Partage ciblé de programmes et séances entre comptes",
"status": "open",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784412007538
},
{
"issueRef": "#52",
"path": "52",
"title": "[Server] Packaging Docker Compose et push Gitea Registry",
"status": "open",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784412011625
},
{
"issueRef": "#53",
"path": "53",
"title": "[Server] Tests API, contrats OpenAPI et vérification d'intégration",
"status": "open",
"priority": "low",
"sprint": null,
"assignedAgentIds": [],
"updatedAt": 1784412015509
} }
] ]
} }