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:
@ -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
|
||||||
|
|||||||
200
.ideai/memory/gametime-server-architecture-sync-sharing.md
Normal file
200
.ideai/memory/gametime-server-architecture-sync-sharing.md
Normal 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.
|
||||||
@ -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
|
||||||
---
|
---
|
||||||
|
|||||||
@ -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).
|
||||||
@ -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
|
||||||
---
|
---
|
||||||
|
|||||||
@ -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
|
||||||
6
.ideai/tickets/47/carnet.md
Normal file
6
.ideai/tickets/47/carnet.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#47"
|
||||||
|
version: 1
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
|
||||||
|
updatedAt: 1784411985578
|
||||||
|
---
|
||||||
16
.ideai/tickets/47/issue.md
Normal file
16
.ideai/tickets/47/issue.md
Normal 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.
|
||||||
6
.ideai/tickets/48/carnet.md
Normal file
6
.ideai/tickets/48/carnet.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#48"
|
||||||
|
version: 1
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
|
||||||
|
updatedAt: 1784411990980
|
||||||
|
---
|
||||||
16
.ideai/tickets/48/issue.md
Normal file
16
.ideai/tickets/48/issue.md
Normal 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.
|
||||||
6
.ideai/tickets/49/carnet.md
Normal file
6
.ideai/tickets/49/carnet.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#49"
|
||||||
|
version: 1
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
|
||||||
|
updatedAt: 1784411997184
|
||||||
|
---
|
||||||
16
.ideai/tickets/49/issue.md
Normal file
16
.ideai/tickets/49/issue.md
Normal 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.
|
||||||
6
.ideai/tickets/50/carnet.md
Normal file
6
.ideai/tickets/50/carnet.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#50"
|
||||||
|
version: 1
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
|
||||||
|
updatedAt: 1784412001974
|
||||||
|
---
|
||||||
16
.ideai/tickets/50/issue.md
Normal file
16
.ideai/tickets/50/issue.md
Normal 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.
|
||||||
6
.ideai/tickets/51/carnet.md
Normal file
6
.ideai/tickets/51/carnet.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#51"
|
||||||
|
version: 1
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
|
||||||
|
updatedAt: 1784412007538
|
||||||
|
---
|
||||||
16
.ideai/tickets/51/issue.md
Normal file
16
.ideai/tickets/51/issue.md
Normal 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.
|
||||||
6
.ideai/tickets/52/carnet.md
Normal file
6
.ideai/tickets/52/carnet.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#52"
|
||||||
|
version: 1
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
|
||||||
|
updatedAt: 1784412011625
|
||||||
|
---
|
||||||
16
.ideai/tickets/52/issue.md
Normal file
16
.ideai/tickets/52/issue.md
Normal 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.
|
||||||
6
.ideai/tickets/53/carnet.md
Normal file
6
.ideai/tickets/53/carnet.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
issueRef: "#53"
|
||||||
|
version: 1
|
||||||
|
updatedBy: {"kind":"agent","agent_id":"f8f40941-ecf7-4830-b9de-8818a099f448"}
|
||||||
|
updatedAt: 1784412015509
|
||||||
|
---
|
||||||
16
.ideai/tickets/53/issue.md
Normal file
16
.ideai/tickets/53/issue.md
Normal 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.
|
||||||
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"nextNumber": 47
|
"nextNumber": 54
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user