Add an append-only ledger of entitlement set rule changes with per-pool effect rows, a preview-and-commit rule change flow, and an automatic drain that settles deferred recomputations. Rules gain a tier reduction policy, resource keys declare over-limit behavior, and the materializer now lowers limits when a rule stops applying. Add entitlement set rule change ledger and preview flow Add an append-only ledger of entitlement set rule changes with a preview-and-commit operator flow. Rule writes now go through an enclosed `core.commit_rule_change` function that files an act row and one obligation per carrying pool, with a drain workflow settling deferred recomputations. The preview dry-runs the materializer with a rule overlay and renders per-pool buckets, reduction-policy disclosures, and provider over-limit consequences. Materializing transactions take a shared advisory rendezvous that rule changes hold exclusively, enforced by a possession assertion. Add History and Entitlement changes surfaces, a rule-less warning on five product-selection surfaces, and a `tier_reduction_policy` column that gates FedWiki parking.
619 lines
18 KiB
Go
619 lines
18 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.29.0
|
|
// source: products.sql
|
|
|
|
package billing
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/lib/pq"
|
|
"github.com/sqlc-dev/pqtype"
|
|
)
|
|
|
|
const anyProduct = `-- name: AnyProduct :one
|
|
SELECT EXISTS(SELECT 1 FROM core.products)
|
|
`
|
|
|
|
// Setup-checklist existence probe.
|
|
func (q *Queries) AnyProduct(ctx context.Context) (bool, error) {
|
|
row := q.db.QueryRowContext(ctx, anyProduct)
|
|
var exists bool
|
|
err := row.Scan(&exists)
|
|
return exists, err
|
|
}
|
|
|
|
const anyPublishedProduct = `-- name: AnyPublishedProduct :one
|
|
SELECT EXISTS(SELECT 1 FROM core.products WHERE lifecycle_status = 'published')
|
|
`
|
|
|
|
// Setup-checklist existence probe.
|
|
func (q *Queries) AnyPublishedProduct(ctx context.Context) (bool, error) {
|
|
row := q.db.QueryRowContext(ctx, anyPublishedProduct)
|
|
var exists bool
|
|
err := row.Scan(&exists)
|
|
return exists, err
|
|
}
|
|
|
|
const countPublishedProducts = `-- name: CountPublishedProducts :one
|
|
SELECT COUNT(*) FROM core.products
|
|
WHERE lifecycle_status = 'published'
|
|
`
|
|
|
|
// Operator overview tile: the size of the live catalog. Counts only
|
|
// lifecycle_status='published' — drafts are work in progress and retired
|
|
// products are history, so neither belongs in a "what we sell today" number.
|
|
func (q *Queries) CountPublishedProducts(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countPublishedProducts)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const countPublishedTierProducts = `-- name: CountPublishedTierProducts :one
|
|
SELECT COUNT(DISTINCT t.product_id) FROM core.plan_ladder_tiers t
|
|
JOIN core.products p ON p.product_id = t.product_id
|
|
WHERE p.lifecycle_status = 'published'
|
|
`
|
|
|
|
// Operator catalog overview (ux-ia-naming 2.1): how many published products
|
|
// are a tier on some plan ladder, vs. off-ladder. A product counts once even
|
|
// when it is shared across more than one ladder (DISTINCT).
|
|
func (q *Queries) CountPublishedTierProducts(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countPublishedTierProducts)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const createProduct = `-- name: CreateProduct :one
|
|
INSERT INTO core.products (name, description, display_category, is_active, is_public, entitlement_set_id, lifecycle_status, key)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
RETURNING product_id, name, description, display_category, metadata, is_active, is_public, created_at, updated_at, entitlement_set_id, lifecycle_status, key
|
|
`
|
|
|
|
type CreateProductParams struct {
|
|
Name string `json:"name"`
|
|
Description sql.NullString `json:"description"`
|
|
DisplayCategory sql.NullString `json:"display_category"`
|
|
IsActive bool `json:"is_active"`
|
|
IsPublic bool `json:"is_public"`
|
|
EntitlementSetID uuid.NullUUID `json:"entitlement_set_id"`
|
|
LifecycleStatus string `json:"lifecycle_status"`
|
|
Key sql.NullString `json:"key"`
|
|
}
|
|
|
|
// `key` is the optional declarative address (entity-keys §3), root-scoped.
|
|
// Product names deliberately carry no uniqueness constraint (two ladders may
|
|
// each have a "Pro"), which is exactly why a caller outside the database
|
|
// needs a key to name one row. The operator create form does not collect
|
|
// one, so a UI-created product leaves it NULL.
|
|
func (q *Queries) CreateProduct(ctx context.Context, arg CreateProductParams) (Product, error) {
|
|
row := q.db.QueryRowContext(ctx, createProduct,
|
|
arg.Name,
|
|
arg.Description,
|
|
arg.DisplayCategory,
|
|
arg.IsActive,
|
|
arg.IsPublic,
|
|
arg.EntitlementSetID,
|
|
arg.LifecycleStatus,
|
|
arg.Key,
|
|
)
|
|
var i Product
|
|
err := row.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getProductByID = `-- name: GetProductByID :one
|
|
SELECT product_id, name, description, display_category, metadata, is_active, is_public, created_at, updated_at, entitlement_set_id, lifecycle_status, key FROM core.products
|
|
WHERE product_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetProductByID(ctx context.Context, productID string) (Product, error) {
|
|
row := q.db.QueryRowContext(ctx, getProductByID, productID)
|
|
var i Product
|
|
err := row.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getProductByKey = `-- name: GetProductByKey :one
|
|
SELECT product_id, name, description, display_category, metadata, is_active, is_public, created_at, updated_at, entitlement_set_id, lifecycle_status, key FROM core.products
|
|
WHERE key = $1
|
|
`
|
|
|
|
// Root key resolver (entity-keys §5). uq_products_key guarantees at most one
|
|
// row matches -- unlike GetProductByName, whose column has no unique
|
|
// constraint at all.
|
|
func (q *Queries) GetProductByKey(ctx context.Context, key sql.NullString) (Product, error) {
|
|
row := q.db.QueryRowContext(ctx, getProductByKey, key)
|
|
var i Product
|
|
err := row.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getProductShape = `-- name: GetProductShape :one
|
|
SELECT product_id, lifecycle_status, is_public, set_present, ladder_count, billing_shape, consumption_shape, active_rule_count, per_unit_rule_count FROM core.product_shape
|
|
WHERE product_id = $1
|
|
`
|
|
|
|
// Diagnostics view (migrations 00006, 00018): independent structural
|
|
// dimensions per product. Readiness reads set_present, billing_shape and
|
|
// active_rule_count, which is the sole source of the answer to whether a
|
|
// product's set provides anything (product-catalog). The two counts are
|
|
// cast because the view's COALESCE over a grouped count gives sqlc no type
|
|
// to infer.
|
|
func (q *Queries) GetProductShape(ctx context.Context, productID string) (CoreProductShape, error) {
|
|
row := q.db.QueryRowContext(ctx, getProductShape, productID)
|
|
var i CoreProductShape
|
|
err := row.Scan(
|
|
&i.ProductID,
|
|
&i.LifecycleStatus,
|
|
&i.IsPublic,
|
|
&i.SetPresent,
|
|
&i.LadderCount,
|
|
&i.BillingShape,
|
|
&i.ConsumptionShape,
|
|
&i.ActiveRuleCount,
|
|
&i.PerUnitRuleCount,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listActiveProducts = `-- name: ListActiveProducts :many
|
|
SELECT product_id, name, description, display_category, metadata, is_active, is_public, created_at, updated_at, entitlement_set_id, lifecycle_status, key FROM core.products
|
|
WHERE is_active = TRUE
|
|
ORDER BY name ASC
|
|
`
|
|
|
|
func (q *Queries) ListActiveProducts(ctx context.Context) ([]Product, error) {
|
|
rows, err := q.db.QueryContext(ctx, listActiveProducts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Product{}
|
|
for rows.Next() {
|
|
var i Product
|
|
if err := rows.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
); 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 listAllProducts = `-- name: ListAllProducts :many
|
|
SELECT product_id, name, description, display_category, metadata, is_active, is_public, created_at, updated_at, entitlement_set_id, lifecycle_status, key FROM core.products
|
|
ORDER BY name ASC
|
|
`
|
|
|
|
func (q *Queries) ListAllProducts(ctx context.Context) ([]Product, error) {
|
|
rows, err := q.db.QueryContext(ctx, listAllProducts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Product{}
|
|
for rows.Next() {
|
|
var i Product
|
|
if err := rows.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
); 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 listProductShapesByIDs = `-- name: ListProductShapesByIDs :many
|
|
SELECT product_id, lifecycle_status, is_public, set_present, ladder_count, billing_shape, consumption_shape, active_rule_count, per_unit_rule_count FROM core.product_shape
|
|
WHERE product_id = ANY($1::uuid[])
|
|
`
|
|
|
|
// Batch reader for the products list's per-page verdict (product-management
|
|
// "The verdict per page, from batches, never cached", design D4): the
|
|
// product_shape view for every product on the page, one query instead of
|
|
// one per row.
|
|
func (q *Queries) ListProductShapesByIDs(ctx context.Context, productIds []string) ([]CoreProductShape, error) {
|
|
rows, err := q.db.QueryContext(ctx, listProductShapesByIDs, pq.Array(productIds))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []CoreProductShape{}
|
|
for rows.Next() {
|
|
var i CoreProductShape
|
|
if err := rows.Scan(
|
|
&i.ProductID,
|
|
&i.LifecycleStatus,
|
|
&i.IsPublic,
|
|
&i.SetPresent,
|
|
&i.LadderCount,
|
|
&i.BillingShape,
|
|
&i.ConsumptionShape,
|
|
&i.ActiveRuleCount,
|
|
&i.PerUnitRuleCount,
|
|
); 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 listProductsByNameCI = `-- name: ListProductsByNameCI :many
|
|
SELECT
|
|
p.product_id,
|
|
p.name,
|
|
p.is_active,
|
|
l.plan_ladder_id AS ladder_id,
|
|
l.name AS ladder_name
|
|
FROM core.products p
|
|
LEFT JOIN core.plan_ladder_tiers t ON t.product_id = p.product_id
|
|
LEFT JOIN core.plan_ladders l ON l.plan_ladder_id = t.plan_ladder_id
|
|
WHERE lower(p.name) = lower($1::text)
|
|
AND ($2::uuid IS NULL OR p.product_id <> $2::uuid)
|
|
ORDER BY p.name ASC, l.name ASC
|
|
`
|
|
|
|
type ListProductsByNameCIParams struct {
|
|
Name string `json:"name"`
|
|
ExcludeProductID uuid.NullUUID `json:"exclude_product_id"`
|
|
}
|
|
|
|
type ListProductsByNameCIRow struct {
|
|
ProductID string `json:"product_id"`
|
|
Name string `json:"name"`
|
|
IsActive bool `json:"is_active"`
|
|
LadderID uuid.NullUUID `json:"ladder_id"`
|
|
LadderName sql.NullString `json:"ladder_name"`
|
|
}
|
|
|
|
// Product duplicate-name warning (entity-keys): products may
|
|
// legitimately share a name (a "Pro" tier on two ladders), so
|
|
// core.products.name carries no uniqueness constraint -- this powers a live,
|
|
// non-blocking warning on the create/edit forms instead of a 422. Matches
|
|
// case-insensitively; when exclude_product_id is set (the edit form's own
|
|
// product), that product is excluded from its own duplicate check. LEFT
|
|
// JOINs the ladder tables so a matching product that is on no ladder still
|
|
// returns one row (ladder columns NULL); the caller groups rows by
|
|
// product_id in Go and collects each product's ladder names.
|
|
func (q *Queries) ListProductsByNameCI(ctx context.Context, arg ListProductsByNameCIParams) ([]ListProductsByNameCIRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listProductsByNameCI, arg.Name, arg.ExcludeProductID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListProductsByNameCIRow{}
|
|
for rows.Next() {
|
|
var i ListProductsByNameCIRow
|
|
if err := rows.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.IsActive,
|
|
&i.LadderID,
|
|
&i.LadderName,
|
|
); 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 listProductsPage = `-- name: ListProductsPage :many
|
|
SELECT product_id, name, description, display_category, metadata, is_active, is_public, created_at, updated_at, entitlement_set_id, lifecycle_status, key, count(*) OVER() AS total_count
|
|
FROM core.products
|
|
WHERE ($1::text IS NULL OR name ILIKE '%' || $1::text || '%')
|
|
AND ($2::text IS NULL OR lifecycle_status = $2::text)
|
|
ORDER BY name ASC
|
|
LIMIT $4::int OFFSET $3::int
|
|
`
|
|
|
|
type ListProductsPageParams struct {
|
|
Q sql.NullString `json:"q"`
|
|
LifecycleStatus sql.NullString `json:"lifecycle_status"`
|
|
PageOffset int32 `json:"page_offset"`
|
|
PageLimit int32 `json:"page_limit"`
|
|
}
|
|
|
|
type ListProductsPageRow struct {
|
|
ProductID string `json:"product_id"`
|
|
Name string `json:"name"`
|
|
Description sql.NullString `json:"description"`
|
|
DisplayCategory sql.NullString `json:"display_category"`
|
|
Metadata pqtype.NullRawMessage `json:"metadata"`
|
|
IsActive bool `json:"is_active"`
|
|
IsPublic bool `json:"is_public"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
EntitlementSetID uuid.NullUUID `json:"entitlement_set_id"`
|
|
LifecycleStatus string `json:"lifecycle_status"`
|
|
Key sql.NullString `json:"key"`
|
|
TotalCount int64 `json:"total_count"`
|
|
}
|
|
|
|
// Products list operator surface (operator-list-scale; product-management
|
|
// "The list takes the scaffold"): the paged, searched, lifecycle-filtered
|
|
// successor to ListAllProducts, in the Organizations list's shape.
|
|
//
|
|
// sqlc.narg(q): NULL matches every row; set, a case-insensitive substring
|
|
// match against the product's name.
|
|
// sqlc.narg(lifecycle_status): NULL matches every lifecycle state; set, an
|
|
// exact facet match (the Draft/Published/Retired pills).
|
|
// count(*) OVER() carries the true total for the filtered set (design D2),
|
|
// so the stated total never reflects only the page.
|
|
func (q *Queries) ListProductsPage(ctx context.Context, arg ListProductsPageParams) ([]ListProductsPageRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listProductsPage,
|
|
arg.Q,
|
|
arg.LifecycleStatus,
|
|
arg.PageOffset,
|
|
arg.PageLimit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListProductsPageRow{}
|
|
for rows.Next() {
|
|
var i ListProductsPageRow
|
|
if err := rows.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
&i.TotalCount,
|
|
); 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 listPublicPlanProducts = `-- name: ListPublicPlanProducts :many
|
|
SELECT p.product_id, p.name, p.description, p.display_category, p.metadata, p.is_active, p.is_public, p.created_at, p.updated_at, p.entitlement_set_id, p.lifecycle_status, p.key FROM core.products p
|
|
WHERE p.is_active = TRUE AND p.is_public = TRUE AND p.lifecycle_status = 'published'
|
|
AND EXISTS (
|
|
SELECT 1 FROM core.plan_ladder_tiers t WHERE t.product_id = p.product_id
|
|
)
|
|
ORDER BY p.name ASC
|
|
`
|
|
|
|
// Plan products are those attached to a ladder via plan_ladder_tiers
|
|
// (Doc 31 Amendment #3); membership is the structural signal —
|
|
// display_category is presentation-only and never branched on.
|
|
func (q *Queries) ListPublicPlanProducts(ctx context.Context) ([]Product, error) {
|
|
rows, err := q.db.QueryContext(ctx, listPublicPlanProducts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Product{}
|
|
for rows.Next() {
|
|
var i Product
|
|
if err := rows.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
); 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 listPublicProducts = `-- name: ListPublicProducts :many
|
|
SELECT product_id, name, description, display_category, metadata, is_active, is_public, created_at, updated_at, entitlement_set_id, lifecycle_status, key FROM core.products
|
|
WHERE is_active = TRUE AND is_public = TRUE AND lifecycle_status = 'published'
|
|
ORDER BY name ASC
|
|
`
|
|
|
|
func (q *Queries) ListPublicProducts(ctx context.Context) ([]Product, error) {
|
|
rows, err := q.db.QueryContext(ctx, listPublicProducts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Product{}
|
|
for rows.Next() {
|
|
var i Product
|
|
if err := rows.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
); 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 updateProduct = `-- name: UpdateProduct :one
|
|
UPDATE core.products
|
|
SET name = $2, description = $3, display_category = $4, is_active = $5, is_public = $6, entitlement_set_id = $7, metadata = $8
|
|
WHERE product_id = $1
|
|
RETURNING product_id, name, description, display_category, metadata, is_active, is_public, created_at, updated_at, entitlement_set_id, lifecycle_status, key
|
|
`
|
|
|
|
type UpdateProductParams struct {
|
|
ProductID string `json:"product_id"`
|
|
Name string `json:"name"`
|
|
Description sql.NullString `json:"description"`
|
|
DisplayCategory sql.NullString `json:"display_category"`
|
|
IsActive bool `json:"is_active"`
|
|
IsPublic bool `json:"is_public"`
|
|
EntitlementSetID uuid.NullUUID `json:"entitlement_set_id"`
|
|
Metadata pqtype.NullRawMessage `json:"metadata"`
|
|
}
|
|
|
|
func (q *Queries) UpdateProduct(ctx context.Context, arg UpdateProductParams) (Product, error) {
|
|
row := q.db.QueryRowContext(ctx, updateProduct,
|
|
arg.ProductID,
|
|
arg.Name,
|
|
arg.Description,
|
|
arg.DisplayCategory,
|
|
arg.IsActive,
|
|
arg.IsPublic,
|
|
arg.EntitlementSetID,
|
|
arg.Metadata,
|
|
)
|
|
var i Product
|
|
err := row.Scan(
|
|
&i.ProductID,
|
|
&i.Name,
|
|
&i.Description,
|
|
&i.DisplayCategory,
|
|
&i.Metadata,
|
|
&i.IsActive,
|
|
&i.IsPublic,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.EntitlementSetID,
|
|
&i.LifecycleStatus,
|
|
&i.Key,
|
|
)
|
|
return i, err
|
|
}
|