internal/store/quotas.go
105 lines · 2887 bytes
7 symbols in this file
1package store
2
3import (
4 "database/sql"
5 "time"
6)
7
8// Limits is an owner's quota overrides; nil means the configured default
9// applies. Orgs is the account's cap on organizations it creates and is
10// always nil for an org.
11type Limits struct {
12 Repos *int64
13 Bytes *int64
14 Orgs *int64
15}
16
17func nullable(n sql.NullInt64) *int64 {
18 if !n.Valid {
19 return nil
20 }
21 return &n.Int64
22}
23
24func orNull(p *int64) any {
25 if p == nil {
26 return nil
27 }
28 return *p
29}
30
31// OwnerLimits reads the overrides of a user or an org.
32func (s *Store) OwnerLimits(kind string, id int64) (Limits, error) {
33 var repos, bytes, orgs sql.NullInt64
34 var err error
35 if kind == "org" {
36 err = s.DB.QueryRow("SELECT repo_limit, byte_limit FROM orgs WHERE id = ?", id).Scan(&repos, &bytes)
37 } else {
38 err = s.DB.QueryRow("SELECT repo_limit, byte_limit, org_limit FROM users WHERE id = ?", id).Scan(&repos, &bytes, &orgs)
39 }
40 if err != nil {
41 return Limits{}, err
42 }
43 return Limits{nullable(repos), nullable(bytes), nullable(orgs)}, nil
44}
45
46// SetOwnerLimits writes the overrides; a nil field clears back to default.
47func (s *Store) SetOwnerLimits(kind string, id int64, l Limits) error {
48 var res sql.Result
49 var err error
50 if kind == "org" {
51 res, err = s.DB.Exec("UPDATE orgs SET repo_limit = ?, byte_limit = ? WHERE id = ?", orNull(l.Repos), orNull(l.Bytes), id)
52 } else {
53 res, err = s.DB.Exec("UPDATE users SET repo_limit = ?, byte_limit = ?, org_limit = ? WHERE id = ?",
54 orNull(l.Repos), orNull(l.Bytes), orNull(l.Orgs), id)
55 }
56 if err != nil {
57 return err
58 }
59 if n, _ := res.RowsAffected(); n == 0 {
60 return ErrNotFound
61 }
62 return nil
63}
64
65// CreatedOrgCount counts the organizations an account created and that
66// still exist.
67func (s *Store) CreatedOrgCount(userID int64) (int64, error) {
68 var n int64
69 err := s.DB.QueryRow("SELECT COUNT(*) FROM orgs WHERE created_by = ?", userID).Scan(&n)
70 return n, err
71}
72
73// ReapPendingUsers deletes self-registered accounts still unverified
74// after maxAge. A pending account owns nothing (it cannot create a
75// repository before verifying), so DeleteUser has nothing to refuse; an
76// account that somehow anchors content is left alone and reported.
77func (s *Store) ReapPendingUsers(maxAge time.Duration) ([]string, error) {
78 cutoff := fmtTime(time.Now().Add(-maxAge))
79 rows, err := s.DB.Query("SELECT id, username FROM users WHERE pending = 1 AND created_at < ?", cutoff)
80 if err != nil {
81 return nil, err
82 }
83 type row struct {
84 id int64
85 name string
86 }
87 var stale []row
88 for rows.Next() {
89 var r row
90 if err := rows.Scan(&r.id, &r.name); err != nil {
91 rows.Close()
92 return nil, err
93 }
94 stale = append(stale, r)
95 }
96 rows.Close()
97 var removed []string
98 for _, r := range stale {
99 if err := s.DeleteUser(r.id); err == nil {
100 removed = append(removed, r.name)
101 s.Audit(0, "pending.expired", map[string]any{"user": r.name})
102 }
103 }
104 return removed, nil
105}