Files
notas-api/docs/API.md
T
Marcos 73a8932c64
Despliegue Automático / desplegar (push) Successful in 43s
Add isPermanentlyDeleted field to Note model and update sync logic
2026-05-19 11:08:30 +02:00

293 lines
6.5 KiB
Markdown

# API de `api_notas`
Esta guía resume las rutas disponibles, qué datos esperan y los tipos más importantes del proyecto.
## Base URL
Todas las rutas cuelgan de:
```text
/api
```
Ejemplo local:
```text
http://localhost:3000/api
```
## Tipos principales
- `UUID`: identificador único en formato UUID.
- `string`: texto plano o cifrado.
- `boolean`: `true` o `false`.
- `number`: valor numérico.
- `ISO date string`: fecha en formato ISO 8601, por ejemplo `2026-05-18T10:00:00.000Z`.
## Rutas disponibles
### `GET /api/status`
Comprueba que la API está viva.
Respuesta ejemplo:
```json
{
"message": "¡API funcionando correctamente!"
}
```
### `POST /api/auth/register`
Crea un usuario nuevo.
Body:
```json
{
"username": "maria",
"password": "123456",
"encrypted_master_key": "texto_cifrado_opcional"
}
```
Campos:
- `username`: `string`, obligatorio.
- `password`: `string`, obligatorio.
- `encrypted_master_key`: `string | null`, opcional.
Respuesta ejemplo:
```json
{
"message": "Usuario registrado con éxito",
"accessToken": "jwt_access_token",
"refreshToken": "token_largo_aleatorio"
}
```
### `POST /api/auth/login`
Inicia sesión y devuelve tokens.
Body:
```json
{
"username": "maria",
"password": "123456",
"deviceName": "Flutter Pixel"
}
```
Campos:
- `username`: `string`, obligatorio.
- `password`: `string`, obligatorio.
- `deviceName`: `string`, opcional.
Respuesta ejemplo:
```json
{
"message": "Login exitoso",
"accessToken": "jwt_access_token",
"refreshToken": "token_largo_aleatorio",
"encrypted_master_key": "texto_cifrado"
}
```
### `POST /api/auth/refresh`
Genera un nuevo `accessToken` usando un `refreshToken` válido.
Body:
```json
{
"refreshToken": "token_largo_aleatorio"
}
```
Campos:
- `refreshToken`: `string`, obligatorio.
Respuesta ejemplo:
```json
{
"accessToken": "nuevo_jwt_access_token"
}
```
### `POST /api/auth/logout`
Revoca la sesión del dispositivo actual.
Body:
```json
{
"refreshToken": "token_largo_aleatorio"
}
```
Campos:
- `refreshToken`: `string`, obligatorio.
Respuesta ejemplo:
```json
{
"message": "Sesión cerrada en el dispositivo actual"
}
```
### `POST /api/sync`
Endpoint único de sincronización offline-first.
Autenticación requerida:
- Enviar `Authorization: Bearer <accessToken>` (obligatorio). El servidor ya no acepta `userId` en el body.
#### Request
Body ejemplo:
```json
{
"lastSyncAt": "2026-05-18T10:00:00.000Z",
"changes": {
"categories": [
{
"id": "uuid-categoria-1",
"encrypted_name": "texto_cifrado...",
"serverVersion": 1,
"isDeleted": false,
"updatedAt": "2026-05-18T10:05:00.000Z"
}
],
"notes": [
{
"id": "uuid-nota-1",
"categoryId": "uuid-categoria-1",
"encrypted_title": "titulo_cifrado...",
"encrypted_body": "cuerpo_cifrado...",
"serverVersion": 1,
"position": 2000,
"isDeleted": true,
"isPermanentlyDeleted": false,
"updatedAt": "2026-05-18T10:10:00.000Z"
}
]
}
}
```
Campos:
- `lastSyncAt`: `string` ISO, opcional. Si no se envía, el servidor usa `1970-01-01T00:00:00.000Z`.
- `changes`: `object`, obligatorio.
- `changes.categories`: `array`, opcional.
- `changes.notes`: `array`, opcional.
#### Estructura de categoría
```json
{
"id": "uuid",
"encrypted_name": "texto_cifrado",
"serverVersion": 1,
"isDeleted": false,
"updatedAt": "2026-05-18T10:05:00.000Z"
}
```
Campos:
- `id`: `UUID`, obligatorio.
- `encrypted_name`: `string`, obligatorio.
- `serverVersion`: `number` entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.
- `isDeleted`: `boolean`, opcional, por defecto `false`.
- `updatedAt`: `ISO date string`, opcional (solo informativo para UI).
#### Estructura de nota
```json
{
"id": "uuid",
"categoryId": "uuid-categoria",
"encrypted_title": "titulo_cifrado",
"encrypted_body": "cuerpo_cifrado",
"serverVersion": 1,
"position": 2000,
"isDeleted": false,
"updatedAt": "2026-05-18T10:10:00.000Z"
}
```
Campos:
- `id`: `UUID`, obligatorio.
- `categoryId`: `UUID | null`, opcional.
- `encrypted_title`: `string`, obligatorio.
- `encrypted_body`: `string`, obligatorio.
- `serverVersion`: `number` entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.
- `position`: `number`, opcional.
- `isDeleted`: `boolean`, opcional, por defecto `false`.
- `isPermanentlyDeleted`: `boolean`, opcional, por defecto `false`. Si llega en `true`, el servidor guarda la nota con `encrypted_title` y `encrypted_body` vacíos, `position` en `0` y `isDeleted` en `true`.
- `updatedAt`: `ISO date string`, opcional (solo informativo para UI).
#### Response
Respuesta ejemplo (nuevo contrato):
```json
{
"serverTimestamp": "2026-05-18T10:20:00.000Z",
"synced": true,
"changes": {
"categories": [],
"notes": [
{
"id": "uuid-nota-2",
"categoryId": null,
"encrypted_title": "titulo_cifrado...",
"encrypted_body": "cuerpo_cifrado...",
"serverVersion": 3,
"position": 0,
"isDeleted": false,
"isPermanentlyDeleted": false,
"updatedAt": "2026-05-18T10:15:00.000Z"
}
]
}
}
```
Campos de salida:
- `serverTimestamp`: `string` ISO con la hora del servidor.
- `synced`: `boolean`.
- `changes.categories`: array con todas las categorías cambiadas en el servidor desde `lastSyncAt`, incluidas las creadas por otros dispositivos.
- `changes.notes`: array con todas las notas cambiadas en el servidor desde `lastSyncAt`, incluidas las creadas por otros dispositivos y las copias creadas automáticamente en caso de conflicto.
## Reglas de sincronización
- El cliente debe guardar su último `lastSyncAt`.
- Un borrado no elimina el registro localmente: se marca con `isDeleted: true`.
- El servidor decide conflictos comparando `serverVersion` (no `updatedAt`).
- En categorías, solo se actualiza si `incoming.serverVersion === serverVersion`. Si no coincide, gana el servidor y no se crea duplicado.
- Si la versión entrante de una nota coincide con la versión actual del servidor, acepta el cambio y sube versión (`v -> v+1`).
- Si la versión del servidor es mayor que la entrante en una nota, el servidor crea una nueva nota duplicada con el contenido entrante para no perder datos.
## Notas importantes
- Las notas y categorías están pensadas para contenido cifrado del lado del cliente.
- El servidor guarda `encrypted_title`, `encrypted_body` y `encrypted_name`, pero no interpreta el contenido.