forgejo-tickets/docs/architecture.md

6.3 KiB

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 as the HTTP framework but relies on gorilla/sessions for session management and 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 for environment variable details and Deployment for running the application.