1package backend23import (4 "context"5 "errors"6 "strings"78 "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)1516// 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 err23 }2425 repo = utils.SanitizeRepo(repo)26 r, err := d.Repository(ctx, repo)27 if err != nil {28 return err29 }3031 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.ErrCollaboratorExist38 }3940 return err41 }4243 wh, err := webhook.NewCollaboratorEvent(ctx, proto.UserFromContext(ctx), r, username, webhook.CollaboratorEventAdded)44 if err != nil {45 return err46 }4748 return webhook.SendEvent(ctx, wh)49}5051// 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.User57 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {58 var err error59 users, err = d.store.ListCollabsByRepoAsUsers(ctx, tx, repo)60 return err61 }); err != nil {62 return nil, db.WrapError(err)63 }6465 var usernames []string66 for _, u := range users {67 usernames = append(usernames, u.Username)68 }6970 return usernames, nil71}7273// 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, nil79 }8081 repo = utils.SanitizeRepo(repo)82 var m models.Collab83 if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {84 var err error85 m, err = d.store.GetCollabByUsernameAndRepo(ctx, tx, username, repo)86 return err87 }); err != nil {88 return -1, false, db.WrapError(err)89 }9091 return m.AccessLevel, m.ID > 0, nil92}9394// 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 err102 }103104 wh, err := webhook.NewCollaboratorEvent(ctx, proto.UserFromContext(ctx), r, username, webhook.CollaboratorEventRemoved)105 if err != nil {106 return err107 }108109 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.ErrCollaboratorNotFound116 }117118 return err119 }120121 return webhook.SendEvent(ctx, wh)122}