Skip to content

Repository files navigation

🧠 ragdesk

A multi-tenant, AI-powered knowledge SaaS

Teams upload their documents and chat with an assistant that answers only from those documents, with citations — Retrieval-Augmented Generation (RAG) as a real, billable product.

CI CD CodeQL License: MIT

Go Python Next.js

🌐 Live demo  ·  one-click Deploy your own
Free tier — the first request may take ~50s to wake the services.

ragdesk is built the way production AI software actually ships: a strongly-typed Go core for tenancy, billing and metering; a Python/FastAPI service for the LLM and embedding pipeline; a Next.js front-end; Postgres + pgvector for rows and vectors; and a provider-agnostic model layer that runs on a free local LLM (Ollama) or a free hosted tier (Gemini, Groq). Every feature shipped in small, tested, atomically-committed phases — CI, CD and CodeQL green — on $0 of paid infrastructure.


🖼️ Demo

Ask your documents — grounded answers, with citations:

RAG chat

Plans, metered usage and one-click upgrade — billing as a first-class feature:

Billing & usage

Landing Workspaces dashboard — multi-tenant
Landing Dashboard
Documents — async ingestion → embeddings Create account
Documents Create account

✨ Features

  • 🏢 Multi-tenant workspaces — organizations, members, roles, hard data isolation
  • 🔐 JWT auth — register/login with bcrypt-hashed passwords, HS256 tokens
  • 📄 Document ingestion — upload → chunk → embed (Ollama) → pgvector, processed async via a Redis queue + worker
  • 💬 RAG chat — streaming answers grounded in your documents, with citations (pgvector cosine retrieval)
  • 🔌 Provider-agnostic LLM — mix & match embeddings and chat: Ollama (local/$0), Gemini & Groq (free hosted tiers), or a deterministic fake for tests/CI
  • 💳 Billing & metering — Stripe subscriptions (test mode), per-workspace usage metering, plan limits enforced with 402 Payment Required; runs $0 with a dev-mode fallback
  • 🔒 Production hardening — rate limiting, structured logs, health probes, govulncheck, CodeQL
  • 🔭 Observability — optional OpenTelemetry tracing across web → API → AI → Postgres (no-op until configured)
  • 🐳 Cloud-native — multi-stage Docker images, docker compose up, GitHub Actions CI + CD (images to GHCR)

🏗️ Architecture

flowchart LR
    User([User]) --> Web[web · Next.js 16 + TS]
    Web -->|REST / SSE| API[api · Go + chi]
    API --> PG[(Postgres + pgvector)]
    API --> Redis[(Redis · cache + queue)]
    API -->|ingest / chat| AI[ai · Python + FastAPI]
    AI --> PG
    AI -->|provider-agnostic| LLM{{Ollama · Gemini · Groq}}
Loading

See docs/architecture.md for the full design.

🧰 Tech stack

Layer Choice
Frontend Next.js 16, TypeScript, Tailwind v4
Core API Go 1.26, chi, pgx, go-redis, JWT, bcrypt
AI service Python, FastAPI, pgvector; Ollama · Gemini · Groq
Data PostgreSQL 16 + pgvector, Redis 7
Billing Stripe (test mode)
Observability OpenTelemetry (OTLP), Jaeger
Infra Docker (multi-stage, distroless), docker-compose, GitHub Actions (CI + CD → GHCR), CodeQL

🚀 Quickstart

git clone https://github.com/thefcan/ragdesk.git
cd ragdesk
cp .env.example .env

# Backend: Postgres (pgvector), Redis, the Go API and the Python AI service
make up                                   # docker compose up --build -d

# Frontend (Next.js) — in another terminal
cd web && npm install && npm run dev      # http://localhost:3000

Try the API directly:

