Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial orgs & teams support #443

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions pkg/backend/access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package backend

import (
"context"

"github.com/charmbracelet/soft-serve/pkg/access"
"github.com/charmbracelet/soft-serve/pkg/proto"
"github.com/charmbracelet/soft-serve/pkg/sshutils"
"golang.org/x/crypto/ssh"
)

// AccessLevel returns the access level of a user for a repository.
//
// It implements backend.Backend.
func (d *Backend) AccessLevel(ctx context.Context, repo string, username string) access.AccessLevel {
user, _ := d.User(ctx, username)
return d.AccessLevelForUser(ctx, repo, user)

Check warning on line 17 in pkg/backend/access.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/access.go#L15-L17

Added lines #L15 - L17 were not covered by tests
}

// AccessLevelByPublicKey returns the access level of a user's public key for a repository.
//
// It implements backend.Backend.
func (d *Backend) AccessLevelByPublicKey(ctx context.Context, repo string, pk ssh.PublicKey) access.AccessLevel {
for _, k := range d.cfg.AdminKeys() {
if sshutils.KeysEqual(pk, k) {
return access.AdminAccess
}
}

user, _ := d.UserByPublicKey(ctx, pk)
if user != nil {
return d.AccessLevel(ctx, repo, user.Username())
}

Check warning on line 33 in pkg/backend/access.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/access.go#L30-L33

Added lines #L30 - L33 were not covered by tests

return d.AccessLevel(ctx, repo, "")

Check warning on line 35 in pkg/backend/access.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/access.go#L35

Added line #L35 was not covered by tests
}

// AccessLevelForUser returns the access level of a user for a repository.
// TODO: user repository ownership

Check failure on line 39 in pkg/backend/access.go

View workflow job for this annotation

GitHub Actions / lint-soft

