Raw
  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}