# SimpleNote - Arquitectura Técnica ## 1. Visión General Sistema de gestión de documentos basado en archivos Markdown con API REST. Diseñado para reemplazar Joplin con una arquitectura más simple. ``` ┌─────────────────┐ ┌──────────────────┐ │ simplenote-cli │◄────►│ simplenote-web │ │ (Commander.js) │ │ (Express + API) │ └─────────────────┘ └────────┬─────────┘ │ ┌────────▼─────────┐ │ File System │ │ (Markdown + JSON)│ └───────────────────┘ ``` ## 2. Componentes ### 2.1 simplenote-web - **Rol**: API REST + Frontend web - **Puerto default**: 3000 - **Estructura interna**: Express.js con routers modulares ### 2.2 simplenote-cli - **Rol**: Cliente de línea de comandos - **Conexión**: HTTP al API de simplenote-web - **Config**: `~/.config/simplenote/config.json` ## 3. Arquitectura de Datos ### 3.1 Estructura de Librerías (Filesystem) ``` data/ # DATA_ROOT configurable └── libraries/ └── {library-id}/ ├── .library.json # Metadata de librería ├── documents/ │ └── {document-id}/ │ ├── index.md # Contenido │ └── .meta.json # Metadata del documento └── sub-libraries/ └── {child-lib-id}/... # Anidamiento recursivo ``` ### 3.2 JSON Manifests **`.library.json`** — Metadata de librería: ```json { "id": "uuid-v4", "name": "Nombre de Librería", "parentId": "parent-uuid | null", "path": "/libraries/uuid", "createdAt": "ISO8601", "updatedAt": "ISO8601" } ``` **`.meta.json`** — Metadata de documento: ```json { "id": "uuid-v4", "title": "string", "tags": ["tag1", "tag2"], "type": "requirement|note|spec|general", "status": "draft|approved|implemented", "priority": "high|medium|low", "createdBy": "agent-id", "createdAt": "ISO8601", "updatedAt": "ISO8601", "libraryId": "uuid" } ``` **`.tag-index.json`** — Índice global de tags (en DATA_ROOT): ```json { "version": 1, "updatedAt": "ISO8601", "tags": { "backend": ["doc-1", "doc-2"], "api": ["doc-1", "doc-3"], "auth": ["doc-2"] } } ``` ### 3.3 Formato de Documento (Markdown) ```markdown --- id: REQ-001 title: Título del Requerimiento type: requirement priority: high status: draft tags: [backend, api] createdBy: agent-id createdAt: 2026-03-28 --- # Título ## Descripción Descripción del requerimiento. ## Criterios de Aceptación - [ ] Criterio 1 - [ ] Criterio 2 ``` ## 4. API REST ### 4.1 Base URL ``` /api/v1 ``` ### 4.2 Autenticación - **Método**: Token Bearer en header `Authorization` - **Formato**: `Authorization: Bearer ` - **Admin tokens**: Generados en setup inicial, almacenados en `.auth-tokens.json` ### 4.3 Endpoints #### Auth | Method | Endpoint | Descripción | |--------|----------|-------------| | POST | `/api/v1/auth/token` | Generar token (admin) | | GET | `/api/v1/auth/verify` | Verificar token válido | #### Documents | Method | Endpoint | Descripción | |--------|----------|-------------| | GET | `/api/v1/documents` | Listar (filtros: tag, library, type) | | GET | `/api/v1/documents/:id` | Obtener documento + metadata | | POST | `/api/v1/documents` | Crear documento | | PUT | `/api/v1/documents/:id` | Actualizar documento | | DELETE | `/api/v1/documents/:id` | Eliminar documento | | GET | `/api/v1/documents/:id/export` | Exportar como Markdown | #### Libraries | Method | Endpoint | Descripción | |--------|----------|-------------| | GET | `/api/v1/libraries` | Listar librerías raíz | | GET | `/api/v1/libraries/:id` | Ver contenido de librería | | POST | `/api/v1/libraries` | Crear librería | | GET | `/api/v1/libraries/:id/tree` | Árbol completo de sublibrerías | | DELETE | `/api/v1/libraries/:id` | Eliminar librería | #### Tags | Method | Endpoint | Descripción | |--------|----------|-------------| | GET | `/api/v1/tags` | Listar todos los tags | | GET | `/api/v1/tags/:tag/documents` | Docs con tag específico | | POST | `/api/v1/documents/:id/tags` | Agregar tags a documento | ## 5. Middleware de Auth ### 5.1 Flujo de Tokens 1. **Setup inicial**: Se genera admin token (almacenado en `.auth-tokens.json`) 2. **CLI login**: `simplenote auth login ` → almacena token en config local 3. **Requests**: Token enviado en header `Authorization: Bearer ` 4. **Verificación**: Middleware busca token en `.auth-tokens.json` ### 5.2 Implementación ```javascript // authMiddleware.js async function authMiddleware(req, res, next) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ error: 'Token required' }); } const tokens = await readJSON('.auth-tokens.json'); if (!tokens.valid.includes(token)) { return res.status(401).json({ error: 'Invalid token' }); } req.token = token; next(); } ``` ### 5.3 Archivo `.auth-tokens.json` ```json { "version": 1, "tokens": [ { "token": "snk_xxxxx", "label": "cli-default", "createdAt": "ISO8601" } ] } ``` ## 6. Estrategia de Indexación de Tags ### 6.1write-through Cuando se crea/actualiza/elimina un documento: 1. Se actualiza su `.meta.json` con los tags 2. Se reconstruye el `.tag-index.json` global ### 6.2 Optimización - El índice se rebuild en memoria al iniciar (rápido para <10k docs) - Para sistemas grandes: rebuild incremental (solo afecta docs modificados) - El índice es un archivo JSON plano para búsqueda O(1) por tag ### 6.3 API de Búsqueda ```javascript // GET /api/v1/tags/backend // → Lee .tag-index.json → returns ["doc-1", "doc-2"] // GET /api/v1/documents?tag=backend // → Busca en índice → filtra docs en memoria → retorna ``` ## 7. CLI API Client ### 7.1 Configuración Local ```json // ~/.config/simplenote/config.json { "apiUrl": "http://localhost:3000/api/v1", "token": "snk_xxxxx", "activeLibrary": "default" } ``` ### 7.2 Cliente HTTP ```javascript // cli/src/api/client.js class SimpleNoteClient { constructor(baseUrl, token) { this.baseUrl = baseUrl; this.token = token; } async request(method, path, body) { const res = await fetch(`${this.baseUrl}${path}`, { method, headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined }); if (!res.ok) { const err = await res.json().catch(() => ({ error: res.statusText })); throw new Error(err.error || `HTTP ${res.status}`); } return res.json(); } // Documents listDocuments(params) { return this.request('GET', '/documents', null); } getDocument(id) { return this.request('GET', `/documents/${id}`); } createDocument(data) { return this.request('POST', '/documents', data); } updateDocument(id, data) { return this.request('PUT', `/documents/${id}`, data); } deleteDocument(id) { return this.request('DELETE', `/documents/${id}`); } // Libraries listLibraries() { return this.request('GET', '/libraries'); } createLibrary(data) { return this.request('POST', '/libraries', data); } // Tags listTags() { return this.request('GET', '/tags'); } getTagDocuments(tag) { return this.request('GET', `/tags/${tag}/documents`); } // Auth verifyToken() { return this.request('GET', '/auth/verify'); } } ``` ## 8. Dependencias NPM ### simplenote-web ```json { "dependencies": { "express": "^4.18.2", "uuid": "^9.0.0", "marked": "^11.0.0", "gray-matter": "^4.0.3" }, "devDependencies": { "nodemon": "^3.0.0" } } ``` ### simplenote-cli ```json { "dependencies": { "commander": "^11.1.0", "axios": "^1.6.0" } } ``` ## 9. Variables de Entorno | Variable | Default | Descripción | |----------|---------|-------------| | `PORT` | 3000 | Puerto del servidor | | `DATA_ROOT` | `./data` | Raíz de documentos | | `HOST` | `0.0.0.0` | Host de binding | | `LOG_LEVEL` | `info` | Nivel de logging | | `CORS_ORIGIN` | `*` | Orígenes CORS permitidos | ## 10. Estructura de Archivos del Proyecto ``` simplenote-web/ ├── src/ │ ├── index.js # Entry point │ ├── app.js # Express setup │ ├── routes/ │ │ ├── documents.js │ │ ├── libraries.js │ │ ├── tags.js │ │ └── auth.js │ ├── services/ │ │ ├── documentService.js │ │ ├── libraryService.js │ │ └── tagService.js │ ├── middleware/ │ │ └── auth.js │ ├── utils/ │ │ ├── markdown.js │ │ ├── fsHelper.js │ │ └── uuid.js │ └── indexers/ │ └── tagIndexer.js ├── data/ # .gitkeep ├── tests/ ├── package.json └── README.md simplenote-cli/ ├── src/ │ ├── index.js # Entry point (Commander) │ ├── commands/ │ │ ├── doc.js │ │ ├── lib.js │ │ └── tag.js │ ├── api/ │ │ └── client.js │ └── config/ │ └── loader.js ├── package.json └── README.md ``` ## 11. Flujo de Operaciones ### 11.1 Crear Documento (CLI) ``` simplenote doc create --title "REQ-001" --tags "backend,api" → POST /api/v1/documents → Service: genera UUID, crea /data/libraries/{lib}/documents/{id}/index.md → Service: crea /data/libraries/{lib}/documents/{id}/.meta.json → Service: rebuild .tag-index.json → Response: { id, path, tags, ... } ``` ### 11.2 Crear Documento (Web) ``` Form POST → /api/v1/documents → Mismo flujo que CLI → Web reconstruye lista con filtros ``` ### 11.3 Búsqueda por Tag ``` GET /api/v1/tags/backend → tagIndexer: lee .tag-index.json → Returns: ["doc-id-1", "doc-id-2"] GET /api/v1/documents?tag=backend → Busca en índice → filtra docs → retorna con metadata ``` ## 12. Seguridad - Tokens con prefijo `snk_` para identificación fácil - Tokens almacenados en texto plano (sistema multi-usuario confiado) - Para producción futura: hash bcrypt + salt - CORS configurable para restringir acceso web ## 13. Líneas de Código Estimadas | Componente | LOC aprox | |------------|-----------| | API REST (Express) | ~600 | | Services (document, library, tag) | ~400 | | Middleware auth | ~50 | | Tag indexer | ~100 | | CLI client | ~300 | | CLI commands | ~400 | | **Total** | ~1850 |