Files
edgeguard-native/internal/services/domains/domains.go
Debian 8293783fe6 feat(backends+domains): HTTP-Protokoll-Flags — force_http1 + disable_h3
Backend: force_http1 (bool, default false) — zwingt HAProxy auf der
Backend-Verbindung zu HTTP/1.1 statt h2,http/1.1 zu verhandeln.
Nötig für Legacy-Apps die kein h2 sprechen.

Domain: disable_h3 (bool, default false) — unterdrückt Alt-Svc-
Response-Header für diese Domain. Browser erhalten keinen h3/QUIC-
Hinweis und bleiben auf h2/http1.1.

Migration 0027, Model+Service+HAProxy-Template+UI+i18n.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 07:18:48 +02:00

148 lines
4.3 KiB
Go

// Package domains implements CRUD against the `domains` table.
package domains
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
)
var ErrNotFound = errors.New("domain not found")
type Repo struct {
Pool *pgxpool.Pool
}
func New(pool *pgxpool.Pool) *Repo { return &Repo{Pool: pool} }
const baseSelect = `
SELECT id, name, active, primary_backend_id, http_to_https,
hsts_enabled, hsts_max_age, hsts_subdomains, hsts_preload,
maintenance_mode, maintenance_message, www_redirect,
rate_limit_rps, max_body_kb, disable_h3,
notes, created_at, updated_at
FROM domains
`
func (r *Repo) List(ctx context.Context) ([]models.Domain, error) {
rows, err := r.Pool.Query(ctx, baseSelect+" ORDER BY name ASC")
if err != nil {
return nil, err
}
defer rows.Close()
out := make([]models.Domain, 0, 16)
for rows.Next() {
d, err := scanDomain(rows)
if err != nil {
return nil, err
}
out = append(out, *d)
}
return out, rows.Err()
}
func (r *Repo) Get(ctx context.Context, id int64) (*models.Domain, error) {
row := r.Pool.QueryRow(ctx, baseSelect+" WHERE id = $1", id)
d, err := scanDomain(row)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
return d, nil
}
func (r *Repo) Create(ctx context.Context, d models.Domain) (*models.Domain, error) {
if d.HSTSMaxAge == 0 {
d.HSTSMaxAge = 31536000
}
row := r.Pool.QueryRow(ctx, `
INSERT INTO domains (name, active, primary_backend_id, http_to_https,
hsts_enabled, hsts_max_age, hsts_subdomains, hsts_preload,
maintenance_mode, maintenance_message, www_redirect,
rate_limit_rps, max_body_kb, disable_h3, notes)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
RETURNING id, name, active, primary_backend_id, http_to_https,
hsts_enabled, hsts_max_age, hsts_subdomains, hsts_preload,
maintenance_mode, maintenance_message, www_redirect,
rate_limit_rps, max_body_kb, disable_h3,
notes, created_at, updated_at`,
d.Name, d.Active, d.PrimaryBackendID, d.HTTPToHTTPS,
d.HSTSEnabled, d.HSTSMaxAge, d.HSTSSubdomains, d.HSTSPreload,
d.MaintenanceMode, d.MaintenanceMessage, d.WWWRedirect,
d.RateLimitRPS, d.MaxBodyKB, d.DisableH3, d.Notes)
return scanDomain(row)
}
func (r *Repo) Update(ctx context.Context, id int64, d models.Domain) (*models.Domain, error) {
if d.HSTSMaxAge == 0 {
d.HSTSMaxAge = 31536000
}
row := r.Pool.QueryRow(ctx, `
UPDATE domains SET
name = $1,
active = $2,
primary_backend_id = $3,
http_to_https = $4,
hsts_enabled = $5,
hsts_max_age = $6,
hsts_subdomains = $7,
hsts_preload = $8,
maintenance_mode = $9,
maintenance_message = $10,
www_redirect = $11,
rate_limit_rps = $12,
max_body_kb = $13,
disable_h3 = $14,
notes = $15,
updated_at = NOW()
WHERE id = $16
RETURNING id, name, active, primary_backend_id, http_to_https,
hsts_enabled, hsts_max_age, hsts_subdomains, hsts_preload,
maintenance_mode, maintenance_message, www_redirect,
rate_limit_rps, max_body_kb, disable_h3,
notes, created_at, updated_at`,
d.Name, d.Active, d.PrimaryBackendID, d.HTTPToHTTPS,
d.HSTSEnabled, d.HSTSMaxAge, d.HSTSSubdomains, d.HSTSPreload,
d.MaintenanceMode, d.MaintenanceMessage, d.WWWRedirect,
d.RateLimitRPS, d.MaxBodyKB, d.DisableH3, d.Notes, id)
out, err := scanDomain(row)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
return out, nil
}
func (r *Repo) Delete(ctx context.Context, id int64) error {
tag, err := r.Pool.Exec(ctx, `DELETE FROM domains WHERE id = $1`, id)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
func scanDomain(row interface{ Scan(...any) error }) (*models.Domain, error) {
var d models.Domain
if err := row.Scan(
&d.ID, &d.Name, &d.Active, &d.PrimaryBackendID, &d.HTTPToHTTPS,
&d.HSTSEnabled, &d.HSTSMaxAge, &d.HSTSSubdomains, &d.HSTSPreload,
&d.MaintenanceMode, &d.MaintenanceMessage, &d.WWWRedirect,
&d.RateLimitRPS, &d.MaxBodyKB, &d.DisableH3,
&d.Notes, &d.CreatedAt, &d.UpdatedAt,
); err != nil {
return nil, err
}
return &d, nil
}