satellite/console: get project members endpoint
This commit adds a new endpoint on the satellite console api to get project members and invitations. issue: https://github.com/storj/storj/issues/6137 Change-Id: I66cb064eeaffb1c34878462b3e6b3be8f3629f4e
This commit is contained in:
parent
887209bc24
commit
683119b835
@ -27,6 +27,33 @@ type Projects struct {
|
||||
service *console.Service
|
||||
}
|
||||
|
||||
// ProjectMembersPage contains information about a page of project members and invitations.
|
||||
type ProjectMembersPage struct {
|
||||
Members []Member `json:"projectMembers"`
|
||||
Invitations []Invitation `json:"projectInvitations"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Order int `json:"order"`
|
||||
OrderDirection int `json:"orderDirection"`
|
||||
Search string `json:"search"`
|
||||
CurrentPage int `json:"currentPage"`
|
||||
PageCount int `json:"pageCount"`
|
||||
}
|
||||
|
||||
// Member is a project member in a ProjectMembersPage.
|
||||
type Member struct {
|
||||
User *console.User `json:"user"`
|
||||
JoinedAt time.Time `json:"joinedAt"`
|
||||
}
|
||||
|
||||
// Invitation is a project invitation in a ProjectMembersPage.
|
||||
type Invitation struct {
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Expired bool `json:"expired"`
|
||||
}
|
||||
|
||||
// NewProjects is a constructor for api analytics controller.
|
||||
func NewProjects(log *zap.Logger, service *console.Service) *Projects {
|
||||
return &Projects{
|
||||
@ -138,6 +165,124 @@ func (p *Projects) GetPagedProjects(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetMembersAndInvitations returns the project's members and invitees.
|
||||
func (p *Projects) GetMembersAndInvitations(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
idParam, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing id route param"))
|
||||
return
|
||||
}
|
||||
|
||||
publicID, err := uuid.FromString(idParam)
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
project, err := p.service.GetProject(ctx, publicID)
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
limitStr := r.URL.Query().Get("limit")
|
||||
if limitStr == "" {
|
||||
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("missing limit query param"))
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("invalid limit parameter: %s", limitStr))
|
||||
return
|
||||
}
|
||||
|
||||
search := r.URL.Query().Get("search")
|
||||
|
||||
pageStr := r.URL.Query().Get("page")
|
||||
if pageStr == "" {
|
||||
pageStr = "1"
|
||||
}
|
||||
page, err := strconv.Atoi(pageStr)
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("invalid page parameter: %s", pageStr))
|
||||
return
|
||||
}
|
||||
|
||||
orderStr := r.URL.Query().Get("order")
|
||||
if orderStr == "" {
|
||||
orderStr = "1"
|
||||
}
|
||||
order, err := strconv.Atoi(orderStr)
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("invalid order parameter: %s", orderStr))
|
||||
return
|
||||
}
|
||||
|
||||
orderDirStr := r.URL.Query().Get("order-direction")
|
||||
if orderDirStr == "" {
|
||||
orderDirStr = "1"
|
||||
}
|
||||
orderDir, err := strconv.Atoi(orderDirStr)
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusBadRequest, errs.New("invalid order-direction parameter: %s", orderDirStr))
|
||||
return
|
||||
}
|
||||
|
||||
var memberPage ProjectMembersPage
|
||||
membersAndInvitations, err := p.service.GetProjectMembersAndInvitations(ctx, project.ID, console.ProjectMembersCursor{
|
||||
Search: search,
|
||||
Limit: uint(limit),
|
||||
Page: uint(page),
|
||||
Order: console.ProjectMemberOrder(order),
|
||||
OrderDirection: console.OrderDirection(orderDir),
|
||||
})
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
memberPage.Search = membersAndInvitations.Search
|
||||
memberPage.Limit = int(membersAndInvitations.Limit)
|
||||
memberPage.Order = int(membersAndInvitations.Order)
|
||||
memberPage.OrderDirection = int(membersAndInvitations.OrderDirection)
|
||||
memberPage.Offset = int(membersAndInvitations.Offset)
|
||||
memberPage.PageCount = int(membersAndInvitations.PageCount)
|
||||
memberPage.CurrentPage = int(membersAndInvitations.CurrentPage)
|
||||
memberPage.TotalCount = int(membersAndInvitations.TotalCount)
|
||||
memberPage.Members = []Member{}
|
||||
memberPage.Invitations = []Invitation{}
|
||||
|
||||
for _, m := range membersAndInvitations.ProjectMembers {
|
||||
user, err := p.service.GetUser(ctx, m.MemberID)
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
member := Member{
|
||||
User: user,
|
||||
JoinedAt: m.CreatedAt,
|
||||
}
|
||||
memberPage.Members = append(memberPage.Members, member)
|
||||
}
|
||||
for _, inv := range membersAndInvitations.ProjectInvitations {
|
||||
invitee := Invitation{
|
||||
Email: inv.Email,
|
||||
CreatedAt: inv.CreatedAt,
|
||||
Expired: p.service.IsProjectInvitationExpired(&inv),
|
||||
}
|
||||
memberPage.Invitations = append(memberPage.Invitations, invitee)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(memberPage)
|
||||
if err != nil {
|
||||
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetSalt returns the project's salt.
|
||||
func (p *Projects) GetSalt(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
384
satellite/console/consoleweb/consoleapi/projects_test.go
Normal file
384
satellite/console/consoleweb/consoleapi/projects_test.go
Normal file
@ -0,0 +1,384 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package consoleapi_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/common/memory"
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/common/testrand"
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/private/testplanet"
|
||||
"storj.io/storj/satellite/console"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleapi"
|
||||
)
|
||||
|
||||
func createTestMembers(ctx context.Context, t *testing.T, db console.DB, p uuid.UUID, owner *uuid.UUID) (_ map[uuid.UUID]console.User, _ map[string]console.User) {
|
||||
members := make(map[uuid.UUID]console.User)
|
||||
invitees := make(map[string]console.User)
|
||||
for i := 0; i < 3; i++ {
|
||||
memberID := testrand.UUID()
|
||||
member, err := db.Users().Insert(ctx, &console.User{
|
||||
ID: memberID,
|
||||
FullName: fmt.Sprintf("Member FullName%c", rune('A'+i)),
|
||||
ShortName: fmt.Sprintf("Member ShortName%c", rune('A'+i)),
|
||||
Email: fmt.Sprintf("member%d@storj.test", i),
|
||||
ProjectLimit: 1,
|
||||
ProjectStorageLimit: (memory.GB * 150).Int64(),
|
||||
ProjectBandwidthLimit: (memory.GB * 150).Int64(),
|
||||
PasswordHash: []byte("test"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
members[memberID] = *member
|
||||
|
||||
_, err = db.ProjectMembers().Insert(ctx, member.ID, p)
|
||||
require.NoError(t, err)
|
||||
|
||||
inviteeID := testrand.UUID()
|
||||
inviteeEmail := fmt.Sprintf("invitee%d@storj.test", i)
|
||||
invitee, err := db.Users().Insert(ctx, &console.User{
|
||||
ID: inviteeID,
|
||||
FullName: fmt.Sprintf("Invitee FullName%c", rune('A'+i)),
|
||||
ShortName: fmt.Sprintf("Invitee ShortName%c", rune('A'+i)),
|
||||
Email: inviteeEmail,
|
||||
ProjectLimit: 1,
|
||||
ProjectStorageLimit: (memory.GB * 150).Int64(),
|
||||
ProjectBandwidthLimit: (memory.GB * 150).Int64(),
|
||||
PasswordHash: []byte("test"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
invitees[inviteeEmail] = *invitee
|
||||
|
||||
_, err = db.ProjectInvitations().Upsert(ctx, &console.ProjectInvitation{
|
||||
ProjectID: p,
|
||||
Email: inviteeEmail,
|
||||
InviterID: owner,
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return members, invitees
|
||||
}
|
||||
|
||||
func TestGetProjectMembersAndInvitationsOrdering(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
p := planet.Uplinks[0].Projects[0].ID
|
||||
|
||||
user, err := sat.DB.Console().Users().GetByEmail(ctx, planet.Uplinks[0].User[sat.ID()].Email)
|
||||
require.NoError(t, err)
|
||||
|
||||
members, invitees := createTestMembers(ctx, t, sat.DB.Console(), p, &user.ID)
|
||||
members[user.ID] = *user
|
||||
|
||||
tokenInfo, err := sat.API.Console.Service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName})
|
||||
require.NoError(t, err)
|
||||
|
||||
expire := time.Now().AddDate(0, 0, 1)
|
||||
cookie := http.Cookie{
|
||||
Name: "_tokenKey",
|
||||
Path: "/",
|
||||
Value: tokenInfo.Token.String(),
|
||||
Expires: expire,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
order, orderDir int
|
||||
}{
|
||||
{ // ascending by name
|
||||
order: 1,
|
||||
orderDir: 1,
|
||||
},
|
||||
{ // descending by name
|
||||
order: 1,
|
||||
orderDir: 2,
|
||||
},
|
||||
{ // ascending by email
|
||||
order: 2,
|
||||
orderDir: 1,
|
||||
},
|
||||
{ // descending by email
|
||||
order: 2,
|
||||
orderDir: 2,
|
||||
},
|
||||
{ // ascending by created at
|
||||
order: 3,
|
||||
orderDir: 1,
|
||||
},
|
||||
{ // descending by created at
|
||||
order: 3,
|
||||
orderDir: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
addr := planet.Satellites[0].API.Console.Listener.Addr().String()
|
||||
|
||||
url := fmt.Sprintf("http://%s/api/v0/projects/%s/members", addr, p.String())
|
||||
url += fmt.Sprintf("?limit=100&page=1&order=%d&order-direction=%d", tt.order, tt.orderDir)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.AddCookie(&cookie)
|
||||
|
||||
client := http.Client{}
|
||||
res, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
|
||||
var membersAndInvitations consoleapi.ProjectMembersPage
|
||||
require.NoError(t, json.Unmarshal(body, &membersAndInvitations))
|
||||
|
||||
respMembers := membersAndInvitations.Members
|
||||
respInvitees := membersAndInvitations.Invitations
|
||||
for i := 1; i < len(respMembers); i++ {
|
||||
switch tt.orderDir {
|
||||
case int(console.Ascending):
|
||||
switch tt.order {
|
||||
case int(console.Name):
|
||||
require.Less(t, members[respMembers[i-1].User.ID].FullName, members[respMembers[i].User.ID].FullName)
|
||||
case int(console.Email):
|
||||
require.Less(t, members[respMembers[i-1].User.ID].Email, members[respMembers[i].User.ID].Email)
|
||||
case int(console.Created):
|
||||
require.Less(t, members[respMembers[i-1].User.ID].CreatedAt, members[respMembers[i].User.ID].CreatedAt)
|
||||
default:
|
||||
t.Error("invalid order", tt.order)
|
||||
}
|
||||
case int(console.Descending):
|
||||
switch tt.order {
|
||||
case int(console.Name):
|
||||
require.Greater(t, members[respMembers[i-1].User.ID].FullName, members[respMembers[i].User.ID].FullName)
|
||||
case int(console.Email):
|
||||
require.Greater(t, members[respMembers[i-1].User.ID].Email, members[respMembers[i].User.ID].Email)
|
||||
case int(console.Created):
|
||||
require.Greater(t, members[respMembers[i-1].User.ID].CreatedAt, members[respMembers[i].User.ID].CreatedAt)
|
||||
default:
|
||||
t.Error("invalid order", tt.order)
|
||||
}
|
||||
default:
|
||||
t.Error("invalid order direction", tt.orderDir)
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(respInvitees); i++ {
|
||||
switch tt.orderDir {
|
||||
case int(console.Ascending):
|
||||
switch tt.order {
|
||||
case int(console.Name):
|
||||
require.Less(t, invitees[respInvitees[i-1].Email].FullName, invitees[respInvitees[i].Email].FullName)
|
||||
case int(console.Email):
|
||||
require.Less(t, invitees[respInvitees[i-1].Email].Email, invitees[respInvitees[i].Email].Email)
|
||||
case int(console.Created):
|
||||
require.Less(t, invitees[respInvitees[i-1].Email].CreatedAt, invitees[respInvitees[i].Email].CreatedAt)
|
||||
default:
|
||||
t.Error("invalid order", tt.order)
|
||||
}
|
||||
case int(console.Descending):
|
||||
switch tt.order {
|
||||
case int(console.Name):
|
||||
require.Greater(t, invitees[respInvitees[i-1].Email].FullName, invitees[respInvitees[i].Email].FullName)
|
||||
case int(console.Email):
|
||||
require.Greater(t, invitees[respInvitees[i-1].Email].Email, invitees[respInvitees[i].Email].Email)
|
||||
case int(console.Created):
|
||||
require.Greater(t, invitees[respInvitees[i-1].Email].CreatedAt, invitees[respInvitees[i].Email].CreatedAt)
|
||||
default:
|
||||
t.Error("invalid order", tt.order)
|
||||
}
|
||||
default:
|
||||
t.Error("invalid order direction", tt.orderDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProjectMembersAndInvitationsSearch(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
p := planet.Uplinks[0].Projects[0].ID
|
||||
|
||||
user, err := sat.DB.Console().Users().GetByEmail(ctx, planet.Uplinks[0].User[sat.ID()].Email)
|
||||
require.NoError(t, err)
|
||||
|
||||
members, invitees := createTestMembers(ctx, t, sat.DB.Console(), p, &user.ID)
|
||||
members[user.ID] = *user
|
||||
|
||||
tokenInfo, err := sat.API.Console.Service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName})
|
||||
require.NoError(t, err)
|
||||
|
||||
expire := time.Now().AddDate(0, 0, 1)
|
||||
cookie := http.Cookie{
|
||||
Name: "_tokenKey",
|
||||
Path: "/",
|
||||
Value: tokenInfo.Token.String(),
|
||||
Expires: expire,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
search string
|
||||
expectedMembers, expectedInvitees int
|
||||
}{
|
||||
{ // all members and invitees
|
||||
"",
|
||||
4,
|
||||
3,
|
||||
},
|
||||
{ // zero members zero invitees
|
||||
"asdf",
|
||||
0,
|
||||
0,
|
||||
},
|
||||
{ // one member one invitee by email
|
||||
"1",
|
||||
1,
|
||||
1,
|
||||
},
|
||||
{ // three members by full name
|
||||
"Member FullName",
|
||||
3,
|
||||
0,
|
||||
},
|
||||
{ // three invitees by email
|
||||
"invitee",
|
||||
0,
|
||||
3,
|
||||
},
|
||||
{ // one member by short name
|
||||
"Member ShortNameA",
|
||||
1,
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
addr := planet.Satellites[0].API.Console.Listener.Addr().String()
|
||||
|
||||
endpoint := fmt.Sprintf("http://%s/api/v0/projects/%s/members?limit=100&page=1&order=1&order-direction=1&", addr, p.String())
|
||||
params := url.Values{}
|
||||
params.Add("search", tt.search)
|
||||
endpoint += params.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.AddCookie(&cookie)
|
||||
|
||||
client := http.Client{}
|
||||
res, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
|
||||
var membersAndInvitations consoleapi.ProjectMembersPage
|
||||
require.NoError(t, json.Unmarshal(body, &membersAndInvitations))
|
||||
|
||||
respMembers := membersAndInvitations.Members
|
||||
respInvitees := membersAndInvitations.Invitations
|
||||
require.Equal(t, tt.expectedMembers, len(respMembers))
|
||||
require.Equal(t, tt.expectedInvitees, len(respInvitees))
|
||||
if tt.search != "" {
|
||||
for _, m := range respMembers {
|
||||
containsSearch := strings.Contains(members[m.User.ID].Email, tt.search) || strings.Contains(members[m.User.ID].FullName, tt.search) || strings.Contains(members[m.User.ID].ShortName, tt.search)
|
||||
require.True(t, containsSearch)
|
||||
}
|
||||
for _, inv := range respInvitees {
|
||||
containsSearch := strings.Contains(inv.Email, tt.search) || strings.Contains(invitees[inv.Email].FullName, tt.search) || strings.Contains(invitees[inv.Email].ShortName, tt.search)
|
||||
require.True(t, containsSearch)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProjectMembersAndInvitationsLimitAndPage(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
p := planet.Uplinks[0].Projects[0].ID
|
||||
|
||||
user, err := sat.DB.Console().Users().GetByEmail(ctx, planet.Uplinks[0].User[sat.ID()].Email)
|
||||
require.NoError(t, err)
|
||||
|
||||
members, _ := createTestMembers(ctx, t, sat.DB.Console(), p, &user.ID)
|
||||
members[user.ID] = *user
|
||||
|
||||
tokenInfo, err := sat.API.Console.Service.Token(ctx, console.AuthUser{Email: user.Email, Password: user.FullName})
|
||||
require.NoError(t, err)
|
||||
|
||||
expire := time.Now().AddDate(0, 0, 1)
|
||||
cookie := http.Cookie{
|
||||
Name: "_tokenKey",
|
||||
Path: "/",
|
||||
Value: tokenInfo.Token.String(),
|
||||
Expires: expire,
|
||||
}
|
||||
|
||||
addr := planet.Satellites[0].API.Console.Listener.Addr().String()
|
||||
|
||||
limit := 1
|
||||
page := 1
|
||||
var previousResult console.ProjectMembersPage
|
||||
for i := 0; i < 2; i++ {
|
||||
endpoint := fmt.Sprintf("http://%s/api/v0/projects/%s/members?order=1&order-direction=1&", addr, p.String())
|
||||
params := url.Values{}
|
||||
params.Add("limit", fmt.Sprint(limit))
|
||||
params.Add("page", fmt.Sprint(page))
|
||||
endpoint += params.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.AddCookie(&cookie)
|
||||
|
||||
client := http.Client{}
|
||||
res, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
|
||||
var membersAndInvitations console.ProjectMembersPage
|
||||
require.NoError(t, json.Unmarshal(body, &membersAndInvitations))
|
||||
|
||||
respMembers := membersAndInvitations.ProjectMembers
|
||||
respInvitees := membersAndInvitations.ProjectInvitations
|
||||
length := len(respMembers) + len(respInvitees)
|
||||
require.Equal(t, limit, length)
|
||||
require.Equal(t, page, int(membersAndInvitations.CurrentPage))
|
||||
if i != 0 {
|
||||
require.NotEqual(t, previousResult, membersAndInvitations)
|
||||
}
|
||||
previousResult = membersAndInvitations
|
||||
limit++
|
||||
page++
|
||||
}
|
||||
})
|
||||
}
|
@ -278,6 +278,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
||||
projectsRouter.Handle("", http.HandlerFunc(projectsController.GetUserProjects)).Methods(http.MethodGet, http.MethodOptions)
|
||||
projectsRouter.Handle("/paged", http.HandlerFunc(projectsController.GetPagedProjects)).Methods(http.MethodGet, http.MethodOptions)
|
||||
projectsRouter.Handle("/{id}/salt", http.HandlerFunc(projectsController.GetSalt)).Methods(http.MethodGet, http.MethodOptions)
|
||||
projectsRouter.Handle("/{id}/members", http.HandlerFunc(projectsController.GetMembersAndInvitations)).Methods(http.MethodGet, http.MethodOptions)
|
||||
projectsRouter.Handle("/{id}/invite", http.HandlerFunc(projectsController.InviteUsers)).Methods(http.MethodPost, http.MethodOptions)
|
||||
projectsRouter.Handle("/{id}/invite-link", http.HandlerFunc(projectsController.GetInviteLink)).Methods(http.MethodGet, http.MethodOptions)
|
||||
projectsRouter.Handle("/invitations", http.HandlerFunc(projectsController.GetUserInvitations)).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
@ -53,10 +53,9 @@ type ProjectMembersPage struct {
|
||||
Order ProjectMemberOrder
|
||||
OrderDirection OrderDirection
|
||||
Offset uint64
|
||||
|
||||
PageCount uint
|
||||
CurrentPage uint
|
||||
TotalCount uint64
|
||||
PageCount uint
|
||||
CurrentPage uint
|
||||
TotalCount uint64
|
||||
}
|
||||
|
||||
// ProjectMemberOrder is used for querying project members in specified order.
|
||||
|
Loading…
Reference in New Issue
Block a user