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