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.ValidateUsername(username); err != nil {
22 return err //nolint:wrapcheck
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 if errors.Is(err, db.ErrDuplicateKey) {
37 return proto.ErrCollaboratorExist
38 }
39
40 return err //nolint:wrapcheck
41 }
42
43 wh, err := webhook.NewCollaboratorEvent(ctx, proto.UserFromContext(ctx), r, username, webhook.CollaboratorEventAdded)
44 if err != nil {
45 return err //nolint:wrapcheck
46 }
47
48 return webhook.SendEvent(ctx, wh) //nolint:wrapcheck
49}
50
51// Collaborators returns a list of collaborators for a repository.
52//
53// It implements backend.Backend.
54func (d *Backend) Collaborators(ctx context.Context, repo string) ([]string, error) {
55 repo = utils.SanitizeRepo(repo)
56 var users []models.User
57 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
58 var err error
59 users, err = d.store.ListCollabsByRepoAsUsers(ctx, tx, repo)
60 return err //nolint:wrapcheck
61 }); err != nil {
62 return nil, db.WrapError(err) //nolint:wrapcheck
63 }
64
65 usernames := make([]string, 0, len(users))
66 for _, u := range users {
67 usernames = append(usernames, u.Username)
68 }
69
70 return usernames, nil
71}
72
73// IsCollaborator returns the access level and true if the user is a collaborator of the repository.
74//
75// It implements backend.Backend.
76func (d *Backend) IsCollaborator(ctx context.Context, repo string, username string) (access.AccessLevel, bool, error) {
77 if username == "" {
78 return -1, false, nil
79 }
80
81 repo = utils.SanitizeRepo(repo)
82 var m models.Collab
83 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
84 var err error
85 m, err = d.store.GetCollabByUsernameAndRepo(ctx, tx, username, repo)
86 return err //nolint:wrapcheck
87 }); err != nil {
88 return -1, false, db.WrapError(err) //nolint:wrapcheck
89 }
90
91 return m.AccessLevel, m.ID > 0, nil
92}
93
94// RemoveCollaborator removes a collaborator from a repository.
95//
96// It implements backend.Backend.
97func (d *Backend) RemoveCollaborator(ctx context.Context, repo string, username string) error {
98 repo = utils.SanitizeRepo(repo)
99 r, err := d.Repository(ctx, repo)
100 if err != nil {
101 return err
102 }
103
104 wh, err := webhook.NewCollaboratorEvent(ctx, proto.UserFromContext(ctx), r, username, webhook.CollaboratorEventRemoved)
105 if err != nil {
106 return err //nolint:wrapcheck
107 }
108
109 if err := db.WrapError(
110 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
111 return d.store.RemoveCollabByUsernameAndRepo(ctx, tx, username, repo)
112 }),
113 ); err != nil {
114 if errors.Is(err, db.ErrRecordNotFound) {
115 return proto.ErrCollaboratorNotFound
116 }
117
118 return err //nolint:wrapcheck
119 }
120
121 return webhook.SendEvent(ctx, wh) //nolint:wrapcheck
122}