Comment should end in a period (godot)
func (d *Backend) AccessLevelForUser(ctx context.Context, repo string, user proto.User) access.AccessLevel {
var username string
anon := d.AnonAccess(ctx)
if user != nil {
username = user.Username()
}

// If the user is an admin, they have admin access.
if user != nil && user.IsAdmin() {
return access.AdminAccess
}

// If the repository exists, check if the user is a collaborator.
r := proto.RepositoryFromContext(ctx)
if r == nil {
r, _ = d.Repository(ctx, repo)
}

if r != nil {
if user != nil {
// If the user is the owner, they have admin access.
if r.UserID() == user.ID() {
return access.AdminAccess
}
}

// If the user is a collaborator, they have return their access level.
collabAccess, isCollab, _ := d.IsCollaborator(ctx, repo, username)
if isCollab {
if anon > collabAccess {
return anon
}

Check warning on line 71 in pkg/backend/access.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/access.go#L70-L71

Added lines #L70 - L71 were not covered by tests
return collabAccess
}

// If the repository is private, the user has no access.
if r.IsPrivate() {
return access.NoAccess
}

// Otherwise, the user has read-only access.
return access.ReadOnlyAccess
}

if user != nil {
// If the repository doesn't exist, the user has read/write access.
if anon > access.ReadWriteAccess {
return anon
}

Check warning on line 88 in pkg/backend/access.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/access.go#L87-L88

Added lines #L87 - L88 were not covered by tests

return access.ReadWriteAccess
}

// If the user doesn't exist, give them the anonymous access level.
return anon
}
28 changes: 21 additions & 7 deletions pkg/backend/collab.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// It implements backend.Backend.
func (d *Backend) AddCollaborator(ctx context.Context, repo string, username string, level access.AccessLevel) error {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
if err := utils.ValidateHandle(username); err != nil {
return err
}

Expand Down Expand Up @@ -50,19 +50,33 @@
func (d *Backend) Collaborators(ctx context.Context, repo string) ([]string, error) {
repo = utils.SanitizeRepo(repo)
var users []models.User
var usernames []string
if err := d.db.TransactionContext(ctx, func(tx *db.Tx) error {
var err error
users, err = d.store.ListCollabsByRepoAsUsers(ctx, tx, repo)
return err
if err != nil {
return err
}

Check warning on line 59 in pkg/backend/collab.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/collab.go#L58-L59

Added lines #L58 - L59 were not covered by tests

ids := make([]int64, len(users))
for i, u := range users {
ids[i] = u.ID
}

handles, err := d.store.ListHandlesForIDs(ctx, tx, ids)
if err != nil {
return err
}

Check warning on line 69 in pkg/backend/collab.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/collab.go#L68-L69

Added lines #L68 - L69 were not covered by tests

for _, h := range handles {
usernames = append(usernames, h.Handle)
}

return nil
}); err != nil {
return nil, db.WrapError(err)
}

var usernames []string
for _, u := range users {
usernames = append(usernames, u.Username)
}

return usernames, nil
}

Expand Down
66 changes: 66 additions & 0 deletions pkg/backend/org.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package backend

import (
"context"

"github.com/charmbracelet/soft-serve/pkg/db/models"
"github.com/charmbracelet/soft-serve/pkg/proto"
)

// CreateOrg creates a new organization.
func (d *Backend) CreateOrg(ctx context.Context, owner proto.User, name, email string) (proto.Org, error) {
o, err := d.store.CreateOrg(ctx, d.db, owner.ID(), name, email)
if err != nil {
return org{}, err
}
return org{o}, err

Check warning on line 16 in pkg/backend/org.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/org.go#L11-L16

Added lines #L11 - L16 were not covered by tests
}

// ListOrgs lists all organizations for a user.
func (d *Backend) ListOrgs(ctx context.Context, user proto.User) ([]proto.Org, error) {
orgs, err := d.store.ListOrgs(ctx, d.db, user.ID())
var r []proto.Org
for _, o := range orgs {
r = append(r, org{o})
}
return r, err

Check warning on line 26 in pkg/backend/org.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/org.go#L20-L26

Added lines #L20 - L26 were not covered by tests
}

// FindOrganization finds an organization belonging to a user by name.
func (d *Backend) FindOrganization(ctx context.Context, user proto.User, name string) (proto.Org, error) {
o, err := d.store.FindOrgByHandle(ctx, d.db, user.ID(), name)
return org{o}, err

Check warning on line 32 in pkg/backend/org.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/org.go#L30-L32

Added lines #L30 - L32 were not covered by tests
}

// DeleteOrganization deletes an organization for a user.
func (d *Backend) DeleteOrganization(ctx context.Context, user proto.User, name string) error {
o, err := d.store.FindOrgByHandle(ctx, d.db, user.ID(), name)
if err != nil {
return err
}
return d.store.DeleteOrgByID(ctx, d.db, user.ID(), o.ID)

Check warning on line 41 in pkg/backend/org.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/org.go#L36-L41

Added lines #L36 - L41 were not covered by tests
}

type org struct {
o models.Organization
}

var _ proto.Org = org{}

// DisplayName implements proto.Org.
func (o org) DisplayName() string {
if o.o.Name.Valid {
return o.o.Name.String
}
return ""

Check warning on line 55 in pkg/backend/org.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/org.go#L51-L55

Added lines #L51 - L55 were not covered by tests
}

// ID implements proto.Org.
func (o org) ID() int64 {
return o.o.ID

Check warning on line 60 in pkg/backend/org.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/org.go#L59-L60

Added lines #L59 - L60 were not covered by tests
}

// Name implements proto.Org.
func (o org) Name() string {
return o.o.Handle.Handle

Check warning on line 65 in pkg/backend/org.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/org.go#L64-L65

Added lines #L64 - L65 were not covered by tests
}
72 changes: 72 additions & 0 deletions pkg/backend/team.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package backend

import (
"context"

"github.com/charmbracelet/soft-serve/pkg/db/models"
"github.com/charmbracelet/soft-serve/pkg/proto"
)

// CreateTeam creates a new team for an organization.
func (d *Backend) CreateTeam(ctx context.Context, org proto.Org, owner proto.User, name string) (proto.Team, error) {
m, err := d.store.CreateTeam(ctx, d.db, owner.ID(), org.ID(), name)
if err != nil {
return team{}, err
}
return team{m}, err

Check warning on line 16 in pkg/backend/team.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/team.go#L11-L16

Added lines #L11 - L16 were not covered by tests
}

// ListTeams lists all teams for a user.
func (d *Backend) ListTeams(ctx context.Context, user proto.User) ([]proto.Team, error) {
teams, err := d.store.ListTeams(ctx, d.db, user.ID())
var r []proto.Team
for _, m := range teams {
r = append(r, team{m})
}
return r, err

Check warning on line 26 in pkg/backend/team.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/team.go#L20-L26

Added lines #L20 - L26 were not covered by tests
}

// GetTeam gets a team by organization id and team name.
func (d *Backend) GetTeam(ctx context.Context, user proto.User, org proto.Org, name string) (proto.Team, error) {
m, err := d.store.FindTeamByOrgName(ctx, d.db, user.ID(), org.ID(), name)
if err != nil {
return team{}, err
}
return team{m}, err

Check warning on line 35 in pkg/backend/team.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/team.go#L30-L35

Added lines #L30 - L35 were not covered by tests
}

// FindTeam finds a team by name.
func (d *Backend) FindTeam(ctx context.Context, user proto.User, name string) ([]proto.Team, error) {
m, err := d.store.FindTeamByName(ctx, d.db, user.ID(), name)
var r []proto.Team
for _, m := range m {
r = append(r, team{m})
}
return r, err

Check warning on line 45 in pkg/backend/team.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/team.go#L39-L45

Added lines #L39 - L45 were not covered by tests
}

// DeleteTeam deletes a team.
func (d *Backend) DeleteTeam(ctx context.Context, _ proto.User, team proto.Team) error {
return d.store.DeleteTeamByID(ctx, d.db, team.ID())

Check warning on line 50 in pkg/backend/team.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/team.go#L49-L50

Added lines #L49 - L50 were not covered by tests
}

type team struct {
t models.Team
}

var _ proto.Team = team{}

// ID implements proto.Team.
func (t team) ID() int64 {
return t.t.ID

Check warning on line 61 in pkg/backend/team.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/team.go#L60-L61

Added lines #L60 - L61 were not covered by tests
}

// Name implements proto.Team.
func (t team) Name() string {
return t.t.Name

Check warning on line 66 in pkg/backend/team.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/team.go#L65-L66

Added lines #L65 - L66 were not covered by tests
}

// Org implements proto.Team.
func (t team) Org() int64 {
return t.t.OrganizationID

Check warning on line 71 in pkg/backend/team.go

View check run for this annotation

Codecov / codecov/patch

pkg/backend/team.go#L70-L71

Added lines #L70 - L71 were not covered by tests
}
Loading
Loading