mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
Environment lanes existed as seeded rows (production/preview/development) with a per-service environment label, but there was no way to list, create, or delete them. Adds the management surface and the switcher: - GET /projects/:id/environments with live service counts (sqlc) - POST /projects/:id/environments — lowercase dns-label names, 1-50 chars, unique per project (409 on dupe) - DELETE /environments/:id — refuses while it holds services (409) - Workspace service list gains env filter chips with counts plus an inline "+ env" creator - CLI: containr environments list|create|delete (--yes on delete); MCP: containr_environments_list/create/delete (delete needs confirm) - OpenAPI: new Environments tag + ProjectEnvironment schema Live-verified: list counts, name validation, dupe 409, empty delete, non-empty 409, CLI list. Generated with [Devin](https://devin.ai) Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
463 lines
12 KiB
Go
463 lines
12 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 countEnvironmentServices = `-- name: CountEnvironmentServices :one
|
|
SELECT COUNT(*) FROM services WHERE environment_id = $1
|
|
`
|
|
|
|
func (q *Queries) CountEnvironmentServices(ctx context.Context, environmentID uuid.UUID) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countEnvironmentServices, environmentID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
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 deleteProjectEnvironment = `-- name: DeleteProjectEnvironment :exec
|
|
DELETE FROM environments WHERE id = $1 AND project_id = $2
|
|
`
|
|
|
|
type DeleteProjectEnvironmentParams struct {
|
|
ID uuid.UUID `json:"id"`
|
|
ProjectID uuid.UUID `json:"project_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteProjectEnvironment(ctx context.Context, arg DeleteProjectEnvironmentParams) error {
|
|
_, err := q.db.ExecContext(ctx, deleteProjectEnvironment, arg.ID, arg.ProjectID)
|
|
return err
|
|
}
|
|
|
|
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 getProjectEnvironmentByName = `-- name: GetProjectEnvironmentByName :one
|
|
SELECT id, name, project_id, created_at, updated_at
|
|
FROM environments
|
|
WHERE project_id = $1 AND name = $2
|
|
`
|
|
|
|
type GetProjectEnvironmentByNameParams struct {
|
|
ProjectID uuid.UUID `json:"project_id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (q *Queries) GetProjectEnvironmentByName(ctx context.Context, arg GetProjectEnvironmentByNameParams) (Environment, error) {
|
|
row := q.db.QueryRowContext(ctx, getProjectEnvironmentByName, arg.ProjectID, arg.Name)
|
|
var i Environment
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.ProjectID,
|
|
&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 listProjectEnvironments = `-- name: ListProjectEnvironments :many
|
|
SELECT e.id, e.name, e.project_id, e.created_at, e.updated_at,
|
|
(SELECT COUNT(*) FROM services s WHERE s.environment_id = e.id) AS service_count
|
|
FROM environments e
|
|
WHERE e.project_id = $1
|
|
ORDER BY e.created_at
|
|
`
|
|
|
|
type ListProjectEnvironmentsRow struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
ProjectID uuid.UUID `json:"project_id"`
|
|
CreatedAt sql.NullTime `json:"created_at"`
|
|
UpdatedAt sql.NullTime `json:"updated_at"`
|
|
ServiceCount int64 `json:"service_count"`
|
|
}
|
|
|
|
func (q *Queries) ListProjectEnvironments(ctx context.Context, projectID uuid.UUID) ([]ListProjectEnvironmentsRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listProjectEnvironments, projectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListProjectEnvironmentsRow{}
|
|
for rows.Next() {
|
|
var i ListProjectEnvironmentsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.ProjectID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ServiceCount,
|
|
); 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()
|
|
}
|