M cmd/felu/main.go => cmd/felu/main.go +10 -8
@@ 1,9 1,11 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
+// nolint
 package main
 
 import (
@@ 25,7 27,7 @@ import (
 var version = "notset-builtin"
 
 var (
-	g errgroup.Group
+	g              errgroup.Group
 	sessionManager *scs.SessionManager
 )
 
@@ 49,16 51,16 @@ func main() {
 	sessionManager.Lifetime = 6 * time.Hour
 
 	frontend := &http.Server{
-		Addr: config.FeluConfig.FrontendBindAddr,
-		Handler: sessionManager.LoadAndSave(routers.SetupFrontendRouter(sessionManager)),
-		ReadTimeout: 5 * time.Second,
+		Addr:         config.FeluConfig.FrontendBindAddr,
+		Handler:      sessionManager.LoadAndSave(routers.SetupFrontendRouter(sessionManager)),
+		ReadTimeout:  5 * time.Second,
 		WriteTimeout: 10 * time.Second,
 	}
 
 	api := &http.Server{
-		Addr: config.FeluConfig.APIBindAddr,
-		Handler: routers.SetupAPIRouter(version),
-		ReadTimeout: 5 * time.Second,
+		Addr:         config.FeluConfig.APIBindAddr,
+		Handler:      routers.SetupAPIRouter(version),
+		ReadTimeout:  5 * time.Second,
 		WriteTimeout: 10 * time.Second,
 	}
 
 
M internal/api/update.go => internal/api/update.go +9 -6
@@ 1,9 1,11 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
+// Package api implements API route handlers.
 package api
 
 import (
@@ 14,13 16,14 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// UpdateA updates an A record based on query params.
 func UpdateA() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		domain := c.Query("domain")
 		if domain == "" {
 			c.JSON(http.StatusBadRequest, gin.H{
 				"status": "error",
-				"error": "no domain was provided",
+				"error":  "no domain was provided",
 			})
 			c.Abort()
 			return
@@ 30,7 33,7 @@ func UpdateA() gin.HandlerFunc {
 		if apiKey == "" {
 			c.JSON(http.StatusBadRequest, gin.H{
 				"status": "error",
-				"error": "no api key was provided",
+				"error":  "no api key was provided",
 			})
 			c.Abort()
 			return
@@ 44,7 47,7 @@ func UpdateA() gin.HandlerFunc {
 		if err := util.CheckARecord(aRecord); err != nil {
 			c.JSON(http.StatusBadRequest, gin.H{
 				"status": "error",
-				"error": err.Error(),
+				"error":  err.Error(),
 			})
 			c.Abort()
 			return
@@ 55,14 58,14 @@ func UpdateA() gin.HandlerFunc {
 			// FIXME: Handle better, "bad api key" is just the most likely scenario
 			c.JSON(http.StatusBadRequest, gin.H{
 				"status": "error",
-				"error": "bad api key",
+				"error":  "bad api key",
 			})
 			c.Abort()
 			return
 		}
 
 		c.JSON(http.StatusOK, gin.H{
-			"status": "success",
+			"status":   "success",
 			"a_record": aRecord,
 		})
 	}
 
M internal/components/adminpartials.templ => internal/components/adminpartials.templ +2 -2
@@ 23,7 23,7 @@ templ AdminPartialUsersList(users []db.User) {
 				<tr class="border">
 					<td>
 						<div class="p-2">
-							<input class="border" value={ user.Id } disabled/>
+							<input class="border" value={ user.ID } disabled/>
 						</div>
 					</td>
 					<td>
@@ 65,7 65,7 @@ templ AdminPartialDomainsList(domains []db.Domain) {
 				<tr class="border">
 					<td>
 						<div class="p-2">
-							<input class="border" value={ domain.Id } disabled/>
+							<input class="border" value={ domain.ID } disabled/>
 						</div>
 					</td>
 					<td>
 
M internal/components/adminpartials_templ.go => internal/components/adminpartials_templ.go +2 -2
@@ 35,7 35,7 @@ func AdminPartialUsersList(users []db.User) templ.Component {
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(user.Id))
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(user.ID))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
@@ 103,7 103,7 @@ func AdminPartialDomainsList(domains []db.Domain) templ.Component {
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(domain.Id))
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(domain.ID))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
 
M internal/components/managepartials_templ.go => internal/components/managepartials_templ.go +11 -11
@@ 81,7 81,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("/manage/domains/%s", domain.Id)))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("/manage/domains/%s", domain.ID)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 89,7 89,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("#domain_patch_error_%s", domain.Id)))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("#domain_patch_error_%s", domain.ID)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 105,7 105,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_patch_error_%s", domain.Id)))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_patch_error_%s", domain.ID)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 113,7 113,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(domain.ApiKey))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(domain.APIKey))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 121,7 121,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_apikey_%s", domain.Id)))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_apikey_%s", domain.ID)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 129,7 129,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, toggleApiKeyVisibility(domain.Id))
+				templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, toggleApiKeyVisibility(domain.ID))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 137,7 137,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				var templ_7745c5c3_Var4 templ.ComponentScript = toggleApiKeyVisibility(domain.Id)
+				var templ_7745c5c3_Var4 templ.ComponentScript = toggleApiKeyVisibility(domain.ID)
 				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4.Call)
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
@@ 146,7 146,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_eye_vis_%s", domain.Id)))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_eye_vis_%s", domain.ID)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 154,7 154,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_eye_hid_%s", domain.Id)))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("domain_eye_hid_%s", domain.ID)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 162,7 162,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("/manage/domains/%s/api_key", domain.Id)))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("/manage/domains/%s/api_key", domain.ID)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
@@ 170,7 170,7 @@ func ManagePartialDomains(domains []db.Domain) templ.Component {
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("/manage/domains/%s", domain.Id)))
+				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("/manage/domains/%s", domain.ID)))
 				if templ_7745c5c3_Err != nil {
 					return templ_7745c5c3_Err
 				}
 
