satellite/{console,satellitedb}: add methods for project invite table

This change immplements methods for interacting with the project member
invitations table.

Resolves #5766

Change-Id: I0090c50f9fde5bcdae4ebdaa72cdcaa84d341b54
This commit is contained in:
Jeremy Wharton 2023-04-25 23:46:48 -05:00
parent defb9eae82
commit 7a17dc5e07
5 changed files with 236 additions and 0 deletions

View File

@ -19,6 +19,8 @@ type DB interface {
Projects() Projects Projects() Projects
// ProjectMembers is a getter for ProjectMembers repository. // ProjectMembers is a getter for ProjectMembers repository.
ProjectMembers() ProjectMembers ProjectMembers() ProjectMembers
// ProjectInvitations is a getter for ProjectInvitations repository.
ProjectInvitations() ProjectInvitations
// APIKeys is a getter for APIKeys repository. // APIKeys is a getter for APIKeys repository.
APIKeys() APIKeys APIKeys() APIKeys
// RegistrationTokens is a getter for RegistrationTokens repository. // RegistrationTokens is a getter for RegistrationTokens repository.

View File

@ -0,0 +1,32 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
package console
import (
"context"
"time"
"storj.io/common/uuid"
)
// ProjectInvitations exposes methods to manage pending project member invitations in the database.
//
// architecture: Database
type ProjectInvitations interface {
// Insert is a method for inserting a project member invitation into the database.
Insert(ctx context.Context, projectID uuid.UUID, email string) (*ProjectInvitation, error)
// GetByProjectID returns all of the project member invitations for the project specified by the given ID.
GetByProjectID(ctx context.Context, projectID uuid.UUID) ([]ProjectInvitation, error)
// GetByEmail returns all of the project member invitations for the specified email address.
GetByEmail(ctx context.Context, email string) ([]ProjectInvitation, error)
// Delete is a method for deleting a project member invitation from the database.
Delete(ctx context.Context, projectID uuid.UUID, email string) error
}
// ProjectInvitation represents a pending project member invitation.
type ProjectInvitation struct {
ProjectID uuid.UUID
Email string
CreatedAt time.Time
}

View File

@ -46,6 +46,11 @@ func (db *ConsoleDB) ProjectMembers() console.ProjectMembers {
return &projectMembers{db.methods, db.db} return &projectMembers{db.methods, db.db}
} }
// ProjectInvitations is a getter for ProjectInvitations repository.
func (db *ConsoleDB) ProjectInvitations() console.ProjectInvitations {
return &projectInvitations{db.methods}
}
// APIKeys is a getter for APIKeys repository. // APIKeys is a getter for APIKeys repository.
func (db *ConsoleDB) APIKeys() console.APIKeys { func (db *ConsoleDB) APIKeys() console.APIKeys {
db.apikeysOnce.Do(func() { db.apikeysOnce.Do(func() {

View File

@ -0,0 +1,101 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
package satellitedb
import (
"context"
"storj.io/common/uuid"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/satellitedb/dbx"
)
// Ensure that projectInvitations implements console.ProjectInvitations.
var _ console.ProjectInvitations = (*projectInvitations)(nil)
// projectInvitations is an implementation of console.ProjectInvitations.
type projectInvitations struct {
db dbx.Methods
}
// Insert is a method for inserting a project member invitation into the database.
func (invites *projectInvitations) Insert(ctx context.Context, projectID uuid.UUID, email string) (_ *console.ProjectInvitation, err error) {
defer mon.Task()(&ctx)(&err)
dbxInvite, err := invites.db.Create_ProjectInvitation(ctx,
dbx.ProjectInvitation_ProjectId(projectID[:]),
dbx.ProjectInvitation_Email(normalizeEmail(email)),
)
if err != nil {
return nil, err
}
return projectInvitationFromDBX(dbxInvite)
}
// GetByProjectID returns all of the project member invitations for the project specified by the given ID.
func (invites *projectInvitations) GetByProjectID(ctx context.Context, projectID uuid.UUID) (_ []console.ProjectInvitation, err error) {
defer mon.Task()(&ctx)(&err)
dbxInvites, err := invites.db.All_ProjectInvitation_By_ProjectId(ctx, dbx.ProjectInvitation_ProjectId(projectID[:]))
if err != nil {
return nil, err
}
return projectInvitationSliceFromDBX(dbxInvites)
}
// GetByEmail returns all of the project member invitations for the specified email address.
func (invites *projectInvitations) GetByEmail(ctx context.Context, email string) (_ []console.ProjectInvitation, err error) {
defer mon.Task()(&ctx)(&err)
dbxInvites, err := invites.db.All_ProjectInvitation_By_Email(ctx, dbx.ProjectInvitation_Email(normalizeEmail(email)))
if err != nil {
return nil, err
}
return projectInvitationSliceFromDBX(dbxInvites)
}
// Delete is a method for deleting a project member invitation from the database.
func (invites *projectInvitations) Delete(ctx context.Context, projectID uuid.UUID, email string) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = invites.db.Delete_ProjectInvitation_By_ProjectId_And_Email(ctx,
dbx.ProjectInvitation_ProjectId(projectID[:]),
dbx.ProjectInvitation_Email(normalizeEmail(email)),
)
return err
}
// projectInvitationFromDBX converts a project member invitation from the database to a *console.ProjectInvitation.
func projectInvitationFromDBX(dbxInvite *dbx.ProjectInvitation) (_ *console.ProjectInvitation, err error) {
if dbxInvite == nil {
return nil, Error.New("dbx invitation is nil")
}
projectID, err := uuid.FromBytes(dbxInvite.ProjectId)
if err != nil {
return nil, err
}
return &console.ProjectInvitation{
ProjectID: projectID,
Email: dbxInvite.Email,
CreatedAt: dbxInvite.CreatedAt,
}, nil
}
// projectInvitationSliceFromDBX converts a project member invitation slice from the database to a
// slice of console.ProjectInvitation.
func projectInvitationSliceFromDBX(dbxInvites []*dbx.ProjectInvitation) (invites []console.ProjectInvitation, err error) {
for _, dbxInvite := range dbxInvites {
invite, err := projectInvitationFromDBX(dbxInvite)
if err != nil {
return nil, err
}
invites = append(invites, *invite)
}
return invites, nil
}

View File

@ -0,0 +1,96 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
package satellitedb_test
import (
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/storj/satellite"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/satellitedb/satellitedbtest"
)
func TestProjectInvitations(t *testing.T) {
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
invitesDB := db.Console().ProjectInvitations()
projectsDB := db.Console().Projects()
projID := testrand.UUID()
projID2 := testrand.UUID()
email := "user@mail.test"
email2 := "user2@mail.test"
_, err := projectsDB.Insert(ctx, &console.Project{ID: projID})
require.NoError(t, err)
_, err = projectsDB.Insert(ctx, &console.Project{ID: projID2})
require.NoError(t, err)
invite, err := invitesDB.Insert(ctx, projID, email)
require.NoError(t, err)
require.WithinDuration(t, time.Now(), invite.CreatedAt, time.Minute)
require.Equal(t, projID, invite.ProjectID)
require.Equal(t, strings.ToUpper(email), invite.Email)
_, err = invitesDB.Insert(ctx, projID, email)
require.Error(t, err)
inviteSameEmail, err := invitesDB.Insert(ctx, projID2, email)
require.NoError(t, err)
inviteSameProject, err := invitesDB.Insert(ctx, projID, email2)
require.NoError(t, err)
t.Run("get invitations by email", func(t *testing.T) {
ctx := testcontext.New(t)
invites, err := invitesDB.GetByEmail(ctx, "nobody@mail.test")
require.NoError(t, err)
require.Empty(t, invites)
invites, err = invitesDB.GetByEmail(ctx, "uSeR@mAiL.tEsT")
require.NoError(t, err)
require.ElementsMatch(t, invites, []console.ProjectInvitation{*invite, *inviteSameEmail})
})
t.Run("get invitations by project ID", func(t *testing.T) {
ctx := testcontext.New(t)
invites, err := invitesDB.GetByProjectID(ctx, testrand.UUID())
require.NoError(t, err)
require.Empty(t, invites)
invites, err = invitesDB.GetByProjectID(ctx, projID)
require.NoError(t, err)
require.ElementsMatch(t, invites, []console.ProjectInvitation{*invite, *inviteSameProject})
})
t.Run("delete invitation", func(t *testing.T) {
ctx := testcontext.New(t)
require.NoError(t, invitesDB.Delete(ctx, projID, email))
invites, err := invitesDB.GetByEmail(ctx, email)
require.NoError(t, err)
require.Equal(t, invites, []console.ProjectInvitation{*inviteSameEmail})
})
t.Run("ensure project removal deletes invitations", func(t *testing.T) {
ctx := testcontext.New(t)
require.NoError(t, projectsDB.Delete(ctx, projID))
invites, err := invitesDB.GetByProjectID(ctx, projID)
require.NoError(t, err)
require.Empty(t, invites)
invites, err = invitesDB.GetByEmail(ctx, email)
require.NoError(t, err)
require.Equal(t, invites, []console.ProjectInvitation{*inviteSameEmail})
})
})
}