Document management platform with AI-powered semantic search. Upload documents, ask questions in plain English, and get synthesized answers grounded in your own content — across multi-tenant workspaces with fine-grained access control. Solves the "where did I see that" problem for teams drowning in PDFs, scanned contracts, and Google Drive folders by combining OCR, Graph RAG over SurrealDB, and natural-language Q&A with citations.
Lunaria is a document management platform built around three pillars: ingest anything (uploads, Google Drive sync, scanned images with OCR + semantic chunking), find anything (Nova — natural-language search powered by Graph RAG over SurrealDB, plus hierarchical tags, saved views, favorites, recents), and share safely (workspace-scoped multi-tenancy, plan-aware RBAC, public portal links with bearer tokens, full audit trails). The system is polyglot — a Nuxt 3 frontend talking to a NestJS 11 backend that delegates layout-aware OCR to a Python FastAPI sidecar running IBM Docling + EasyOCR. Documents land in S3, get extracted into semantic chunks, and are ingested into a SurrealDB knowledge graph so search traverses entity relationships rather than raw cosine similarity. The dev environment ships as a single docker compose up with seven services orchestrated for full prod parity, and the test suite covers 290+ Vitest + Playwright tests with persistent auth fixtures.
Upload hits the NestJS backend → file persisted to S3 (LocalStack in dev) → BullMQ enqueues an extraction job → worker forwards the file to the Python docling-service over HTTP → Docling runs EasyOCR + HybridChunker and returns markdown plus semantic chunks → chunks ingested into the SurrealDB knowledge graph → Gemini-backed extraction pulls structured data when applicable → AiTokenUsage table tracks per-workspace LLM spend for billing.
Instead of dumping embeddings into a vector DB, Lunaria builds a knowledge graph in SurrealDB linking entities across documents. Search traverses the graph for relationship-aware retrieval, which outperforms pure cosine similarity on multi-hop questions where the answer requires connecting facts across multiple documents.
WorkspaceContextMiddleware extracts `x-workspace-id` from every request, validates membership, and injects the context into the request lifecycle. Every Prisma query is then workspace-filtered automatically — making cross-tenant data leaks structurally impossible without an explicit, audited override.
Three roles (Owner / Admin / Member) plus a `resolveEffectiveRole()` clamp that downgrades Admin capabilities to Member on free / family / plus tiers. A pyramid rule prevents granting roles higher than your own and prevents modifying peers of equal or higher rank. CASL's @casl/ability and @casl/prisma plus a PermissionsGuard and @RequirePermissions() decorators enforce permissions per resource.
Three runtimes by design: Nuxt 3 frontend deployed on Vercel, NestJS 11 backend on a Node runtime with PostgreSQL + DynamoDB + SurrealDB + Redis behind it, and a Python 3.11 FastAPI docling-service running as an ECS Fargate task in a private VPC. The service boundary keeps OCR's heavy Python deps off the Node hot path and lets each tier scale independently.
Modules talk via EventEmitter2 rather than direct service imports. Domain events (`user.created`, `document.uploaded`, `document.processed`) are consumed by @OnEvent listeners that handle audit logging, notifications, and Google Drive sync — keeping cross-cutting concerns from polluting core domain code and making it trivial to add new listeners without touching publishers.
Every major entity carries `isDeleted` / `deletedAt` / `deletedBy` columns plus an `originalLocation` JSON field that snapshots the entity at delete time. A TrashItem table tracks the restore deadline; TrashService.restore() rebuilds within the 30-day window. The snapshot approach means restores work even if the surrounding hierarchy has changed since the delete.
plugins/api.js exposes a $fetch instance with auth + workspace headers and global 401 handling; composables/useAPI.js wraps useFetch reactively; api/ holds domain modules like EntitlementsAPI.getEntitlements(). Clean separation makes header injection a single concern, mocking trivial in tests, and prevents API logic from leaking into components.
Entitlements are fetched on app boot into a Pinia store; useQuota().canUse('LUNARIA_AI_CREDITS_PER_MONTH') gates UI affordances; the server independently enforces hard limits so the client can't be bypassed. Color-coded quota status (ok / warning 80% / critical 90% / exceeded) drives consistent UX across upgrade prompts and progress bars.
Natural-language Q&A over your documents with synthesized answers, source citations, suggested questions, and saved searches
Multi-file drag-and-drop upload, Google Drive folder sync, OCR for scanned images, and inline PDF/image preview with metadata sidebar
Hierarchical tags with drag-to-nest, saved views (filtered collections), linked views (multi-criteria filters), favorites, and recents
OAuth-based account linking, folder-level auto-sync, and per-file processing status via LensGoogleDriveFileLog
Public portal links with bearer tokens, configurable expiration, scoped permissions, and read-only guest mode
Server-Sent Events stream powering an in-app inbox plus toast notifications for uploads, shares, and mentions
Per-request workspace switching via `x-workspace-id`, member invitations, ownership transfer, and Teamspace → Group sub-organizations
Quota-aware UI with server-enforced hard limits, color-coded status (ok / warning / critical / exceeded), and graceful upgrade flows
Per-document activity log of views, downloads, and shares with full audit history















