# Backend Architecture This document explains the backend in beginner-friendly language. ## Big picture ```text Browser / Frontend | | HTTP JSON requests v cmd/server/main.go | +-- config.Load() reads environment variables +-- db.Connect() creates PostgreSQL pool +-- chi router maps URLs to handler functions +-- handlers read/write DB and return JSON | v PostgreSQL + local uploads or S3 ``` The backend is one Go HTTP server. There is no framework magic: `main.go` wires everything manually. ## Request flow example Example: frontend calls `GET /api/resources?limit=20`. 1. `main.go` has this route: ```go r.Get("/resources", handlers.ListResources) ``` 2. The `/api` router has middleware: ```go r.Use(func(next http.Handler) http.Handler { return handlers.WithPool(next, pool) }) ``` This places the PostgreSQL pool into the request context. 3. `handlers.ListResources` calls `poolFrom(r)` to get the DB pool. 4. It reads query string filters (`q`, `type`, `language`, `category`, `sort`, `tag`). 5. It builds SQL, queries PostgreSQL, scans rows into `ResourceDTO`. 6. It returns JSON using `writeJSON`. ## Why `internal/`? In Go, a folder named `internal` can only be imported by code inside the same module tree. This prevents other modules from depending on private implementation packages. This project uses: ```text internal/config private config package internal/db private DB package internal/auth private JWT package internal/handlers private HTTP handler package internal/seed private seed package ``` ## Main packages ### `cmd/server` `cmd/server/main.go` is the executable entry point. Responsibilities: - Load environment config. - Connect to PostgreSQL. - Ensure wallet nonce table when enabled. - Create upload directory and placeholder file. - Optionally seed the first admin. - Configure optional S3 upload client. - Register all HTTP middleware and routes. - Start `http.ListenAndServe`. ### `internal/config` `config.Load()` reads env vars and fills defaults. It returns a `Config` struct. Important pattern: ```go addr := os.Getenv("HTTP_ADDR") if addr == "" { addr = ":8080" } ``` This means env vars are optional in local development. ### `internal/db` `db.Connect(ctx, databaseURL)` parses `DATABASE_URL`, creates a pgx connection pool, then pings the DB. The app shares this pool for all requests. ### `internal/auth` Two JWT token types exist: - Admin token: issuer `ark-admin`, includes `admin_id` and email. - Wallet user token: issuer `ark-user`, includes wallet address and role `user`. Do not mix these. Admin middleware only accepts admin tokens. ### `internal/handlers` Handlers are grouped by topic: - `public.go`: public category/resource list/detail, search logs, counters. - `admin.go`: admin login/dashboard/resource CRUD/tag replacement. - `wallet_auth.go`: wallet nonce and signature verification. - `upload.go`: admin file upload. - `middleware.go`: admin auth middleware. - `context.go`: stores DB pool in request context. - `util.go`: shared JSON helpers. ## Database model summary Main tables from `migrations/001_init.sql`: - `admins`: admin email + bcrypt password hash. - `categories`: resource categories with multilingual names. - `resources`: library items; can be ppt/image/video/link/etc. - `tags`: reusable tag names. - `resource_tags`: many-to-many join table. - `search_logs`: saved public search terms. Wallet auth table from migration/startup DDL: - `wallet_auth_nonces`: one temporary nonce per wallet address. ## Route groups ### Public routes These do not require auth: - health: `/healthz`, `/health` - categories/resources under `/api` - wallet auth nonce/verify/me (me requires wallet JWT) - resource view/download/share/favorite counters ### Admin routes `POST /api/admin/login` is public because it returns the admin token. Everything in this group requires admin JWT: - dashboard - search logs - resource list/get/create/update/delete - upload - admin categories list ## Upload behavior Admin upload endpoint: `POST /api/admin/upload` with multipart field `file`. If S3 is configured: ```text S3_BUCKET non-empty + AWS config loads => upload to S3 ``` Otherwise: ```text save file to UPLOAD_DIR and return /uploads/ ``` The max upload size is 512 MiB (`uploadMaxBytes`). ## Things to be careful with - `migrations/001_init.sql` is not safe to run repeatedly on the same DB. - `JWT_SECRET` default is for dev only. - `RUN_WALLET_AUTH_SCHEMA=true` runs DDL at startup. Use false if the runtime DB user should not create tables. - The app does not auto-load `.env`. - Public resources are filtered to published + public; admin list sees all resources.