ali-ahnaf / ali-ahnaf/pocket_pixel
Offline support: sync endpoint and conflict resolution (Backend)
- Lingua principale
- TypeScript
- Stelle
- 14
- Fork
- 91
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
## 📡 Context / Background
Pocket Pixel is adding offline support so users can create transactions and update data while disconnected. When they come back online, those queued operations need to be replayed against the server. This is the **backend** half — see the linked frontend issue for the service worker and local caching.
> **This is part of an epic.** The frontend issue handles the offline queue and sync trigger; this issue handles the API endpoint(s) that accept and process those queued operations.
---
## 🐛 Problem / Goal
When a user performs write operations offline (creating transactions, updating debts, etc.), those operations are queued on the client. When the network returns, the frontend needs an API endpoint to replay them. The backend currently has no dedicated sync surface — individual operations are fine, but there is no way to submit a batch of pending operations or handle the timestamp/ordering issues that come with offline queuing.
---
## 🛠️ Suggested Approach
The API is in `packages/api/src/` and follows a strict layered pattern: **route → service → repository → entity**.
### 1. Design the sync payload
Define a shared DTO in `packages/shared/src/contracts/` (e.g. `sync.ts`):
```ts
// Each pending operation the client sends
export interface SyncOperation {
type: 'CREATE_TRANSACTION' | 'UPDATE_DEBT' | ...;
payload: unknown; // typed per operation
clientTimestamp: string; // ISO8601 — when the user performed the action
}
export interface SyncRequest {
operations: SyncOperation[];
}
export interface SyncResult {
succeeded: string[]; // operation indices or client-generated IDs
failed: Array<{ index: number; reason: string }>;
}
```
Export from `contracts/index.ts` and rebuild: `npm run build:shared`.
### 2. Add the sync route
Create `packages/api/src/routes/sync/sync.route.ts`:
- Mount on `POST /api/users/:userId/sync`
- Protected by `requireAuth`
- Validates body with Joi schema matching `SyncRequest`
- Wraps handler in `asyncHandler` — **no try/catch**
- Calls a `syncService.processOperations(userId, operations)`
### 3. Add the sync service
`packages/api/src/services/sync.service.ts`:
- Iterate over each operation
- Dispatch to the appropriate existing service (e.g. `transactionService.create(...)`)
- Collect results (succeeded/failed) and return `SyncResult`
- Use `clientTimestamp` to preserve user intent ordering where relevant (e.g. set `createdAt` on new records to the client timestamp rather than server `now()`)
### 4. Handle conflicts gracefully
A simple **last-write-wins** strategy is acceptable for v1:
- If an operation tries to update a record that was already updated server-side after the client's `clientTimestamp`, accept the sync (or return a `failed` entry with a clear reason)
- Do **not** hard-fail the entire batch for one bad operation — process the rest and report per-operation results
### 5. Run migrations if entities change
```bash
npm run migration:generate
npm run migration:run
```
---
## ✅ Acceptance Criteria
- [ ] `POST /api/users/:userId/sync` endpoint exists and requires authentication
- [ ] Accepts a batch of typed operations and processes each one
- [ ] Returns a per-operation result (succeeded / failed with reason)
- [ ] One bad operation does not abort the whole batch
- [ ] `clientTimestamp` is respected where relevant (e.g. transaction `createdAt`)
- [ ] Input validated via Joi schema tied to the shared DTO
- [ ] Unit tests for the sync service are added
- [ ] Existing tests still pass (`npm run test:api`)
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.