satellite/console: return edge URL overrides in project info responses
API responses containing project information now contain the edge service URL overrides configured for that project. The overrides are based on the project's default placement. References #6188 Change-Id: Ifc3dc74e75c0f5daf0419ac3be184415c65b202e
This commit is contained in:
parent
89d682f49f
commit
c8f4f5210d
@ -36,10 +36,6 @@ func TestAdminProjectGeofenceAPI(t *testing.T) {
|
|||||||
project, err := sat.DB.Console().Projects().Get(ctx, uplink.Projects[0].ID)
|
project, err := sat.DB.Console().Projects().Get(ctx, uplink.Projects[0].ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// update project set default placement to EEA
|
|
||||||
project.DefaultPlacement = storj.EEA
|
|
||||||
require.NoError(t, sat.DB.Console().Projects().Update(ctx, project))
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
project uuid.UUID
|
project uuid.UUID
|
||||||
|
113
satellite/console/config.go
Normal file
113
satellite/console/config.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (C) 2023 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"storj.io/common/storj"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config keeps track of core console service configuration parameters.
|
||||||
|
type Config struct {
|
||||||
|
PasswordCost int `help:"password hashing cost (0=automatic)" testDefault:"4" default:"0"`
|
||||||
|
OpenRegistrationEnabled bool `help:"enable open registration" default:"false" testDefault:"true"`
|
||||||
|
DefaultProjectLimit int `help:"default project limits for users" default:"1" testDefault:"5"`
|
||||||
|
AsOfSystemTimeDuration time.Duration `help:"default duration for AS OF SYSTEM TIME" devDefault:"-5m" releaseDefault:"-5m" testDefault:"0"`
|
||||||
|
LoginAttemptsWithoutPenalty int `help:"number of times user can try to login without penalty" default:"3"`
|
||||||
|
FailedLoginPenalty float64 `help:"incremental duration of penalty for failed login attempts in minutes" default:"2.0"`
|
||||||
|
ProjectInvitationExpiration time.Duration `help:"duration that project member invitations are valid for" default:"168h"`
|
||||||
|
UserBalanceForUpgrade int64 `help:"amount of base units of US micro dollars needed to upgrade user's tier status" default:"10000000"`
|
||||||
|
PlacementEdgeURLOverrides PlacementEdgeURLOverrides `help:"placement-specific edge service URL overrides in the format {\"placementID\": {\"authService\": \"...\", \"publicLinksharing\": \"...\", \"internalLinksharing\": \"...\"}, \"placementID2\": ...}"`
|
||||||
|
UsageLimits UsageLimitsConfig
|
||||||
|
Captcha CaptchaConfig
|
||||||
|
Session SessionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptchaConfig contains configurations for login/registration captcha system.
|
||||||
|
type CaptchaConfig struct {
|
||||||
|
Login MultiCaptchaConfig `json:"login"`
|
||||||
|
Registration MultiCaptchaConfig `json:"registration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiCaptchaConfig contains configurations for Recaptcha and Hcaptcha systems.
|
||||||
|
type MultiCaptchaConfig struct {
|
||||||
|
Recaptcha SingleCaptchaConfig `json:"recaptcha"`
|
||||||
|
Hcaptcha SingleCaptchaConfig `json:"hcaptcha"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SingleCaptchaConfig contains configurations abstract captcha system.
|
||||||
|
type SingleCaptchaConfig struct {
|
||||||
|
Enabled bool `help:"whether or not captcha is enabled" default:"false" json:"enabled"`
|
||||||
|
SiteKey string `help:"captcha site key" json:"siteKey"`
|
||||||
|
SecretKey string `help:"captcha secret key" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionConfig contains configurations for session management.
|
||||||
|
type SessionConfig struct {
|
||||||
|
InactivityTimerEnabled bool `help:"indicates if session can be timed out due inactivity" default:"true"`
|
||||||
|
InactivityTimerDuration int `help:"inactivity timer delay in seconds" default:"600"`
|
||||||
|
InactivityTimerViewerEnabled bool `help:"indicates whether remaining session time is shown for debugging" default:"false"`
|
||||||
|
Duration time.Duration `help:"duration a session is valid for (superseded by inactivity timer delay if inactivity timer is enabled)" default:"168h"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EdgeURLOverrides contains edge service URL overrides.
|
||||||
|
type EdgeURLOverrides struct {
|
||||||
|
AuthService string `json:"authService,omitempty"`
|
||||||
|
PublicLinksharing string `json:"publicLinksharing,omitempty"`
|
||||||
|
InternalLinksharing string `json:"internalLinksharing,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlacementEdgeURLOverrides represents a mapping between placement IDs and edge service URL overrides.
|
||||||
|
type PlacementEdgeURLOverrides struct {
|
||||||
|
overrideMap map[storj.PlacementConstraint]EdgeURLOverrides
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that PlacementEdgeOverrides implements pflag.Value.
|
||||||
|
var _ pflag.Value = (*PlacementEdgeURLOverrides)(nil)
|
||||||
|
|
||||||
|
// Type implements pflag.Value.
|
||||||
|
func (PlacementEdgeURLOverrides) Type() string { return "console.PlacementEdgeURLOverrides" }
|
||||||
|
|
||||||
|
// String implements pflag.Value.
|
||||||
|
func (ov *PlacementEdgeURLOverrides) String() string {
|
||||||
|
if ov == nil || len(ov.overrideMap) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
overrides, err := json.Marshal(ov.overrideMap)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(overrides)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set implements pflag.Value.
|
||||||
|
func (ov *PlacementEdgeURLOverrides) Set(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
overrides := make(map[storj.PlacementConstraint]EdgeURLOverrides)
|
||||||
|
err := json.Unmarshal([]byte(s), &overrides)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ov.overrideMap = overrides
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the edge service URL overrides for the given placement ID.
|
||||||
|
func (ov *PlacementEdgeURLOverrides) Get(placement storj.PlacementConstraint) (overrides EdgeURLOverrides, ok bool) {
|
||||||
|
if ov == nil {
|
||||||
|
return EdgeURLOverrides{}, false
|
||||||
|
}
|
||||||
|
overrides, ok = ov.overrideMap[placement]
|
||||||
|
return overrides, ok
|
||||||
|
}
|
@ -83,7 +83,7 @@ func (p *Projects) GetUserProjects(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
response := make([]console.ProjectInfo, 0)
|
response := make([]console.ProjectInfo, 0)
|
||||||
for _, project := range projects {
|
for _, project := range projects {
|
||||||
response = append(response, project.GetMinimal())
|
response = append(response, p.service.GetMinimalProject(&project))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(response)
|
err = json.NewEncoder(w).Encode(response)
|
||||||
@ -156,7 +156,7 @@ func (p *Projects) GetPagedProjects(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, project := range projectsPage.Projects {
|
for _, project := range projectsPage.Projects {
|
||||||
pageToSend.Projects = append(pageToSend.Projects, project.GetMinimal())
|
pageToSend.Projects = append(pageToSend.Projects, p.service.GetMinimalProject(&project))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(pageToSend)
|
err = json.NewEncoder(w).Encode(pageToSend)
|
||||||
@ -287,7 +287,7 @@ func (p *Projects) CreateProject(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
err = json.NewEncoder(w).Encode(project.GetMinimal())
|
err = json.NewEncoder(w).Encode(p.service.GetMinimalProject(project))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
p.serveJSONError(ctx, w, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,15 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"storj.io/common/memory"
|
"storj.io/common/memory"
|
||||||
|
"storj.io/common/storj"
|
||||||
"storj.io/common/testcontext"
|
"storj.io/common/testcontext"
|
||||||
"storj.io/common/testrand"
|
"storj.io/common/testrand"
|
||||||
"storj.io/common/uuid"
|
"storj.io/common/uuid"
|
||||||
"storj.io/storj/private/testplanet"
|
"storj.io/storj/private/testplanet"
|
||||||
|
"storj.io/storj/satellite"
|
||||||
"storj.io/storj/satellite/console"
|
"storj.io/storj/satellite/console"
|
||||||
"storj.io/storj/satellite/console/consoleweb/consoleapi"
|
"storj.io/storj/satellite/console/consoleweb/consoleapi"
|
||||||
)
|
)
|
||||||
@ -373,3 +376,95 @@ func TestDeleteProjectMembers(t *testing.T) {
|
|||||||
require.Contains(t, string(body), "error")
|
require.Contains(t, string(body), "error")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEdgeURLOverrides(t *testing.T) {
|
||||||
|
var (
|
||||||
|
noOverridePlacementID storj.PlacementConstraint
|
||||||
|
partialOverridePlacementID storj.PlacementConstraint = 1
|
||||||
|
fullOverridePlacementID storj.PlacementConstraint = 2
|
||||||
|
|
||||||
|
authServiceURL = "auth.storj.io"
|
||||||
|
publicLinksharingURL = "public-link.storj.io"
|
||||||
|
internalLinksharingURL = "link.storj.io"
|
||||||
|
)
|
||||||
|
|
||||||
|
testplanet.Run(t, testplanet.Config{
|
||||||
|
SatelliteCount: 1, UplinkCount: 1,
|
||||||
|
Reconfigure: testplanet.Reconfigure{
|
||||||
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||||
|
err := config.Console.PlacementEdgeURLOverrides.Set(
|
||||||
|
fmt.Sprintf(
|
||||||
|
`{
|
||||||
|
"%d": {"authService": "%s"},
|
||||||
|
"%d": {
|
||||||
|
"authService": "%s",
|
||||||
|
"publicLinksharing": "%s",
|
||||||
|
"internalLinksharing": "%s"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
partialOverridePlacementID, authServiceURL,
|
||||||
|
fullOverridePlacementID, authServiceURL, publicLinksharingURL, internalLinksharingURL,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||||
|
sat := planet.Satellites[0]
|
||||||
|
|
||||||
|
project, err := sat.DB.Console().Projects().Get(ctx, planet.Uplinks[0].Projects[0].ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
user, err := sat.API.Console.Service.GetUser(ctx, project.OwnerID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
placement *storj.PlacementConstraint
|
||||||
|
expectedEdgeURLs *console.EdgeURLOverrides
|
||||||
|
}{
|
||||||
|
{"nil placement", nil, nil},
|
||||||
|
{"placement with no overrides", &noOverridePlacementID, nil},
|
||||||
|
{
|
||||||
|
"placement with partial override",
|
||||||
|
&partialOverridePlacementID,
|
||||||
|
&console.EdgeURLOverrides{AuthService: authServiceURL},
|
||||||
|
}, {
|
||||||
|
"placement with full override",
|
||||||
|
&fullOverridePlacementID,
|
||||||
|
&console.EdgeURLOverrides{
|
||||||
|
AuthService: authServiceURL,
|
||||||
|
PublicLinksharing: publicLinksharingURL,
|
||||||
|
InternalLinksharing: internalLinksharingURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := sat.DB.Testing().RawDB().ExecContext(ctx,
|
||||||
|
"UPDATE projects SET default_placement = $1 WHERE id = $2",
|
||||||
|
tt.placement, project.ID,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
count, err := result.RowsAffected()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, count)
|
||||||
|
|
||||||
|
body, status, err := doRequestWithAuth(ctx, t, sat, user, http.MethodGet, "projects", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, status)
|
||||||
|
|
||||||
|
var infos []console.ProjectInfo
|
||||||
|
require.NoError(t, json.Unmarshal(body, &infos))
|
||||||
|
require.NotEmpty(t, infos)
|
||||||
|
|
||||||
|
if tt.expectedEdgeURLs == nil {
|
||||||
|
require.Nil(t, infos[0].EdgeURLOverrides)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NotNil(t, infos[0].EdgeURLOverrides)
|
||||||
|
require.Equal(t, *tt.expectedEdgeURLs, *infos[0].EdgeURLOverrides)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -125,12 +125,13 @@ type UpsertProjectInfo struct {
|
|||||||
|
|
||||||
// ProjectInfo holds data sent via user facing http endpoints.
|
// ProjectInfo holds data sent via user facing http endpoints.
|
||||||
type ProjectInfo struct {
|
type ProjectInfo struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
OwnerID uuid.UUID `json:"ownerId"`
|
OwnerID uuid.UUID `json:"ownerId"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
MemberCount int `json:"memberCount"`
|
MemberCount int `json:"memberCount"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
EdgeURLOverrides *EdgeURLOverrides `json:"edgeURLOverrides,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProjectsCursor holds info for project
|
// ProjectsCursor holds info for project
|
||||||
@ -194,15 +195,3 @@ func ValidateNameAndDescription(name string, description string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMinimal returns a ProjectInfo copy of a project.
|
|
||||||
func (p Project) GetMinimal() ProjectInfo {
|
|
||||||
return ProjectInfo{
|
|
||||||
ID: p.PublicID,
|
|
||||||
Name: p.Name,
|
|
||||||
OwnerID: p.OwnerID,
|
|
||||||
Description: p.Description,
|
|
||||||
MemberCount: p.MemberCount,
|
|
||||||
CreatedAt: p.CreatedAt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -187,48 +187,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config keeps track of core console service configuration parameters.
|
|
||||||
type Config struct {
|
|
||||||
PasswordCost int `help:"password hashing cost (0=automatic)" testDefault:"4" default:"0"`
|
|
||||||
OpenRegistrationEnabled bool `help:"enable open registration" default:"false" testDefault:"true"`
|
|
||||||
DefaultProjectLimit int `help:"default project limits for users" default:"1" testDefault:"5"`
|
|
||||||
AsOfSystemTimeDuration time.Duration `help:"default duration for AS OF SYSTEM TIME" devDefault:"-5m" releaseDefault:"-5m" testDefault:"0"`
|
|
||||||
LoginAttemptsWithoutPenalty int `help:"number of times user can try to login without penalty" default:"3"`
|
|
||||||
FailedLoginPenalty float64 `help:"incremental duration of penalty for failed login attempts in minutes" default:"2.0"`
|
|
||||||
ProjectInvitationExpiration time.Duration `help:"duration that project member invitations are valid for" default:"168h"`
|
|
||||||
UserBalanceForUpgrade int64 `help:"amount of base units of US micro dollars needed to upgrade user's tier status" default:"10000000"`
|
|
||||||
UsageLimits UsageLimitsConfig
|
|
||||||
Captcha CaptchaConfig
|
|
||||||
Session SessionConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// CaptchaConfig contains configurations for login/registration captcha system.
|
|
||||||
type CaptchaConfig struct {
|
|
||||||
Login MultiCaptchaConfig `json:"login"`
|
|
||||||
Registration MultiCaptchaConfig `json:"registration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiCaptchaConfig contains configurations for Recaptcha and Hcaptcha systems.
|
|
||||||
type MultiCaptchaConfig struct {
|
|
||||||
Recaptcha SingleCaptchaConfig `json:"recaptcha"`
|
|
||||||
Hcaptcha SingleCaptchaConfig `json:"hcaptcha"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SingleCaptchaConfig contains configurations abstract captcha system.
|
|
||||||
type SingleCaptchaConfig struct {
|
|
||||||
Enabled bool `help:"whether or not captcha is enabled" default:"false" json:"enabled"`
|
|
||||||
SiteKey string `help:"captcha site key" json:"siteKey"`
|
|
||||||
SecretKey string `help:"captcha secret key" json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionConfig contains configurations for session management.
|
|
||||||
type SessionConfig struct {
|
|
||||||
InactivityTimerEnabled bool `help:"indicates if session can be timed out due inactivity" default:"true"`
|
|
||||||
InactivityTimerDuration int `help:"inactivity timer delay in seconds" default:"600"`
|
|
||||||
InactivityTimerViewerEnabled bool `help:"indicates whether remaining session time is shown for debugging" default:"false"`
|
|
||||||
Duration time.Duration `help:"duration a session is valid for (superseded by inactivity timer delay if inactivity timer is enabled)" default:"168h"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Payments separates all payment related functionality.
|
// Payments separates all payment related functionality.
|
||||||
type Payments struct {
|
type Payments struct {
|
||||||
service *Service
|
service *Service
|
||||||
@ -1614,6 +1572,24 @@ func (s *Service) GetUsersProjects(ctx context.Context) (ps []Project, err error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMinimalProject returns a ProjectInfo copy of a project.
|
||||||
|
func (s *Service) GetMinimalProject(project *Project) ProjectInfo {
|
||||||
|
info := ProjectInfo{
|
||||||
|
ID: project.PublicID,
|
||||||
|
Name: project.Name,
|
||||||
|
OwnerID: project.OwnerID,
|
||||||
|
Description: project.Description,
|
||||||
|
MemberCount: project.MemberCount,
|
||||||
|
CreatedAt: project.CreatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if edgeURLs, ok := s.config.PlacementEdgeURLOverrides.Get(project.DefaultPlacement); ok {
|
||||||
|
info.EdgeURLOverrides = &edgeURLs
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
// GenGetUsersProjects is a method for querying all projects for generated api.
|
// GenGetUsersProjects is a method for querying all projects for generated api.
|
||||||
func (s *Service) GenGetUsersProjects(ctx context.Context) (ps []Project, httpErr api.HTTPError) {
|
func (s *Service) GenGetUsersProjects(ctx context.Context) (ps []Project, httpErr api.HTTPError) {
|
||||||
var err error
|
var err error
|
||||||
|
@ -68,13 +68,22 @@ func (projects *projects) GetByUserID(ctx context.Context, userID uuid.UUID) (_
|
|||||||
defer mon.Task()(&ctx)(&err)
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
rows, err := projects.sdb.Query(ctx, projects.sdb.Rebind(`
|
rows, err := projects.sdb.Query(ctx, projects.sdb.Rebind(`
|
||||||
SELECT projects.id, projects.public_id, projects.name, projects.description, projects.owner_id, projects.rate_limit, projects.max_buckets, projects.created_at,
|
SELECT
|
||||||
|
projects.id,
|
||||||
|
projects.public_id,
|
||||||
|
projects.name,
|
||||||
|
projects.description,
|
||||||
|
projects.owner_id,
|
||||||
|
projects.rate_limit,
|
||||||
|
projects.max_buckets,
|
||||||
|
projects.created_at,
|
||||||
|
COALESCE(projects.default_placement, 0),
|
||||||
(SELECT COUNT(*) FROM project_members WHERE project_id = projects.id) AS member_count
|
(SELECT COUNT(*) FROM project_members WHERE project_id = projects.id) AS member_count
|
||||||
FROM projects
|
FROM projects
|
||||||
JOIN project_members ON projects.id = project_members.project_id
|
JOIN project_members ON projects.id = project_members.project_id
|
||||||
WHERE project_members.member_id = ?
|
WHERE project_members.member_id = ?
|
||||||
ORDER BY name ASC
|
ORDER BY name ASC
|
||||||
`), userID)
|
`), userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -84,7 +93,18 @@ func (projects *projects) GetByUserID(ctx context.Context, userID uuid.UUID) (_
|
|||||||
var rateLimit, maxBuckets sql.NullInt32
|
var rateLimit, maxBuckets sql.NullInt32
|
||||||
projectsToSend := make([]console.Project, 0)
|
projectsToSend := make([]console.Project, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err = rows.Scan(&nextProject.ID, &nextProject.PublicID, &nextProject.Name, &nextProject.Description, &nextProject.OwnerID, &rateLimit, &maxBuckets, &nextProject.CreatedAt, &nextProject.MemberCount)
|
err = rows.Scan(
|
||||||
|
&nextProject.ID,
|
||||||
|
&nextProject.PublicID,
|
||||||
|
&nextProject.Name,
|
||||||
|
&nextProject.Description,
|
||||||
|
&nextProject.OwnerID,
|
||||||
|
&rateLimit,
|
||||||
|
&maxBuckets,
|
||||||
|
&nextProject.CreatedAt,
|
||||||
|
&nextProject.DefaultPlacement,
|
||||||
|
&nextProject.MemberCount,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -366,14 +386,23 @@ func (projects *projects) ListByOwnerID(ctx context.Context, ownerID uuid.UUID,
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows, err := projects.sdb.Query(ctx, projects.sdb.Rebind(`
|
rows, err := projects.sdb.Query(ctx, projects.sdb.Rebind(`
|
||||||
SELECT id, public_id, name, description, owner_id, rate_limit, max_buckets, created_at,
|
SELECT
|
||||||
|
id,
|
||||||
|
public_id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
owner_id,
|
||||||
|
rate_limit,
|
||||||
|
max_buckets,
|
||||||
|
created_at,
|
||||||
|
COALESCE(default_placement, 0),
|
||||||
(SELECT COUNT(*) FROM project_members WHERE project_id = projects.id) AS member_count
|
(SELECT COUNT(*) FROM project_members WHERE project_id = projects.id) AS member_count
|
||||||
FROM projects
|
FROM projects
|
||||||
WHERE owner_id = ?
|
WHERE owner_id = ?
|
||||||
ORDER BY name ASC
|
ORDER BY name ASC
|
||||||
OFFSET ? ROWS
|
OFFSET ? ROWS
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
`), ownerID, page.Offset, page.Limit+1) // add 1 to limit to see if there is another page
|
`), ownerID, page.Offset, page.Limit+1) // add 1 to limit to see if there is another page
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return console.ProjectsPage{}, err
|
return console.ProjectsPage{}, err
|
||||||
}
|
}
|
||||||
@ -391,7 +420,18 @@ func (projects *projects) ListByOwnerID(ctx context.Context, ownerID uuid.UUID,
|
|||||||
}
|
}
|
||||||
var rateLimit, maxBuckets sql.NullInt32
|
var rateLimit, maxBuckets sql.NullInt32
|
||||||
nextProject := &console.Project{}
|
nextProject := &console.Project{}
|
||||||
err = rows.Scan(&nextProject.ID, &nextProject.PublicID, &nextProject.Name, &nextProject.Description, &nextProject.OwnerID, &rateLimit, &maxBuckets, &nextProject.CreatedAt, &nextProject.MemberCount)
|
err = rows.Scan(
|
||||||
|
&nextProject.ID,
|
||||||
|
&nextProject.PublicID,
|
||||||
|
&nextProject.Name,
|
||||||
|
&nextProject.Description,
|
||||||
|
&nextProject.OwnerID,
|
||||||
|
&rateLimit,
|
||||||
|
&maxBuckets,
|
||||||
|
&nextProject.CreatedAt,
|
||||||
|
&nextProject.DefaultPlacement,
|
||||||
|
&nextProject.MemberCount,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return console.ProjectsPage{}, err
|
return console.ProjectsPage{}, err
|
||||||
}
|
}
|
||||||
|
3
scripts/testdata/satellite-config.yaml.lock
vendored
3
scripts/testdata/satellite-config.yaml.lock
vendored
@ -337,6 +337,9 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
|||||||
# indicates if the overview onboarding step should render with pathways
|
# indicates if the overview onboarding step should render with pathways
|
||||||
# console.pathway-overview-enabled: true
|
# console.pathway-overview-enabled: true
|
||||||
|
|
||||||
|
# placement-specific edge service URL overrides in the format {"placementID": {"authService": "...", "publicLinksharing": "...", "internalLinksharing": "..."}, "placementID2": ...}
|
||||||
|
# console.placement-edge-url-overrides: ""
|
||||||
|
|
||||||
# whether to allow purchasing pricing packages
|
# whether to allow purchasing pricing packages
|
||||||
# console.pricing-packages-enabled: false
|
# console.pricing-packages-enabled: false
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user