1package backend
2
3import (
4 "context"
5 "errors"
6 "strings"
7
8 "github.com/charmbracelet/soft-serve/pkg/access"
9 "github.com/charmbracelet/soft-serve/pkg/db"
10 "github.com/charmbracelet/soft-serve/pkg/db/models"
11 "github.com/charmbracelet/soft-serve/pkg/proto"
12 "github.com/charmbracelet/soft-serve/pkg/utils"
13 "github.com/charmbracelet/soft-serve/pkg/webhook"
14)
15
16// AddCollaborator adds a collaborator to a repository.
17//
18// It implements backend.Backend.
19func (d *Backend) AddCollaborator(ctx context.Context, repo string, username string, level access.AccessLevel) error {
20 username = strings.ToLower(username)
21 if err := utils.ValidateHandle(username); err != nil {
22 return err
23 }
24
25 repo = utils.SanitizeRepo(repo)
26 r, err := d.Repository(ctx, repo)
27 if err != nil {
28 return err
29 }
30
31 if err := db.WrapError(
32 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
33 return d.store.AddCollabByUsernameAndRepo(ctx, tx, username, repo, level)
34 }),
35 ); err != nil {
36 return err
37 }
38
39 wh, err := webhook.NewCollaboratorEvent(ctx, proto.UserFromContext(ctx), r, username, webhook.CollaboratorEventAdded)
40 if err != nil {
41 return err
42 }
43
44 return webhook.SendEvent(ctx, wh)
45}
46
47// Collaborators returns a list of collaborators for a repository.
48//
49// It implements backend.Backend.
50func (d *Backend) Collaborators(ctx context.Context, repo string) ([]string, error) {
51 repo = utils.SanitizeRepo(repo)
52 var users []models.User
53 var usernames []string
54 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
55 var err error
56 users, err = d.store.ListCollabsByRepoAsUsers(ctx, tx, repo)
57 if err != nil {
58 return err
59 }
60
61 ids := make([]int64, len(users))
62 for i, u := range users {
63 ids[i] = u.ID
64 }
65
66 handles, err := d.store.ListHandlesForIDs(ctx, tx, ids)
67 if err != nil {
68 return err
69 }
70
71 for _, h := range handles {
72 usernames = append(usernames, h.Handle)
73 }
74
75 return nil
76 }); err != nil {
77 return nil, db.WrapError(err)
78 }
79
80 return usernames, nil
81}
82
83// IsCollaborator returns the access level and true if the user is a collaborator of the repository.
84//
85// It implements backend.Backend.
86func (d *Backend) IsCollaborator(ctx context.Context, repo string, username string) (access.AccessLevel, bool, error) {
87 if username == "" {
88 return -1, false, nil
89 }
90
91 repo = utils.SanitizeRepo(repo)
92 var m models.Collab
93 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
94 var err error
95 m, err = d.store.GetCollabByUsernameAndRepo(ctx, tx, username, repo)
96 return err
97 }); err != nil {
98 return -1, false, db.WrapError(err)
99 }
100
101 return m.AccessLevel, m.ID > 0, nil
102}
103
104// RemoveCollaborator removes a collaborator from a repository.
105//
106// It implements backend.Backend.
107func (d *Backend) RemoveCollaborator(ctx context.Context, repo string, username string) error {
108 repo = utils.SanitizeRepo(repo)
109 r, err := d.Repository(ctx, repo)
110 if err != nil {
111 return err
112 }
113
114 wh, err := webhook.NewCollaboratorEvent(ctx, proto.UserFromContext(ctx), r, username, webhook.CollaboratorEventRemoved)
115 if err != nil {
116 return err
117 }
118
119 if err := db.WrapError(
120 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
121 return d.store.RemoveCollabByUsernameAndRepo(ctx, tx, username, repo)
122 }),
123 ); err != nil {
124 if errors.Is(err, db.ErrRecordNotFound) {
125 return proto.ErrCollaboratorNotFound
126 }
127
128 return err
129 }
130
131 return webhook.SendEvent(ctx, wh)
132}