feat(db): Phase 1 — DB-Schema, goose-Migrations, GORM-Models
Initialer Schema-Set (8 Migrationen, 13 Tabellen) für EdgeGuard v1: users + audit_log + system_settings, ha_nodes, backends/domains/ routing_rules/tls_certs, forward_proxy_acls, wireguard_peers, firewall_rules, dns_zones/dns_records, licenses. Migrations liegen in internal/database/migrations/ (analog mail-gateway) und werden per //go:embed ins Binary gepackt — keine separate SQL-Dateien im .deb. ValidateMigrations + Test schützen vor Duplicate-Versionen (mail-gateway 2026-05-08-Vorfall). GORM-Models für alle Tabellen, sensible Felder (password_hash, private_key_enc) sind json:"-". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
18
internal/models/audit.go
Normal file
18
internal/models/audit.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AuditLog struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Actor string `gorm:"column:actor" json:"actor"`
|
||||
Action string `gorm:"column:action" json:"action"`
|
||||
Subject *string `gorm:"column:subject" json:"subject,omitempty"`
|
||||
Detail json.RawMessage `gorm:"column:detail;type:jsonb" json:"detail,omitempty"`
|
||||
NodeID *string `gorm:"column:node_id" json:"node_id,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
func (AuditLog) TableName() string { return "audit_log" }
|
||||
17
internal/models/backend.go
Normal file
17
internal/models/backend.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Backend struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"column:name;uniqueIndex" json:"name"`
|
||||
Scheme string `gorm:"column:scheme" json:"scheme"`
|
||||
Address string `gorm:"column:address" json:"address"`
|
||||
Port int `gorm:"column:port" json:"port"`
|
||||
HealthCheckPath *string `gorm:"column:health_check_path" json:"health_check_path,omitempty"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (Backend) TableName() string { return "backends" }
|
||||
30
internal/models/dns.go
Normal file
30
internal/models/dns.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type DNSZone struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"column:name;uniqueIndex" json:"name"`
|
||||
ZoneType string `gorm:"column:zone_type" json:"zone_type"`
|
||||
Description *string `gorm:"column:description" json:"description,omitempty"`
|
||||
ManagedBy string `gorm:"column:managed_by" json:"managed_by"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (DNSZone) TableName() string { return "dns_zones" }
|
||||
|
||||
type DNSRecord struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
ZoneID int64 `gorm:"column:zone_id" json:"zone_id"`
|
||||
Name string `gorm:"column:name" json:"name"`
|
||||
RecordType string `gorm:"column:record_type" json:"record_type"`
|
||||
Value string `gorm:"column:value" json:"value"`
|
||||
TTL int `gorm:"column:ttl" json:"ttl"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (DNSRecord) TableName() string { return "dns_records" }
|
||||
9
internal/models/doc.go
Normal file
9
internal/models/doc.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Package models holds the GORM data models for edgeguard-api: users,
|
||||
// audit_log, system_settings, ha_nodes, backends, domains,
|
||||
// routing_rules, tls_certs, forward_proxy_acls, wireguard_peers,
|
||||
// firewall_rules, dns_zones, dns_records, licenses.
|
||||
//
|
||||
// Schema is owned by goose (internal/database/migrations/) — these
|
||||
// structs only describe the row layout for query convenience; never
|
||||
// rely on GORM's AutoMigrate against this package.
|
||||
package models
|
||||
17
internal/models/domain.go
Normal file
17
internal/models/domain.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Domain struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"column:name;uniqueIndex" json:"name"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
PrimaryBackendID *int64 `gorm:"column:primary_backend_id" json:"primary_backend_id,omitempty"`
|
||||
HTTPToHTTPS bool `gorm:"column:http_to_https" json:"http_to_https"`
|
||||
HSTSEnabled bool `gorm:"column:hsts_enabled" json:"hsts_enabled"`
|
||||
Notes *string `gorm:"column:notes" json:"notes,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (Domain) TableName() string { return "domains" }
|
||||
17
internal/models/firewall_rule.go
Normal file
17
internal/models/firewall_rule.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type FirewallRule struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Chain string `gorm:"column:chain" json:"chain"`
|
||||
Priority int `gorm:"column:priority" json:"priority"`
|
||||
MatchExpr string `gorm:"column:match_expr" json:"match_expr"`
|
||||
Action string `gorm:"column:action" json:"action"`
|
||||
Comment *string `gorm:"column:comment" json:"comment,omitempty"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (FirewallRule) TableName() string { return "firewall_rules" }
|
||||
18
internal/models/forward_proxy_acl.go
Normal file
18
internal/models/forward_proxy_acl.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type ForwardProxyACL struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"column:name" json:"name"`
|
||||
ACLType string `gorm:"column:acl_type" json:"acl_type"`
|
||||
Value string `gorm:"column:value" json:"value"`
|
||||
Action string `gorm:"column:action" json:"action"`
|
||||
Priority int `gorm:"column:priority" json:"priority"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
Comment *string `gorm:"column:comment" json:"comment,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (ForwardProxyACL) TableName() string { return "forward_proxy_acls" }
|
||||
19
internal/models/ha_node.go
Normal file
19
internal/models/ha_node.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type HANode struct {
|
||||
ID string `gorm:"column:id;primaryKey" json:"id"`
|
||||
Name string `gorm:"column:name" json:"name"`
|
||||
FQDN string `gorm:"column:fqdn;uniqueIndex" json:"fqdn"`
|
||||
APIURL string `gorm:"column:api_url" json:"api_url"`
|
||||
PublicIP *string `gorm:"column:public_ip;type:inet" json:"public_ip,omitempty"`
|
||||
InternalIP *string `gorm:"column:internal_ip;type:inet" json:"internal_ip,omitempty"`
|
||||
Role string `gorm:"column:role" json:"role"`
|
||||
LastSeen *time.Time `gorm:"column:last_seen" json:"last_seen,omitempty"`
|
||||
JoinedAt time.Time `gorm:"column:joined_at" json:"joined_at"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (HANode) TableName() string { return "ha_nodes" }
|
||||
22
internal/models/license.go
Normal file
22
internal/models/license.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type License struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
LicenseKey string `gorm:"column:license_key;uniqueIndex" json:"license_key"`
|
||||
Status string `gorm:"column:status" json:"status"`
|
||||
ValidUntil *time.Time `gorm:"column:valid_until" json:"valid_until,omitempty"`
|
||||
LastVerifiedAt *time.Time `gorm:"column:last_verified_at" json:"last_verified_at,omitempty"`
|
||||
LastVerifiedNode *string `gorm:"column:last_verified_node" json:"last_verified_node,omitempty"`
|
||||
ActiveDomainsAtVerify *int `gorm:"column:active_domains_at_verify" json:"active_domains_at_verify,omitempty"`
|
||||
Payload json.RawMessage `gorm:"column:payload;type:jsonb" json:"payload,omitempty"`
|
||||
LastError *string `gorm:"column:last_error" json:"last_error,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (License) TableName() string { return "licenses" }
|
||||
16
internal/models/routing_rule.go
Normal file
16
internal/models/routing_rule.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type RoutingRule struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
DomainID int64 `gorm:"column:domain_id" json:"domain_id"`
|
||||
PathPrefix string `gorm:"column:path_prefix" json:"path_prefix"`
|
||||
BackendID int64 `gorm:"column:backend_id" json:"backend_id"`
|
||||
Priority int `gorm:"column:priority" json:"priority"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (RoutingRule) TableName() string { return "routing_rules" }
|
||||
11
internal/models/system_setting.go
Normal file
11
internal/models/system_setting.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type SystemSetting struct {
|
||||
Key string `gorm:"column:key;primaryKey" json:"key"`
|
||||
Value string `gorm:"column:value" json:"value"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (SystemSetting) TableName() string { return "system_settings" }
|
||||
20
internal/models/tls_cert.go
Normal file
20
internal/models/tls_cert.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type TLSCert struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Domain string `gorm:"column:domain;uniqueIndex" json:"domain"`
|
||||
Issuer string `gorm:"column:issuer" json:"issuer"`
|
||||
Status string `gorm:"column:status" json:"status"`
|
||||
CertPath *string `gorm:"column:cert_path" json:"cert_path,omitempty"`
|
||||
KeyPath *string `gorm:"column:key_path" json:"key_path,omitempty"`
|
||||
NotBefore *time.Time `gorm:"column:not_before" json:"not_before,omitempty"`
|
||||
NotAfter *time.Time `gorm:"column:not_after" json:"not_after,omitempty"`
|
||||
LastRenewedAt *time.Time `gorm:"column:last_renewed_at" json:"last_renewed_at,omitempty"`
|
||||
LastError *string `gorm:"column:last_error" json:"last_error,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (TLSCert) TableName() string { return "tls_certs" }
|
||||
16
internal/models/user.go
Normal file
16
internal/models/user.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type User struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Email string `gorm:"column:email;uniqueIndex" json:"email"`
|
||||
PasswordHash string `gorm:"column:password_hash" json:"-"`
|
||||
Role string `gorm:"column:role" json:"role"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
LastLoginAt *time.Time `gorm:"column:last_login_at" json:"last_login_at,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (User) TableName() string { return "users" }
|
||||
23
internal/models/wireguard_peer.go
Normal file
23
internal/models/wireguard_peer.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type WireGuardPeer struct {
|
||||
ID int64 `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"column:name;uniqueIndex" json:"name"`
|
||||
PeerType string `gorm:"column:peer_type" json:"peer_type"`
|
||||
PublicKey string `gorm:"column:public_key;uniqueIndex" json:"public_key"`
|
||||
PrivateKeyEnc *string `gorm:"column:private_key_enc" json:"-"`
|
||||
PresharedKeyEnc *string `gorm:"column:preshared_key_enc" json:"-"`
|
||||
AllowedIPs string `gorm:"column:allowed_ips" json:"allowed_ips"`
|
||||
Endpoint *string `gorm:"column:endpoint" json:"endpoint,omitempty"`
|
||||
ListenPort *int `gorm:"column:listen_port" json:"listen_port,omitempty"`
|
||||
PersistentKeepalive *int `gorm:"column:persistent_keepalive" json:"persistent_keepalive,omitempty"`
|
||||
LastHandshakeAt *time.Time `gorm:"column:last_handshake_at" json:"last_handshake_at,omitempty"`
|
||||
Active bool `gorm:"column:active" json:"active"`
|
||||
Notes *string `gorm:"column:notes" json:"notes,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (WireGuardPeer) TableName() string { return "wireguard_peers" }
|
||||
Reference in New Issue
Block a user