# 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.