feat(server): add brand kit management and project binding
Add a brand kit domain with memory and postgres stores, a BrandKitService for CRUD and resolution, and REST endpoints to list, upsert, and delete brand kits. Projects can bind a brand kit whose resolved context and reference images feed into the agent's long-term memory and design prompts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,22 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const clearDefaultBrandKits = `-- name: ClearDefaultBrandKits :exec
|
||||
UPDATE brand_kits
|
||||
SET is_default = FALSE
|
||||
WHERE user_id = $1 AND is_default = TRUE AND id <> $2
|
||||
`
|
||||
|
||||
type ClearDefaultBrandKitsParams struct {
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ClearDefaultBrandKits(ctx context.Context, arg ClearDefaultBrandKitsParams) error {
|
||||
_, err := q.db.Exec(ctx, clearDefaultBrandKits, arg.UserID, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const createCanvasSnapshot = `-- name: CreateCanvasSnapshot :exec
|
||||
INSERT INTO canvas_snapshots (id, project_id, user_id, version, canvas, viewport_json, nodes_json, connections_json, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
@@ -140,6 +156,24 @@ func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) er
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteBrandKit = `-- name: DeleteBrandKit :one
|
||||
DELETE FROM brand_kits
|
||||
WHERE id = $1 AND user_id = $2
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
type DeleteBrandKitParams struct {
|
||||
ID string `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) DeleteBrandKit(ctx context.Context, arg DeleteBrandKitParams) (string, error) {
|
||||
row := q.db.QueryRow(ctx, deleteBrandKit, arg.ID, arg.UserID)
|
||||
var id string
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const deleteConnectionsByProject = `-- name: DeleteConnectionsByProject :exec
|
||||
DELETE FROM canvas_connections
|
||||
WHERE project_id = $1 AND user_id = $2
|
||||
@@ -200,6 +234,32 @@ func (q *Queries) DeleteProject(ctx context.Context, arg DeleteProjectParams) er
|
||||
return err
|
||||
}
|
||||
|
||||
const getBrandKit = `-- name: GetBrandKit :one
|
||||
SELECT id, user_id, name, document, is_default, created_at, updated_at
|
||||
FROM brand_kits
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`
|
||||
|
||||
type GetBrandKitParams struct {
|
||||
ID string `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetBrandKit(ctx context.Context, arg GetBrandKitParams) (BrandKit, error) {
|
||||
row := q.db.QueryRow(ctx, getBrandKit, arg.ID, arg.UserID)
|
||||
var i BrandKit
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Name,
|
||||
&i.Document,
|
||||
&i.IsDefault,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getCanvasSnapshot = `-- name: GetCanvasSnapshot :one
|
||||
SELECT id, project_id, user_id, version, canvas, viewport_json, nodes_json, connections_json, created_at
|
||||
FROM canvas_snapshots
|
||||
@@ -229,8 +289,30 @@ func (q *Queries) GetCanvasSnapshot(ctx context.Context, arg GetCanvasSnapshotPa
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getDefaultBrandKit = `-- name: GetDefaultBrandKit :one
|
||||
SELECT id, user_id, name, document, is_default, created_at, updated_at
|
||||
FROM brand_kits
|
||||
WHERE user_id = $1 AND is_default = TRUE
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetDefaultBrandKit(ctx context.Context, userID pgtype.UUID) (BrandKit, error) {
|
||||
row := q.db.QueryRow(ctx, getDefaultBrandKit, userID)
|
||||
var i BrandKit
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Name,
|
||||
&i.Document,
|
||||
&i.IsDefault,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getProject = `-- name: GetProject :one
|
||||
SELECT id, user_id, title, brief, status, thumbnail, canvas, version, snapshot_id, last_thread_id, viewport_x, viewport_y, viewport_k, updated_at
|
||||
SELECT id, user_id, title, brief, status, thumbnail, canvas, version, snapshot_id, last_thread_id, brand_kit_id, viewport_x, viewport_y, viewport_k, updated_at
|
||||
FROM projects
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`
|
||||
@@ -254,6 +336,7 @@ func (q *Queries) GetProject(ctx context.Context, arg GetProjectParams) (Project
|
||||
&i.Version,
|
||||
&i.SnapshotID,
|
||||
&i.LastThreadID,
|
||||
&i.BrandKitID,
|
||||
&i.ViewportX,
|
||||
&i.ViewportY,
|
||||
&i.ViewportK,
|
||||
@@ -262,6 +345,41 @@ func (q *Queries) GetProject(ctx context.Context, arg GetProjectParams) (Project
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listBrandKits = `-- name: ListBrandKits :many
|
||||
SELECT id, user_id, name, document, is_default, created_at, updated_at
|
||||
FROM brand_kits
|
||||
WHERE user_id = $1
|
||||
ORDER BY updated_at DESC, id ASC
|
||||
`
|
||||
|
||||
func (q *Queries) ListBrandKits(ctx context.Context, userID pgtype.UUID) ([]BrandKit, error) {
|
||||
rows, err := q.db.Query(ctx, listBrandKits, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []BrandKit
|
||||
for rows.Next() {
|
||||
var i BrandKit
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Name,
|
||||
&i.Document,
|
||||
&i.IsDefault,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listConnectionsByProject = `-- name: ListConnectionsByProject :many
|
||||
SELECT id, project_id, user_id, from_node_id, to_node_id
|
||||
FROM canvas_connections
|
||||
@@ -414,20 +532,21 @@ func (q *Queries) ListNodesByProject(ctx context.Context, arg ListNodesByProject
|
||||
}
|
||||
|
||||
const listProjects = `-- name: ListProjects :many
|
||||
SELECT id, user_id, title, brief, status, thumbnail, updated_at
|
||||
SELECT id, user_id, title, brief, status, thumbnail, brand_kit_id, updated_at
|
||||
FROM projects
|
||||
WHERE user_id = $1
|
||||
ORDER BY updated_at DESC
|
||||
`
|
||||
|
||||
type ListProjectsRow struct {
|
||||
ID string `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
Title string `json:"title"`
|
||||
Brief string `json:"brief"`
|
||||
Status string `json:"status"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
Title string `json:"title"`
|
||||
Brief string `json:"brief"`
|
||||
Status string `json:"status"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
BrandKitID pgtype.Text `json:"brand_kit_id"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListProjects(ctx context.Context, userID pgtype.UUID) ([]ListProjectsRow, error) {
|
||||
@@ -446,6 +565,7 @@ func (q *Queries) ListProjects(ctx context.Context, userID pgtype.UUID) ([]ListP
|
||||
&i.Brief,
|
||||
&i.Status,
|
||||
&i.Thumbnail,
|
||||
&i.BrandKitID,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -459,7 +579,7 @@ func (q *Queries) ListProjects(ctx context.Context, userID pgtype.UUID) ([]ListP
|
||||
}
|
||||
|
||||
const listProjectsPage = `-- name: ListProjectsPage :many
|
||||
SELECT id, user_id, title, brief, status, thumbnail, updated_at
|
||||
SELECT id, user_id, title, brief, status, thumbnail, brand_kit_id, updated_at
|
||||
FROM projects
|
||||
WHERE user_id = $1
|
||||
ORDER BY updated_at DESC
|
||||
@@ -473,13 +593,14 @@ type ListProjectsPageParams struct {
|
||||
}
|
||||
|
||||
type ListProjectsPageRow struct {
|
||||
ID string `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
Title string `json:"title"`
|
||||
Brief string `json:"brief"`
|
||||
Status string `json:"status"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
Title string `json:"title"`
|
||||
Brief string `json:"brief"`
|
||||
Status string `json:"status"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
BrandKitID pgtype.Text `json:"brand_kit_id"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListProjectsPage(ctx context.Context, arg ListProjectsPageParams) ([]ListProjectsPageRow, error) {
|
||||
@@ -498,6 +619,7 @@ func (q *Queries) ListProjectsPage(ctx context.Context, arg ListProjectsPagePara
|
||||
&i.Brief,
|
||||
&i.Status,
|
||||
&i.Thumbnail,
|
||||
&i.BrandKitID,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -577,6 +699,51 @@ func (q *Queries) UpdateProjectCanvasSnapshot(ctx context.Context, arg UpdatePro
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertBrandKit = `-- name: UpsertBrandKit :one
|
||||
INSERT INTO brand_kits (id, user_id, name, document, is_default, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
document = EXCLUDED.document,
|
||||
is_default = EXCLUDED.is_default,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
WHERE brand_kits.user_id = EXCLUDED.user_id
|
||||
RETURNING id, user_id, name, document, is_default, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpsertBrandKitParams struct {
|
||||
ID string `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Document []byte `json:"document"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpsertBrandKit(ctx context.Context, arg UpsertBrandKitParams) (BrandKit, error) {
|
||||
row := q.db.QueryRow(ctx, upsertBrandKit,
|
||||
arg.ID,
|
||||
arg.UserID,
|
||||
arg.Name,
|
||||
arg.Document,
|
||||
arg.IsDefault,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
)
|
||||
var i BrandKit
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Name,
|
||||
&i.Document,
|
||||
&i.IsDefault,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const upsertCanvasNode = `-- name: UpsertCanvasNode :exec
|
||||
INSERT INTO canvas_nodes (id, project_id, user_id, node_type, title, x, y, width, height, content, tone, status, sort_order, parent_id, layer_role, font_size, font_family, font_weight, color, text_align, line_height, letter_spacing, text_decoration, text_transform, opacity, fill_color, stroke_color, stroke_width, stroke_style, flip_x, flip_y, rotation)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32)
|
||||
@@ -688,8 +855,8 @@ func (q *Queries) UpsertCanvasNode(ctx context.Context, arg UpsertCanvasNodePara
|
||||
}
|
||||
|
||||
const upsertProject = `-- name: UpsertProject :exec
|
||||
INSERT INTO projects (id, user_id, title, brief, status, thumbnail, canvas, version, snapshot_id, last_thread_id, viewport_x, viewport_y, viewport_k, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
INSERT INTO projects (id, user_id, title, brief, status, thumbnail, canvas, version, snapshot_id, last_thread_id, brand_kit_id, viewport_x, viewport_y, viewport_k, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
user_id = EXCLUDED.user_id,
|
||||
title = EXCLUDED.title,
|
||||
@@ -700,6 +867,7 @@ ON CONFLICT (id) DO UPDATE SET
|
||||
version = EXCLUDED.version,
|
||||
snapshot_id = EXCLUDED.snapshot_id,
|
||||
last_thread_id = EXCLUDED.last_thread_id,
|
||||
brand_kit_id = EXCLUDED.brand_kit_id,
|
||||
viewport_x = EXCLUDED.viewport_x,
|
||||
viewport_y = EXCLUDED.viewport_y,
|
||||
viewport_k = EXCLUDED.viewport_k,
|
||||
@@ -717,6 +885,7 @@ type UpsertProjectParams struct {
|
||||
Version string `json:"version"`
|
||||
SnapshotID string `json:"snapshot_id"`
|
||||
LastThreadID string `json:"last_thread_id"`
|
||||
BrandKitID pgtype.Text `json:"brand_kit_id"`
|
||||
ViewportX float64 `json:"viewport_x"`
|
||||
ViewportY float64 `json:"viewport_y"`
|
||||
ViewportK float64 `json:"viewport_k"`
|
||||
@@ -735,6 +904,7 @@ func (q *Queries) UpsertProject(ctx context.Context, arg UpsertProjectParams) er
|
||||
arg.Version,
|
||||
arg.SnapshotID,
|
||||
arg.LastThreadID,
|
||||
arg.BrandKitID,
|
||||
arg.ViewportX,
|
||||
arg.ViewportY,
|
||||
arg.ViewportK,
|
||||
|
||||
Reference in New Issue
Block a user