This guide is a fast, high-level map of the project for new contributors. It focuses on where code lives, what owns what, and how requests/flows move through the system.
Deep MTG is a full-stack app for generating and managing Magic: The Gathering decks.
- Frontend: Next.js app (
frontend/) for UI, Google sign-in, and user interactions. - Backend: Django + Django Ninja API (
app/) for business logic, data, auth token exchange, and deck orchestration. - Async workers: Celery for long-running AI deck generation tasks and periodic memory maintenance.
- Data stores:
- Postgres for relational app data (users, decks, cards, build tasks, refresh tokens, memories, maintenance reports)
- Redis for rate limits and quota tracking
- Qdrant for vector search over card embeddings and AI memories
- Reverse proxy: Caddy routes
/to frontend and/api/*to backend in local/dev docker flows.
README.md: setup, production commands, and card ingestion commandsdocker-compose*.yml,Dockerfile,Caddyfile: containerized runtimeapp/: Django backend projectfrontend/: Next.js frontend projectdata/done/*.json: MTG JSON set data used for ingestion
app/: Django project config (settings.py,urls.py,celery.py)appauth/: auth routes, JWT/refresh token logic, middlewareappuser/: user-account features (export/delete)appcards/: card/deck domain models + routes + ingestion commandsappai/: deck-building orchestration, build status, Celery tasksappsearch/: semantic card search integration (Qdrant)appcore/: shared infra modules (redis client, decorators, shared utilities)
app/: App Router pages + route handlersdashboard/,decks/,cards/search/,login/, etc.backend-auth/*: Next route handlers that bridge FE auth session to BE cookiesapi/auth/[...nextauth]/route.ts: NextAuth endpoint
lib/: auth/session/backend fetch helperscomponents/: shared UI componentsproxy.ts: route protection/redirect middleware
app/app/urls.py creates a single NinjaAPI at /api/app/ and mounts routers:
/api/app/ai/*→ deck generation workflows/api/app/cards/*→ cards and decks/api/app/search/*→ card semantic search/api/app/user/*→ account operations/api/app/token/*→ token exchange/refresh (no access-token auth on these routes)
/healthz is a plain health check endpoint.
- Handles Google token exchange and refresh-token rotation.
- Issues backend JWT access tokens + refresh tokens.
- Applies auth rate limits.
CookieAuthCSRFMiddlewareenforces CSRF when authenticated via backend auth cookies on unsafe methods.
- Owns card/deck data models:
Card,PrintingDeck,DeckCard,DailyDeckTheme
- Exposes routes for:
- deck list/detail/full detail/update/delete
- daily theme
- card set codes and tags
- Maintains deck validity basics and card metadata mapping.
- Owns async deck build orchestration:
- create deck build task records
- enforce daily build quota
- enforce relevance guardrails
- enqueue Celery task (
construct_deck) - expose build status polling API
- Owns long-term AI memory system:
- memory write/search tools used by deck-constructor agents
- memory persistence in Postgres + Qdrant (
memoriescollection) - memory maintenance graph and report generation
- Exposes search endpoint for cards using:
- request-level filters (set codes/colors/tags)
- vector query to Qdrant
- DB hydration of card IDs back into card payloads
- Enforces layered rate limits (5s, 1m, 1h windows).
- User self-service endpoints:
- account export
- two-step account deletion (request token + confirm deletion)
- Adds export/delete rate-limiting and short-lived signed deletion token flow.
- Celery app is initialized in
app/app/celery.py. - Task queues include
defaultandllm. - Deck construction task (
appai/tasks/construct_deck.py) updates build state transitions (PENDING→IN_PROGRESS→COMPLETED/FAILED). - Periodic jobs in Django settings run:
- daily deck theme generation (interval-triggered with internal once-per-day guard)
- memory maintenance (
appai/tasks/memory_maintenance.py) to consolidate or replace clustered memories - cleanup of old deck-build task records
- Memory records are stored in
appai.models.Memoryand mirrored to Qdrant collectionmemories. - During deck construction, agents can search memories and write new memories through
appai/services/agents/tools/memory_tools.py. - The maintenance graph (
appai/services/graphs/memory_maintenance.py) retrieves memories, embeds missing vectors, runs UMAP + HDBSCAN clustering, then calls a maintenance agent to rewrite clusters. - A
MemoryMaintenanceReportrecord is persisted for each completed maintenance run. - The scheduled maintenance task is checked every 30 minutes, but it short-circuits if:
- a report was already created today, or
- fewer than 10 new memories were written since the last report.
app/login: Google sign-in UX and auth error display.app/dashboard: deck list, filters, generation status polling, navigation hub.app/decks/generate: prompt-based generation UX, quota checks, set-code selection, status polling.app/decks/[deckId]: full deck view/edit/delete/regenerate and card replacement exploration.app/cards/search: semantic card search with filters and optional source-deck context.app/dashboard/account: export account data and delete account flow.
- NextAuth (
lib/auth.ts) manages Google OAuth and stores Google ID token in session. proxy.tsprotects/dashboardand/decksroutes.app/providers.tsxrunsBackendUserSync:- if signed in, exchange Google token for backend tokens
- if sync fails, clear backend cookies and force sign-out
frontend/app/backend-auth/* routes are the bridge between browser session and backend API auth:
/backend-auth/exchange: call backend/api/app/token/exchange; set cookies:backend_access_token(httpOnly)backend_refresh_token(httpOnly)backend_csrf_token(readable by browser JS)
/backend-auth/refresh: rotate backend tokens using refresh token cookie/backend-auth/clear: clear backend auth cookies
lib/backend-auth.ts centralizes API calling with:
backendFetchwrapper- CSRF header injection for unsafe methods
- 401 recovery via refresh then exchange fallback
- User signs in via Google (NextAuth).
- Frontend session includes
googleAuthToken. BackendUserSynccalls/backend-auth/exchange.- Route handler calls backend
/api/app/token/exchange. - Backend validates Google token and issues backend access/refresh tokens.
- Browser gets backend auth cookies and can call
/api/app/*throughbackendFetch.
For a dedicated walkthrough, see docs/deck-building.md.
- User submits prompt in
decks/generate. - FE POSTs
/api/app/ai/deck/with prompt (+ optionaldeck_idand set filters). - BE checks:
- daily quota
- deck ownership (if regenerating)
- no active pollable build already running
- relevance guardrail
- BE creates
DeckBuildTask+ enqueues Celeryconstruct_decktask. - FE polls
/api/app/ai/deck/build_status/{task_id}/every ~2.5s. - On
COMPLETED, FE navigates to/decks/{deckId}and fetches full deck data.
- FE loads deck summaries from
/api/app/cards/deck/. - FE loads pollable statuses from
/api/app/ai/deck/statuses/. - FE periodically checks active build statuses and refreshes deck list.
- FE loads set-code/tag filters from card metadata endpoints.
- User submits query + filters to
/api/app/search/search/. - BE applies rate limits, builds DSL query, searches Qdrant, and hydrates card rows from Postgres.
- FE renders ranked card results and relevance scores.
- FE triggers export via
/api/app/user/me/export/and downloads JSON. - For deletion, FE requests token via
/api/app/user/me/delete-request/. - FE confirms deletion via
/api/app/user/me/with token payload. - BE validates signed token + nonce, deletes user, FE clears auth and signs out.
In typical order:
manage.py 1_add_cards --card-json-path ...- parse MTGJSON
- upsert base card records + printings
manage.py 2_generate_card_summaries- generate LLM summaries + tags for cards missing summary
manage.py 3_embed_cards- generate card embeddings
- upsert vectors into Qdrant
- Deck-constructor agent can call
subagent_memory_searchto retrieve relevant memory summaries. - Agent can call
write_memoryto persist cross-task insights. - Memory write path stores to Postgres (
Memory) and upserts embedding payload to Qdrant (memories). - Periodic maintenance clusters memory vectors and rewrites stale/duplicate clusters.
- Maintenance outputs are recorded in
MemoryMaintenanceReportfor observability.
- Read this guide, then skim backend
app/app/urls.pyand frontendfrontend/app/routes. - Run app stack in docker and verify:
- login works
- dashboard loads
- can start a deck generation and observe status transitions
- Learn one vertical slice end-to-end:
- start with
frontend/app/decks/generate/page.tsx - follow API calls into
app/appai/routes/build_deck.py - follow async task into
app/appai/tasks/construct_deck.py
- start with
- Use the ingestion commands only when refreshing card corpus/vector index.
- Most frontend API calls assume same-origin routing and cookie-based backend auth.
- Backend uses JWT access tokens + rotating refresh tokens for API auth.
- Auth rate limiting trusts forwarded client IP headers only when
REMOTE_ADDRis inAUTH_RATE_LIMIT_TRUSTED_PROXY_CIDRS; set this to your internal proxy CIDRs in staging/production. - Long-running AI work is asynchronous; always think in terms of task IDs + status polling.
- Search quality and deck generation quality depend on the freshness of card summaries and embeddings.
- Memories are shared across users/agents; memory content should stay general and non-user-specific.