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
This commit is contained in:
413
ARCHITECTURE.md
Normal file
413
ARCHITECTURE.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user