feat: public platform, admin console, templates, landing docs

Projects become publicly browsable after admin approval; management and
secret-adjacent reads (logs, variables, exec, connection URLs) require
owner/member/admin auth, and platform controls sit behind RequireAdmin.

- Backend: OptionalAuth middleware, read/authed/admin route groups,
  projects.is_approved + service_templates.owner_id migration,
  visibility-aware sqlc queries, admin overview/users/approval endpoints,
  EnsureAdminAccount bootstrap (ADMIN_* env), 30 seeded templates with
  owner CRUD, managed-database connection URLs gated owner+admin, fix
  DB.Stats() infinite recursion.
- Frontend: expandable persisted sidebar + clickable user menu, public
  landing page with searchable docs browser (GitHub sync, bundled
  snapshot + localStorage fallback), DocsBrowser shared with /docs,
  template create/edit/JSON-upload for owned templates, database create
  dialog, admin console (stats, approvals, project edit/delete, user
  admin toggles), HA/Security/Usage polish with dead CSS tokens mapped
  to real theme vars, anonymous-safe gating on every mutation control.
- Tests: safe window.localStorage access fixes happy-dom crashes in
  PlatformShell and canvas storage.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-22 19:58:59 +02:00
1 parent 8f5789bf93
commit 8b6214a0f5
62 files changed
+4986 -1133

No files matched your search

