- SETUP: local dev guide (env vars, DB migrate, backend/frontend, troubleshooting) - HOSTING: bare-metal guide (systemd + Nginx + TLS + backups) and Docker Compose guide (deploy, reverse proxy, updates, backup/restore) - API: REST contract with curl examples and Socket.IO event protocol - ARCHITECTURE: stack, data model, auth model, realtime design, theming - ROADMAP: webapp MVP/next steps plus Tauri desktop clients for Windows, macOS and Linux with release-binary plan
2.1 KiB
2.1 KiB
Architecture
Stack
- Frontend: Vue 3 (Composition API +
<script setup>), Vite, Vue Router, Pinia, Axios, Socket.IO client, TipTap editor. Theme via CSS variables (data-theme). - Backend: Node.js 20, Express 4, Socket.IO 4,
pg(node-postgres), JWT, bcryptjs. - DB: PostgreSQL 14+. Single schema file (
backend/src/db/schema.sql), applied vianpm run migrateorpsql.
Data model
users (id, email unique, name, password_hash)
└─< workspace_members (workspace_id, user_id, role) >─┐
workspaces (id, name, owner_id -> users) <───────────────┘
└─< pages (id, workspace_id, parent_id self-FK, title, icon, cover,
content_json JSONB, content_html TEXT, position, is_archived)
└─< comments (id, page_id, author_id, body)
- Nesting via
parent_idself-reference (ON DELETE CASCADEremoves subtrees). - Owner auto-added as
workspace_members(role=owner)via trigger. updated_atmaintained by trigger.
Auth & authorization
- Register/login return a signed JWT (
sub=user.id). Client stores it inlocalStorageand sendsAuthorization: Bearer. - Every workspace/page route resolves the caller's membership role server-side;
viewers are blocked from mutations (
403). - Socket.IO handshake verifies JWT;
page:joinandpage:editre-check membership and role against the DB (never trust client role).
Realtime design (MVP)
- One Socket.IO room per page (
page:<id>). Edits are debounced client-side (~800ms), persisted via RESTPATCH /pages/:id, then broadcastpage:updated. page:editsocket events also persist server-side for clients that only use sockets.- Presence: server keeps
room -> socketId -> usermap, emitspresence:update. - No CRDT/OT in MVP — last-write-wins. Concurrent edits converge on the last saved
version; remote updates are ignored while the local user is the sender (
from). See ROADMAP for planned Yjs-based conflict-free editing.
Theming
useThemeStore(light/dark/system), persisted inlocalStorage, applied asdocument.documentElement.dataset.theme. All colors are CSS variables.