# register (bootstraps a default workspace) and call an authenticated endpoint
TOKEN=$(curl -s -X POST localhost:8080/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com","password":"supersecret"}' | jq -r .token)

curl -s localhost:8080/workspaces -H "Authorization: Bearer $TOKEN"

🔌 API (today)

Browse it interactively in Swagger UI at /docs (raw spec at /openapi.yaml).

Method Path Auth Description
POST /auth/register public Create a user + default workspace, returns a JWT
POST /auth/login public Exchange credentials for a JWT
GET /workspaces Bearer List the caller's workspaces
POST /workspaces Bearer Create a workspace
GET /workspaces/{id} Bearer Get a workspace (members only)
GET /workspaces/{id}/members Bearer List members
POST /workspaces/{id}/members Bearer Add a member (owner/admin)
GET /workspaces/{id}/documents Bearer List a workspace's documents
POST /workspaces/{id}/documents Bearer Upload a document (async ingestion)
POST /workspaces/{id}/chat Bearer Ask a question — streaming RAG answer with citations
GET /workspaces/{id}/billing Bearer Plan, limits and current-period usage
POST /workspaces/{id}/billing/checkout Bearer (owner) Start an upgrade (Stripe checkout, or dev confirm)
POST /workspaces/{id}/billing/portal Bearer (owner) Open the Stripe billing portal (manage / cancel)
POST /billing/webhook Stripe-signed Apply subscription changes from verified events (idempotent)
GET /healthz · /readyz · /version · /metrics public Probes, build info & Prometheus metrics

💳 Billing & metering

Billing is modelled per workspace (the tenant). Plans and their limits live in code; usage is metered durably in Postgres and enforced at the API edge.

Plan Documents Chat messages / month Price
Free 25 100 $0
Pro 1,000 5,000 $29 / mo
  • Metering — a usage_counters table buckets usage per (workspace, month, metric) and is incremented atomically (INSERT … ON CONFLICT … DO UPDATE). Documents are counted directly.
  • Enforcement — over-limit document uploads and chat messages are rejected with 402 Payment Required before any expensive work runs. The document cap is applied inside the insert so concurrent uploads can't slip past it.
  • Provider-agnostic payments — a billing.Provider interface mirrors the LLM layer. With Stripe test-mode keys it creates a hosted Checkout Session and fulfils the upgrade from a signature-verified webhook. With no keys it runs a $0 dev mode: a local dev-confirm endpoint stands in for the webhook, so the whole flow is demoable for free.
  • Self-serve management — owners upgrade, and manage or cancel through the Stripe billing portal (a local cancel stands in when running $0).
  • Safe by default — the webhook is signature-verified (forged events are rejected) and idempotent (Stripe delivers at least once; processed event ids are recorded and skipped), the API refuses to start with a Stripe key but no webhook secret, and only a workspace owner can change the plan.

☁️ Deploy

Deploy to Render

Every push to main builds and publishes both service images to GHCR (ghcr.io/thefcan/ragdesk-api, ghcr.io/thefcan/ragdesk-ai), so the stack runs on any container host. A one-click Render Blueprint (render.yaml) provisions Postgres + pgvector, Redis, the API, the AI service and the web app on the free tier — Postgres, Redis and the shared secrets are wired automatically.

See docs/deploy.md for the full guide (Render, Fly.io, Vercel, or your own docker compose).

🔭 Observability

Both services emit OpenTelemetry traces — a no-op until OTEL_EXPORTER_OTLP_ENDPOINT is set, so the default run pays nothing. The Go API instruments inbound HTTP, the outbound call to the AI service, and Postgres queries; the Python service continues the same trace through FastAPI and its DB calls. A request shows up as one connected trace: web → API → AI → Postgres.

# View traces locally in Jaeger (http://localhost:16686)
OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 docker compose --profile observability up

The API also exposes Prometheus metrics at /metrics — per-route request rate, latency (a histogram) and error counts, plus Go runtime and process metrics — ready to scrape with no extra setup.

💸 Runs on $0

Every component has a free path: a local LLM (Ollama) or free hosted tiers (Gemini, Groq), Postgres+pgvector and Redis in Docker, Stripe test mode, Render/Vercel free tiers, and GitHub Actions for public repos.

📄 License

MIT © 2026 Furkan Can Karafil

About

Multi-tenant RAG SaaS — Go API + FastAPI AI service + Next.js, PostgreSQL/pgvector, cited answers

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages