package api import ( "context" "git.misaka.ren/m1saka/dsh-web/internal/store" "log" "time" ) // RunQuotaMonitor reports soft quota crossings; it never claims to constrain // runtime writes and never deletes user data to recover capacity. func (a *API) RunQuotaMonitor(ctx context.Context) { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() exceeded := map[string]bool{} for { a.checkQuotas(ctx, exceeded) select { case <-ctx.Done(): return case <-ticker.C: } } } func (a *API) checkQuotas(ctx context.Context, exceeded map[string]bool) { rows, err := a.s.DB.QueryContext(ctx, "SELECT id,quota FROM users") if err != nil { return } var users []store.User for rows.Next() { var u store.User if rows.Scan(&u.ID, &u.Quota) != nil { rows.Close() return } users = append(users, u) } err = rows.Err() rows.Close() if err != nil { return } for _, u := range users { if ctx.Err() != nil { return } used, err := a.userUsage(u) if err != nil { log.Printf("quota usage unavailable for user %s", u.ID) continue } over := used > u.Quota if over && !exceeded[u.ID] { a.s.AddAudit(u.ID, "quota.exceeded", u.ID, "soft quota exceeded; runtime writes remain unrestricted") } if !over && exceeded[u.ID] { a.s.AddAudit(u.ID, "quota.recovered", u.ID, "") } exceeded[u.ID] = over } }