Code documentation

This commit is contained in:
2025-10-29 08:36:10 +00:00
parent 8d2ce27a74
commit 244b882f11
7 changed files with 458 additions and 11 deletions

View File

@@ -1,3 +1,37 @@
// Package config
// Path: /internal/platform/config
// File: config.go
//
// Purpose
// Provide a safe one-time initialization and global access point for
// the application's Config object, once it has been constructed during
// bootstrap.
//
// This allows other packages to retrieve configuration without needing
// dependency injection at every call site, while still preventing
// accidental mutation after init.
//
// Responsibilities (as implemented here)
// 1) Store a single *Config instance for the lifetime of the process.
// 2) Ensure Init() can only succeed once via sync.Once.
// 3) Expose Get() as a global accessor.
//
// Design notes
// - Config is written once at startup via Init() inside bootstrap.
// - Calls to Init() after the first are ignored silently.
// - Get() may return nil if called before Init() — caller must ensure
// bootstrap has completed.
//
// TODOs (from current architectural direction)
// - Evaluate replacing global access with explicit dependency injection
// in future modules for stronger compile-time guarantees.
// - Consider panicking or logging if Get() is called before Init().
// - Move non-static configuration into runtime struct(s) owned by App.
// - Ensure immutability: avoid mutating Config fields after Init().
//
// Change log
// [2025-10-28] Documentation aligned with real runtime responsibilities.
package config
import (

View File

@@ -1,3 +1,34 @@
// Package config
// Path: /internal/platform/config
// File: load.go
//
// Purpose
// Responsible solely for loading strongly-typed application configuration
// from a JSON file on disk. This is the *input* stage of configuration
// lifecycle — the resulting Config is consumed by bootstrap and may be
// optionally stored globally via config.Init().
//
// Responsibilities (as implemented here)
// 1) Read configuration JSON file from a specified path.
// 2) Deserialize into the Config struct (strongly typed).
// 3) Return the populated Config value or an error.
//
// Design notes
// - Path is caller-controlled (bootstrap decides where config.json lives).
// - No defaults or validation are enforced here — errors bubble to bootstrap.
// - Pure function: no globals mutated, safe for tests and reuse.
// - Load returns a **value**, not a pointer, avoiding accidental mutation
// unless caller explicitly stores it.
//
// TODOs (from current architecture direction)
// - Add schema validation for required config fields.
// - Add environment override support for deployment flexibility.
// - Consider merging with a future layered config system (file + env + flags).
// - Emit structured errors including path details for troubleshooting.
//
// Change log
// [2025-10-29] Documentation aligned with bootstrap integration and config.Init() use.
package config
import (
@@ -5,6 +36,11 @@ import (
"os"
)
// Load reads the JSON configuration file located at `path`
// and unmarshals it into a Config struct.
//
// Caller is responsible for passing the result into bootstrap and/or
// config.Init() to make it globally available.
func Load(path string) (Config, error) {
var cfg Config

View File

@@ -1,40 +1,78 @@
// Package config
// Path: /internal/platform/config
// File: types.go
//
// Purpose
// Defines the strongly-typed configuration structure for the entire system.
// Populated from JSON via config.Load() and stored in bootstrap for use by:
// - MySQL connectivity + pooling
// - HTTP server binding + security mode
// - SCS session configuration
// - CSRF cookie policy
// - External licensing API configuration
// - Template meta (site-wide branding)
//
// Design notes
// - Nested struct fields map directly to JSON blocks.
// - Types are primarily string-based for durations (parsed by bootstrap).
// - Field names reflect actual usage in the code today.
// - All configuration values are held immutable after bootstrap.Load.
//
// TODOs (observations from current design)
// - Add `json:"database"` tag to Database struct for JSON consistency
// (currently missing; loader still works due to exported field name fallback).
// - Validate required fields at bootstrap (server/port/site name).
// - Move sensitive fields (password/API key) to env-driven overrides.
// - Convert duration strings to `time.Duration` when loading (type-safe).
//
// Change log
// [2025-10-29] Documentation created to align with full settings usage.
package config
// Config represents all runtime configuration for the application.
// Loaded from JSON and passed into bootstrap for wiring platform components.
type Config struct {
// CSRF cookie naming and storage controls
CSRF struct {
CookieName string `json:"cookieName"`
} `json:"csrf"`
// Database connection settings + tuning
Database struct {
Server string `json:"server"`
Port int `json:"port"`
DatabaseNamed string `json:"databaseName"`
MaxOpenConnections int `json:"maxOpenConnections"`
MaxIdleConnections int `json:"maxIdleConnections"`
ConnectionMaxLifetime string `json:"connectionMaxLifetime"`
DatabaseName string `json:"databaseName"`
MaxOpenConnections int `json:"maxOpenConnections"` // optional tuning
MaxIdleConnections int `json:"maxIdleConnections"` // optional tuning
ConnectionMaxLifetime string `json:"connectionMaxLifetime"` // duration as string, parsed in bootstrap
Username string `json:"username"`
Password string `json:"password"`
Password string `json:"password"` // sensitive; consider environment secrets
}
// HTTP server exposure and security toggles
HttpServer struct {
Port int `json:"port"`
Address string `json:"address"`
ProductionMode bool `json:"productionMode"`
ProductionMode bool `json:"productionMode"` // controls Secure cookie flag
} `json:"httpServer"`
// Remote licensing API service configuration
License struct {
APIURL string `json:"apiUrl"`
APIKey string `json:"apiKey"`
APIKey string `json:"apiKey"` // sensitive; consider environment secrets
} `json:"license"`
// Session (SCS) configuration: cookie names, lifetime + idle timeout
Session struct {
CookieName string `json:"cookieName"`
Lifetime string `json:"lifetime"`
IdleTimeout string `json:"idleTimeout"`
Lifetime string `json:"lifetime"` // duration as string; parsed in platform/session
IdleTimeout string `json:"idleTimeout"` // duration as string; parsed in platform/session
RememberCookieName string `json:"rememberCookieName"`
RememberDuration string `json:"rememberDuration"`
RememberDuration string `json:"rememberDuration"` // duration as string; parsed in Remember middleware
} `json:"session"`
// Site metadata provided to templates (branding/UI only)
Site struct {
SiteName string `json:"siteName"`
CopyrightYearStart int `json:"copyrightYearStart"`

View File

@@ -1,3 +1,40 @@
// Package csrf
// Path: /internal/platform/csrf
// File: csrf.go
//
// Purpose
//
// Centralized CSRF protection wrapper using justinas/nosurf.
// Applies default CSRF protections across the entire HTTP handler tree
// after SCS session load/save wrapping.
//
// Responsibilities (as implemented here)
// 1. Construct a nosurf middleware handler over the provided http.Handler.
// 2. Configure the base CSRF cookie using values from the App configuration.
// 3. Enforce HttpOnly and SameSite=Lax defaults.
// 4. Enable Secure flag automatically in production mode.
//
// HTTP stack order (per bootstrap)
//
// Gin Router → SCS LoadAndSave → CSRF Wrapper → http.Server
//
// Design notes
// - The nosurf package automatically:
// - Inserts CSRF token into responses (e.g., via nosurf.Token(c.Request))
// - Validates token on state-changing requests (POST, PUT, etc.)
// - CSRF cookie name is configurable via config.Config.
// - Secure flag is tied to cfg.HttpServer.ProductionMode (recommended).
// - Global protection: all routed POSTs are covered automatically.
//
// TODOs (observations from current implementation)
// - Expose helper to fetch token into Gin templates via context key.
// - Consider SameSiteStrictMode once OAuth/external logins are defined.
// - Add domain and MaxAge settings for more precise control.
// - Provide per-route opt-outs if needed for webhook endpoints.
//
// Change log
//
// [2025-10-29] Documentation updated to reflect middleware position and cookie policy.
package csrf
import (
@@ -8,6 +45,11 @@ import (
"github.com/justinas/nosurf"
)
// Wrap applies nosurf CSRF middleware to the given handler,
// configuring the CSRF cookie based on App configuration.
//
// Caller must ensure this is positioned *outside* SCS LoadAndSave
// so CSRF can access session data when generating/validating tokens.
func Wrap(h http.Handler, cfg config.Config) http.Handler {
cs := nosurf.New(h)
cs.SetBaseCookie(http.Cookie{

View File

@@ -1,3 +1,39 @@
// Package databasePlatform
// Path: /internal/platform/database
// File: schema.go
//
// Purpose
// Bootstrap and verify the initial application schema for MySQL using
// embedded SQL. Applies the full schema only when the target database
// is detected as "empty" via a probe query.
//
// Responsibilities (as implemented here)
// 1) Detect whether the schema has been initialized by probing the users table.
// 2) If empty, apply the embedded initial schema inside a single transaction.
// 3) Use helper ExecScript to execute multi-statement SQL safely.
// 4) Fail fast with contextual errors on probe/apply failures.
//
// Idempotency strategy
// - Uses migrationSQL.ProbeUsersTable to query a known table and count rows.
// - If count > 0, assumes schema exists and exits without applying SQL.
// - This makes startup safe to repeat across restarts.
//
// Design notes
// - InitialSchema is embedded (no external SQL files at runtime).
// - Application is all-or-nothing via a single transaction.
// - Console prints currently provide debug visibility during boot.
// - Probe focuses on the "users" table as the presence indicator.
//
// TODOs (observations from current implementation)
// - Replace debug prints with structured logging (levelled).
// - Consider probing for table existence rather than row count to avoid
// the edge case where users table exists but has zero rows.
// - Introduce a schema version table for forward migrations.
// - Expand error context to include which statement failed in ExecScript.
//
// Change log
// [2025-10-29] Documentation aligned with embedded migrations and probe logic.
package databasePlatform
import (
@@ -8,9 +44,12 @@ import (
migrationSQL "synlotto-website/internal/storage/migrations"
)
// EnsureInitialSchema ensures the database contains the baseline schema.
// If the probe indicates an existing install, the function is a no-op.
func EnsureInitialSchema(db *sql.DB) error {
fmt.Println("✅ EnsureInitialSchema called") // temp debug
// Probe: if users table exists & has rows, treat schema as present.
var cnt int
if err := db.QueryRow(migrationSQL.ProbeUsersTable).Scan(&cnt); err != nil {
return fmt.Errorf("probe users table failed: %w", err)
@@ -20,9 +59,10 @@ func EnsureInitialSchema(db *sql.DB) error {
return nil
}
// sanity: show embedded SQL length so we know it actually embedded
// Sanity: visibility for embedded SQL payload size.
fmt.Printf("📦 Initial SQL bytes: %d\n", len(migrationSQL.InitialSchema)) // temp debug
// Apply full schema atomically.
tx, err := db.Begin()
if err != nil {
return err

View File

@@ -1,3 +1,35 @@
// Package session
// Path: /internal/platform/session
// File: session.go
//
// Purpose
// Initialize and configure the SCS (Server-Side Sessions) session manager
// based on application configuration. Controls session lifetime, idle timeout,
// cookie policy, and security posture.
//
// Responsibilities (as implemented here)
// 1) Create SCS session manager used globally via bootstrap.
// 2) Parse session lifetime + idle timeout from configuration.
// 3) Apply secure cookie settings (HttpOnly, SameSite, Secure if production).
// 4) Provide sensible defaults if configuration is invalid.
//
// Design notes
// - SCS stores session data server-side (DB, file, mem, etc. — backend not set here).
// - Cookie lifespan is enforced server-side (not just client expiry).
// - Secure flag toggled via cfg.HttpServer.ProductionMode.
// - Defaults keep application functional even if config is incomplete.
//
// TODOs (observations from current implementation)
// - Add structured validation + error logging for invalid duration strings.
// - Move secure cookie flag to config for more granular environment control.
// - Consider enabling:
// • Cookie.Persist (for "keep me logged in" flows)
// • Cookie.SameSite = StrictMode by default
// - Potentially expose SCS store configuration here (DB-backed sessions).
//
// Change log
// [2025-10-29] Documentation aligned with final session architecture.
package session
import (
@@ -9,6 +41,8 @@ import (
"github.com/alexedwards/scs/v2"
)
// New constructs a new SCS SessionManager using values from Config,
// falling back to secure defaults if configuration is missing/invalid.
func New(cfg config.Config) *scs.SessionManager {
s := scs.New()