Falsafa

Falsafa Overview

System architecture, data flows, and key integration contracts for the Falsafa platform, an AI-powered reading companion.

Falsafa means "philosophy" in Urdu. The platform is an AI-powered reading companion that extracts characters from books and lets readers have philosophical conversations with them.

This document covers the system architecture, data flows, and key integration contracts. It is written for developers who need to understand the whole stack.

For the full cross-component HLD see System High-Level Design - covers trust boundaries, session management at every layer, complete end-to-end flows with payload traces, and the cross-component error propagation matrix.

Package Layout

The codebase is three independent packages sharing a directory. There is no root package.json or monorepo tool.

falsafa/
├── frontend/          Next.js 16, Supabase, Stripe (user-facing app)
├── backend/           FastAPI, BookNLP, Qdrant, TypeSense, Redis (ML/LLM service)
└── docs/              FumaDocs site (public documentation)

Each package has its own package.json (or requirements.txt), lockfile, .env*, and Dockerfile. They communicate over HTTP only.

System Architecture

The frontend is the orchestrator. It talks to Supabase directly for auth, database reads, and storage uploads. It proxies chat streaming and book processing through the FastAPI backend, and handles payment intents through Stripe. The backend never talks to Stripe. The backend talks to Supabase for authentication and data reads, but the frontend is always the system of record for user-facing data (library, wishlist, notifications).

Auth Model

Supabase Auth is the single source of truth. The frontend middleware reads the session cookie and checks profiles.status to block banned users. Server components and route handlers use auth() (which reads the session, then hydrates role and status via the admin client with the service-role key). The ML backend trusts the user_id and book_id that the frontend sends and does not verify tokens. Blocked users are filtered before they reach any protected page or the backend.

Key Data Flows

Book Upload and Character Processing

The frontend uploads files directly to Supabase Storage, inserts the book row, and sends a one-shot POST to the backend. The backend returns 202 immediately and processes asynchronously via a Redis-backed job queue. The job runs BookNLP (heavy sync work), then the LLM character analysis, then inserts characters, then chunks and indexes the full text into Qdrant and TypeSense in parallel. Per-character and per-chunk failures are isolated so one bad character or chunk never aborts the whole book.

Chat Streaming

The frontend proxies all chat requests through /api/chat, which validates session ownership before forwarding to the backend. The backend acquires a Redis per-session lock (30s TTL, Lua CAS release) to prevent concurrent writes. Query rewriting resolves pronouns against the character's psychology, then Qdrant and TypeSense searches run in parallel. Results are deduplicated, reranked, and injected into the LLM system prompt. The streaming response is piped through as SSE. Messages are persisted after the stream completes.

Data Ownership

ConcernOwnerStore
Auth (users, sessions)Supabase AuthSupabase managed tables
Books metadataFrontendSupabase books table
Characters + system promptsBackend (via frontend upload)Supabase characters table + disk
Chat sessionsFrontend creates, Backend readsSupabase chat_sessions
Chat messagesBackend writesSupabase messages
Book text (vector index)BackendQdrant per-book collections
Book text (BM25 index)BackendTypeSense per-book collections
Session cache + locksBackendRedis
Profile images, book covers, book filesFrontendSupabase Storage
User library, wishlist, notificationsFrontendSupabase tables
PaymentsFrontend + StripeStripe + Supabase purchases
Platform settingsFrontend adminSupabase settings table

Key Integration Contracts

The frontend and backend communicate over two endpoints defined in frontend/api.md. These are the system's critical interfaces.

POST /character/processing (Book ingestion). Frontend sends {book_id, file_url, user_id}. Backend downloads the file from the signed URL, validates it is .txt or .md, and queues a background job. Returns 202 on acceptance, 422 on invalid file or missing book.

POST /character/chat (Chat streaming). Frontend sends {user_id, character_id, book_id, session_id, user_message}. Backend validates the session exists (the frontend is responsible for session lifecycle), runs query rewriting and RAG, and streams back SSE tokens. Returns 404 if the session does not exist, 429 (as an SSE event) if the session is busy with another request, and 502 (as an SSE event) if the LLM call fails mid-stream.

The frontend uses a service-role Supabase client for admin operations and the browser client (anon key with RLS) for user-scoped reads. The backend uses the service-role key for all its Supabase reads.

Environment Configuration

Each package has its own env file. The critical variables that cross package boundaries:

VariableSet inUsed by
NEXT_PUBLIC_SUPABASE_URL + NEXT_PUBLIC_SUPABASE_ANON_KEYfrontend/.env.localFrontend browser + server clients
SUPABASE_SERVICE_ROLE_KEYfrontend/.env.localFrontend admin client
ML_SERVICE_URL / NEXT_PUBLIC_ML_SERVICE_URLfrontend/.env.localChat proxy + upload (default https://api.falsafa.syedkhalid.tech)
BACKEND_API_KEYfrontend/.env.localChat proxy (X-API-Key header)
STRIPE_* keysfrontend/.env.localPayments and webhook
SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEYbackend/.envBackend database reads
QDRANT_URL + QDRANT_API_KEYbackend/.envVector search
TYPESENSE_API_KEY + TYPESENSE_NODESbackend/.envFull-text search
REDIS_URLbackend/.envQueue, cache, locks
LLM_PROVIDER + *_API_KEYbackend/.envLLM routing
EMBEDDING_PROVIDER + *_API_KEYbackend/.envEmbedding routing
NEXT_PUBLIC_DEBUG_MODEfrontend/.env.localBypasses all Supabase/Stripe

The frontend has a DEBUG_MODE that makes it runnable with no external services. The backend requires Supabase, Redis, Qdrant, TypeSense, and at least one LLM provider to function.

Where the System Stops

  • Paid books: the is_free: true flag is hard-coded in the upload service. The checkout flow, Stripe webhook, and purchases table exist but no book is ever created as paid.
  • Book covers, category images, file CRUD: six API routes are stubs returning 401. All active uploads go through Supabase Storage.
  • Real-time notifications: there are no Supabase Realtime subscriptions. Notifications are fetched on page load and after explicit markAsRead.
  • Book comment moderation: comments are auto-approved. The admin moderation UI exists but no comment ever enters a pending state.
  • Chat preferences are experimental: the user preference modifier block is built on the frontend and documented as "used by backend, exported for reuse," but there is no import path from the Python backend. The preferences are stored but may not be injected into the system prompt at runtime.
  • Two chat session flows coexist: a legacy one (Supabase auth, characterId-only) and a current one (NextAuth, requires library entry). The book-detail page uses the current one; parts of the app may still use the legacy path.

On this page