1package storage
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "time"
8)
9
10func (s *Store) RevokeInvitationBatchAudited(ctx context.Context, batchID, actor string) (int, error) {
11 const operation = "revoke invitation batch"
12 var revoked int
13 err := s.audited(ctx, operation, actor, func(tx *sql.Tx, at time.Time) error {
14 var members int
15 if err := tx.QueryRowContext(ctx, `SELECT count(*) FROM invitations WHERE batch_id = ?`, batchID).Scan(&members); err != nil {
16 return classify(operation, err)
17 }
18 if members == 0 {
19 return fmt.Errorf("%s: %w", operation, ErrNotFound)
20 }
21 ids, err := queryIDs(ctx, tx, operation, `
22SELECT id FROM invitations
23WHERE batch_id = ? AND used_at IS NULL AND revoked_at IS NULL AND expires_at > ?
24ORDER BY id`, batchID, timeToInt(at))
25 if err != nil {
26 return err
27 }
28 for _, id := range ids {
29 if _, err := execCount(ctx, tx, operation, `UPDATE invitations SET revoked_at = ? WHERE id = ? AND used_at IS NULL AND revoked_at IS NULL`, timeToInt(at), id); err != nil {
30 return err
31 }
32 if err := writeAudit(ctx, tx, operation, actor, "invitation.revoke", id, at, map[string]any{"batch": batchID}); err != nil {
33 return err
34 }
35 }
36 revoked = len(ids)
37 return nil
38 })
39 if err != nil {
40 return 0, err
41 }
42 return revoked, nil
43}