Files
Containr/app/backend/internal/database/sqlcdb/projects.sql.go
T
Tomas DvorakandDevin 8b6214a0f5 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>
2026-09-22 19:58:59 +02:00

367 lines
9.7 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: projects.sql
package sqlcdb
import (
"context"
"database/sql"
"github.com/google/uuid"
)
const countProjectsByUser = `-- name: CountProjectsByUser :one
SELECT COUNT(*)::bigint AS total
FROM projects p
WHERE
(
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 (
$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"`
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.IsAdmin, arg.Search)
var total int64
err := row.Scan(&total)
return total, err
}
const createProject = `-- name: CreateProject :one
INSERT INTO projects (name, description, owner_id)
VALUES ($1, $2, $3)
RETURNING id, name, description, owner_id, is_approved, created_at, updated_at
`
type CreateProjectParams struct {
Name string `json:"name"`
Description sql.NullString `json:"description"`
OwnerID uuid.UUID `json:"owner_id"`
}
func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) {
row := q.db.QueryRowContext(ctx, createProject, arg.Name, arg.Description, arg.OwnerID)
var i Project
err := row.Scan(
&i.ID,
&i.Name,
&i.Description,
&i.OwnerID,
&i.IsApproved,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteProjectByID = `-- name: DeleteProjectByID :execrows
DELETE FROM projects
WHERE id = $1
`
func (q *Queries) DeleteProjectByID(ctx context.Context, projectID uuid.UUID) (int64, error) {
result, err := q.db.ExecContext(ctx, deleteProjectByID, projectID)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const getProjectByIDForUser = `-- name: GetProjectByIDForUser :one
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.is_approved
OR p.owner_id = $2
OR $3::bool
OR EXISTS (
SELECT 1
FROM project_members pm
WHERE pm.project_id = p.id AND pm.user_id = $2
)
)
`
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, arg.IsAdmin)
var i Project
err := row.Scan(
&i.ID,
&i.Name,
&i.Description,
&i.OwnerID,
&i.IsApproved,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getProjectOwnerByID = `-- name: GetProjectOwnerByID :one
SELECT owner_id
FROM projects
WHERE id = $1
`
func (q *Queries) GetProjectOwnerByID(ctx context.Context, projectID uuid.UUID) (uuid.UUID, error) {
row := q.db.QueryRowContext(ctx, getProjectOwnerByID, projectID)
var owner_id uuid.UUID
err := row.Scan(&owner_id)
return owner_id, err
}
const getProjectRoleForUser = `-- name: GetProjectRoleForUser :one
SELECT (CASE
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 = $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.IsAdmin, arg.ProjectID)
var role string
err := row.Scan(&role)
return role, err
}
const insertProjectEnvironment = `-- name: InsertProjectEnvironment :exec
INSERT INTO environments (name, project_id)
VALUES ($1, $2)
`
type InsertProjectEnvironmentParams struct {
Name string `json:"name"`
ProjectID uuid.UUID `json:"project_id"`
}
func (q *Queries) InsertProjectEnvironment(ctx context.Context, arg InsertProjectEnvironmentParams) error {
_, err := q.db.ExecContext(ctx, insertProjectEnvironment, arg.Name, arg.ProjectID)
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,
COUNT(DISTINCT d.id)::bigint AS deployment_count,
COUNT(DISTINCT CASE WHEN s.status = 'running' THEN s.id END)::bigint AS running_services,
(
SELECT d2.created_at
FROM deployments d2
JOIN services s2 ON s2.id = d2.service_id
WHERE s2.project_id = p.id
ORDER BY d2.created_at DESC
LIMIT 1
) AS last_deployment
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.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 (
$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 $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"`
}
type ListProjectsWithStatsByUserRow struct {
ID uuid.UUID `json:"id"`
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"`
DeploymentCount int64 `json:"deployment_count"`
RunningServices int64 `json:"running_services"`
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,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListProjectsWithStatsByUserRow{}
for rows.Next() {
var i ListProjectsWithStatsByUserRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Description,
&i.OwnerID,
&i.IsApproved,
&i.CreatedAt,
&i.UpdatedAt,
&i.ServiceCount,
&i.DeploymentCount,
&i.RunningServices,
&i.LastDeployment,
); 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 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
name = COALESCE($1, name),
description = COALESCE($2, description),
updated_at = NOW()
WHERE id = $3
`
type UpdateProjectByIDParams struct {
Name sql.NullString `json:"name"`
Description sql.NullString `json:"description"`
ProjectID uuid.UUID `json:"project_id"`
}
func (q *Queries) UpdateProjectByID(ctx context.Context, arg UpdateProjectByIDParams) (int64, error) {
result, err := q.db.ExecContext(ctx, updateProjectByID, arg.Name, arg.Description, arg.ProjectID)
if err != nil {
return 0, err
}
return result.RowsAffected()
}