+1 -1
View File
@@ -55,5 +55,5 @@ func (db *DB) Health(ctx context.Context) error {
// Stats returns connection pool statistics for monitoring
func (db *DB) Stats() sql.DBStats {
return db.Stats()
return db.DB.Stats()
}
@@ -149,37 +149,18 @@ func (q *Queries) GetDatabaseBackupByIDAndDatabaseAndUser(ctx context.Context, a
return i, err
}
const getDatabaseServiceByIDAndUser = `-- name: GetDatabaseServiceByIDAndUser :one
SELECT id, name, type, status, version, plan, region, connection_url, backup_schedule, next_backup_at, created_at, updated_at
const getDatabaseServiceByID = `-- name: GetDatabaseServiceByID :one
SELECT id, user_id, name, type, status, version, plan, region, connection_url, backup_schedule, next_backup_at, created_at, updated_at
FROM database_services
WHERE id = $1 AND user_id = $2
WHERE id = $1
`
type GetDatabaseServiceByIDAndUserParams struct {
ID string `json:"id"`
UserID string `json:"user_id"`
}
type GetDatabaseServiceByIDAndUserRow struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
Version string `json:"version"`
Plan string `json:"plan"`
Region string `json:"region"`
ConnectionUrl sql.NullString `json:"connection_url"`
BackupSchedule sql.NullString `json:"backup_schedule"`
NextBackupAt sql.NullTime `json:"next_backup_at"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
func (q *Queries) GetDatabaseServiceByIDAndUser(ctx context.Context, arg GetDatabaseServiceByIDAndUserParams) (GetDatabaseServiceByIDAndUserRow, error) {
row := q.db.QueryRowContext(ctx, getDatabaseServiceByIDAndUser, arg.ID, arg.UserID)
var i GetDatabaseServiceByIDAndUserRow
func (q *Queries) GetDatabaseServiceByID(ctx context.Context, id string) (DatabaseService, error) {
row := q.db.QueryRowContext(ctx, getDatabaseServiceByID, id)
var i DatabaseService
err := row.Scan(
&i.ID,
&i.UserID,
&i.Name,
&i.Type,
&i.Status,
@@ -195,6 +176,94 @@ func (q *Queries) GetDatabaseServiceByIDAndUser(ctx context.Context, arg GetData
return i, err
}
const getDatabaseServiceByIDAndUser = `-- name: GetDatabaseServiceByIDAndUser :one
SELECT id, user_id, name, type, status, version, plan, region, connection_url, backup_schedule, next_backup_at, created_at, updated_at
FROM database_services
WHERE id = $1 AND user_id = $2
`
type GetDatabaseServiceByIDAndUserParams struct {
ID string `json:"id"`
UserID string `json:"user_id"`
}
func (q *Queries) GetDatabaseServiceByIDAndUser(ctx context.Context, arg GetDatabaseServiceByIDAndUserParams) (DatabaseService, error) {
row := q.db.QueryRowContext(ctx, getDatabaseServiceByIDAndUser, arg.ID, arg.UserID)
var i DatabaseService
err := row.Scan(
&i.ID,
&i.UserID,
&i.Name,
&i.Type,
&i.Status,
&i.Version,
&i.Plan,
&i.Region,
&i.ConnectionUrl,
&i.BackupSchedule,
&i.NextBackupAt,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getDatabaseServiceOwnerID = `-- name: GetDatabaseServiceOwnerID :one
SELECT user_id
FROM database_services
WHERE id = $1
`
func (q *Queries) GetDatabaseServiceOwnerID(ctx context.Context, id string) (string, error) {
row := q.db.QueryRowContext(ctx, getDatabaseServiceOwnerID, id)
var user_id string
err := row.Scan(&user_id)
return user_id, err
}
const listAllDatabaseServices = `-- name: ListAllDatabaseServices :many
SELECT id, user_id, name, type, status, version, plan, region, connection_url, backup_schedule, next_backup_at, created_at, updated_at
FROM database_services
ORDER BY created_at DESC
`
func (q *Queries) ListAllDatabaseServices(ctx context.Context) ([]DatabaseService, error) {
rows, err := q.db.QueryContext(ctx, listAllDatabaseServices)
if err != nil {
return nil, err
}
defer rows.Close()
items := []DatabaseService{}
for rows.Next() {
var i DatabaseService
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.Name,
&i.Type,
&i.Status,
&i.Version,
&i.Plan,
&i.Region,
&i.ConnectionUrl,
&i.BackupSchedule,
&i.NextBackupAt,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDatabaseBackupsByDatabaseAndUser = `-- name: ListDatabaseBackupsByDatabaseAndUser :many
SELECT b.id, b.database_id, b.size, b.status, b.backup_path, b.created_at, b.completed_at
FROM database_backups b
@@ -242,38 +311,24 @@ func (q *Queries) ListDatabaseBackupsByDatabaseAndUser(ctx context.Context, arg
}
const listDatabaseServicesByUser = `-- name: ListDatabaseServicesByUser :many
SELECT id, name, type, status, version, plan, region, connection_url, backup_schedule, next_backup_at, created_at, updated_at
SELECT id, user_id, name, type, status, version, plan, region, connection_url, backup_schedule, next_backup_at, created_at, updated_at
FROM database_services
WHERE user_id = $1
ORDER BY created_at DESC
`
type ListDatabaseServicesByUserRow struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
Version string `json:"version"`
Plan string `json:"plan"`
Region string `json:"region"`
ConnectionUrl sql.NullString `json:"connection_url"`
BackupSchedule sql.NullString `json:"backup_schedule"`
NextBackupAt sql.NullTime `json:"next_backup_at"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
func (q *Queries) ListDatabaseServicesByUser(ctx context.Context, userID string) ([]ListDatabaseServicesByUserRow, error) {
func (q *Queries) ListDatabaseServicesByUser(ctx context.Context, userID string) ([]DatabaseService, error) {
rows, err := q.db.QueryContext(ctx, listDatabaseServicesByUser, userID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListDatabaseServicesByUserRow{}
items := []DatabaseService{}
for rows.Next() {
var i ListDatabaseServicesByUserRow
var i DatabaseService
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.Name,
&i.Type,
&i.Status,
@@ -154,6 +154,7 @@ type Project struct {
Name string `json:"name"`
Description sql.NullString `json:"description"`
OwnerID uuid.UUID `json:"owner_id"`
IsApproved bool `json:"is_approved"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
@@ -205,6 +206,7 @@ type ServiceTemplate struct {
Config json.RawMessage `json:"config"`
Variables pqtype.NullRawMessage `json:"variables"`
IsOfficial sql.NullBool `json:"is_official"`
OwnerID uuid.NullUUID `json:"owner_id"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
@@ -16,25 +16,31 @@ const countProjectsByUser = `-- name: CountProjectsByUser :one
SELECT COUNT(*)::bigint AS total
FROM projects p
WHERE
(p.owner_id = $1 OR EXISTS (
SELECT 1
FROM project_members pm
WHERE pm.project_id = p.id AND pm.user_id = $1
))
(
p.is_approved
OR p.owner_id = $1
OR $2::bool
OR EXISTS (
SELECT 1
FROM project_members pm
WHERE pm.project_id = p.id AND pm.user_id = $1
)
)
AND (
$2::text IS NULL
OR p.name ILIKE ('%' || $2::text || '%')
OR COALESCE(p.description, '') ILIKE ('%' || $2::text || '%')
$3::text IS NULL
OR p.name ILIKE ('%' || $3::text || '%')
OR COALESCE(p.description, '') ILIKE ('%' || $3::text || '%')
)
`
type CountProjectsByUserParams struct {
UserID uuid.UUID `json:"user_id"`
Search sql.NullString `json:"search"`
UserID uuid.UUID `json:"user_id"`
IsAdmin bool `json:"is_admin"`
Search sql.NullString `json:"search"`
}
func (q *Queries) CountProjectsByUser(ctx context.Context, arg CountProjectsByUserParams) (int64, error) {
row := q.db.QueryRowContext(ctx, countProjectsByUser, arg.UserID, arg.Search)
row := q.db.QueryRowContext(ctx, countProjectsByUser, arg.UserID, arg.IsAdmin, arg.Search)
var total int64
err := row.Scan(&total)
return total, err
@@ -43,7 +49,7 @@ func (q *Queries) CountProjectsByUser(ctx context.Context, arg CountProjectsByUs
const createProject = `-- name: CreateProject :one
INSERT INTO projects (name, description, owner_id)
VALUES ($1, $2, $3)
RETURNING id, name, description, owner_id, created_at, updated_at
RETURNING id, name, description, owner_id, is_approved, created_at, updated_at
`
type CreateProjectParams struct {
@@ -60,6 +66,7 @@ func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (P
&i.Name,
&i.Description,
&i.OwnerID,
&i.IsApproved,
&i.CreatedAt,
&i.UpdatedAt,
)
@@ -80,11 +87,13 @@ func (q *Queries) DeleteProjectByID(ctx context.Context, projectID uuid.UUID) (i
}
const getProjectByIDForUser = `-- name: GetProjectByIDForUser :one
SELECT p.id, p.name, p.description, p.owner_id, p.created_at, p.updated_at
SELECT p.id, p.name, p.description, p.owner_id, p.is_approved, p.created_at, p.updated_at
FROM projects p
WHERE p.id = $1
AND (
p.owner_id = $2
p.is_approved
OR p.owner_id = $2
OR $3::bool
OR EXISTS (
SELECT 1
FROM project_members pm
@@ -96,16 +105,18 @@ WHERE p.id = $1
type GetProjectByIDForUserParams struct {
ProjectID uuid.UUID `json:"project_id"`
UserID uuid.UUID `json:"user_id"`
IsAdmin bool `json:"is_admin"`
}
func (q *Queries) GetProjectByIDForUser(ctx context.Context, arg GetProjectByIDForUserParams) (Project, error) {
row := q.db.QueryRowContext(ctx, getProjectByIDForUser, arg.ProjectID, arg.UserID)
row := q.db.QueryRowContext(ctx, getProjectByIDForUser, arg.ProjectID, arg.UserID, arg.IsAdmin)
var i Project
err := row.Scan(
&i.ID,
&i.Name,
&i.Description,
&i.OwnerID,
&i.IsApproved,
&i.CreatedAt,
&i.UpdatedAt,
)
@@ -127,21 +138,22 @@ func (q *Queries) GetProjectOwnerByID(ctx context.Context, projectID uuid.UUID)
const getProjectRoleForUser = `-- name: GetProjectRoleForUser :one
SELECT (CASE
WHEN p.owner_id = $1 THEN 'owner'
WHEN p.owner_id = $1 OR $2::bool THEN 'owner'
ELSE COALESCE(pm.role, '')
END)::text AS role
FROM projects p
LEFT JOIN project_members pm ON p.id = pm.project_id AND pm.user_id = $1
WHERE p.id = $2
WHERE p.id = $3
`
type GetProjectRoleForUserParams struct {
UserID uuid.UUID `json:"user_id"`
IsAdmin bool `json:"is_admin"`
ProjectID uuid.UUID `json:"project_id"`
}
func (q *Queries) GetProjectRoleForUser(ctx context.Context, arg GetProjectRoleForUserParams) (string, error) {
row := q.db.QueryRowContext(ctx, getProjectRoleForUser, arg.UserID, arg.ProjectID)
row := q.db.QueryRowContext(ctx, getProjectRoleForUser, arg.UserID, arg.IsAdmin, arg.ProjectID)
var role string
err := row.Scan(&role)
return role, err
@@ -162,12 +174,52 @@ func (q *Queries) InsertProjectEnvironment(ctx context.Context, arg InsertProjec
return err
}
const listPendingProjects = `-- name: ListPendingProjects :many
SELECT p.id, p.name, p.description, p.owner_id, p.is_approved, p.created_at, p.updated_at
FROM projects p
WHERE p.is_approved = false
ORDER BY p.created_at DESC
`
func (q *Queries) ListPendingProjects(ctx context.Context) ([]Project, error) {
rows, err := q.db.QueryContext(ctx, listPendingProjects)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Project{}
for rows.Next() {
var i Project
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Description,
&i.OwnerID,
&i.IsApproved,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectsWithStatsByUser = `-- name: ListProjectsWithStatsByUser :many
SELECT
p.id,
p.name,
p.description,
p.owner_id,
p.is_approved,
p.created_at,
p.updated_at,
COUNT(DISTINCT s.id)::bigint AS service_count,
@@ -185,23 +237,29 @@ FROM projects p
LEFT JOIN services s ON s.project_id = p.id
LEFT JOIN deployments d ON d.service_id = s.id
WHERE
(p.owner_id = $1 OR EXISTS (
SELECT 1
FROM project_members pm
WHERE pm.project_id = p.id AND pm.user_id = $1
))
AND (
$2::text IS NULL
OR p.name ILIKE ('%' || $2::text || '%')
OR COALESCE(p.description, '') ILIKE ('%' || $2::text || '%')
(
p.is_approved
OR p.owner_id = $1
OR $2::bool
OR EXISTS (
SELECT 1
FROM project_members pm
WHERE pm.project_id = p.id AND pm.user_id = $1
)
)
GROUP BY p.id, p.name, p.description, p.owner_id, p.created_at, p.updated_at
AND (
$3::text IS NULL
OR p.name ILIKE ('%' || $3::text || '%')
OR COALESCE(p.description, '') ILIKE ('%' || $3::text || '%')
)
GROUP BY p.id, p.name, p.description, p.owner_id, p.is_approved, p.created_at, p.updated_at
ORDER BY p.updated_at DESC
LIMIT $4 OFFSET $3
LIMIT $5 OFFSET $4
`
type ListProjectsWithStatsByUserParams struct {
UserID uuid.UUID `json:"user_id"`
IsAdmin bool `json:"is_admin"`
Search sql.NullString `json:"search"`
OffsetCount int32 `json:"offset_count"`
LimitCount int32 `json:"limit_count"`
@@ -212,6 +270,7 @@ type ListProjectsWithStatsByUserRow struct {
Name string `json:"name"`
Description sql.NullString `json:"description"`
OwnerID uuid.UUID `json:"owner_id"`
IsApproved bool `json:"is_approved"`
CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
ServiceCount int64 `json:"service_count"`
@@ -220,9 +279,13 @@ type ListProjectsWithStatsByUserRow struct {
LastDeployment sql.NullTime `json:"last_deployment"`
}
// Visibility model: a project is readable when it is approved for public
// display, when the caller owns or is a member of it, or when the caller is a
// platform admin. Anonymous callers pass user_id = uuid.Nil, is_admin = false.
func (q *Queries) ListProjectsWithStatsByUser(ctx context.Context, arg ListProjectsWithStatsByUserParams) ([]ListProjectsWithStatsByUserRow, error) {
rows, err := q.db.QueryContext(ctx, listProjectsWithStatsByUser,
arg.UserID,
arg.IsAdmin,
arg.Search,
arg.OffsetCount,
arg.LimitCount,
@@ -239,6 +302,7 @@ func (q *Queries) ListProjectsWithStatsByUser(ctx context.Context, arg ListProje
&i.Name,
&i.Description,
&i.OwnerID,
&i.IsApproved,
&i.CreatedAt,
&i.UpdatedAt,
&i.ServiceCount,
@@ -259,6 +323,25 @@ func (q *Queries) ListProjectsWithStatsByUser(ctx context.Context, arg ListProje
return items, nil
}
const setProjectApproved = `-- name: SetProjectApproved :execrows
UPDATE projects
SET is_approved = $1, updated_at = NOW()
WHERE id = $2
`
type SetProjectApprovedParams struct {
IsApproved bool `json:"is_approved"`
ProjectID uuid.UUID `json:"project_id"`
}
func (q *Queries) SetProjectApproved(ctx context.Context, arg SetProjectApprovedParams) (int64, error) {
result, err := q.db.ExecContext(ctx, setProjectApproved, arg.IsApproved, arg.ProjectID)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const updateProjectByID = `-- name: UpdateProjectByID :execrows
UPDATE projects
SET
@@ -25,10 +25,12 @@ type Querier interface {
CreateNotification(ctx context.Context, arg CreateNotificationParams) error
CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error)
CreateServiceFromTemplate(ctx context.Context, arg CreateServiceFromTemplateParams) error
CreateUserTemplate(ctx context.Context, arg CreateUserTemplateParams) error
DatabaseServiceExistsByIDAndUser(ctx context.Context, arg DatabaseServiceExistsByIDAndUserParams) (bool, error)
DeleteAgent(ctx context.Context, id string) error
DeleteDatabaseServiceByIDAndUser(ctx context.Context, arg DeleteDatabaseServiceByIDAndUserParams) error
DeleteProjectByID(ctx context.Context, projectID uuid.UUID) (int64, error)
DeleteUserTemplate(ctx context.Context, arg DeleteUserTemplateParams) (int64, error)
GetActiveAgentAuthTokenByHash(ctx context.Context, tokenHash string) (AgentAuthToken, error)
GetAgent(ctx context.Context, id string) (NodeAgent, error)
GetAgentByHostAndIP(ctx context.Context, arg GetAgentByHostAndIPParams) (NodeAgent, error)
@@ -36,7 +38,9 @@ type Querier interface {
GetContainer(ctx context.Context, id string) (ContainerInstance, error)
GetContainerForAgent(ctx context.Context, arg GetContainerForAgentParams) (ContainerInstance, error)
GetDatabaseBackupByIDAndDatabaseAndUser(ctx context.Context, arg GetDatabaseBackupByIDAndDatabaseAndUserParams) (DatabaseBackup, error)
GetDatabaseServiceByIDAndUser(ctx context.Context, arg GetDatabaseServiceByIDAndUserParams) (GetDatabaseServiceByIDAndUserRow, error)
GetDatabaseServiceByID(ctx context.Context, id string) (DatabaseService, error)
GetDatabaseServiceByIDAndUser(ctx context.Context, arg GetDatabaseServiceByIDAndUserParams) (DatabaseService, error)
GetDatabaseServiceOwnerID(ctx context.Context, id string) (string, error)
GetProjectByIDForUser(ctx context.Context, arg GetProjectByIDForUserParams) (Project, error)
GetProjectOwnerByID(ctx context.Context, projectID uuid.UUID) (uuid.UUID, error)
GetProjectOwnerID(ctx context.Context, id uuid.UUID) (uuid.UUID, error)
@@ -47,16 +51,21 @@ type Querier interface {
ListAgentAuthTokens(ctx context.Context) ([]AgentAuthToken, error)
ListAgentHeartbeatsSince(ctx context.Context, arg ListAgentHeartbeatsSinceParams) ([]AgentHeartbeat, error)
ListAgents(ctx context.Context) ([]NodeAgent, error)
ListAllDatabaseServices(ctx context.Context) ([]DatabaseService, error)
ListCommandsForAgent(ctx context.Context, nodeAgentID string) ([]AgentCommand, error)
ListContainersForAgent(ctx context.Context, nodeAgentID string) ([]ContainerInstance, error)
ListDatabaseBackupsByDatabaseAndUser(ctx context.Context, arg ListDatabaseBackupsByDatabaseAndUserParams) ([]DatabaseBackup, error)
ListDatabaseServicesByUser(ctx context.Context, userID string) ([]ListDatabaseServicesByUserRow, error)
ListDatabaseServicesByUser(ctx context.Context, userID string) ([]DatabaseService, error)
ListDueDatabaseBackups(ctx context.Context) ([]ListDueDatabaseBackupsRow, error)
ListNotificationsByUser(ctx context.Context, arg ListNotificationsByUserParams) ([]Notification, error)
ListPendingCommands(ctx context.Context, nodeAgentID string) ([]AgentCommand, error)
ListPendingProjects(ctx context.Context) ([]Project, error)
// Visibility model: a project is readable when it is approved for public
// display, when the caller owns or is a member of it, or when the caller is a
// platform admin. Anonymous callers pass user_id = uuid.Nil, is_admin = false.
ListProjectsWithStatsByUser(ctx context.Context, arg ListProjectsWithStatsByUserParams) ([]ListProjectsWithStatsByUserRow, error)
ListServiceTemplates(ctx context.Context) ([]ServiceTemplate, error)
ListServiceTemplatesByCategory(ctx context.Context, category string) ([]ServiceTemplate, error)
ListServiceTemplatesByCategoryForUser(ctx context.Context, arg ListServiceTemplatesByCategoryForUserParams) ([]ServiceTemplate, error)
ListServiceTemplatesForUser(ctx context.Context, ownerID uuid.NullUUID) ([]ServiceTemplate, error)
MarkAllNotificationsReadByUser(ctx context.Context, arg MarkAllNotificationsReadByUserParams) error
MarkNotificationReadByIDAndUser(ctx context.Context, arg MarkNotificationReadByIDAndUserParams) error
RevokeAgentAuthToken(ctx context.Context, id uuid.UUID) (AgentAuthToken, error)
@@ -66,6 +75,7 @@ type Querier interface {
SetDatabaseServiceStatusAndConnectionByID(ctx context.Context, arg SetDatabaseServiceStatusAndConnectionByIDParams) error
SetDatabaseServiceStatusByID(ctx context.Context, arg SetDatabaseServiceStatusByIDParams) error
SetDatabaseServiceStatusByIDAndUser(ctx context.Context, arg SetDatabaseServiceStatusByIDAndUserParams) error
SetProjectApproved(ctx context.Context, arg SetProjectApprovedParams) (int64, error)
TouchAgentAuthToken(ctx context.Context, tokenHash string) error
UpdateAgent(ctx context.Context, arg UpdateAgentParams) (NodeAgent, error)
UpdateAgentHeartbeat(ctx context.Context, arg UpdateAgentHeartbeatParams) error
@@ -74,7 +84,9 @@ type Querier interface {
UpdateDatabaseServiceNameByIDAndUser(ctx context.Context, arg UpdateDatabaseServiceNameByIDAndUserParams) error
UpdateDatabaseServicePlanByIDAndUser(ctx context.Context, arg UpdateDatabaseServicePlanByIDAndUserParams) error
UpdateProjectByID(ctx context.Context, arg UpdateProjectByIDParams) (int64, error)
UpdateUserTemplate(ctx context.Context, arg UpdateUserTemplateParams) (int64, error)
UpsertEnvironmentVariable(ctx context.Context, arg UpsertEnvironmentVariableParams) error
UpsertServiceTemplate(ctx context.Context, arg UpsertServiceTemplateParams) error
}
var _ Querier = (*Queries)(nil)
@@ -8,8 +8,10 @@ package sqlcdb
import (
"context"
"database/sql"
"encoding/json"
"github.com/google/uuid"
"github.com/sqlc-dev/pqtype"
)
const countServicesByProjectAndName = `-- name: CountServicesByProjectAndName :one
@@ -122,6 +124,66 @@ func (q *Queries) CreateServiceFromTemplate(ctx context.Context, arg CreateServi
return err
}
const createUserTemplate = `-- name: CreateUserTemplate :exec
INSERT INTO service_templates (id, name, description, category, logo, config, variables, is_official, owner_id)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
false,
$8
)
`
type CreateUserTemplateParams struct {
ID string `json:"id"`
Name string `json:"name"`
Description sql.NullString `json:"description"`
Category string `json:"category"`
Logo sql.NullString `json:"logo"`
Config json.RawMessage `json:"config"`
Variables pqtype.NullRawMessage `json:"variables"`
OwnerID uuid.NullUUID `json:"owner_id"`
}
func (q *Queries) CreateUserTemplate(ctx context.Context, arg CreateUserTemplateParams) error {
_, err := q.db.ExecContext(ctx, createUserTemplate,
arg.ID,
arg.Name,
arg.Description,
arg.Category,
arg.Logo,
arg.Config,
arg.Variables,
arg.OwnerID,
)
return err
}
const deleteUserTemplate = `-- name: DeleteUserTemplate :execrows
DELETE FROM service_templates
WHERE id = $1
AND owner_id = $2
AND is_official = false
`
type DeleteUserTemplateParams struct {
ID string `json:"id"`
OwnerID uuid.NullUUID `json:"owner_id"`
}
func (q *Queries) DeleteUserTemplate(ctx context.Context, arg DeleteUserTemplateParams) (int64, error) {
result, err := q.db.ExecContext(ctx, deleteUserTemplate, arg.ID, arg.OwnerID)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const getProjectOwnerID = `-- name: GetProjectOwnerID :one
SELECT owner_id
FROM projects
@@ -136,7 +198,7 @@ func (q *Queries) GetProjectOwnerID(ctx context.Context, id uuid.UUID) (uuid.UUI
}
const getServiceTemplateByID = `-- name: GetServiceTemplateByID :one
SELECT id, name, description, category, logo, config, variables, is_official, created_at, updated_at
SELECT id, name, description, category, logo, config, variables, is_official, owner_id, created_at, updated_at
FROM service_templates
WHERE id = $1
`
@@ -153,20 +215,28 @@ func (q *Queries) GetServiceTemplateByID(ctx context.Context, id string) (Servic
&i.Config,
&i.Variables,
&i.IsOfficial,
&i.OwnerID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listServiceTemplates = `-- name: ListServiceTemplates :many
SELECT id, name, description, category, logo, config, variables, is_official, created_at, updated_at
const listServiceTemplatesByCategoryForUser = `-- name: ListServiceTemplatesByCategoryForUser :many
SELECT id, name, description, category, logo, config, variables, is_official, owner_id, created_at, updated_at
FROM service_templates
WHERE category = $1
AND (is_official = true OR owner_id = $2)
ORDER BY is_official DESC, name ASC
`
func (q *Queries) ListServiceTemplates(ctx context.Context) ([]ServiceTemplate, error) {
rows, err := q.db.QueryContext(ctx, listServiceTemplates)
type ListServiceTemplatesByCategoryForUserParams struct {
Category string `json:"category"`
OwnerID uuid.NullUUID `json:"owner_id"`
}
func (q *Queries) ListServiceTemplatesByCategoryForUser(ctx context.Context, arg ListServiceTemplatesByCategoryForUserParams) ([]ServiceTemplate, error) {
rows, err := q.db.QueryContext(ctx, listServiceTemplatesByCategoryForUser, arg.Category, arg.OwnerID)
if err != nil {
return nil, err
}
@@ -183,6 +253,7 @@ func (q *Queries) ListServiceTemplates(ctx context.Context) ([]ServiceTemplate,
&i.Config,
&i.Variables,
&i.IsOfficial,
&i.OwnerID,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
@@ -199,15 +270,15 @@ func (q *Queries) ListServiceTemplates(ctx context.Context) ([]ServiceTemplate,
return items, nil
}
const listServiceTemplatesByCategory = `-- name: ListServiceTemplatesByCategory :many
SELECT id, name, description, category, logo, config, variables, is_official, created_at, updated_at
const listServiceTemplatesForUser = `-- name: ListServiceTemplatesForUser :many
SELECT id, name, description, category, logo, config, variables, is_official, owner_id, created_at, updated_at
FROM service_templates
WHERE category = $1
WHERE is_official = true OR owner_id = $1
ORDER BY is_official DESC, name ASC
`
func (q *Queries) ListServiceTemplatesByCategory(ctx context.Context, category string) ([]ServiceTemplate, error) {
rows, err := q.db.QueryContext(ctx, listServiceTemplatesByCategory, category)
func (q *Queries) ListServiceTemplatesForUser(ctx context.Context, ownerID uuid.NullUUID) ([]ServiceTemplate, error) {
rows, err := q.db.QueryContext(ctx, listServiceTemplatesForUser, ownerID)
if err != nil {
return nil, err
}
@@ -224,6 +295,7 @@ func (q *Queries) ListServiceTemplatesByCategory(ctx context.Context, category s
&i.Config,
&i.Variables,
&i.IsOfficial,
&i.OwnerID,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
@@ -240,6 +312,48 @@ func (q *Queries) ListServiceTemplatesByCategory(ctx context.Context, category s
return items, nil
}
const updateUserTemplate = `-- name: UpdateUserTemplate :execrows
UPDATE service_templates
SET name = $1,
description = $2,
category = $3,
logo = $4,
config = $5,
variables = $6,
updated_at = NOW()
WHERE id = $7
AND owner_id = $8
AND is_official = false
`
type UpdateUserTemplateParams struct {
Name string `json:"name"`
Description sql.NullString `json:"description"`
Category string `json:"category"`
Logo sql.NullString `json:"logo"`
Config json.RawMessage `json:"config"`
Variables pqtype.NullRawMessage `json:"variables"`
ID string `json:"id"`
OwnerID uuid.NullUUID `json:"owner_id"`
}
func (q *Queries) UpdateUserTemplate(ctx context.Context, arg UpdateUserTemplateParams) (int64, error) {
result, err := q.db.ExecContext(ctx, updateUserTemplate,
arg.Name,
arg.Description,
arg.Category,
arg.Logo,
arg.Config,
arg.Variables,
arg.ID,
arg.OwnerID,
)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const upsertEnvironmentVariable = `-- name: UpsertEnvironmentVariable :exec
INSERT INTO environment_variables (id, service_id, key, value, is_secret, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
@@ -271,3 +385,51 @@ func (q *Queries) UpsertEnvironmentVariable(ctx context.Context, arg UpsertEnvir
)
return err
}
const upsertServiceTemplate = `-- name: UpsertServiceTemplate :exec
INSERT INTO service_templates (id, name, description, category, logo, config, variables, is_official)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8
)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
description = EXCLUDED.description,
category = EXCLUDED.category,
logo = EXCLUDED.logo,
config = EXCLUDED.config,
variables = EXCLUDED.variables,
is_official = EXCLUDED.is_official,
updated_at = NOW()
`
type UpsertServiceTemplateParams struct {
ID string `json:"id"`
Name string `json:"name"`
Description sql.NullString `json:"description"`
Category string `json:"category"`
Logo sql.NullString `json:"logo"`
Config json.RawMessage `json:"config"`
Variables pqtype.NullRawMessage `json:"variables"`
IsOfficial sql.NullBool `json:"is_official"`
}
func (q *Queries) UpsertServiceTemplate(ctx context.Context, arg UpsertServiceTemplateParams) error {
_, err := q.db.ExecContext(ctx, upsertServiceTemplate,
arg.ID,
arg.Name,
arg.Description,
arg.Category,
arg.Logo,
arg.Config,
arg.Variables,
arg.IsOfficial,
)
return err
}