refactor(db): port service clone/move ops to sqlc (#76)

Tranche 2: cloneServiceRow's INSERT...SELECT (volumes and traefik_labels
now read straight from the source row — traefik_labels was silently
dropped before, and the jsonb param round-trip goes away), domain and
variable copies, name-conflict counts, move UPDATE, project owner check.
Also fixes serviceWriteAccess: user_id is a context string, not a
uuid.UUID — the tranche-1 cast silently failed owner checks for
non-admins. Live tests cover the domain invariants and a full clone
round-trip (config, domains, variables).

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

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomáš DvořákandDevin authored and GitHub committed 2026-10-10 03:25:07 +02:00
1 parent 302c595380
commit 687555fc78
6 files changed
+328 -61

No files matched your search

+2 -1
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"net/http" "net/http"
"regexp" "regexp"
@@ -210,7 +211,7 @@ func basicAuthUsernames(encoded string) []string {
// serviceWriteAccess mirrors the service update handler's access check. // serviceWriteAccess mirrors the service update handler's access check.
func serviceWriteAccess(c *gin.Context, db *database.DB, serviceID uuid.UUID) bool { func serviceWriteAccess(c *gin.Context, db *database.DB, serviceID uuid.UUID) bool {
userID, _ := c.Get("user_id") userID, _ := c.Get("user_id")
uid, _ := userID.(uuid.UUID) uid, _ := uuid.Parse(fmt.Sprint(userID))
ok, err := sqlcdb.New(db.DB).ServiceWriteAccess(context.Background(), sqlcdb.ServiceWriteAccessParams{ ok, err := sqlcdb.New(db.DB).ServiceWriteAccess(context.Background(), sqlcdb.ServiceWriteAccessParams{
ID: serviceID, ID: serviceID,
OwnerID: uid, OwnerID: uid,
+80
View File
@@ -143,3 +143,83 @@ func TestServiceDomainsLiveInvariants(t *testing.T) {
t.Fatalf("legacy after delete = %q, want %q", legacy, d1.Domain) t.Fatalf("legacy after delete = %q, want %q", legacy, d1.Domain)
} }
} }
// Live clone test: cloneServiceRow must copy config, domains, and variables
// verbatim, start stopped, and land in the target project/environment.
func TestCloneServiceRowLive(t *testing.T) {
dsn := testDSNString(t)
db, err := database.NewConnection(dsn)
if err != nil {
t.Fatalf("connect: %v", err)
}
defer db.Close()
ctx := context.Background()
q := sqlcdb.New(db.DB)
userID := uuid.New()
projectID := uuid.New()
envID := uuid.New()
serviceID := uuid.New()
suffix := strings.ReplaceAll(serviceID.String()[:8], "-", "")
must := func(err error) {
t.Helper()
if err != nil {
t.Fatalf("setup: %v", err)
}
}
_, err = db.Exec(`INSERT INTO users (id, email, password_hash, name) VALUES ($1, $2, 'x', 'test')`,
userID, "clone-"+suffix+"@test.local")
must(err)
_, err = db.Exec(`INSERT INTO projects (id, name, owner_id, is_approved) VALUES ($1, $2, $3, true)`,
projectID, "clone-"+suffix, userID)
must(err)
_, err = db.Exec(`INSERT INTO environments (id, name, project_id) VALUES ($1, 'production', $2)`,
envID, projectID)
must(err)
_, err = db.Exec(`INSERT INTO services (id, name, project_id, environment_id, service_type, source_type,
image_name, port, volumes, traefik_labels, maintenance_mode)
VALUES ($1, $2, $3, $4, 'web', 'image', 'traefik/whoami:latest', 80,
'[{"mount":"/data"}]'::jsonb,
'{"middlewares.rl.ratelimit.average":"100"}'::jsonb, true)`,
serviceID, "clonesrc-"+suffix, projectID, envID)
must(err)
defer func() { _, _ = db.Exec(`DELETE FROM users WHERE id = $1`, userID) }()
if _, err := q.CreateServiceDomain(ctx, sqlcdb.CreateServiceDomainParams{
ServiceID: serviceID, Domain: "src-" + suffix + ".example.com", IsDefault: true}); err != nil {
t.Fatalf("seed domain: %v", err)
}
if _, err := db.Exec(`INSERT INTO environment_variables (id, service_id, key, value, is_secret)
VALUES ($1, $2, 'FOO', 'bar', false)`, uuid.New(), serviceID); err != nil {
t.Fatalf("seed var: %v", err)
}
cloneID, err := cloneServiceRow(db, serviceID, projectID, "clone-"+suffix, "")
if err != nil {
t.Fatalf("clone: %v", err)
}
var status, imageName, env string
var maintenance bool
var volumes, labels []byte
if err := db.QueryRow(`SELECT status, image_name, COALESCE(environment,''), maintenance_mode,
volumes, traefik_labels FROM services WHERE id = $1`, cloneID).
Scan(&status, &imageName, &env, &maintenance, &volumes, &labels); err != nil {
t.Fatalf("read clone: %v", err)
}
if status != "stopped" || imageName != "traefik/whoami:latest" || env != "production" {
t.Fatalf("clone mismatch: %v %v %v", status, imageName, env)
}
if !maintenance || string(volumes) == "[]" || string(labels) == "{}" {
t.Fatalf("clone dropped config: maint=%v vol=%s labels=%s", maintenance, volumes, labels)
}
doms := loadServiceDomains(db, cloneID)
if len(doms) != 1 || doms[0].Domain != "src-"+suffix+".example.com" || !doms[0].IsDefault {
t.Fatalf("domains not cloned: %+v", doms)
}
var varVal string
if err := db.QueryRow(`SELECT value FROM environment_variables WHERE service_id = $1 AND key = 'FOO'`,
cloneID).Scan(&varVal); err != nil || varVal != "bar" {
t.Fatalf("vars not cloned: %v %q", err, varVal)
}
}
+42 -60
View File
@@ -1,11 +1,14 @@
package api package api
import ( import (
"context"
"database/sql"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"containr/internal/database" "containr/internal/database"
"containr/internal/database/sqlcdb"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
@@ -53,11 +56,8 @@ func handleCloneService(c *gin.Context) {
if name == "" { if name == "" {
name = source.Name + "-copy" name = source.Name + "-copy"
} }
var count int count, _ := sqlcdb.New(db.(*database.DB).DB).CountServicesByName(
_ = db.(*database.DB).QueryRow( c.Request.Context(), sqlcdb.CountServicesByNameParams{ProjectID: targetProjectID, Name: name})
`SELECT COUNT(*) FROM services WHERE project_id = $1 AND name = $2`,
targetProjectID, name,
).Scan(&count)
if count > 0 { if count > 0 {
c.JSON(http.StatusConflict, gin.H{"error": "Service name already exists in target project", "code": "CONFLICT"}) c.JSON(http.StatusConflict, gin.H{"error": "Service name already exists in target project", "code": "CONFLICT"})
return return
@@ -84,8 +84,11 @@ func handleCloneService(c *gin.Context) {
// inherits the source's. Used by the clone endpoint and preview // inherits the source's. Used by the clone endpoint and preview
// environments, which additionally override git_branch and domains. // environments, which additionally override git_branch and domains.
func cloneServiceRow(db *database.DB, sourceID uuid.UUID, targetProjectID uuid.UUID, name, environment string) (uuid.UUID, error) { func cloneServiceRow(db *database.DB, sourceID uuid.UUID, targetProjectID uuid.UUID, name, environment string) (uuid.UUID, error) {
var sourceEnv string ctx := context.Background()
if err := db.QueryRow(`SELECT COALESCE(environment, '') FROM services WHERE id = $1`, sourceID).Scan(&sourceEnv); err != nil { q := sqlcdb.New(db.DB)
sourceEnv, err := q.GetServiceEnvironment(ctx, sourceID)
if err != nil {
return uuid.Nil, err return uuid.Nil, err
} }
if environment == "" { if environment == "" {
@@ -102,46 +105,31 @@ func cloneServiceRow(db *database.DB, sourceID uuid.UUID, targetProjectID uuid.U
maintenance, basicAuth := serviceAccess(db, sourceID) maintenance, basicAuth := serviceAccess(db, sourceID)
newID := uuid.New() newID := uuid.New()
now := time.Now() now := time.Now()
// string() so pq sends text — []byte would go as bytea and fail ::jsonb.
volumesJSON := string(loadServiceVolumesJSON(db, sourceID))
_, err = db.Exec( err = q.CloneServiceRow(ctx, sqlcdb.CloneServiceRowParams{
`INSERT INTO services ID: newID,
(id, project_id, name, environment_id, service_type, source_type, source_url, image_name, ProjectID: targetProjectID,
build_command, start_command, type, status, image, command, environment, Name: name,
git_repo, git_branch, build_path, cpu, memory, replicas, port, domain, EnvironmentID: environmentID,
healthcheck_path, restart_policy, volumes, maintenance_mode, basic_auth_users, Environment: sql.NullString{String: environment, Valid: true},
builder, cpu_reserve, memory_reserve, static_build_cmd, static_dir, MaintenanceMode: maintenance,
created_at, updated_at) BasicAuthUsers: basicAuth,
SELECT $1, $2, $3, $4, service_type, source_type, source_url, image_name, CreatedAt: sql.NullTime{Time: now, Valid: true},
build_command, start_command, type, 'stopped', image, command, $5, ID_2: sourceID,
git_repo, git_branch, build_path, cpu, memory, replicas, port, domain, })
healthcheck_path, restart_policy, $6::jsonb, $7, $8,
builder, cpu_reserve, memory_reserve, static_build_cmd, static_dir,
$9, $9
FROM services WHERE id = $10`,
newID, targetProjectID, name, environmentID, environment, volumesJSON,
maintenance, basicAuth, now, sourceID,
)
if err != nil { if err != nil {
return uuid.Nil, err return uuid.Nil, err
} }
if _, err := db.Exec( if err := q.CloneServiceDomains(ctx, sqlcdb.CloneServiceDomainsParams{
`INSERT INTO service_domains (service_id, domain, is_default, cert_type) ServiceID: newID, ServiceID_2: sourceID}); err != nil {
SELECT $1, domain, is_default, cert_type FROM service_domains WHERE service_id = $2`, _ = q.DeleteServiceByID(ctx, newID)
newID, sourceID,
); err != nil {
_, _ = db.Exec(`DELETE FROM services WHERE id = $1`, newID)
return uuid.Nil, err return uuid.Nil, err
} }
// Variable values are copied as stored — ciphertext stays ciphertext. // Variable values are copied as stored — ciphertext stays ciphertext.
if _, err := db.Exec( if err := q.CloneServiceVariables(ctx, sqlcdb.CloneServiceVariablesParams{
`INSERT INTO environment_variables (id, service_id, key, value, is_secret) ServiceID: newID, ServiceID_2: sourceID}); err != nil {
SELECT gen_random_uuid(), $1, key, value, is_secret FROM environment_variables WHERE service_id = $2`, _ = q.DeleteServiceByID(ctx, newID)
newID, sourceID,
); err != nil {
_, _ = db.Exec(`DELETE FROM services WHERE id = $1`, newID)
return uuid.Nil, err return uuid.Nil, err
} }
return newID, nil return newID, nil
@@ -187,11 +175,8 @@ func handleMoveService(c *gin.Context) {
return return
} }
var count int count, _ := sqlcdb.New(db.(*database.DB).DB).CountServicesByName(
_ = db.(*database.DB).QueryRow( c.Request.Context(), sqlcdb.CountServicesByNameParams{ProjectID: req.ProjectID, Name: service.Name})
`SELECT COUNT(*) FROM services WHERE project_id = $1 AND name = $2`,
req.ProjectID, service.Name,
).Scan(&count)
if count > 0 { if count > 0 {
c.JSON(http.StatusConflict, gin.H{"error": "A service with this name already exists in the target project", "code": "CONFLICT"}) c.JSON(http.StatusConflict, gin.H{"error": "A service with this name already exists in the target project", "code": "CONFLICT"})
return return
@@ -207,15 +192,18 @@ func handleMoveService(c *gin.Context) {
return return
} }
res, err := db.(*database.DB).Exec( n, err := sqlcdb.New(db.(*database.DB).DB).MoveServiceProject(
`UPDATE services SET project_id = $1, environment_id = $2, updated_at = $3 WHERE id = $4`, c.Request.Context(), sqlcdb.MoveServiceProjectParams{
req.ProjectID, environmentID, time.Now(), serviceID, ProjectID: req.ProjectID,
) EnvironmentID: environmentID,
UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true},
ID: serviceID,
})
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to move service"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to move service"})
return return
} }
if n, _ := res.RowsAffected(); n == 0 { if n == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Service not found", "code": "NOT_FOUND"}) c.JSON(http.StatusNotFound, gin.H{"error": "Service not found", "code": "NOT_FOUND"})
return return
} }
@@ -229,16 +217,10 @@ func ownsProject(db *database.DB, projectID uuid.UUID, userID string, isAdmin bo
if isAdmin { if isAdmin {
return true return true
} }
var owner string uid, err := uuid.Parse(userID)
err := db.QueryRow(`SELECT owner_id FROM projects WHERE id = $1`, projectID).Scan(&owner) if err != nil {
return err == nil && owner == userID return false
}
// loadServiceVolumesJSON returns the raw stored mounts for a verbatim copy.
func loadServiceVolumesJSON(db *database.DB, serviceID uuid.UUID) []byte {
var raw []byte
if err := db.QueryRow(`SELECT volumes FROM services WHERE id = $1`, serviceID).Scan(&raw); err != nil || len(raw) == 0 {
return []byte("[]")
} }
return raw owner, err := sqlcdb.New(db.DB).GetProjectOwner(context.Background(), projectID)
return err == nil && owner == uid
} }
@@ -18,12 +18,16 @@ type Querier interface {
AggregateNodeMetrics(ctx context.Context, arg AggregateNodeMetricsParams) ([]AggregateNodeMetricsRow, error) AggregateNodeMetrics(ctx context.Context, arg AggregateNodeMetricsParams) ([]AggregateNodeMetricsRow, error)
ClearServiceDomainDefault(ctx context.Context, serviceID uuid.UUID) error ClearServiceDomainDefault(ctx context.Context, serviceID uuid.UUID) error
ClearServiceNodePins(ctx context.Context, nodeID sql.NullString) (int64, error) ClearServiceNodePins(ctx context.Context, nodeID sql.NullString) (int64, error)
CloneServiceDomains(ctx context.Context, arg CloneServiceDomainsParams) error
CloneServiceRow(ctx context.Context, arg CloneServiceRowParams) error
CloneServiceVariables(ctx context.Context, arg CloneServiceVariablesParams) error
CompleteCommand(ctx context.Context, arg CompleteCommandParams) (AgentCommand, error) CompleteCommand(ctx context.Context, arg CompleteCommandParams) (AgentCommand, error)
CountDatabaseServicesByUserAndName(ctx context.Context, arg CountDatabaseServicesByUserAndNameParams) (int64, error) CountDatabaseServicesByUserAndName(ctx context.Context, arg CountDatabaseServicesByUserAndNameParams) (int64, error)
CountDatabasesUsingBackupTarget(ctx context.Context, targetID sql.NullString) (int64, error) CountDatabasesUsingBackupTarget(ctx context.Context, targetID sql.NullString) (int64, error)
CountEnvironmentServices(ctx context.Context, environmentID uuid.UUID) (int64, error) CountEnvironmentServices(ctx context.Context, environmentID uuid.UUID) (int64, error)
CountProjectsByUser(ctx context.Context, arg CountProjectsByUserParams) (int64, error) CountProjectsByUser(ctx context.Context, arg CountProjectsByUserParams) (int64, error)
CountServiceDomains(ctx context.Context, serviceID uuid.UUID) (int64, error) CountServiceDomains(ctx context.Context, serviceID uuid.UUID) (int64, error)
CountServicesByName(ctx context.Context, arg CountServicesByNameParams) (int64, error)
CountServicesByProjectAndName(ctx context.Context, arg CountServicesByProjectAndNameParams) (int64, error) CountServicesByProjectAndName(ctx context.Context, arg CountServicesByProjectAndNameParams) (int64, error)
CountUnreadNotificationsByUser(ctx context.Context, userID uuid.UUID) (int64, error) CountUnreadNotificationsByUser(ctx context.Context, userID uuid.UUID) (int64, error)
CreateAgent(ctx context.Context, arg CreateAgentParams) (NodeAgent, error) CreateAgent(ctx context.Context, arg CreateAgentParams) (NodeAgent, error)
@@ -54,6 +58,7 @@ type Querier interface {
DeleteOutboundWebhookByIDAndUser(ctx context.Context, arg DeleteOutboundWebhookByIDAndUserParams) error DeleteOutboundWebhookByIDAndUser(ctx context.Context, arg DeleteOutboundWebhookByIDAndUserParams) error
DeleteProjectByID(ctx context.Context, projectID uuid.UUID) (int64, error) DeleteProjectByID(ctx context.Context, projectID uuid.UUID) (int64, error)
DeleteProjectEnvironment(ctx context.Context, arg DeleteProjectEnvironmentParams) error DeleteProjectEnvironment(ctx context.Context, arg DeleteProjectEnvironmentParams) error
DeleteServiceByID(ctx context.Context, id uuid.UUID) error
DeleteServiceContainersOnAgent(ctx context.Context, arg DeleteServiceContainersOnAgentParams) error DeleteServiceContainersOnAgent(ctx context.Context, arg DeleteServiceContainersOnAgentParams) error
DeleteServiceDomain(ctx context.Context, arg DeleteServiceDomainParams) (int64, error) DeleteServiceDomain(ctx context.Context, arg DeleteServiceDomainParams) (int64, error)
DeleteUserInvite(ctx context.Context, id uuid.UUID) error DeleteUserInvite(ctx context.Context, id uuid.UUID) error
@@ -76,12 +81,14 @@ type Querier interface {
GetOutboundWebhookByIDAndUser(ctx context.Context, arg GetOutboundWebhookByIDAndUserParams) (OutboundWebhook, error) GetOutboundWebhookByIDAndUser(ctx context.Context, arg GetOutboundWebhookByIDAndUserParams) (OutboundWebhook, error)
GetProjectByIDForUser(ctx context.Context, arg GetProjectByIDForUserParams) (Project, error) GetProjectByIDForUser(ctx context.Context, arg GetProjectByIDForUserParams) (Project, error)
GetProjectEnvironmentByName(ctx context.Context, arg GetProjectEnvironmentByNameParams) (Environment, error) GetProjectEnvironmentByName(ctx context.Context, arg GetProjectEnvironmentByNameParams) (Environment, error)
GetProjectOwner(ctx context.Context, id uuid.UUID) (uuid.UUID, error)
GetProjectOwnerByID(ctx context.Context, projectID uuid.UUID) (uuid.UUID, error) GetProjectOwnerByID(ctx context.Context, projectID uuid.UUID) (uuid.UUID, error)
GetProjectOwnerID(ctx context.Context, id uuid.UUID) (uuid.UUID, error) GetProjectOwnerID(ctx context.Context, id uuid.UUID) (uuid.UUID, error)
GetProjectRoleForUser(ctx context.Context, arg GetProjectRoleForUserParams) (string, error) GetProjectRoleForUser(ctx context.Context, arg GetProjectRoleForUserParams) (string, error)
GetServiceAccess(ctx context.Context, id uuid.UUID) (GetServiceAccessRow, error) GetServiceAccess(ctx context.Context, id uuid.UUID) (GetServiceAccessRow, error)
GetServiceDomainDefault(ctx context.Context, serviceID uuid.UUID) (string, error) GetServiceDomainDefault(ctx context.Context, serviceID uuid.UUID) (string, error)
GetServiceDomainFirst(ctx context.Context, serviceID uuid.UUID) (string, error) GetServiceDomainFirst(ctx context.Context, serviceID uuid.UUID) (string, error)
GetServiceEnvironment(ctx context.Context, id uuid.UUID) (string, error)
GetServicePlacementBrief(ctx context.Context, id uuid.UUID) (GetServicePlacementBriefRow, error) GetServicePlacementBrief(ctx context.Context, id uuid.UUID) (GetServicePlacementBriefRow, error)
GetServiceTemplateByID(ctx context.Context, id string) (ServiceTemplate, error) GetServiceTemplateByID(ctx context.Context, id string) (ServiceTemplate, error)
GetUserInviteByTokenHash(ctx context.Context, tokenHash string) (UserInvite, error) GetUserInviteByTokenHash(ctx context.Context, tokenHash string) (UserInvite, error)
@@ -135,6 +142,7 @@ type Querier interface {
MarkAllNotificationsReadByUser(ctx context.Context, arg MarkAllNotificationsReadByUserParams) error MarkAllNotificationsReadByUser(ctx context.Context, arg MarkAllNotificationsReadByUserParams) error
MarkNotificationReadByIDAndUser(ctx context.Context, arg MarkNotificationReadByIDAndUserParams) error MarkNotificationReadByIDAndUser(ctx context.Context, arg MarkNotificationReadByIDAndUserParams) error
MarkUserInviteUsed(ctx context.Context, arg MarkUserInviteUsedParams) error MarkUserInviteUsed(ctx context.Context, arg MarkUserInviteUsedParams) error
MoveServiceProject(ctx context.Context, arg MoveServiceProjectParams) (int64, error)
// Resource-aware: lowest memory utilisation first, then cpu, then container // Resource-aware: lowest memory utilisation first, then cpu, then container
// count. Agents without telemetry sort last (NULL → worst score). // count. Agents without telemetry sort last (NULL → worst score).
PickLeastLoadedAgent(ctx context.Context) (string, error) PickLeastLoadedAgent(ctx context.Context) (string, error)
@@ -0,0 +1,158 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: service_ops.sql
package sqlcdb
import (
"context"
"database/sql"
"github.com/google/uuid"
)
const cloneServiceDomains = `-- name: CloneServiceDomains :exec
INSERT INTO service_domains (service_id, domain, is_default, cert_type)
SELECT $1, domain, is_default, cert_type FROM service_domains sd WHERE sd.service_id = $2
`
type CloneServiceDomainsParams struct {
ServiceID uuid.UUID `json:"service_id"`
ServiceID_2 uuid.UUID `json:"service_id_2"`
}
func (q *Queries) CloneServiceDomains(ctx context.Context, arg CloneServiceDomainsParams) error {
_, err := q.db.ExecContext(ctx, cloneServiceDomains, arg.ServiceID, arg.ServiceID_2)
return err
}
const cloneServiceRow = `-- name: CloneServiceRow :exec
INSERT INTO services
(id, project_id, name, environment_id, service_type, source_type, source_url, image_name,
build_command, start_command, type, status, image, command, environment,
git_repo, git_branch, build_path, cpu, memory, replicas, port, domain,
healthcheck_path, restart_policy, volumes, maintenance_mode, basic_auth_users,
builder, cpu_reserve, memory_reserve, static_build_cmd, static_dir,
traefik_labels, created_at, updated_at)
SELECT $1, $2, $3, $4, service_type, source_type, source_url, image_name,
build_command, start_command, type, 'stopped', image, command, $5,
git_repo, git_branch, build_path, cpu, memory, replicas, port, domain,
healthcheck_path, restart_policy, volumes, $6, $7,
builder, cpu_reserve, memory_reserve, static_build_cmd, static_dir,
traefik_labels, $8, $8
FROM services s WHERE s.id = $9
`
type CloneServiceRowParams struct {
ID uuid.UUID `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
Name string `json:"name"`
EnvironmentID uuid.UUID `json:"environment_id"`
Environment sql.NullString `json:"environment"`
MaintenanceMode bool `json:"maintenance_mode"`
BasicAuthUsers string `json:"basic_auth_users"`
CreatedAt sql.NullTime `json:"created_at"`
ID_2 uuid.UUID `json:"id_2"`
}
func (q *Queries) CloneServiceRow(ctx context.Context, arg CloneServiceRowParams) error {
_, err := q.db.ExecContext(ctx, cloneServiceRow,
arg.ID,
arg.ProjectID,
arg.Name,
arg.EnvironmentID,
arg.Environment,
arg.MaintenanceMode,
arg.BasicAuthUsers,
arg.CreatedAt,
arg.ID_2,
)
return err
}
const cloneServiceVariables = `-- name: CloneServiceVariables :exec
INSERT INTO environment_variables (id, service_id, key, value, is_secret)
SELECT gen_random_uuid(), $1, key, value, is_secret FROM environment_variables ev WHERE ev.service_id = $2
`
type CloneServiceVariablesParams struct {
ServiceID uuid.UUID `json:"service_id"`
ServiceID_2 uuid.UUID `json:"service_id_2"`
}
func (q *Queries) CloneServiceVariables(ctx context.Context, arg CloneServiceVariablesParams) error {
_, err := q.db.ExecContext(ctx, cloneServiceVariables, arg.ServiceID, arg.ServiceID_2)
return err
}
const countServicesByName = `-- name: CountServicesByName :one
SELECT COUNT(*) FROM services WHERE project_id = $1 AND name = $2
`
type CountServicesByNameParams struct {
ProjectID uuid.UUID `json:"project_id"`
Name string `json:"name"`
}
func (q *Queries) CountServicesByName(ctx context.Context, arg CountServicesByNameParams) (int64, error) {
row := q.db.QueryRowContext(ctx, countServicesByName, arg.ProjectID, arg.Name)
var count int64
err := row.Scan(&count)
return count, err
}
const deleteServiceByID = `-- name: DeleteServiceByID :exec
DELETE FROM services WHERE id = $1
`
func (q *Queries) DeleteServiceByID(ctx context.Context, id uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteServiceByID, id)
return err
}
const getProjectOwner = `-- name: GetProjectOwner :one
SELECT owner_id FROM projects WHERE id = $1
`
func (q *Queries) GetProjectOwner(ctx context.Context, id uuid.UUID) (uuid.UUID, error) {
row := q.db.QueryRowContext(ctx, getProjectOwner, id)
var owner_id uuid.UUID
err := row.Scan(&owner_id)
return owner_id, err
}
const getServiceEnvironment = `-- name: GetServiceEnvironment :one
SELECT COALESCE(environment, '') FROM services WHERE id = $1
`
func (q *Queries) GetServiceEnvironment(ctx context.Context, id uuid.UUID) (string, error) {
row := q.db.QueryRowContext(ctx, getServiceEnvironment, id)
var environment string
err := row.Scan(&environment)
return environment, err
}
const moveServiceProject = `-- name: MoveServiceProject :execrows
UPDATE services SET project_id = $1, environment_id = $2, updated_at = $3 WHERE id = $4
`
type MoveServiceProjectParams struct {
ProjectID uuid.UUID `json:"project_id"`
EnvironmentID uuid.UUID `json:"environment_id"`
UpdatedAt sql.NullTime `json:"updated_at"`
ID uuid.UUID `json:"id"`
}
func (q *Queries) MoveServiceProject(ctx context.Context, arg MoveServiceProjectParams) (int64, error) {
result, err := q.db.ExecContext(ctx, moveServiceProject,
arg.ProjectID,
arg.EnvironmentID,
arg.UpdatedAt,
arg.ID,
)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
+38
View File
@@ -0,0 +1,38 @@
-- name: CountServicesByName :one
SELECT COUNT(*) FROM services WHERE project_id = $1 AND name = $2;
-- name: GetServiceEnvironment :one
SELECT COALESCE(environment, '') FROM services WHERE id = $1;
-- name: CloneServiceRow :exec
INSERT INTO services
(id, project_id, name, environment_id, service_type, source_type, source_url, image_name,
build_command, start_command, type, status, image, command, environment,
git_repo, git_branch, build_path, cpu, memory, replicas, port, domain,
healthcheck_path, restart_policy, volumes, maintenance_mode, basic_auth_users,
builder, cpu_reserve, memory_reserve, static_build_cmd, static_dir,
traefik_labels, created_at, updated_at)
SELECT $1, $2, $3, $4, service_type, source_type, source_url, image_name,
build_command, start_command, type, 'stopped', image, command, $5,
git_repo, git_branch, build_path, cpu, memory, replicas, port, domain,
healthcheck_path, restart_policy, volumes, $6, $7,
builder, cpu_reserve, memory_reserve, static_build_cmd, static_dir,
traefik_labels, $8, $8
FROM services s WHERE s.id = $9;
-- name: CloneServiceDomains :exec
INSERT INTO service_domains (service_id, domain, is_default, cert_type)
SELECT $1, domain, is_default, cert_type FROM service_domains sd WHERE sd.service_id = $2;
-- name: CloneServiceVariables :exec
INSERT INTO environment_variables (id, service_id, key, value, is_secret)
SELECT gen_random_uuid(), $1, key, value, is_secret FROM environment_variables ev WHERE ev.service_id = $2;
-- name: DeleteServiceByID :exec
DELETE FROM services WHERE id = $1;
-- name: MoveServiceProject :execrows
UPDATE services SET project_id = $1, environment_id = $2, updated_at = $3 WHERE id = $4;
-- name: GetProjectOwner :one
SELECT owner_id FROM projects WHERE id = $1;