Files
Arkie-Library-Backend/docs/ARCHITECTURE.md

181 lines
4.7 KiB
Markdown
Raw Normal View History

2026-05-18 07:56:27 +08:00
# 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/<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.