1package storage
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7 "fmt"
8)
9
10func (s *Store) AddMembership(ctx context.Context, userID, groupID string) error {
11 const operation = "add membership"
12 if err := s.checkOpen(operation); err != nil {
13 return err
14 }
15 return addMembership(ctx, s.sql, operation, userID, groupID)
16}
17
18func addMembership(ctx context.Context, q execer, operation, userID, groupID string) error {
19 if _, err := q.ExecContext(ctx, `INSERT INTO memberships (user_id, group_id) VALUES (?, ?)`, userID, groupID); err != nil {
20 return classify(operation, err)
21 }
22 return nil
23}
24
25func (s *Store) RemoveMembership(ctx context.Context, userID, groupID string) error {
26 const operation = "remove membership"
27 if err := s.checkOpen(operation); err != nil {
28 return err
29 }
30 result, err := s.sql.ExecContext(ctx, `DELETE FROM memberships WHERE user_id = ? AND group_id = ?`, userID, groupID)
31 if err != nil {
32 return classify(operation, err)
33 }
34 return requireChanged(operation, result)
35}
36
37func (s *Store) GroupsForUser(ctx context.Context, userID string) ([]Group, error) {
38 const operation = "list groups for user"
39 if err := s.checkOpen(operation); err != nil {
40 return nil, err
41 }
42 rows, err := s.sql.QueryContext(ctx, `
43SELECT g.id, g.name, g.description, g.gid_number, g.created_at
44FROM groups g
45JOIN memberships m ON m.group_id = g.id
46WHERE m.user_id = ?
47ORDER BY g.name, g.id`, userID)
48 if err != nil {
49 return nil, classify(operation, err)
50 }
51 defer rows.Close()
52 var groups []Group
53 for rows.Next() {
54 group, err := scanGroup(rows)
55 if err != nil {
56 return nil, classify(operation, err)
57 }
58 groups = append(groups, group)
59 }
60 if err := rows.Err(); err != nil {
61 return nil, classify(operation, err)
62 }
63 return groups, nil
64}
65
66func (s *Store) UsersInGroup(ctx context.Context, groupID string) ([]User, error) {
67 const operation = "list users in group"
68 if err := s.checkOpen(operation); err != nil {
69 return nil, err
70 }
71 rows, err := s.sql.QueryContext(ctx, `
72SELECT u.id, u.username, u.display_name,
73 u.password_hash, u.disabled, u.uid_number, u.created_at, u.last_login_at, u.credentials_epoch
74FROM users u
75JOIN memberships m ON m.user_id = u.id
76WHERE m.group_id = ?
77ORDER BY u.username, u.id`, groupID)
78 if err != nil {
79 return nil, classify(operation, err)
80 }
81 defer rows.Close()
82 var users []User
83 for rows.Next() {
84 user, err := scanUser(rows)
85 if err != nil {
86 return nil, classify(operation, err)
87 }
88 users = append(users, user)
89 }
90 if err := rows.Err(); err != nil {
91 return nil, classify(operation, err)
92 }
93 return users, nil
94}
95
96func (s *Store) GoogleLinkByUser(ctx context.Context, userID string) (GoogleLink, error) {
97 return s.googleLinkBy(ctx, "get google link by user", `SELECT user_id, google_sub, linked_at FROM google_links WHERE user_id = ?`, userID)
98}
99
100func (s *Store) GoogleLinkBySubject(ctx context.Context, googleSub string) (GoogleLink, error) {
101 return s.googleLinkBy(ctx, "get google link by subject", `SELECT user_id, google_sub, linked_at FROM google_links WHERE google_sub = ?`, googleSub)
102}
103
104func (s *Store) googleLinkBy(ctx context.Context, operation, query, arg string) (GoogleLink, error) {
105 if err := s.checkOpen(operation); err != nil {
106 return GoogleLink{}, err
107 }
108 var link GoogleLink
109 var linkedAt int64
110 err := s.sql.QueryRowContext(ctx, query, arg).Scan(&link.UserID, &link.GoogleSub, &linkedAt)
111 if errors.Is(err, sql.ErrNoRows) {
112 return GoogleLink{}, fmt.Errorf("%s: %w", operation, ErrNotFound)
113 }
114 if err != nil {
115 return GoogleLink{}, classify(operation, err)
116 }
117 link.LinkedAt = intToTime(linkedAt)
118 return link, nil
119}