mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
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:
1 parent
8f5789bf93
commit
8b6214a0f5
62 files changed
+4986
-1133
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user