Add cleanup, minor fixes
This commit is contained in:
1 parent
97dc0a3570
commit
284e4e7080
7 files changed
+107
-15
No files matched your search
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in new issue
Block a user