Refactor API documentation and model attributes for consistency: rename 'isDeleted' to 'delete' and 'encrypted_title'/'encrypted_body' to 'title'/'body'
Despliegue Automático / desplegar (push) Successful in 4m25s

This commit is contained in:
2026-08-15 18:53:09 +02:00
parent ff768cf556
commit f3a0391507
4 changed files with 42 additions and 42 deletions
+21 -21
View File
@@ -170,7 +170,7 @@ Endpoint único de sincronización offline-first.
Autenticación requerida: Autenticación requerida:
- Enviar `Authorization: Bearer <accessToken>` (obligatorio). El servidor ya no acepta `userId` en el body. - Enviar `Authorization: Bearer <accessToken>` (obligatorio).
#### Request #### Request
@@ -185,7 +185,7 @@ Body ejemplo:
"id": "uuid-categoria-1", "id": "uuid-categoria-1",
"encrypted_name": "texto_cifrado...", "encrypted_name": "texto_cifrado...",
"serverVersion": 1, "serverVersion": 1,
"isDeleted": false, "delete": false,
"updatedAt": "2026-05-18T10:05:00.000Z" "updatedAt": "2026-05-18T10:05:00.000Z"
} }
], ],
@@ -193,12 +193,12 @@ Body ejemplo:
{ {
"id": "uuid-nota-1", "id": "uuid-nota-1",
"categoryId": "uuid-categoria-1", "categoryId": "uuid-categoria-1",
"encrypted_title": "titulo_cifrado...", "title": "titulo_cifrado...",
"encrypted_body": "cuerpo_cifrado...", "body": "cuerpo_cifrado...",
"serverVersion": 1, "serverVersion": 1,
"position": 2000, "position": 2000,
"isDeleted": true, "delete": true,
"isPermanentlyDeleted": false, "permanentDelete": false,
"updatedAt": "2026-05-18T10:10:00.000Z" "updatedAt": "2026-05-18T10:10:00.000Z"
} }
] ]
@@ -220,7 +220,7 @@ Campos:
"id": "uuid", "id": "uuid",
"name": "texto_cifrado", "name": "texto_cifrado",
"serverVersion": 1, "serverVersion": 1,
"isDeleted": false, "delete": false,
"isDirty": true, "isDirty": true,
"colorValue": 4281558681, "colorValue": 4281558681,
"iconCodePoint": 58896, "iconCodePoint": 58896,
@@ -233,7 +233,7 @@ Campos:
- `id`: `UUID`, obligatorio. - `id`: `UUID`, obligatorio.
- `name`: `string` (cifrado), obligatorio. Equivale a `encrypted_name`; ambos contienen el nombre encriptado. - `name`: `string` (cifrado), obligatorio. Equivale a `encrypted_name`; ambos contienen el nombre encriptado.
- `serverVersion`: `number` entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio. - `serverVersion`: `number` entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.
- `isDeleted`: `boolean`, opcional, por defecto `false`. - `delete`: `boolean`, opcional, por defecto `false`.
- `isDirty`: `boolean`, opcional. El servidor lo ignora en la escritura y devuelve `false` en la respuesta de sync. - `isDirty`: `boolean`, opcional. El servidor lo ignora en la escritura y devuelve `false` en la respuesta de sync.
- `colorValue`: `number`, opcional. - `colorValue`: `number`, opcional.
- `iconCodePoint`: `number`, opcional. - `iconCodePoint`: `number`, opcional.
@@ -245,11 +245,11 @@ Campos:
{ {
"id": "uuid", "id": "uuid",
"categoryId": "uuid-categoria", "categoryId": "uuid-categoria",
"encrypted_title": "titulo_cifrado", "title": "titulo_cifrado",
"encrypted_body": "cuerpo_cifrado", "body": "cuerpo_cifrado",
"serverVersion": 1, "serverVersion": 1,
"position": 2000, "position": 2000,
"isDeleted": false, "delete": false,
"updatedAt": "2026-05-18T10:10:00.000Z" "updatedAt": "2026-05-18T10:10:00.000Z"
} }
``` ```
@@ -258,12 +258,12 @@ Campos:
- `id`: `UUID`, obligatorio. - `id`: `UUID`, obligatorio.
- `categoryId`: `UUID | null`, opcional. - `categoryId`: `UUID | null`, opcional.
- `encrypted_title`: `string`, obligatorio. - `title`: `string`, obligatorio.
- `encrypted_body`: `string`, obligatorio. - `body`: `string`, obligatorio.
- `serverVersion`: `number` entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio. - `serverVersion`: `number` entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.
- `position`: `number`, opcional. - `position`: `number`, opcional.
- `isDeleted`: `boolean`, opcional, por defecto `false`. - `delete`: `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`. - `permanentDelete`: `boolean`, opcional, por defecto `false`. Si llega en `true`, el servidor guarda la nota con `title` y `body` vacíos, `position` en `0` y `delete` en `true`.
- `updatedAt`: `ISO date string`, opcional (solo informativo para UI). - `updatedAt`: `ISO date string`, opcional (solo informativo para UI).
#### Response #### Response
@@ -280,12 +280,12 @@ Respuesta ejemplo (nuevo contrato):
{ {
"id": "uuid-nota-2", "id": "uuid-nota-2",
"categoryId": null, "categoryId": null,
"encrypted_title": "titulo_cifrado...", "title": "titulo_cifrado...",
"encrypted_body": "cuerpo_cifrado...", "body": "cuerpo_cifrado...",
"serverVersion": 3, "serverVersion": 3,
"position": 0, "position": 0,
"isDeleted": false, "delete": false,
"isPermanentlyDeleted": false, "permanentDelete": false,
"updatedAt": "2026-05-18T10:15:00.000Z" "updatedAt": "2026-05-18T10:15:00.000Z"
} }
] ]
@@ -303,7 +303,7 @@ Campos de salida:
## Reglas de sincronización ## Reglas de sincronización
- El cliente debe guardar su último `lastSyncAt`. - El cliente debe guardar su último `lastSyncAt`.
- Un borrado no elimina el registro localmente: se marca con `isDeleted: true`. - Un borrado no elimina el registro localmente: se marca con `delete: true`.
- El servidor decide conflictos comparando `serverVersion` (no `updatedAt`). - 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. - 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 entrante de una nota coincide con la versión actual del servidor, acepta el cambio y sube versión (`v -> v+1`).
@@ -312,4 +312,4 @@ Campos de salida:
## Notas importantes ## Notas importantes
- Las notas y categorías están pensadas para contenido cifrado del lado del cliente. - 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. - El servidor guarda `title`, `body` y `encrypted_name`, pero no interpreta el contenido.
+1 -1
View File
@@ -24,7 +24,7 @@ const Category = sequelize.define('Category', {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
defaultValue: 1, defaultValue: 1,
}, },
isDeleted: { delete: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
defaultValue: false defaultValue: false
} }
+4 -4
View File
@@ -8,11 +8,11 @@ const Note = sequelize.define('Note', {
type: DataTypes.UUID, type: DataTypes.UUID,
primaryKey: true, primaryKey: true,
}, },
encrypted_title: { title: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: false allowNull: false
}, },
encrypted_body: { body: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: false allowNull: false
}, },
@@ -24,11 +24,11 @@ const Note = sequelize.define('Note', {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
defaultValue: 1, defaultValue: 1,
}, },
isDeleted: { delete: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
defaultValue: false defaultValue: false
}, },
isPermanentlyDeleted: { permanentDelete: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
defaultValue: false defaultValue: false
} }
+16 -16
View File
@@ -16,7 +16,7 @@ const normalizeCategory = (category) => ({
encrypted_name: category.encrypted_name, encrypted_name: category.encrypted_name,
colorValue: category.colorValue ?? null, colorValue: category.colorValue ?? null,
iconCodePoint: category.iconCodePoint ?? null, iconCodePoint: category.iconCodePoint ?? null,
isDeleted: Boolean(category.isDeleted), delete: Boolean(category.delete),
serverVersion: category.serverVersion || 1, serverVersion: category.serverVersion || 1,
isDirty: false, isDirty: false,
updatedAt: category.updatedAt instanceof Date ? category.updatedAt.toISOString() : toIso(category.updatedAt) updatedAt: category.updatedAt instanceof Date ? category.updatedAt.toISOString() : toIso(category.updatedAt)
@@ -25,28 +25,28 @@ const normalizeCategory = (category) => ({
const normalizeNote = (note) => ({ const normalizeNote = (note) => ({
id: note.id, id: note.id,
categoryId: note.categoryId || null, categoryId: note.categoryId || null,
encrypted_title: note.encrypted_title, title: note.title,
encrypted_body: note.encrypted_body, body: note.body,
position: note.position, position: note.position,
isDeleted: Boolean(note.isDeleted), delete: Boolean(note.delete),
isPermanentlyDeleted: Boolean(note.isPermanentlyDeleted), permanentDelete: Boolean(note.permanentDelete),
serverVersion: note.serverVersion || 1, serverVersion: note.serverVersion || 1,
updatedAt: note.updatedAt instanceof Date ? note.updatedAt.toISOString() : toIso(note.updatedAt) updatedAt: note.updatedAt instanceof Date ? note.updatedAt.toISOString() : toIso(note.updatedAt)
}); });
const buildStoredNoteData = (incomingNote, existingNote = null) => { const buildStoredNoteData = (incomingNote, existingNote = null) => {
const isPermanentlyDeleted = Boolean(incomingNote.isPermanentlyDeleted); const permanentDelete = Boolean(incomingNote.permanentDelete);
const position = isPermanentlyDeleted const position = permanentDelete
? 0 ? 0
: (incomingNote.position ?? existingNote?.position ?? 0); : (incomingNote.position ?? existingNote?.position ?? 0);
return { return {
categoryId: incomingNote.categoryId || null, categoryId: incomingNote.categoryId || null,
encrypted_title: isPermanentlyDeleted ? '' : incomingNote.encrypted_title, title: permanentDelete ? '' : incomingNote.title,
encrypted_body: isPermanentlyDeleted ? '' : incomingNote.encrypted_body, body: permanentDelete ? '' : incomingNote.body,
position, position,
isDeleted: Boolean(incomingNote.isDeleted) || isPermanentlyDeleted, delete: Boolean(incomingNote.delete) || permanentDelete,
isPermanentlyDeleted permanentDelete
}; };
}; };
@@ -94,7 +94,7 @@ class SyncService {
encrypted_name: incomingCategory.encrypted_name, encrypted_name: incomingCategory.encrypted_name,
colorValue: incomingCategory.colorValue ?? null, colorValue: incomingCategory.colorValue ?? null,
iconCodePoint: incomingCategory.iconCodePoint ?? null, iconCodePoint: incomingCategory.iconCodePoint ?? null,
isDeleted: Boolean(incomingCategory.isDeleted), delete: Boolean(incomingCategory.delete),
serverVersion: 1 serverVersion: 1
}, { transaction }); }, { transaction });
continue; continue;
@@ -106,7 +106,7 @@ class SyncService {
encrypted_name: incomingCategory.encrypted_name, encrypted_name: incomingCategory.encrypted_name,
colorValue: incomingCategory.colorValue ?? existingCategory.colorValue ?? null, colorValue: incomingCategory.colorValue ?? existingCategory.colorValue ?? null,
iconCodePoint: incomingCategory.iconCodePoint ?? existingCategory.iconCodePoint ?? null, iconCodePoint: incomingCategory.iconCodePoint ?? existingCategory.iconCodePoint ?? null,
isDeleted: Boolean(incomingCategory.isDeleted), delete: Boolean(incomingCategory.delete),
serverVersion: serverVersion + 1 serverVersion: serverVersion + 1
}, { transaction }); }, { transaction });
} else { } else {
@@ -115,10 +115,10 @@ class SyncService {
} }
for (const incomingNote of incomingNotes) { for (const incomingNote of incomingNotes) {
const isPermanentlyDeleted = Boolean(incomingNote.isPermanentlyDeleted); const permanentDelete = Boolean(incomingNote.permanentDelete);
if (!incomingNote.id || (!isPermanentlyDeleted && (!incomingNote.encrypted_title || !incomingNote.encrypted_body))) { if (!incomingNote.id || (!permanentDelete && (!incomingNote.title || !incomingNote.body))) {
throw new Error('Cada nota debe incluir id, encrypted_title y encrypted_body'); throw new Error('Cada nota debe incluir id, title y body');
} }
const incomingBaseVersion = getIncomingVersion('nota', incomingNote); const incomingBaseVersion = getIncomingVersion('nota', incomingNote);