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.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}