collab.go

  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}