mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add sharing info to /workspaces endpoint (#21049)
closes: https://github.com/coder/internal/issues/858 Similar to https://github.com/coder/coder/pull/19375, this one uses system permissions for fetching actual user and group data. Modifies the `workspaces_expanded` view to fetch the required data; this way it's made available to all code paths that make use of it. Also fixes a bug in a test helper function that can result in `null` being saved to the DB for `user_acl` or `group_acl` and break tests; a defensive check constraint that prevents this is worth a PR, e.g: `ALTER TABLE workspaces ADD CONSTRAINT group_acl_is_object CHECK (jsonb_typeof(group_acl) = 'object');` Also adds missing `OwnerName` in `ConvertWorkspaceRows`.
This commit is contained in:
@@ -440,10 +440,18 @@ func Workspace(t testing.TB, db database.Store, orig database.WorkspaceTable) da
|
||||
workspace.DormantAt = orig.DormantAt
|
||||
}
|
||||
if len(orig.UserACL) > 0 || len(orig.GroupACL) > 0 {
|
||||
userACL := orig.UserACL
|
||||
if userACL == nil {
|
||||
userACL = database.WorkspaceACL{}
|
||||
}
|
||||
groupACL := orig.GroupACL
|
||||
if groupACL == nil {
|
||||
groupACL = database.WorkspaceACL{}
|
||||
}
|
||||
err = db.UpdateWorkspaceACLByID(genCtx, database.UpdateWorkspaceACLByIDParams{
|
||||
ID: workspace.ID,
|
||||
UserACL: orig.UserACL,
|
||||
GroupACL: orig.GroupACL,
|
||||
UserACL: userACL,
|
||||
GroupACL: groupACL,
|
||||
})
|
||||
require.NoError(t, err, "set workspace ACL")
|
||||
workspace.UserACL = orig.UserACL
|
||||
|
||||
Generated
+7
-1
@@ -2938,7 +2938,13 @@ CREATE VIEW workspaces_expanded AS
|
||||
templates.display_name AS template_display_name,
|
||||
templates.icon AS template_icon,
|
||||
templates.description AS template_description,
|
||||
tasks.id AS task_id
|
||||
tasks.id AS task_id,
|
||||
COALESCE(( SELECT jsonb_object_agg(acl.key, jsonb_build_object('name', COALESCE(g.name, ''::text), 'avatar_url', COALESCE(g.avatar_url, ''::text))) AS jsonb_object_agg
|
||||
FROM (jsonb_each(workspaces.group_acl) acl(key, value)
|
||||
LEFT JOIN groups g ON ((g.id = (acl.key)::uuid)))), '{}'::jsonb) AS group_acl_display_info,
|
||||
COALESCE(( SELECT jsonb_object_agg(acl.key, jsonb_build_object('name', COALESCE(vu.name, ''::text), 'avatar_url', COALESCE(vu.avatar_url, ''::text))) AS jsonb_object_agg
|
||||
FROM (jsonb_each(workspaces.user_acl) acl(key, value)
|
||||
LEFT JOIN visible_users vu ON ((vu.id = (acl.key)::uuid)))), '{}'::jsonb) AS user_acl_display_info
|
||||
FROM ((((workspaces
|
||||
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
|
||||
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
DROP VIEW workspaces_expanded;
|
||||
|
||||
-- Revert to passing through raw user_acl and group_acl columns.
|
||||
CREATE VIEW workspaces_expanded AS
|
||||
SELECT workspaces.id,
|
||||
workspaces.created_at,
|
||||
workspaces.updated_at,
|
||||
workspaces.owner_id,
|
||||
workspaces.organization_id,
|
||||
workspaces.template_id,
|
||||
workspaces.deleted,
|
||||
workspaces.name,
|
||||
workspaces.autostart_schedule,
|
||||
workspaces.ttl,
|
||||
workspaces.last_used_at,
|
||||
workspaces.dormant_at,
|
||||
workspaces.deleting_at,
|
||||
workspaces.automatic_updates,
|
||||
workspaces.favorite,
|
||||
workspaces.next_start_at,
|
||||
workspaces.group_acl,
|
||||
workspaces.user_acl,
|
||||
visible_users.avatar_url AS owner_avatar_url,
|
||||
visible_users.username AS owner_username,
|
||||
visible_users.name AS owner_name,
|
||||
organizations.name AS organization_name,
|
||||
organizations.display_name AS organization_display_name,
|
||||
organizations.icon AS organization_icon,
|
||||
organizations.description AS organization_description,
|
||||
templates.name AS template_name,
|
||||
templates.display_name AS template_display_name,
|
||||
templates.icon AS template_icon,
|
||||
templates.description AS template_description,
|
||||
tasks.id AS task_id
|
||||
FROM ((((workspaces
|
||||
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
|
||||
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
|
||||
JOIN templates ON ((workspaces.template_id = templates.id)))
|
||||
LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id)));
|
||||
|
||||
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';
|
||||
@@ -0,0 +1,65 @@
|
||||
DROP VIEW workspaces_expanded;
|
||||
|
||||
-- Expand more by including group_acl_display_info and
|
||||
-- user_acl_display_info columns with the actors' name and avatar.
|
||||
CREATE VIEW workspaces_expanded AS
|
||||
SELECT workspaces.id,
|
||||
workspaces.created_at,
|
||||
workspaces.updated_at,
|
||||
workspaces.owner_id,
|
||||
workspaces.organization_id,
|
||||
workspaces.template_id,
|
||||
workspaces.deleted,
|
||||
workspaces.name,
|
||||
workspaces.autostart_schedule,
|
||||
workspaces.ttl,
|
||||
workspaces.last_used_at,
|
||||
workspaces.dormant_at,
|
||||
workspaces.deleting_at,
|
||||
workspaces.automatic_updates,
|
||||
workspaces.favorite,
|
||||
workspaces.next_start_at,
|
||||
workspaces.group_acl,
|
||||
workspaces.user_acl,
|
||||
visible_users.avatar_url AS owner_avatar_url,
|
||||
visible_users.username AS owner_username,
|
||||
visible_users.name AS owner_name,
|
||||
organizations.name AS organization_name,
|
||||
organizations.display_name AS organization_display_name,
|
||||
organizations.icon AS organization_icon,
|
||||
organizations.description AS organization_description,
|
||||
templates.name AS template_name,
|
||||
templates.display_name AS template_display_name,
|
||||
templates.icon AS template_icon,
|
||||
templates.description AS template_description,
|
||||
tasks.id AS task_id,
|
||||
-- Workspace ACL actors' display info
|
||||
COALESCE((
|
||||
SELECT jsonb_object_agg(
|
||||
acl.key,
|
||||
jsonb_build_object(
|
||||
'name', COALESCE(g.name, ''),
|
||||
'avatar_url', COALESCE(g.avatar_url, '')
|
||||
)
|
||||
)
|
||||
FROM jsonb_each(workspaces.group_acl) AS acl
|
||||
LEFT JOIN groups g ON g.id = acl.key::uuid
|
||||
), '{}'::jsonb) AS group_acl_display_info,
|
||||
COALESCE((
|
||||
SELECT jsonb_object_agg(
|
||||
acl.key,
|
||||
jsonb_build_object(
|
||||
'name', COALESCE(vu.name, ''),
|
||||
'avatar_url', COALESCE(vu.avatar_url, '')
|
||||
)
|
||||
)
|
||||
FROM jsonb_each(workspaces.user_acl) AS acl
|
||||
LEFT JOIN visible_users vu ON vu.id = acl.key::uuid
|
||||
), '{}'::jsonb) AS user_acl_display_info
|
||||
FROM ((((workspaces
|
||||
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
|
||||
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
|
||||
JOIN templates ON ((workspaces.template_id = templates.id)))
|
||||
LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id)));
|
||||
|
||||
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';
|
||||
@@ -658,7 +658,7 @@ func ConvertUserRows(rows []GetUsersRow) []User {
|
||||
return users
|
||||
}
|
||||
|
||||
func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
|
||||
func ConvertWorkspaceRows(rows []GetWorkspacesRow) ([]Workspace, error) {
|
||||
workspaces := make([]Workspace, len(rows))
|
||||
for i, r := range rows {
|
||||
workspaces[i] = Workspace{
|
||||
@@ -679,6 +679,7 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
|
||||
Favorite: r.Favorite,
|
||||
OwnerAvatarUrl: r.OwnerAvatarUrl,
|
||||
OwnerUsername: r.OwnerUsername,
|
||||
OwnerName: r.OwnerName,
|
||||
OrganizationName: r.OrganizationName,
|
||||
OrganizationDisplayName: r.OrganizationDisplayName,
|
||||
OrganizationIcon: r.OrganizationIcon,
|
||||
@@ -690,9 +691,31 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
|
||||
NextStartAt: r.NextStartAt,
|
||||
TaskID: r.TaskID,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
err = workspaces[i].UserACL.Scan(r.UserACL)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("scan user ACL %q: %w", r.UserACL, err)
|
||||
}
|
||||
err = workspaces[i].GroupACL.Scan(r.GroupACL)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("scan group ACL %q: %w", r.GroupACL, err)
|
||||
}
|
||||
|
||||
err = workspaces[i].UserACLDisplayInfo.Scan(r.UserACLDisplayInfo)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("scan user ACL display info %q: %w",
|
||||
r.UserACLDisplayInfo, err)
|
||||
}
|
||||
err = workspaces[i].GroupACLDisplayInfo.Scan(r.GroupACLDisplayInfo)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("scan group ACL display info %q: %w",
|
||||
r.GroupACLDisplayInfo, err)
|
||||
}
|
||||
}
|
||||
|
||||
return workspaces
|
||||
return workspaces, nil
|
||||
}
|
||||
|
||||
func (g Group) IsEveryone() bool {
|
||||
|
||||
@@ -323,6 +323,8 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
|
||||
&i.TemplateIcon,
|
||||
&i.TemplateDescription,
|
||||
&i.TaskID,
|
||||
&i.GroupACLDisplayInfo,
|
||||
&i.UserACLDisplayInfo,
|
||||
&i.TemplateVersionID,
|
||||
&i.TemplateVersionName,
|
||||
&i.LatestBuildCompletedAt,
|
||||
|
||||
+32
-30
@@ -4645,36 +4645,38 @@ type WebpushSubscription struct {
|
||||
|
||||
// Joins in the display name information such as username, avatar, and organization name.
|
||||
type Workspace struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
|
||||
Deleted bool `db:"deleted" json:"deleted"`
|
||||
Name string `db:"name" json:"name"`
|
||||
AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
|
||||
Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
|
||||
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
|
||||
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
|
||||
DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
|
||||
AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
|
||||
Favorite bool `db:"favorite" json:"favorite"`
|
||||
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
|
||||
GroupACL WorkspaceACL `db:"group_acl" json:"group_acl"`
|
||||
UserACL WorkspaceACL `db:"user_acl" json:"user_acl"`
|
||||
OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"`
|
||||
OwnerUsername string `db:"owner_username" json:"owner_username"`
|
||||
OwnerName string `db:"owner_name" json:"owner_name"`
|
||||
OrganizationName string `db:"organization_name" json:"organization_name"`
|
||||
OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"`
|
||||
OrganizationIcon string `db:"organization_icon" json:"organization_icon"`
|
||||
OrganizationDescription string `db:"organization_description" json:"organization_description"`
|
||||
TemplateName string `db:"template_name" json:"template_name"`
|
||||
TemplateDisplayName string `db:"template_display_name" json:"template_display_name"`
|
||||
TemplateIcon string `db:"template_icon" json:"template_icon"`
|
||||
TemplateDescription string `db:"template_description" json:"template_description"`
|
||||
TaskID uuid.NullUUID `db:"task_id" json:"task_id"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
|
||||
Deleted bool `db:"deleted" json:"deleted"`
|
||||
Name string `db:"name" json:"name"`
|
||||
AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
|
||||
Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
|
||||
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
|
||||
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
|
||||
DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
|
||||
AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
|
||||
Favorite bool `db:"favorite" json:"favorite"`
|
||||
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
|
||||
GroupACL WorkspaceACL `db:"group_acl" json:"group_acl"`
|
||||
UserACL WorkspaceACL `db:"user_acl" json:"user_acl"`
|
||||
OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"`
|
||||
OwnerUsername string `db:"owner_username" json:"owner_username"`
|
||||
OwnerName string `db:"owner_name" json:"owner_name"`
|
||||
OrganizationName string `db:"organization_name" json:"organization_name"`
|
||||
OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"`
|
||||
OrganizationIcon string `db:"organization_icon" json:"organization_icon"`
|
||||
OrganizationDescription string `db:"organization_description" json:"organization_description"`
|
||||
TemplateName string `db:"template_name" json:"template_name"`
|
||||
TemplateDisplayName string `db:"template_display_name" json:"template_display_name"`
|
||||
TemplateIcon string `db:"template_icon" json:"template_icon"`
|
||||
TemplateDescription string `db:"template_description" json:"template_description"`
|
||||
TaskID uuid.NullUUID `db:"task_id" json:"task_id"`
|
||||
GroupACLDisplayInfo WorkspaceACLDisplayInfo `db:"group_acl_display_info" json:"group_acl_display_info"`
|
||||
UserACLDisplayInfo WorkspaceACLDisplayInfo `db:"user_acl_display_info" json:"user_acl_display_info"`
|
||||
}
|
||||
|
||||
type WorkspaceAgent struct {
|
||||
|
||||
@@ -22295,7 +22295,7 @@ func (q *sqlQuerier) GetWorkspaceACLByID(ctx context.Context, id uuid.UUID) (Get
|
||||
|
||||
const getWorkspaceByAgentID = `-- name: GetWorkspaceByAgentID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
|
||||
FROM
|
||||
workspaces_expanded as workspaces
|
||||
WHERE
|
||||
@@ -22357,13 +22357,15 @@ func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUI
|
||||
&i.TemplateIcon,
|
||||
&i.TemplateDescription,
|
||||
&i.TaskID,
|
||||
&i.GroupACLDisplayInfo,
|
||||
&i.UserACLDisplayInfo,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceByID = `-- name: GetWorkspaceByID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
|
||||
FROM
|
||||
workspaces_expanded
|
||||
WHERE
|
||||
@@ -22406,13 +22408,15 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp
|
||||
&i.TemplateIcon,
|
||||
&i.TemplateDescription,
|
||||
&i.TaskID,
|
||||
&i.GroupACLDisplayInfo,
|
||||
&i.UserACLDisplayInfo,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceByOwnerIDAndName = `-- name: GetWorkspaceByOwnerIDAndName :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
|
||||
FROM
|
||||
workspaces_expanded as workspaces
|
||||
WHERE
|
||||
@@ -22462,13 +22466,15 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo
|
||||
&i.TemplateIcon,
|
||||
&i.TemplateDescription,
|
||||
&i.TaskID,
|
||||
&i.GroupACLDisplayInfo,
|
||||
&i.UserACLDisplayInfo,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceByResourceID = `-- name: GetWorkspaceByResourceID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
|
||||
FROM
|
||||
workspaces_expanded as workspaces
|
||||
WHERE
|
||||
@@ -22525,13 +22531,15 @@ func (q *sqlQuerier) GetWorkspaceByResourceID(ctx context.Context, resourceID uu
|
||||
&i.TemplateIcon,
|
||||
&i.TemplateDescription,
|
||||
&i.TaskID,
|
||||
&i.GroupACLDisplayInfo,
|
||||
&i.UserACLDisplayInfo,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceByWorkspaceAppID = `-- name: GetWorkspaceByWorkspaceAppID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
|
||||
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
|
||||
FROM
|
||||
workspaces_expanded as workspaces
|
||||
WHERE
|
||||
@@ -22600,6 +22608,8 @@ func (q *sqlQuerier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspace
|
||||
&i.TemplateIcon,
|
||||
&i.TemplateDescription,
|
||||
&i.TaskID,
|
||||
&i.GroupACLDisplayInfo,
|
||||
&i.UserACLDisplayInfo,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -22649,7 +22659,7 @@ SELECT
|
||||
),
|
||||
filtered_workspaces AS (
|
||||
SELECT
|
||||
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, workspaces.task_id,
|
||||
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, workspaces.task_id, workspaces.group_acl_display_info, workspaces.user_acl_display_info,
|
||||
latest_build.template_version_id,
|
||||
latest_build.template_version_name,
|
||||
latest_build.completed_at as latest_build_completed_at,
|
||||
@@ -22933,7 +22943,7 @@ WHERE
|
||||
-- @authorize_filter
|
||||
), filtered_workspaces_order AS (
|
||||
SELECT
|
||||
fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.task_id, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_external_agent
|
||||
fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.task_id, fw.group_acl_display_info, fw.user_acl_display_info, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_external_agent
|
||||
FROM
|
||||
filtered_workspaces fw
|
||||
ORDER BY
|
||||
@@ -22954,7 +22964,7 @@ WHERE
|
||||
$25
|
||||
), filtered_workspaces_order_with_summary AS (
|
||||
SELECT
|
||||
fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_external_agent
|
||||
fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.group_acl_display_info, fwo.user_acl_display_info, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_external_agent
|
||||
FROM
|
||||
filtered_workspaces_order fwo
|
||||
-- Return a technical summary row with total count of workspaces.
|
||||
@@ -22991,6 +23001,8 @@ WHERE
|
||||
'', -- template_icon
|
||||
'', -- template_description
|
||||
'00000000-0000-0000-0000-000000000000'::uuid, -- task_id
|
||||
'{}'::jsonb, -- group_acl_display_info
|
||||
'{}'::jsonb, -- user_acl_display_info
|
||||
-- Extra columns added to ` + "`" + `filtered_workspaces` + "`" + `
|
||||
'00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id
|
||||
'', -- template_version_name
|
||||
@@ -23009,7 +23021,7 @@ WHERE
|
||||
filtered_workspaces
|
||||
)
|
||||
SELECT
|
||||
fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.task_id, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_external_agent,
|
||||
fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.task_id, fwos.group_acl_display_info, fwos.user_acl_display_info, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_external_agent,
|
||||
tc.count
|
||||
FROM
|
||||
filtered_workspaces_order_with_summary fwos
|
||||
@@ -23078,6 +23090,8 @@ type GetWorkspacesRow struct {
|
||||
TemplateIcon string `db:"template_icon" json:"template_icon"`
|
||||
TemplateDescription string `db:"template_description" json:"template_description"`
|
||||
TaskID uuid.NullUUID `db:"task_id" json:"task_id"`
|
||||
GroupACLDisplayInfo interface{} `db:"group_acl_display_info" json:"group_acl_display_info"`
|
||||
UserACLDisplayInfo interface{} `db:"user_acl_display_info" json:"user_acl_display_info"`
|
||||
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
|
||||
TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"`
|
||||
LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"`
|
||||
@@ -23160,6 +23174,8 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams)
|
||||
&i.TemplateIcon,
|
||||
&i.TemplateDescription,
|
||||
&i.TaskID,
|
||||
&i.GroupACLDisplayInfo,
|
||||
&i.UserACLDisplayInfo,
|
||||
&i.TemplateVersionID,
|
||||
&i.TemplateVersionName,
|
||||
&i.LatestBuildCompletedAt,
|
||||
|
||||
@@ -451,6 +451,8 @@ WHERE
|
||||
'', -- template_icon
|
||||
'', -- template_description
|
||||
'00000000-0000-0000-0000-000000000000'::uuid, -- task_id
|
||||
'{}'::jsonb, -- group_acl_display_info
|
||||
'{}'::jsonb, -- user_acl_display_info
|
||||
-- Extra columns added to `filtered_workspaces`
|
||||
'00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id
|
||||
'', -- template_version_name
|
||||
|
||||
@@ -91,6 +91,12 @@ sql:
|
||||
- column: "workspaces_expanded.group_acl"
|
||||
go_type:
|
||||
type: "WorkspaceACL"
|
||||
- column: "workspaces_expanded.user_acl_display_info"
|
||||
go_type:
|
||||
type: "WorkspaceACLDisplayInfo"
|
||||
- column: "workspaces_expanded.group_acl_display_info"
|
||||
go_type:
|
||||
type: "WorkspaceACLDisplayInfo"
|
||||
- column: "notification_templates.actions"
|
||||
go_type:
|
||||
type: "[]byte"
|
||||
@@ -159,6 +165,8 @@ sql:
|
||||
jwt: JWT
|
||||
user_acl: UserACL
|
||||
group_acl: GroupACL
|
||||
user_acl_display_info: UserACLDisplayInfo
|
||||
group_acl_display_info: GroupACLDisplayInfo
|
||||
troubleshooting_url: TroubleshootingURL
|
||||
default_ttl: DefaultTTL
|
||||
motd_file: MOTDFile
|
||||
|
||||
@@ -67,9 +67,10 @@ func (t *TemplateACL) Scan(src interface{}) error {
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
return json.Unmarshal([]byte(v), &t)
|
||||
case []byte, json.RawMessage:
|
||||
//nolint
|
||||
return json.Unmarshal(v.([]byte), &t)
|
||||
case []byte:
|
||||
return json.Unmarshal(v, &t)
|
||||
case json.RawMessage:
|
||||
return json.Unmarshal(v, &t)
|
||||
}
|
||||
|
||||
return xerrors.Errorf("unexpected type %T", src)
|
||||
@@ -85,9 +86,10 @@ func (t *WorkspaceACL) Scan(src interface{}) error {
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
return json.Unmarshal([]byte(v), &t)
|
||||
case []byte, json.RawMessage:
|
||||
//nolint
|
||||
return json.Unmarshal(v.([]byte), &t)
|
||||
case []byte:
|
||||
return json.Unmarshal(v, &t)
|
||||
case json.RawMessage:
|
||||
return json.Unmarshal(v, &t)
|
||||
}
|
||||
|
||||
return xerrors.Errorf("unexpected type %T", src)
|
||||
@@ -112,6 +114,27 @@ type WorkspaceACLEntry struct {
|
||||
Permissions []policy.Action `json:"permissions"`
|
||||
}
|
||||
|
||||
// WorkspaceACLDisplayInfo supplements workspace ACLs with the actors'
|
||||
// display info. Key is string rather than uuid.UUID as this aligns
|
||||
// with how RBAC represents actor IDs.
|
||||
type WorkspaceACLDisplayInfo map[string]struct {
|
||||
Name string `json:"name"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
|
||||
// WorkspaceACLDisplayInfo is only used to read from the DB.
|
||||
func (w *WorkspaceACLDisplayInfo) Scan(src interface{}) error {
|
||||
switch v := src.(type) {
|
||||
case string:
|
||||
return json.Unmarshal([]byte(v), w)
|
||||
case []byte:
|
||||
return json.Unmarshal(v, w)
|
||||
case json.RawMessage:
|
||||
return json.Unmarshal(v, w)
|
||||
}
|
||||
return xerrors.Errorf("unexpected type %T", src)
|
||||
}
|
||||
|
||||
type ExternalAuthProvider struct {
|
||||
ID string `json:"id"`
|
||||
Optional bool `json:"optional,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user