package store import ( "context" "strings" "time" "unicode" "unicode/utf8" ) // Maintenance ist eine angemeldete Wartung eines Knotens. type Maintenance struct { Since time.Time Until time.Time // Nullwert = unbefristet Reason string // intern (Log, Monitoring) – nie öffentlich PublicReason string // optional, ersetzt den Standardtext der Wartungsseite } // Active meldet, ob die Wartung zum Zeitpunkt now gilt (nil = keine). func (m *Maintenance) Active(now time.Time) bool { return m != nil && (m.Until.IsZero() || now.Before(m.Until)) } const ( maxPublicReason = 300 maxReason = 500 ) // validText: einzeilig, ohne Steuerzeichen, höchstens max Zeichen. func validText(s string, max int) bool { return utf8.RuneCountInString(s) <= max && !strings.ContainsFunc(s, unicode.IsControl) } // SetNodeMaintenance meldet eine Wartung an (Since = jetzt). m.Until muss // in der Zukunft liegen oder Nullwert (unbefristet) sein. func (s *Store) SetNodeMaintenance(ctx context.Context, nodeID string, m Maintenance) error { now := s.now() switch { case !m.Until.IsZero() && !m.Until.After(now): return invalid("Wartungsende liegt nicht in der Zukunft") case !validText(m.PublicReason, maxPublicReason): return invalid("öffentlicher Grund: einzeilig, höchstens %d Zeichen", maxPublicReason) case !validText(m.Reason, maxReason): return invalid("Grund: einzeilig, höchstens %d Zeichen", maxReason) } var until any if !m.Until.IsZero() { until = formatTime(m.Until) } res, err := s.db.ExecContext(ctx, `UPDATE nodes SET maintenance_since = ?, maintenance_until = ?, maintenance_reason = ?, maintenance_public_reason = ? WHERE id = ?`, formatTime(now), until, m.Reason, m.PublicReason, nodeID) return requireAffected(res, err, "Knoten "+nodeID) } // ClearNodeMaintenance beendet die Wartung eines Knotens. func (s *Store) ClearNodeMaintenance(ctx context.Context, nodeID string) error { res, err := s.db.ExecContext(ctx, `UPDATE nodes SET maintenance_since = NULL, maintenance_until = NULL, maintenance_reason = '', maintenance_public_reason = '' WHERE id = ?`, nodeID) return requireAffected(res, err, "Knoten "+nodeID) }