Files
simplenote-web/ARCHITECTURE.md
Bulma 90e4dd0807 feat(architecture): add complete technical architecture for SimpleNote
- 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
2026-03-28 03:18:25 +00:00

414 lines
11 KiB
Markdown

# 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 <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
1. **Setup inicial**: Se genera admin token (almacenado en `.auth-tokens.json`)
2. **CLI login**: `simplenote auth login <token>` → almacena token en config local
3. **Requests**: Token enviado en header `Authorization: Bearer <token>`
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 |