internal/store/quotas.go
82 lines · 2046 bytes
1package store
2
3import (
4 "database/sql"
5 "time"
6)
7
8// UserLimits is an account's quota overrides; nil means the configured
9// default applies.
10type UserLimits struct {
11 Repos *int64
12 Bytes *int64
13}
14
15func (s *Store) UserLimits(userID int64) (UserLimits, error) {
16 var repos, bytes sql.NullInt64
17 err := s.DB.QueryRow("SELECT repo_limit, byte_limit FROM users WHERE id = ?", userID).Scan(&repos, &bytes)
18 if err != nil {
19 return UserLimits{}, err
20 }
21 var l UserLimits
22 if repos.Valid {
23 l.Repos = &repos.Int64
24 }
25 if bytes.Valid {
26 l.Bytes = &bytes.Int64
27 }
28 return l, nil
29}
30
31// SetUserLimits writes the overrides; a nil field clears back to default.
32func (s *Store) SetUserLimits(userID int64, l UserLimits) error {
33 var repos, bytes any
34 if l.Repos != nil {
35 repos = *l.Repos
36 }
37 if l.Bytes != nil {
38 bytes = *l.Bytes
39 }
40 res, err := s.DB.Exec("UPDATE users SET repo_limit = ?, byte_limit = ? WHERE id = ?", repos, bytes, userID)
41 if err != nil {
42 return err
43 }
44 if n, _ := res.RowsAffected(); n == 0 {
45 return ErrNotFound
46 }
47 return nil
48}
49
50// ReapPendingUsers deletes self-registered accounts still unverified
51// after maxAge. A pending account owns nothing (it cannot create a
52// repository before verifying), so DeleteUser has nothing to refuse; an
53// account that somehow anchors content is left alone and reported.
54func (s *Store) ReapPendingUsers(maxAge time.Duration) ([]string, error) {
55 cutoff := fmtTime(time.Now().Add(-maxAge))
56 rows, err := s.DB.Query("SELECT id, username FROM users WHERE pending = 1 AND created_at < ?", cutoff)
57 if err != nil {
58 return nil, err
59 }
60 type row struct {
61 id int64
62 name string
63 }
64 var stale []row
65 for rows.Next() {
66 var r row
67 if err := rows.Scan(&r.id, &r.name); err != nil {
68 rows.Close()
69 return nil, err
70 }
71 stale = append(stale, r)
72 }
73 rows.Close()
74 var removed []string
75 for _, r := range stale {
76 if err := s.DeleteUser(r.id); err == nil {
77 removed = append(removed, r.name)
78 s.Audit(0, "pending.expired", map[string]any{"user": r.name})
79 }
80 }
81 return removed, nil
82}