- ARCHITECTURE.md: main architecture document - api-spec.yaml: full OpenAPI 3.0 spec - folder-structure.md: detailed folder layout - data-format.md: JSON schemas for .meta.json, .library.json, .tag-index.json - env-template.md: environment variables documentation - cli-protocol.md: CLI-to-API communication protocol
11 KiB
11 KiB
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:
{
"id": "uuid-v4",
"name": "Nombre de Librería",
"parentId": "parent-uuid | null",
"path": "/libraries/uuid",
"createdAt": "ISO8601",
"updatedAt": "ISO8601"
}
.meta.json — Metadata de documento:
{
"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):
{
"version": 1,
"updatedAt": "ISO8601",
"tags": {
"backend": ["doc-1", "doc-2"],
"api": ["doc-1", "doc-3"],
"auth": ["doc-2"]
}
}
3.3 Formato de Documento (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 <token> - 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
- Setup inicial: Se genera admin token (almacenado en
.auth-tokens.json) - CLI login:
simplenote auth login <token>→ almacena token en config local - Requests: Token enviado en header
Authorization: Bearer <token> - Verificación: Middleware busca token en
.auth-tokens.json
5.2 Implementación
// 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
{
"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:
- Se actualiza su
.meta.jsoncon los tags - Se reconstruye el
.tag-index.jsonglobal
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
// 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
// ~/.config/simplenote/config.json
{
"apiUrl": "http://localhost:3000/api/v1",
"token": "snk_xxxxx",
"activeLibrary": "default"
}
7.2 Cliente HTTP
// 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
{
"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
{
"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 |