Add cleanup, minor fixes

This commit is contained in:
blankhex committed 2026-10-21 20:13:27 +03:00
1 parent 97dc0a3570
commit 284e4e7080
7 files changed
+107 -15

No files matched your search

+21 -6
View File
@@ -24,6 +24,11 @@ const (
DefaultShortSession = 24 * time.Hour
DefaultLongSession = 30 * 24 * time.Hour
DefaultTokenSize = 32
// DefaultCleanupInterval is how often expired sessions, spent/expired
// invites and never-activated users are purged in the background. A
// non-positive value disables the background cleanup.
DefaultCleanupInterval = 1 * time.Hour
)
type Config struct {
@@ -33,10 +38,14 @@ type Config struct {
// to build absolute links (e.g. the avatar URL embedded in Jitsi JWTs).
// Leave empty to fall back to the request's Host.
PublicBaseURL string
Jitsi JitsiConfig
Cors CorsConfig
Sessions SessionConfig
Cookies CookieConfig
// CleanupInterval controls the background maintenance loop that purges
// expired sessions, spent/expired invites and never-activated users.
// Non-positive disables it.
CleanupInterval time.Duration
Jitsi JitsiConfig
Cors CorsConfig
Sessions SessionConfig
Cookies CookieConfig
}
type JitsiConfig struct {
@@ -66,8 +75,9 @@ type CookieConfig struct {
func DefaultConfig() Config {
return Config{
Addr: DefaultAddr,
DatabasePath: DefaultDatabasePath,
Addr: DefaultAddr,
DatabasePath: DefaultDatabasePath,
CleanupInterval: DefaultCleanupInterval,
Jitsi: JitsiConfig{
Domain: "meet.jit.si",
},
@@ -133,6 +143,11 @@ func ConfigFromEnvironment() Config {
cfg.Sessions.LongDuration = parsed
}
}
if v := os.Getenv("CLEANUP_INTERVAL"); v != "" {
if parsed, err := time.ParseDuration(v); err == nil {
cfg.CleanupInterval = parsed
}
}
cfg.Jitsi.Secret = os.Getenv("JITSI_SECRET")
cfg.Jitsi.AppId = os.Getenv("JITSI_APP_ID")
+24 -2
View File
@@ -6,6 +6,28 @@ import (
"time"
)
// sanitizeRoomName trims a room name and collapses any character that is not
// allowed in a room name into a single dash, stripping leading/trailing dashes.
// Case is preserved because Jitsi room names are case-sensitive.
func sanitizeRoomName(value string) string {
var builder strings.Builder
builder.Grow(len(value))
previousDash := false
for _, r := range strings.TrimSpace(value) {
allowed := (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_'
if allowed {
builder.WriteRune(r)
previousDash = false
continue
}
if !previousDash {
builder.WriteByte('-')
previousDash = true
}
}
return strings.Trim(builder.String(), "-")
}
type joinRoomRequest struct {
Room string `json:"room"`
}
@@ -54,7 +76,7 @@ func (h *Handler) joinRoom(w http.ResponseWriter, r *http.Request) {
return
}
room := strings.TrimSpace(req.Room)
room := sanitizeRoomName(req.Room)
if room == "" {
writeError(w, http.StatusBadRequest, "room is required")
return
@@ -93,7 +115,7 @@ func (h *Handler) joinRoom(w http.ResponseWriter, r *http.Request) {
func (h *Handler) deleteRecentRoom(w http.ResponseWriter, r *http.Request) {
u := userFromRequest(r)
room := strings.TrimSpace(r.PathValue("room"))
room := sanitizeRoomName(r.PathValue("room"))
if room == "" {
writeError(w, http.StatusBadRequest, "room is required")
return
+42
View File
@@ -149,6 +149,46 @@ func (h *Handler) Close() error {
return h.db.Close()
}
// Cleanup purges expired sessions, spent/expired invites and never-activated
// users. Each target is cleaned independently so a single failure does not
// prevent the others from running.
func (h *Handler) Cleanup(ctx context.Context) {
if err := h.sessions.CleanupSessions(ctx); err != nil {
slog.Error("cleanup sessions", slog.String("op", "rest.Cleanup"), slog.Any("error", err))
}
if err := h.invites.CleanupInvites(ctx); err != nil {
slog.Error("cleanup invites", slog.String("op", "rest.Cleanup"), slog.Any("error", err))
}
if err := h.users.CleanupUsers(ctx); err != nil {
slog.Error("cleanup users", slog.String("op", "rest.Cleanup"), slog.Any("error", err))
}
}
// runCleanupLoop runs Cleanup immediately and then on every tick until ctx is
// cancelled. It is a no-op when the configured interval is non-positive.
func (h *Handler) runCleanupLoop(ctx context.Context) {
interval := h.cfg.CleanupInterval
if interval <= 0 {
slog.Info("background cleanup disabled", slog.String("op", "rest.runCleanupLoop"))
return
}
slog.Info("background cleanup started", slog.String("op", "rest.runCleanupLoop"), slog.Duration("interval", interval))
h.Cleanup(ctx)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
h.Cleanup(ctx)
}
}
}
func (h *Handler) Router() http.Handler {
mux := http.NewServeMux()
h.registerRoutes(mux)
@@ -156,6 +196,8 @@ func (h *Handler) Router() http.Handler {
}
func (h *Handler) Serve(ctx context.Context) error {
go h.runCleanupLoop(ctx)
server := &http.Server{
Addr: h.cfg.Addr,
Handler: h.Router(),