software steht so weit.

jetzt build + docker build und testen
This commit is contained in:
br committed 2026-10-02 13:25:34 +02:00
1 parent 42f0c0accb
commit ca6cdcca63
90 files changed
+10298 -480

No files matched your search

+49 -19
View File
@@ -50,8 +50,12 @@ type Tenant struct {
AllowedDomains []string
ValidUntil time.Time
Status TenantStatus
CreatedAt time.Time
UpdatedAt time.Time
Features []string // freigeschaltete Lizenz-Features (z. B. custom_middleware)
// PaymentOverdueSince: seit wann eine Zahlung offen ist (Nullwert = nichts
// offen); daraus berechnet die Policy die Mahnstufe.
PaymentOverdueSince time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
// normalize setzt Defaults (Tier community, Status active, leere
@@ -66,6 +70,9 @@ func (t *Tenant) normalize() error {
if t.AllowedDomains == nil {
t.AllowedDomains = []string{}
}
if t.Features == nil {
t.Features = []string{}
}
t.ValidUntil = normTime(t.ValidUntil)
switch {
@@ -89,6 +96,11 @@ func (t *Tenant) normalize() error {
return invalid("leerer Eintrag in allowed_domains")
}
}
for _, f := range t.Features {
if blank(f) {
return invalid("leerer Eintrag in features")
}
}
return nil
}
@@ -98,40 +110,47 @@ func (s *Store) CreateTenant(ctx context.Context, t Tenant) (Tenant, error) {
if err := t.normalize(); err != nil {
return Tenant{}, err
}
domains, err := json.Marshal(t.AllowedDomains)
if err != nil {
return Tenant{}, err
}
domains, _ := json.Marshal(t.AllowedDomains)
features, _ := json.Marshal(t.Features)
now := normTime(s.now())
t.CreatedAt, t.UpdatedAt = now, now
t.PaymentOverdueSince = time.Time{}
_, err = s.db.ExecContext(ctx, `INSERT INTO tenants
(id, name, email, tier, max_routes, allowed_domains, valid_until, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
_, err := s.db.ExecContext(ctx, `INSERT INTO tenants
(id, name, email, tier, max_routes, allowed_domains, valid_until, status, created_at, updated_at, features)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
t.ID, t.Name, t.Email, t.Tier, t.MaxRoutes, string(domains),
formatTime(t.ValidUntil), t.Status, formatTime(now), formatTime(now))
formatTime(t.ValidUntil), t.Status, formatTime(now), formatTime(now), string(features))
if err != nil {
return Tenant{}, mapConstraint(err, "Mandant "+t.ID)
}
return t, nil
}
const tenantColumns = `id, name, email, tier, max_routes, allowed_domains, valid_until, status, created_at, updated_at`
const tenantColumns = `id, name, email, tier, max_routes, allowed_domains, valid_until, status, created_at, updated_at,
features, payment_overdue_since`
func scanTenant(row interface{ Scan(...any) error }) (Tenant, error) {
var t Tenant
var domains string
var domains, features string
err := row.Scan(&t.ID, &t.Name, &t.Email, &t.Tier, &t.MaxRoutes, &domains,
dbTime{&t.ValidUntil}, &t.Status, dbTime{&t.CreatedAt}, dbTime{&t.UpdatedAt})
dbTime{&t.ValidUntil}, &t.Status, dbTime{&t.CreatedAt}, dbTime{&t.UpdatedAt},
&features, dbTime{&t.PaymentOverdueSince})
if err != nil {
return Tenant{}, err
}
if err := json.Unmarshal([]byte(domains), &t.AllowedDomains); err != nil {
return Tenant{}, fmt.Errorf("store: allowed_domains von %s: %w", t.ID, err)
}
if err := json.Unmarshal([]byte(features), &t.Features); err != nil {
return Tenant{}, fmt.Errorf("store: features von %s: %w", t.ID, err)
}
if t.AllowedDomains == nil {
t.AllowedDomains = []string{}
}
if t.Features == nil {
t.Features = []string{}
}
return t, nil
}
@@ -168,15 +187,14 @@ func (s *Store) UpdateTenant(ctx context.Context, t Tenant) (Tenant, error) {
if err := t.normalize(); err != nil {
return Tenant{}, err
}
domains, err := json.Marshal(t.AllowedDomains)
if err != nil {
return Tenant{}, err
}
domains, _ := json.Marshal(t.AllowedDomains)
features, _ := json.Marshal(t.Features)
res, err := s.db.ExecContext(ctx, `UPDATE tenants SET
name = ?, email = ?, tier = ?, max_routes = ?, allowed_domains = ?, valid_until = ?, status = ?, updated_at = ?
name = ?, email = ?, tier = ?, max_routes = ?, allowed_domains = ?, valid_until = ?, status = ?, updated_at = ?,
features = ?
WHERE id = ?`,
t.Name, t.Email, t.Tier, t.MaxRoutes, string(domains), formatTime(t.ValidUntil), t.Status,
formatTime(s.now()), t.ID)
formatTime(s.now()), string(features), t.ID)
if err := requireAffected(res, err, "Mandant "+t.ID); err != nil {
return Tenant{}, err
}
@@ -193,6 +211,18 @@ func (s *Store) SetTenantStatus(ctx context.Context, id string, status TenantSta
return requireAffected(res, err, "Mandant "+id)
}
// SetTenantPayment setzt, seit wann eine Zahlung offen ist (Nullwert =
// bezahlt). Grundlage der Mahnstufen.
func (s *Store) SetTenantPayment(ctx context.Context, id string, overdueSince time.Time) error {
var v any
if !overdueSince.IsZero() {
v = formatTime(overdueSince)
}
res, err := s.db.ExecContext(ctx, `UPDATE tenants SET payment_overdue_since = ?, updated_at = ? WHERE id = ?`,
v, formatTime(s.now()), id)
return requireAffected(res, err, "Mandant "+id)
}
// DeleteTenant löscht den Mandanten samt seiner Knoten und Tokens.
func (s *Store) DeleteTenant(ctx context.Context, id string) error {
res, err := s.db.ExecContext(ctx, `DELETE FROM tenants WHERE id = ?`, id)