Files
Arkie-Library-Backend/docs/ARCHITECTURE.md
2026-05-18 07:56:27 +08:00

4.7 KiB

Backend Architecture

This document explains the backend in beginner-friendly language.

Big picture

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:
    r.Get("/resources", handlers.ListResources)
    
  2. The /api router has middleware:
    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:

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:

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:

S3_BUCKET non-empty + AWS config loads => upload to S3

Otherwise:

save file to UPLOAD_DIR and return /uploads/<filename>

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.