M internal/config/config.go => internal/config/config.go +11 -7
@@ 1,13 1,16 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
+// Package config implements the global program config.
 package config
 
 import "git.src.quest/~skye/erya-go/util"
 
+// FeluConfig is a global for accessing the global config.
 var FeluConfig *config
 
 type config struct {
@@ 19,7 22,7 @@ type config struct {
 	// Initial email for the admin user, only used if no admin account (e.g. first boot)
 	InitialAdminEmail string
 	// Initial password for the admin user, only used if no admin account (e.g. first boot)
-	InitialAdminPwd   string
+	InitialAdminPwd string
 
 	LogLevel string
 
@@ 33,17 36,18 @@ type config struct {
 	DNSBindIP   string
 	DNSBindPort int32
 	// Domain pattern, no leading dot, but with trailing dot
-	DNSPattern  string
+	DNSPattern string
 }
 
+// InitConfig initializes the global program config from environment variables.
 func InitConfig() {
-	FeluConfig = &config {
+	FeluConfig = &config{
 		ServiceName: util.LoadEnvStr("FELU_SERVICE_NAME", "FeluDDNS"),
 
 		APIUrl: util.LoadEnvStr("FELU_API_URL", "MUST_SET"),
 
 		InitialAdminEmail: util.LoadEnvStr("FELU_INITIAL_ADMIN_EMAIL", "admin@example.com"),
-		InitialAdminPwd: util.LoadEnvStr("FELU_INITIAL_ADMIN_PWD", "feluadmin"),
+		InitialAdminPwd:   util.LoadEnvStr("FELU_INITIAL_ADMIN_PWD", "feluadmin"),
 
 		LogLevel: util.LoadEnvStr("FELU_LOG_LEVEL", "info"),
 
@@ 53,8 57,8 @@ func InitConfig() {
 
 		APIBindAddr: util.LoadEnvStr("FELU_API_BIND_ADDR", "0.0.0.0:8081"),
 
-		DNSBindIP: util.LoadEnvStr("FELU_DNS_BIND_IP", "0.0.0.0"),
+		DNSBindIP:   util.LoadEnvStr("FELU_DNS_BIND_IP", "0.0.0.0"),
 		DNSBindPort: util.LoadEnvInt32("FELU_DNS_BIND_PORT", 53),
-		DNSPattern: util.LoadEnvStr("FELU_DNS_PATTERN", "."),
+		DNSPattern:  util.LoadEnvStr("FELU_DNS_PATTERN", "."),
 	}
 }
 
M internal/db/db.go => internal/db/db.go +10 -2
@@ 1,9 1,11 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
+// Package db implements database migrations and manipulation utilities.
 package db
 
 import (
@@ 11,15 13,18 @@ import (
 	"log/slog"
 
 	"git.src.quest/~skye/felu-ddns/internal/config"
+	// nolint
 	_ "github.com/mattn/go-sqlite3"
 )
 
+// DBConn is a global for accessing the database connection.
 var DBConn *sql.DB
 
+// InitDB initializes the programs database.
 func InitDB() error {
 	var err error
 	DBConn, err = sql.Open("sqlite3",
-		config.FeluConfig.DataDir + "felu.db?_foreign_keys=true")
+		config.FeluConfig.DataDir+"felu.db?_foreign_keys=true")
 	if err != nil {
 		return err
 	}
@@ 29,6 34,9 @@ func InitDB() error {
 	return nil
 }
 
+// InitAdminUser initializes the programs admin user.
+//
+// Errors out if at least one admin user already exists.
 func InitAdminUser() error {
 	rows, err := DBConn.Query(`SELECT id FROM users WHERE is_admin = TRUE`)
 	if err != nil {
 
M internal/db/domains.go => internal/db/domains.go +39 -28
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package db
 
 import (
@@ 13,23 14,26 @@ import (
 	"github.com/oklog/ulid/v2"
 )
 
+// DomainOwner contains a domains owners information.
 type DomainOwner struct {
 	ID    string
 	Email string
 }
 
+// Domain contains a domains information.
 type Domain struct {
-	Id      string
-	ApiKey  string
-	Domain  string
-	A       string
+	ID     string
+	APIKey string
+	Domain string
+	A      string
 
 	Owner DomainOwner
 }
 
-func FetchDomainsForUser(userId string) ([]Domain, error) {
+// FetchDomainsForUser fetches all domains for a specific user.
+func FetchDomainsForUser(userID string) ([]Domain, error) {
 	rows, err := DBConn.Query(`SELECT id, apikey, ddns_domain, a_record
-		FROM domains WHERE owner = $1`, userId)
+		FROM domains WHERE owner = $1`, userID)
 	if err != nil {
 		return nil, err
 	}
@@ 38,7 42,7 @@ func FetchDomainsForUser(userId string) ([]Domain, error) {
 	var domains []Domain
 	for rows.Next() {
 		var domain Domain
-		err = rows.Scan(&domain.Id, &domain.ApiKey, &domain.Domain, &domain.A)
+		err = rows.Scan(&domain.ID, &domain.APIKey, &domain.Domain, &domain.A)
 		if err != nil {
 			return nil, err
 		}
@@ 52,6 56,7 @@ func FetchDomainsForUser(userId string) ([]Domain, error) {
 	return domains, nil
 }
 
+// FetchAllDomains fetches all domains.
 func FetchAllDomains() ([]Domain, error) {
 	rows, err := DBConn.Query(`SELECT id, ddns_domain, a_record, owner
 		FROM domains`)
@@ 63,7 68,7 @@ func FetchAllDomains() ([]Domain, error) {
 	var domains []Domain
 	for rows.Next() {
 		var domain Domain
-		err = rows.Scan(&domain.Id, &domain.Domain, &domain.A, &domain.Owner.ID)
+		err = rows.Scan(&domain.ID, &domain.Domain, &domain.A, &domain.Owner.ID)
 		if err != nil {
 			return nil, err
 		}
@@ 84,9 89,10 @@ func FetchAllDomains() ([]Domain, error) {
 	return domains, nil
 }
 
+// CreateDomain creates a domains.
 func CreateDomain(domain string, aRecord string, owner string) error {
 	ulid := ulid.Make().String()
-	apikey := util.GenApiKey()
+	apikey := util.GenAPIKey()
 	_, err := DBConn.Exec(`INSERT INTO domains(id, apikey, ddns_domain, a_record, owner)
 		VALUES ($1, $2, $3, $4, $5)`, ulid, apikey, domain, aRecord, owner)
 	if err != nil {
@@ 96,56 102,61 @@ func CreateDomain(domain string, aRecord string, owner string) error {
 	return nil
 }
 
-func DeleteDomain(id string, user_id string) error {
-	_, err := DBConn.Exec(`DELETE FROM domains WHERE id = $1 AND owner = $2`, id, user_id)
+// DeleteDomain deletes a domain.
+func DeleteDomain(id string, userID string) error {
+	_, err := DBConn.Exec(`DELETE FROM domains WHERE id = $1 AND owner = $2`, id, userID)
 	if err != nil {
 		return err
 	}
 	return nil
 }
 
-func DeleteDomainsForUser(userId string) error {
-	_, err := DBConn.Exec(`DELETE FROM domains WHERE owner = $1`, userId)
+// DeleteDomainsForUser deletes all domains for a user.
+func DeleteDomainsForUser(userID string) error {
+	_, err := DBConn.Exec(`DELETE FROM domains WHERE owner = $1`, userID)
 	if err != nil {
 		return err
 	}
 	return nil
 }
 
-func FetchDomainARecord(ddns_domain string) (string, error) {
+// FetchDomainARecord fetches the A record of a domain.
+func FetchDomainARecord(ddnsDomain string) (string, error) {
 	var aRecord string
 	err := DBConn.QueryRow(`SELECT a_record FROM domains WHERE ddns_domain = $1`,
-		ddns_domain).Scan(&aRecord)
+		ddnsDomain).Scan(&aRecord)
 	if err != nil {
 		return "", err
 	}
 	return aRecord, nil
 }
 
-func UpdateDomainARecord(ddns_domain string, providedApiKey string, aRecord string) error {
-	var domainApiKey string
+// UpdateDomainARecord updates the A record of a domain.
+func UpdateDomainARecord(ddnsDomain string, providedAPIKey string, aRecord string) error {
+	var domainAPIKey string
 	err := DBConn.QueryRow(`SELECT apikey FROM domains WHERE ddns_domain = $1`,
-		ddns_domain).Scan(&domainApiKey)
+		ddnsDomain).Scan(&domainAPIKey)
 	if err != nil {
 		return err
 	}
 
-	if domainApiKey != providedApiKey {
+	if domainAPIKey != providedAPIKey {
 		return errors.New("API key doesn't match")
 	}
 
 	_, err = DBConn.Exec(`UPDATE domains SET a_record = $1 WHERE ddns_domain = $2`,
-		aRecord, ddns_domain)
+		aRecord, ddnsDomain)
 	if err != nil {
 		return err
 	}
-	
+
 	return nil
 }
 
-func UpdateDomainARecordManual(id string, userId string, aRecord string) error {
+// UpdateDomainARecordManual updates the A record of a domain.
+func UpdateDomainARecordManual(id string, userID string, aRecord string) error {
 	_, err := DBConn.Exec(`UPDATE domains SET a_record = $1 WHERE id = $2 AND owner = $3`,
-		aRecord, id, userId)
+		aRecord, id, userID)
 	if err != nil {
 		return err
 	}
@@ 153,10 164,10 @@ func UpdateDomainARecordManual(id string, userId string, aRecord string) error {
 	return nil
 }
 
-
-func RefreshDomainApiKey(id string, user_id string) error {
-	apiKey := util.GenApiKey()
-	_, err := DBConn.Exec(`UPDATE domains SET apikey = $1 WHERE id = $2 AND owner = $3`, apiKey, id, user_id)
+// RefreshDomainAPIKey refreshes the API key of a domain.
+func RefreshDomainAPIKey(id string, userID string) error {
+	apiKey := util.GenAPIKey()
+	_, err := DBConn.Exec(`UPDATE domains SET apikey = $1 WHERE id = $2 AND owner = $3`, apiKey, id, userID)
 	if err != nil {
 		return err
 	}
 
M internal/db/migrations.go => internal/db/migrations.go +6 -6
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package db
 
 import (
@@ 12,12 13,12 @@ import (
 	"os"
 )
 
-var migrationsTable string = "schema_migrations"
+var migrationsTable = "schema_migrations"
 
 func runMigrations() {
 	slog.Info("Running migrations")
 
-	var schemaVersion int = 0
+	var schemaVersion int
 
 	schemaVersionQuery := fmt.Sprintf(
 		`SELECT schema_version
@@ 36,7 37,7 @@ func runMigrations() {
 	if schemaVersion != len(migrations) {
 		for i := 0; i < len(migrations); i++ {
 			if i >= schemaVersion {
-				slog.Info("Running migration", slog.Int("version", i + 1)) // + 1 is just a visual thing
+				slog.Info("Running migration", slog.Int("version", i+1)) // + 1 is just a visual thing
 				_, err := DBConn.Exec(migrations[i])
 				if err != nil {
 					slog.Error("Migration failed to run!", slog.Int("version", i))
@@ 55,9 56,8 @@ func runMigrations() {
 		if err != nil {
 			slog.Error("Migrations ran, but was not able to create migration entry")
 			os.Exit(1)
-		} else {
-			slog.Info("Migrations ran successfully")
 		}
+		slog.Info("Migrations ran successfully")
 	} else {
 		slog.Info("No migrations to run")
 	}
 
M internal/db/users.go => internal/db/users.go +19 -7
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package db
 
 import (
@@ 14,6 15,7 @@ import (
 	"github.com/oklog/ulid/v2"
 )
 
+// CreateUser creates a user.
 func CreateUser(email string, pwd string) error {
 	argon := argon2.DefaultConfig()
 	encoded, err := argon.HashEncoded([]byte(pwd))
@@ 32,6 34,7 @@ func CreateUser(email string, pwd string) error {
 	return nil
 }
 
+// CreateAdmin creates an admin user.
 func CreateAdmin(email string, pwd string) error {
 	argon := argon2.DefaultConfig()
 	encoded, err := argon.HashEncoded([]byte(pwd))
@@ 50,17 53,19 @@ func CreateAdmin(email string, pwd string) error {
 	return nil
 }
 
+// User contains a users relevant information.
 type User struct {
-	Id    string
+	ID      string
 	Email   string
 	IsAdmin bool
 }
 
+// FetchUserWithCreds fetches a users information based on given credentials.
 func FetchUserWithCreds(email string, pwd string) (*User, error) {
-	user := User{ Email: email }
+	user := User{Email: email}
 	var encodedPwd string
 	err := DBConn.QueryRow(`SELECT id, pwd FROM users WHERE email = $1`,
-		email).Scan(&user.Id, &encodedPwd)
+		email).Scan(&user.ID, &encodedPwd)
 	if err == sql.ErrNoRows {
 		return nil, errors.New("User not found")
 	}
@@ 79,8 84,9 @@ func FetchUserWithCreds(email string, pwd string) (*User, error) {
 	return &user, nil
 }
 
-func FetchUserWithId(id string) (*User, error) {
-	user := User{ Id: id }
+// FetchUserWithID fetches a users information.
+func FetchUserWithID(id string) (*User, error) {
+	user := User{ID: id}
 	err := DBConn.QueryRow(`SELECT email, is_admin FROM users WHERE id = $1`,
 		id).Scan(&user.Email, &user.IsAdmin)
 	if err == sql.ErrNoRows {
@@ 93,6 99,7 @@ func FetchUserWithId(id string) (*User, error) {
 	return &user, nil
 }
 
+// FetchAllUsers fetches all users.
 func FetchAllUsers() ([]User, error) {
 	rows, err := DBConn.Query(`SELECT id, email, is_admin FROM users`)
 	if err != nil {
@@ 103,7 110,7 @@ func FetchAllUsers() ([]User, error) {
 	var users []User
 	for rows.Next() {
 		var user User
-		err = rows.Scan(&user.Id, &user.Email, &user.IsAdmin)
+		err = rows.Scan(&user.ID, &user.Email, &user.IsAdmin)
 		if err != nil {
 			return nil, err
 		}
@@ 117,6 124,7 @@ func FetchAllUsers() ([]User, error) {
 	return users, nil
 }
 
+// FetchUserEmail fetches a users email address.
 func FetchUserEmail(id string) (string, error) {
 	var email string
 	err := DBConn.QueryRow(`SELECT email FROM users WHERE id = $1`,
@@ 128,6 136,7 @@ func FetchUserEmail(id string) (string, error) {
 	return email, nil
 }
 
+// DeleteUser deletes a user.
 func DeleteUser(id string) error {
 	err := DeleteDomainsForUser(id)
 	if err != nil {
@@ 140,6 149,7 @@ func DeleteUser(id string) error {
 	return nil
 }
 
+// UpdateUserEmail updates a users email address.
 func UpdateUserEmail(id string, email string) error {
 	_, err := DBConn.Exec(`UPDATE users SET email = $1 WHERE id = $2`,
 		email, id)
@@ 149,6 159,7 @@ func UpdateUserEmail(id string, email string) error {
 	return nil
 }
 
+// UpdateUserPassword updates a users password.
 func UpdateUserPassword(id string, pwd string) error {
 	argon := argon2.DefaultConfig()
 	encoded, err := argon.HashEncoded([]byte(pwd))
@@ 165,6 176,7 @@ func UpdateUserPassword(id string, pwd string) error {
 	return nil
 }
 
+// VerifyUserPassword verifies a users password.
 func VerifyUserPassword(id string, pwd string) bool {
 	// FIXME: Currently doesn't return any errors, and just return false in error cases
 	// I mean... Shouldn't really error, but who knows
 
M internal/dns/handle.go => internal/dns/handle.go +3 -2
@@ 1,14 1,15 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package dns
 
 import "github.com/miekg/dns"
 
-func handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) {
+func handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
 	m := new(dns.Msg)
 	m.SetReply(r)
 	m.Compress = false
 
M internal/dns/query.go => internal/dns/query.go +4 -3
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package dns
 
 import (
@@ 33,8 34,8 @@ func handleARecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
 			m.SetRcode(r, dns.RcodeNameError)
 		} else {
 			m.Answer = append(m.Answer, &dns.A{
-				Hdr: dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60 },
-				A: net.ParseIP(aRecord),
+				Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60},
+				A:   net.ParseIP(aRecord),
 			})
 		}
 	} else {
 
M internal/dns/server.go => internal/dns/server.go +6 -3
@@ 1,9 1,11 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
+// Package dns implements all required DNS functionality for the program.
 package dns
 
 import (
@@ 11,12 13,13 @@ import (
 	"github.com/miekg/dns"
 )
 
+// Run starts the DNS server.
 func Run(addr string) error {
-	dns.HandleFunc(config.FeluConfig.DNSPattern, handleDnsRequest)
+	dns.HandleFunc(config.FeluConfig.DNSPattern, handleDNSRequest)
 
 	server := &dns.Server{
 		Addr: addr,
-		Net: "udp",
+		Net:  "udp",
 	}
 
 	return server.ListenAndServe()
 
M internal/handlers/admin.go => internal/handlers/admin.go +5 -1
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 13,18 14,21 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// ManageAdmin returns a gin handler
 func ManageAdmin() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		c.HTML(http.StatusOK, "", components.ManageAdmin())
 	}
 }
 
+// ManageAdminUsers returns a gin handler
 func ManageAdminUsers() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		c.HTML(http.StatusOK, "", components.ManageAdminUsers())
 	}
 }
 
+// ManageAdminDomains returns a gin handler
 func ManageAdminDomains() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		c.HTML(http.StatusOK, "", components.ManageAdminDomains())
 
M internal/handlers/adminpartials.go => internal/handlers/adminpartials.go +4 -1
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 14,6 15,7 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// AdminPartialUsersList returns a gin handler
 func AdminPartialUsersList() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		users, err := db.FetchAllUsers()
@@ 27,6 29,7 @@ func AdminPartialUsersList() gin.HandlerFunc {
 	}
 }
 
+// AdminPartialDomainsList returns a gin handler
 func AdminPartialDomainsList() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		users, err := db.FetchAllDomains()
 
M internal/handlers/auth.go => internal/handlers/auth.go +5 -2
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 19,6 20,7 @@ type postAuthLoginData struct {
 	Password string `form:"password"`
 }
 
+// AuthLogin returns a gin handler
 func AuthLogin(sm *scs.SessionManager) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		data := &postAuthLoginData{}
@@ 34,12 36,13 @@ func AuthLogin(sm *scs.SessionManager) gin.HandlerFunc {
 			return
 		}
 
-		sm.Put(c.Request.Context(), "user_id", user.Id)
+		sm.Put(c.Request.Context(), "user_id", user.ID)
 
 		c.Header("HX-Redirect", "/manage")
 	}
 }
 
+// AuthLogout returns a gin handler
 func AuthLogout(sm *scs.SessionManager) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		sm.Destroy(c.Request.Context())
 
M internal/handlers/domains.go => internal/handlers/domains.go +15 -10
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 21,6 22,7 @@ type postDomainData struct {
 	ARecord string `form:"a_record"`
 }
 
+// PostDomain returns a gin handler
 func PostDomain() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		data := &postDomainData{}
@@ 52,14 54,14 @@ func PostDomain() gin.HandlerFunc {
 			return
 		}
 
-		userId, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
 			c.Abort()
 			return
 		}
 
-		err := db.CreateDomain(data.Domain, data.ARecord, userId.(string))
+		err := db.CreateDomain(data.Domain, data.ARecord, userID.(string))
 		if err != nil {
 			// FIXME: Handle better
 			c.String(http.StatusInternalServerError, "Something went wrong while creating a new domain")
@@ 71,9 73,10 @@ func PostDomain() gin.HandlerFunc {
 	}
 }
 
+// PatchDomain returns a gin handler
 func PatchDomain() gin.HandlerFunc {
 	return func(c *gin.Context) {
-		userId, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
 			c.Abort()
@@ 89,7 92,7 @@ func PatchDomain() gin.HandlerFunc {
 				c.Abort()
 				return
 			}
-			err := db.UpdateDomainARecordManual(id, userId.(string), aRecord)
+			err := db.UpdateDomainARecordManual(id, userID.(string), aRecord)
 			if err != nil {
 				// FIXME: Handle better
 				c.String(http.StatusInternalServerError, "Something went wrong while updating the a record")
@@ 102,18 105,19 @@ func PatchDomain() gin.HandlerFunc {
 	}
 }
 
+// DeleteDomain returns a gin handler
 func DeleteDomain() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		id := c.Param("id")
 
-		userId, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
 			c.Abort()
 			return
 		}
 
-		err := db.DeleteDomain(id, userId.(string))
+		err := db.DeleteDomain(id, userID.(string))
 		if err != nil {
 			// FIXME: Handle better
 			c.String(http.StatusInternalServerError, "Something went wrong while deleting the domain")
@@ 125,18 129,19 @@ func DeleteDomain() gin.HandlerFunc {
 	}
 }
 
-func RefreshDomainApiKey() gin.HandlerFunc {
+// RefreshDomainAPIKey returns a gin handler
+func RefreshDomainAPIKey() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		id := c.Param("id")
 
-		userId, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
 			c.Abort()
 			return
 		}
 
-		err := db.RefreshDomainApiKey(id, userId.(string))
+		err := db.RefreshDomainAPIKey(id, userID.(string))
 		if err != nil {
 			// FIXME: Handle better
 			c.String(http.StatusInternalServerError, "Something went wrong while updating the api key")
 
M internal/handlers/index.go => internal/handlers/index.go +4 -1
@@ 1,9 1,11 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
+// Package handlers implements all request handlers.
 package handlers
 
 import (
@@ 13,6 15,7 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// Index returns a gin handler
 func Index() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		c.HTML(http.StatusOK, "", components.Index())
 
M internal/handlers/login.go => internal/handlers/login.go +3 -1
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 13,6 14,7 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// Login returns a gin handler
 func Login() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		c.HTML(http.StatusOK, "", components.Login())
 
M internal/handlers/manage.go => internal/handlers/manage.go +6 -3
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 14,22 15,24 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// Manage returns a gin handler
 func Manage() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		c.HTML(http.StatusOK, "", components.Manage())
 	}
 }
 
+// ManageUser returns a gin handler
 func ManageUser() gin.HandlerFunc {
 	return func(c *gin.Context) {
-		user_id, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that, S01E01")
 			c.Abort()
 			return
 		}
 
-		user, err := db.FetchUserWithId(user_id.(string))
+		user, err := db.FetchUserWithID(userID.(string))
 		if err != nil {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that, S01E02")
 			c.Abort()
 
M internal/handlers/managepartials.go => internal/handlers/managepartials.go +5 -3
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 14,16 15,17 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// ManagePartialDomains returns a gin handler
 func ManagePartialDomains() gin.HandlerFunc {
 	return func(c *gin.Context) {
-		user_id, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
 			c.Abort()
 			return
 		}
 
-		domains, err := db.FetchDomainsForUser(user_id.(string))
+		domains, err := db.FetchDomainsForUser(userID.(string))
 		if err != nil {
 			c.String(http.StatusInternalServerError, "Failed to fetch domains for user")
 			c.Abort()
 
M internal/handlers/user.go => internal/handlers/user.go +12 -8
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 20,6 21,7 @@ type postUserPasswordData struct {
 	ConfirmNewPassword string `form:"confirm_new_password"`
 }
 
+// PostUserPassword returns a gin handler
 func PostUserPassword() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		data := &postUserPasswordData{}
@@ 40,20 42,20 @@ func PostUserPassword() gin.HandlerFunc {
 			return
 		}
 
-		userId, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
 			c.Abort()
 			return
 		}
 
-		if !db.VerifyUserPassword(userId.(string), data.CurrentPassword) {
+		if !db.VerifyUserPassword(userID.(string), data.CurrentPassword) {
 			c.String(http.StatusBadRequest, "Current password is not correct")
 			c.Abort()
 			return
 		}
 
-		err := db.UpdateUserPassword(userId.(string), data.NewPassword)
+		err := db.UpdateUserPassword(userID.(string), data.NewPassword)
 		if err != nil {
 			// FIXME: Handle better
 			c.String(http.StatusInternalServerError, "Something went wrong while deleting the user")
@@ 69,6 71,7 @@ type postUserEmailData struct {
 	Email string `form:"email"`
 }
 
+// PostUserEmail returns a gin handler
 func PostUserEmail() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		data := &postUserEmailData{}
@@ 84,14 87,14 @@ func PostUserEmail() gin.HandlerFunc {
 			return
 		}
 
-		userId, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
 			c.Abort()
 			return
 		}
 
-		err := db.UpdateUserEmail(userId.(string), data.Email)
+		err := db.UpdateUserEmail(userID.(string), data.Email)
 		if err != nil {
 			// FIXME: Handle better
 			c.String(http.StatusInternalServerError, "Something went wrong while deleting the user")
@@ 103,16 106,17 @@ func PostUserEmail() gin.HandlerFunc {
 	}
 }
 
+// DeleteUser returns a gin handler
 func DeleteUser(sm *scs.SessionManager) gin.HandlerFunc {
 	return func(c *gin.Context) {
-		userId, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if !exists {
 			c.String(http.StatusInternalServerError, "This should not be possible, but don't quote me on that")
 			c.Abort()
 			return
 		}
 
-		err := db.DeleteUser(userId.(string))
+		err := db.DeleteUser(userID.(string))
 		if err != nil {
 			// FIXME: Handle better
 			c.String(http.StatusInternalServerError, "Something went wrong while deleting the user")
 
M internal/handlers/users.go => internal/handlers/users.go +3 -1
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package handlers
 
 import (
@@ 18,6 19,7 @@ type postUserData struct {
 	InitialPwd string `form:"initial_pwd"`
 }
 
+// PostUser returns a gin handler
 func PostUser() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		data := &postUserData{}
 
M internal/log/ginformat.go => internal/log/ginformat.go +3 -1
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package log
 
 import (
@@ 13,6 14,7 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// GinFormat is a custom gin log formatting function.
 func GinFormat(param gin.LogFormatterParams, router string) string {
 	var statusColor, methodColor, resetColor string
 	if param.IsOutputColor() {
 
M internal/log/log.go => internal/log/log.go +6 -3
@@ 1,9 1,11 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
+// Package log implements logging utilities.
 package log
 
 import (
@@ 12,6 14,7 @@ import (
 	"path/filepath"
 )
 
+// InitDefaultLogger initializes the default logger.
 func InitDefaultLogger(logLevel string) {
 	var feluLogLevel = new(slog.LevelVar)
 	switch logLevel {
@@ 35,8 38,8 @@ func InitDefaultLogger(logLevel string) {
 	}
 
 	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
-		Level: feluLogLevel,
-		AddSource: true,
+		Level:       feluLogLevel,
+		AddSource:   true,
 		ReplaceAttr: replace,
 	}))
 
 
M internal/middlewares/admin.go => internal/middlewares/admin.go +5 -3
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package middlewares
 
 import (
@@ 13,11 14,12 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// AdminOnly returns a gin middleware for checking if a user is an admin.
 func AdminOnly() gin.HandlerFunc {
 	return func(c *gin.Context) {
-		user_id, exists := c.Get("user_id")
+		userID, exists := c.Get("user_id")
 		if exists {
-			user, err := db.FetchUserWithId(user_id.(string))
+			user, err := db.FetchUserWithID(userID.(string))
 			if err == nil {
 				if user.IsAdmin {
 					c.Next()
 
M internal/middlewares/auth.go => internal/middlewares/auth.go +6 -4
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package middlewares
 
 import (
@@ 13,16 14,17 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// SessionExists returns a gin middleware for checking if a session exists.
 func SessionExists(sm *scs.SessionManager) gin.HandlerFunc {
 	return func(c *gin.Context) {
-		user_id := sm.Get(c.Request.Context(), "user_id")
-		if user_id != nil {
+		userID := sm.Get(c.Request.Context(), "user_id")
+		if userID != nil {
 			if c.Request.URL.Path == "/login" {
 				c.Redirect(http.StatusTemporaryRedirect, "/manage")
 				c.Abort()
 			} else {
 				// Set user_id in context, if needed later (e.g. AdminOnly middleware)
-				c.Set("user_id", user_id)
+				c.Set("user_id", userID)
 				// TODO: Validate in db?
 				c.Next()
 			}
 
A internal/middlewares/middlewares.go => internal/middlewares/middlewares.go +9 -0
@@ 0,0 1,9 @@
+/*
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
+ * more information.
+ */
+
+// Package middlewares implements gin middlewares.
+package middlewares
 
M internal/renderer/renderer.go => internal/renderer/renderer.go +9 -3
@@ 1,9 1,11 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
+// Package renderer implements templ rendering.
 package renderer
 
 import (
@@ 14,12 16,14 @@ import (
 	"github.com/gin-gonic/gin/render"
 )
 
+// TemplRender holds the required info for rendering templ components.
 type TemplRender struct {
 	Code int
 	Data templ.Component
 }
 
-func (t TemplRender) Render (w http.ResponseWriter) error {
+// Render renders templ templates to HTML (and friends).
+func (t TemplRender) Render(w http.ResponseWriter) error {
 	w.WriteHeader(t.Code)
 	if t.Data != nil {
 		return t.Data.Render(context.Background(), w)
@@ 28,11 32,13 @@ func (t TemplRender) Render (w http.ResponseWriter) error {
 	return nil
 }
 
+// WriteContentType sets the Content-Type header.
 func (t TemplRender) WriteContentType(w http.ResponseWriter) {
 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
 }
 
-func (t *TemplRender) Instance(name string, data interface{}) render.Render {
+// Instance returns an instance of the templ renderer.
+func (t *TemplRender) Instance( /* name */ _ string, data interface{}) render.Render {
 	if templData, ok := data.(templ.Component); ok {
 		return &TemplRender{
 			Code: http.StatusOK,
 
M internal/routers/api.go => internal/routers/api.go +3 -1
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package routers
 
 import (
@@ 14,6 15,7 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// SetupAPIRouter returns the API router.
 func SetupAPIRouter(version string) *gin.Engine {
 	r := gin.New()
 	r.Use(gin.Recovery())
 
M internal/routers/frontend.go => internal/routers/frontend.go +4 -2
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package routers
 
 import (
@@ 15,6 16,7 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// SetupFrontendRouter returns the frontend router.
 func SetupFrontendRouter(sm *scs.SessionManager) *gin.Engine {
 	r := gin.New()
 	r.Use(gin.Recovery())
@@ 46,7 48,7 @@ func SetupFrontendRouter(sm *scs.SessionManager) *gin.Engine {
 		manage.POST("/domains", handlers.PostDomain())
 		manage.PATCH("/domains/:id", handlers.PatchDomain())
 		manage.DELETE("/domains/:id", handlers.DeleteDomain())
-		manage.POST("/domains/:id/api_key", handlers.RefreshDomainApiKey())
+		manage.POST("/domains/:id/api_key", handlers.RefreshDomainAPIKey())
 
 		manage.GET("/partials/domains", handlers.ManagePartialDomains())
 	}
 
A internal/routers/routers.go => internal/routers/routers.go +9 -0
@@ 0,0 1,9 @@
+/*
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
+ * more information.
+ */
+
+// Package routers implements both the frontend and API routers.
+package routers
 
M internal/util/apikey.go => internal/util/apikey.go +5 -3
@@ 1,20 1,22 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package util
 
 import "math/rand"
 
 const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 
-func GenApiKey() string {
+// GenAPIKey returns a 48 char string.
+func GenAPIKey() string {
 	// NOTE: "Good enough"
 	b := make([]byte, 48)
 	for i := range b {
-		b[i] = chars[rand.Int63() % int64(len(chars))]
+		b[i] = chars[rand.Int63()%int64(len(chars))]
 	}
 	return string(b)
 }
 
M internal/util/check.go => internal/util/check.go +3 -1
@@ 1,9 1,10 @@
 /*
- * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
  *
  * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
  * more information.
  */
+
 package util
 
 import (
@@ 11,6 12,7 @@ import (
 	"net"
 )
 
+// CheckARecord verifies the validity of an A record.
 func CheckARecord(aRecord string) error {
 	if net.ParseIP(aRecord).To4() == nil {
 		return errors.New("Invalid A record")
 
A internal/util/util.go => internal/util/util.go +9 -0
@@ 0,0 1,9 @@
+/*
+ * Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
+ * more information.
+ */
+
+// Package util implements a variety of small utility functions.
+package util