From f3a0391507bd1df908054f522491fdb6eaa3109c Mon Sep 17 00:00:00 2001 From: Marcos Date: Sat, 15 Aug 2026 18:53:09 +0200 Subject: [PATCH] Refactor API documentation and model attributes for consistency: rename 'isDeleted' to 'delete' and 'encrypted_title'/'encrypted_body' to 'title'/'body' --- docs/API.md | 42 ++++++++++++++++++------------------- src/models/Category.js | 2 +- src/models/Note.js | 8 +++---- src/services/syncService.js | 32 ++++++++++++++-------------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/docs/API.md b/docs/API.md index ad20f53..d7d9cd5 100644 --- a/docs/API.md +++ b/docs/API.md @@ -170,7 +170,7 @@ Endpoint único de sincronización offline-first. Autenticación requerida: -- Enviar `Authorization: Bearer ` (obligatorio). El servidor ya no acepta `userId` en el body. +- Enviar `Authorization: Bearer ` (obligatorio). #### Request @@ -185,7 +185,7 @@ Body ejemplo: "id": "uuid-categoria-1", "encrypted_name": "texto_cifrado...", "serverVersion": 1, - "isDeleted": false, + "delete": false, "updatedAt": "2026-05-18T10:05:00.000Z" } ], @@ -193,12 +193,12 @@ Body ejemplo: { "id": "uuid-nota-1", "categoryId": "uuid-categoria-1", - "encrypted_title": "titulo_cifrado...", - "encrypted_body": "cuerpo_cifrado...", + "title": "titulo_cifrado...", + "body": "cuerpo_cifrado...", "serverVersion": 1, "position": 2000, - "isDeleted": true, - "isPermanentlyDeleted": false, + "delete": true, + "permanentDelete": false, "updatedAt": "2026-05-18T10:10:00.000Z" } ] @@ -220,7 +220,7 @@ Campos: "id": "uuid", "name": "texto_cifrado", "serverVersion": 1, - "isDeleted": false, + "delete": false, "isDirty": true, "colorValue": 4281558681, "iconCodePoint": 58896, @@ -233,7 +233,7 @@ Campos: - `id`: `UUID`, obligatorio. - `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. -- `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. - `colorValue`: `number`, opcional. - `iconCodePoint`: `number`, opcional. @@ -245,11 +245,11 @@ Campos: { "id": "uuid", "categoryId": "uuid-categoria", - "encrypted_title": "titulo_cifrado", - "encrypted_body": "cuerpo_cifrado", + "title": "titulo_cifrado", + "body": "cuerpo_cifrado", "serverVersion": 1, "position": 2000, - "isDeleted": false, + "delete": false, "updatedAt": "2026-05-18T10:10:00.000Z" } ``` @@ -258,12 +258,12 @@ Campos: - `id`: `UUID`, obligatorio. - `categoryId`: `UUID | null`, opcional. -- `encrypted_title`: `string`, obligatorio. -- `encrypted_body`: `string`, obligatorio. +- `title`: `string`, obligatorio. +- `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`. +- `delete`: `boolean`, opcional, por defecto `false`. +- `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). #### Response @@ -280,12 +280,12 @@ Respuesta ejemplo (nuevo contrato): { "id": "uuid-nota-2", "categoryId": null, - "encrypted_title": "titulo_cifrado...", - "encrypted_body": "cuerpo_cifrado...", + "title": "titulo_cifrado...", + "body": "cuerpo_cifrado...", "serverVersion": 3, "position": 0, - "isDeleted": false, - "isPermanentlyDeleted": false, + "delete": false, + "permanentDelete": false, "updatedAt": "2026-05-18T10:15:00.000Z" } ] @@ -303,7 +303,7 @@ Campos de salida: ## Reglas de sincronización - 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`). - 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`). @@ -312,4 +312,4 @@ Campos de salida: ## 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. +- El servidor guarda `title`, `body` y `encrypted_name`, pero no interpreta el contenido. diff --git a/src/models/Category.js b/src/models/Category.js index 265409f..0dec885 100644 --- a/src/models/Category.js +++ b/src/models/Category.js @@ -24,7 +24,7 @@ const Category = sequelize.define('Category', { type: DataTypes.INTEGER, defaultValue: 1, }, - isDeleted: { + delete: { type: DataTypes.BOOLEAN, defaultValue: false } diff --git a/src/models/Note.js b/src/models/Note.js index 5bbb058..9084c9f 100644 --- a/src/models/Note.js +++ b/src/models/Note.js @@ -8,11 +8,11 @@ const Note = sequelize.define('Note', { type: DataTypes.UUID, primaryKey: true, }, - encrypted_title: { + title: { type: DataTypes.TEXT, allowNull: false }, - encrypted_body: { + body: { type: DataTypes.TEXT, allowNull: false }, @@ -24,11 +24,11 @@ const Note = sequelize.define('Note', { type: DataTypes.INTEGER, defaultValue: 1, }, - isDeleted: { + delete: { type: DataTypes.BOOLEAN, defaultValue: false }, - isPermanentlyDeleted: { + permanentDelete: { type: DataTypes.BOOLEAN, defaultValue: false } diff --git a/src/services/syncService.js b/src/services/syncService.js index b8a0489..684c2ca 100644 --- a/src/services/syncService.js +++ b/src/services/syncService.js @@ -16,7 +16,7 @@ const normalizeCategory = (category) => ({ encrypted_name: category.encrypted_name, colorValue: category.colorValue ?? null, iconCodePoint: category.iconCodePoint ?? null, - isDeleted: Boolean(category.isDeleted), + delete: Boolean(category.delete), serverVersion: category.serverVersion || 1, isDirty: false, updatedAt: category.updatedAt instanceof Date ? category.updatedAt.toISOString() : toIso(category.updatedAt) @@ -25,28 +25,28 @@ const normalizeCategory = (category) => ({ const normalizeNote = (note) => ({ id: note.id, categoryId: note.categoryId || null, - encrypted_title: note.encrypted_title, - encrypted_body: note.encrypted_body, + title: note.title, + body: note.body, position: note.position, - isDeleted: Boolean(note.isDeleted), - isPermanentlyDeleted: Boolean(note.isPermanentlyDeleted), + delete: Boolean(note.delete), + permanentDelete: Boolean(note.permanentDelete), serverVersion: note.serverVersion || 1, updatedAt: note.updatedAt instanceof Date ? note.updatedAt.toISOString() : toIso(note.updatedAt) }); const buildStoredNoteData = (incomingNote, existingNote = null) => { - const isPermanentlyDeleted = Boolean(incomingNote.isPermanentlyDeleted); - const position = isPermanentlyDeleted + const permanentDelete = Boolean(incomingNote.permanentDelete); + const position = permanentDelete ? 0 : (incomingNote.position ?? existingNote?.position ?? 0); return { categoryId: incomingNote.categoryId || null, - encrypted_title: isPermanentlyDeleted ? '' : incomingNote.encrypted_title, - encrypted_body: isPermanentlyDeleted ? '' : incomingNote.encrypted_body, + title: permanentDelete ? '' : incomingNote.title, + body: permanentDelete ? '' : incomingNote.body, position, - isDeleted: Boolean(incomingNote.isDeleted) || isPermanentlyDeleted, - isPermanentlyDeleted + delete: Boolean(incomingNote.delete) || permanentDelete, + permanentDelete }; }; @@ -94,7 +94,7 @@ class SyncService { encrypted_name: incomingCategory.encrypted_name, colorValue: incomingCategory.colorValue ?? null, iconCodePoint: incomingCategory.iconCodePoint ?? null, - isDeleted: Boolean(incomingCategory.isDeleted), + delete: Boolean(incomingCategory.delete), serverVersion: 1 }, { transaction }); continue; @@ -106,7 +106,7 @@ class SyncService { encrypted_name: incomingCategory.encrypted_name, colorValue: incomingCategory.colorValue ?? existingCategory.colorValue ?? null, iconCodePoint: incomingCategory.iconCodePoint ?? existingCategory.iconCodePoint ?? null, - isDeleted: Boolean(incomingCategory.isDeleted), + delete: Boolean(incomingCategory.delete), serverVersion: serverVersion + 1 }, { transaction }); } else { @@ -115,10 +115,10 @@ class SyncService { } 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))) { - throw new Error('Cada nota debe incluir id, encrypted_title y encrypted_body'); + if (!incomingNote.id || (!permanentDelete && (!incomingNote.title || !incomingNote.body))) { + throw new Error('Cada nota debe incluir id, title y body'); } const incomingBaseVersion = getIncomingVersion('nota', incomingNote);