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