127 lines
6.3 KiB
Markdown
127 lines
6.3 KiB
Markdown
# Architecture
|
|
|
|
## Two-Server Design
|
|
|
|
The application runs two separate HTTP servers from a single binary:
|
|
|
|
- **Public server** (default `:8080`) — Customer-facing UI: registration, login, ticket management, OAuth callbacks, Forgejo webhooks.
|
|
- **Admin server** (default `:8081`) — Internal admin panel: dashboard, user/ticket/repo management. Protected by Tailscale authentication.
|
|
|
|
Both servers are started as goroutines in `cmd/server/main.go` and share the same database connection, template renderer, and service instances. Graceful shutdown handles SIGINT/SIGTERM with a 30-second timeout.
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
cmd/
|
|
server/
|
|
main.go # Entry point — wires dependencies, starts both servers
|
|
|
|
internal/
|
|
config/
|
|
config.go # Loads env vars into Config struct
|
|
database/
|
|
database.go # PostgreSQL connection (GORM) and migration runner
|
|
models/
|
|
models.go # GORM models: User, OAuthAccount, Session, Repo, Ticket, TicketComment, EmailToken
|
|
auth/
|
|
auth.go # Service: register, login, password hashing, session create/destroy
|
|
session.go # Session middleware, RequireAuth, CurrentUser helpers
|
|
store.go # PGStore — PostgreSQL-backed gorilla/sessions store
|
|
tokens.go # Email verification and password reset token generation/redemption
|
|
oauth.go # Google and Microsoft OAuth provider setup, FindOrCreateOAuthUser
|
|
apple.go # Apple Sign In provider (JWT client secret, ID token parsing)
|
|
middleware/
|
|
middleware.go # RequestID, Logging, Recovery middleware
|
|
csrf.go # CSRF protection via gorilla/csrf adapted for Gin
|
|
handlers/
|
|
public/
|
|
routes.go # Public router setup and route registration
|
|
home.go # Landing page
|
|
auth.go # Login, register, logout, email verification, password reset
|
|
oauth.go # OAuth login/callback for Google, Microsoft, Apple
|
|
tickets.go # Ticket list, create, detail, add comment
|
|
webhook.go # Forgejo webhook receiver (issue close -> ticket close)
|
|
admin/
|
|
routes.go # Admin router setup and route registration
|
|
auth.go # Tailscale whois-based authentication middleware
|
|
dashboard.go # Dashboard with aggregate counts
|
|
users.go # User list, detail, create
|
|
tickets.go # Ticket list (with status filter), detail, status update
|
|
repos.go # Repo list, create, edit
|
|
templates/
|
|
render.go # Template renderer: parses layouts + partials + pages, injects PageData
|
|
funcs.go # Template helper functions
|
|
email/
|
|
email.go # Postmark email client: verification, password reset, ticket closed, welcome
|
|
templates.go # HTML email templates
|
|
forgejo/
|
|
client.go # Forgejo API client: create issue, create comment
|
|
webhook.go # Webhook signature verification (HMAC-SHA256) and payload parsing
|
|
|
|
web/
|
|
templates/
|
|
layouts/
|
|
base.html # Public page layout
|
|
admin.html # Admin page layout
|
|
pages/ # Page templates (mapped by path: "login", "tickets/list", "admin/dashboard", etc.)
|
|
partials/ # Shared partials (nav, flash messages)
|
|
static/
|
|
css/
|
|
input.css # Tailwind CSS source
|
|
output.css # Compiled CSS (generated by build)
|
|
```
|
|
|
|
## Package Responsibilities
|
|
|
|
| Package | Role |
|
|
|---------|------|
|
|
| `config` | Reads all configuration from environment variables; validates required fields |
|
|
| `database` | Opens the GORM PostgreSQL connection; runs auto-migrations including enum types and custom indexes |
|
|
| `models` | Defines all database models and the `AutoMigrate` function |
|
|
| `auth` | User registration/login with bcrypt, session management via PostgreSQL-backed gorilla/sessions, email token generation/redemption, OAuth user linking |
|
|
| `middleware` | Request ID generation, structured logging, panic recovery, CSRF protection |
|
|
| `handlers/public` | All customer-facing HTTP handlers and route wiring |
|
|
| `handlers/admin` | All admin HTTP handlers, Tailscale auth middleware, and route wiring |
|
|
| `templates` | Parses Go HTML templates with layout/partial/page inheritance; injects current user, CSRF token, and flash messages into every render |
|
|
| `email` | Sends transactional emails via Postmark: verification, password reset, ticket closed notification, welcome/account creation |
|
|
| `forgejo` | Forgejo API client for creating issues and comments; webhook signature verification and payload parsing |
|
|
|
|
## Request Lifecycle
|
|
|
|
### Public Server
|
|
|
|
```
|
|
Request
|
|
-> RequestID middleware (generates X-Request-ID header)
|
|
-> Logging middleware (logs method, path, status, duration)
|
|
-> Recovery middleware (catches panics)
|
|
-> Session middleware (loads user from session cookie into context)
|
|
-> CSRF middleware (gorilla/csrf — on routes that need it)
|
|
-> Handler (processes request)
|
|
-> Template render (injects User, CSRFToken, Flash into PageData)
|
|
```
|
|
|
|
The webhook endpoint (`POST /webhooks/forgejo/:repoSlug`) sits outside the CSRF group since it authenticates via HMAC signature.
|
|
|
|
### Admin Server
|
|
|
|
```
|
|
Request
|
|
-> RequestID middleware
|
|
-> Logging middleware
|
|
-> Recovery middleware
|
|
-> Tailscale auth middleware (whois lookup to verify allowed user)
|
|
-> Handler
|
|
-> Template render
|
|
```
|
|
|
|
The admin server has no session/CSRF middleware — authentication is handled entirely by Tailscale identity.
|
|
|
|
## Gin + Gorilla Interop
|
|
|
|
The application uses [Gin](https://github.com/gin-gonic/gin) as the HTTP framework but relies on [gorilla/sessions](https://github.com/gorilla/sessions) for session management and [gorilla/csrf](https://github.com/gorilla/csrf) for CSRF protection.
|
|
|
|
Gorilla middleware expects `http.ResponseWriter` and `*http.Request`, while Gin provides `*gin.Context`. The CSRF middleware bridges this by wrapping Gin's handler chain as an `http.Handler` and passing `c.Writer` / `c.Request` through. After gorilla/csrf processes the request (potentially adding context values), the modified `*http.Request` is written back to `c.Request` so downstream handlers can access the CSRF token via `csrf.Token(req)`.
|
|
|
|
See [Configuration](./configuration.md) for environment variable details and [Deployment](./deployment.md) for running the application.
|