70725193eb
Addresses findings from consolidated Phase 1 code review: - Add composite unique indexes + composite FKs so subscription_prompts and packages cannot reference mismatched (tenant_id, package_id, prompt_id) tuples, enforcing ACL lineage at the schema level. - Add status CHECK constraints on all KOL tables as defense-in-depth. - ListActiveSubscribersForPackage: drop meaningless tenant_id IS NOT NULL, add end_at expiry filter so fan-out skips expired subscribers. - ApproveKolSubscription: add status='pending' guard to prevent double-approval. - ListSubscriptionPromptsByTenant: join subscription/prompt/package/profile lifecycle so hidden/archived content no longer leaks; surface kol_display_name for UI. - ListPublishedKolPackages: exclude expired subscribers from marketplace count. - KolDashboardTrend: rewrite as CTE with day-series, returning both daily usage and new subscription counts. - Remove cosmetic tenant_id IS NOT NULL from dashboard overview queries (the column is NOT NULL by schema). - check_tenant_scope.sh: require tenant_id = sqlc.(n?)arg filter, reject tenant_id IS NOT NULL as a fake scope, maintain explicit exemption list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
422 lines
12 KiB
Go
422 lines
12 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.30.0
|
|
// source: kol_subscription.sql
|
|
|
|
package generated
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const approveKolSubscription = `-- name: ApproveKolSubscription :exec
|
|
UPDATE kol_subscriptions
|
|
SET status = 'active',
|
|
approved_by = $1,
|
|
start_at = NOW(),
|
|
end_at = $2,
|
|
updated_at = NOW()
|
|
WHERE id = $3
|
|
AND tenant_id = $4::bigint
|
|
AND status = 'pending'
|
|
`
|
|
|
|
type ApproveKolSubscriptionParams struct {
|
|
ApprovedBy pgtype.Int8 `json:"approved_by"`
|
|
EndAt pgtype.Timestamptz `json:"end_at"`
|
|
ID int64 `json:"id"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
}
|
|
|
|
func (q *Queries) ApproveKolSubscription(ctx context.Context, arg ApproveKolSubscriptionParams) error {
|
|
_, err := q.db.Exec(ctx, approveKolSubscription,
|
|
arg.ApprovedBy,
|
|
arg.EndAt,
|
|
arg.ID,
|
|
arg.TenantID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const createKolSubscription = `-- name: CreateKolSubscription :one
|
|
INSERT INTO kol_subscriptions (tenant_id, package_id, status)
|
|
VALUES ($1::bigint, $2, 'pending')
|
|
RETURNING id, tenant_id, package_id, status, approved_by, start_at, end_at, created_at, updated_at
|
|
`
|
|
|
|
type CreateKolSubscriptionParams struct {
|
|
TenantID int64 `json:"tenant_id"`
|
|
PackageID int64 `json:"package_id"`
|
|
}
|
|
|
|
func (q *Queries) CreateKolSubscription(ctx context.Context, arg CreateKolSubscriptionParams) (KolSubscription, error) {
|
|
row := q.db.QueryRow(ctx, createKolSubscription, arg.TenantID, arg.PackageID)
|
|
var i KolSubscription
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.TenantID,
|
|
&i.PackageID,
|
|
&i.Status,
|
|
&i.ApprovedBy,
|
|
&i.StartAt,
|
|
&i.EndAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const createSubscriptionPrompt = `-- name: CreateSubscriptionPrompt :exec
|
|
INSERT INTO kol_subscription_prompts (tenant_id, subscription_id, package_id, prompt_id, status)
|
|
VALUES ($1::bigint, $2,
|
|
$3, $4, 'active')
|
|
ON CONFLICT (subscription_id, prompt_id) DO NOTHING
|
|
`
|
|
|
|
type CreateSubscriptionPromptParams struct {
|
|
TenantID int64 `json:"tenant_id"`
|
|
SubscriptionID int64 `json:"subscription_id"`
|
|
PackageID int64 `json:"package_id"`
|
|
PromptID int64 `json:"prompt_id"`
|
|
}
|
|
|
|
func (q *Queries) CreateSubscriptionPrompt(ctx context.Context, arg CreateSubscriptionPromptParams) error {
|
|
_, err := q.db.Exec(ctx, createSubscriptionPrompt,
|
|
arg.TenantID,
|
|
arg.SubscriptionID,
|
|
arg.PackageID,
|
|
arg.PromptID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const getActiveKolSubscription = `-- name: GetActiveKolSubscription :one
|
|
SELECT id, tenant_id, package_id, status, approved_by, start_at, end_at, created_at, updated_at
|
|
FROM kol_subscriptions
|
|
WHERE tenant_id = $1::bigint
|
|
AND package_id = $2
|
|
AND status IN ('pending', 'active')
|
|
`
|
|
|
|
type GetActiveKolSubscriptionParams struct {
|
|
TenantID int64 `json:"tenant_id"`
|
|
PackageID int64 `json:"package_id"`
|
|
}
|
|
|
|
func (q *Queries) GetActiveKolSubscription(ctx context.Context, arg GetActiveKolSubscriptionParams) (KolSubscription, error) {
|
|
row := q.db.QueryRow(ctx, getActiveKolSubscription, arg.TenantID, arg.PackageID)
|
|
var i KolSubscription
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.TenantID,
|
|
&i.PackageID,
|
|
&i.Status,
|
|
&i.ApprovedBy,
|
|
&i.StartAt,
|
|
&i.EndAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getKolSubscriptionByID = `-- name: GetKolSubscriptionByID :one
|
|
SELECT id, tenant_id, package_id, status, approved_by, start_at, end_at, created_at, updated_at
|
|
FROM kol_subscriptions
|
|
WHERE id = $1
|
|
AND tenant_id = $2::bigint
|
|
`
|
|
|
|
type GetKolSubscriptionByIDParams struct {
|
|
ID int64 `json:"id"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
}
|
|
|
|
func (q *Queries) GetKolSubscriptionByID(ctx context.Context, arg GetKolSubscriptionByIDParams) (KolSubscription, error) {
|
|
row := q.db.QueryRow(ctx, getKolSubscriptionByID, arg.ID, arg.TenantID)
|
|
var i KolSubscription
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.TenantID,
|
|
&i.PackageID,
|
|
&i.Status,
|
|
&i.ApprovedBy,
|
|
&i.StartAt,
|
|
&i.EndAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getSubscriptionPromptByID = `-- name: GetSubscriptionPromptByID :one
|
|
SELECT sp.id, sp.tenant_id, sp.subscription_id, sp.package_id, sp.prompt_id,
|
|
sp.status, sp.granted_at, sp.revoked_at,
|
|
s.status AS subscription_status, s.end_at AS subscription_end_at,
|
|
p.name AS prompt_name, p.platform_hint, p.status AS prompt_status,
|
|
p.published_revision_no,
|
|
k.name AS package_name, k.status AS package_status
|
|
FROM kol_subscription_prompts sp
|
|
JOIN kol_subscriptions s ON s.id = sp.subscription_id
|
|
JOIN kol_prompts p ON p.id = sp.prompt_id AND p.deleted_at IS NULL
|
|
JOIN kol_packages k ON k.id = sp.package_id AND k.deleted_at IS NULL
|
|
WHERE sp.id = $1
|
|
AND sp.tenant_id = $2::bigint
|
|
`
|
|
|
|
type GetSubscriptionPromptByIDParams struct {
|
|
ID int64 `json:"id"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
}
|
|
|
|
type GetSubscriptionPromptByIDRow struct {
|
|
ID int64 `json:"id"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
SubscriptionID int64 `json:"subscription_id"`
|
|
PackageID int64 `json:"package_id"`
|
|
PromptID int64 `json:"prompt_id"`
|
|
Status string `json:"status"`
|
|
GrantedAt pgtype.Timestamptz `json:"granted_at"`
|
|
RevokedAt pgtype.Timestamptz `json:"revoked_at"`
|
|
SubscriptionStatus string `json:"subscription_status"`
|
|
SubscriptionEndAt pgtype.Timestamptz `json:"subscription_end_at"`
|
|
PromptName string `json:"prompt_name"`
|
|
PlatformHint pgtype.Text `json:"platform_hint"`
|
|
PromptStatus string `json:"prompt_status"`
|
|
PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"`
|
|
PackageName string `json:"package_name"`
|
|
PackageStatus string `json:"package_status"`
|
|
}
|
|
|
|
func (q *Queries) GetSubscriptionPromptByID(ctx context.Context, arg GetSubscriptionPromptByIDParams) (GetSubscriptionPromptByIDRow, error) {
|
|
row := q.db.QueryRow(ctx, getSubscriptionPromptByID, arg.ID, arg.TenantID)
|
|
var i GetSubscriptionPromptByIDRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.TenantID,
|
|
&i.SubscriptionID,
|
|
&i.PackageID,
|
|
&i.PromptID,
|
|
&i.Status,
|
|
&i.GrantedAt,
|
|
&i.RevokedAt,
|
|
&i.SubscriptionStatus,
|
|
&i.SubscriptionEndAt,
|
|
&i.PromptName,
|
|
&i.PlatformHint,
|
|
&i.PromptStatus,
|
|
&i.PublishedRevisionNo,
|
|
&i.PackageName,
|
|
&i.PackageStatus,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listActiveSubscribersForPackage = `-- name: ListActiveSubscribersForPackage :many
|
|
SELECT id, tenant_id, package_id, status, approved_by, start_at, end_at, created_at, updated_at
|
|
FROM kol_subscriptions
|
|
WHERE package_id = $1
|
|
AND status = 'active'
|
|
AND (end_at IS NULL OR end_at > NOW())
|
|
`
|
|
|
|
// Cross-tenant read: used by platform admin for subscription fan-out.
|
|
// Excludes expired subscriptions so new prompts are not granted to them.
|
|
func (q *Queries) ListActiveSubscribersForPackage(ctx context.Context, packageID int64) ([]KolSubscription, error) {
|
|
rows, err := q.db.Query(ctx, listActiveSubscribersForPackage, packageID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []KolSubscription
|
|
for rows.Next() {
|
|
var i KolSubscription
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.TenantID,
|
|
&i.PackageID,
|
|
&i.Status,
|
|
&i.ApprovedBy,
|
|
&i.StartAt,
|
|
&i.EndAt,
|
|
&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 listKolSubscriptions = `-- name: ListKolSubscriptions :many
|
|
SELECT id, tenant_id, package_id, status, approved_by, start_at, end_at, created_at, updated_at
|
|
FROM kol_subscriptions
|
|
WHERE tenant_id = $1::bigint
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
func (q *Queries) ListKolSubscriptions(ctx context.Context, tenantID int64) ([]KolSubscription, error) {
|
|
rows, err := q.db.Query(ctx, listKolSubscriptions, tenantID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []KolSubscription
|
|
for rows.Next() {
|
|
var i KolSubscription
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.TenantID,
|
|
&i.PackageID,
|
|
&i.Status,
|
|
&i.ApprovedBy,
|
|
&i.StartAt,
|
|
&i.EndAt,
|
|
&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 listSubscriptionPromptsByTenant = `-- name: ListSubscriptionPromptsByTenant :many
|
|
SELECT sp.id, sp.tenant_id, sp.subscription_id, sp.package_id, sp.prompt_id,
|
|
sp.status, sp.granted_at, sp.revoked_at,
|
|
p.name AS prompt_name, p.platform_hint, p.published_revision_no,
|
|
k.name AS package_name,
|
|
pf.display_name AS kol_display_name
|
|
FROM kol_subscription_prompts sp
|
|
JOIN kol_subscriptions s ON s.id = sp.subscription_id
|
|
AND s.tenant_id = sp.tenant_id
|
|
AND s.package_id = sp.package_id
|
|
AND s.status = 'active'
|
|
AND (s.end_at IS NULL OR s.end_at > NOW())
|
|
JOIN kol_prompts p ON p.id = sp.prompt_id
|
|
AND p.package_id = sp.package_id
|
|
AND p.status = 'active'
|
|
AND p.published_revision_no IS NOT NULL
|
|
AND p.deleted_at IS NULL
|
|
JOIN kol_packages k ON k.id = sp.package_id
|
|
AND k.status = 'published'
|
|
AND k.deleted_at IS NULL
|
|
JOIN kol_profiles pf ON pf.id = k.kol_profile_id
|
|
AND pf.status = 'active'
|
|
AND pf.deleted_at IS NULL
|
|
WHERE sp.tenant_id = $1::bigint
|
|
AND sp.status = 'active'
|
|
ORDER BY sp.granted_at DESC
|
|
`
|
|
|
|
type ListSubscriptionPromptsByTenantRow struct {
|
|
ID int64 `json:"id"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
SubscriptionID int64 `json:"subscription_id"`
|
|
PackageID int64 `json:"package_id"`
|
|
PromptID int64 `json:"prompt_id"`
|
|
Status string `json:"status"`
|
|
GrantedAt pgtype.Timestamptz `json:"granted_at"`
|
|
RevokedAt pgtype.Timestamptz `json:"revoked_at"`
|
|
PromptName string `json:"prompt_name"`
|
|
PlatformHint pgtype.Text `json:"platform_hint"`
|
|
PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"`
|
|
PackageName string `json:"package_name"`
|
|
KolDisplayName string `json:"kol_display_name"`
|
|
}
|
|
|
|
func (q *Queries) ListSubscriptionPromptsByTenant(ctx context.Context, tenantID int64) ([]ListSubscriptionPromptsByTenantRow, error) {
|
|
rows, err := q.db.Query(ctx, listSubscriptionPromptsByTenant, tenantID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListSubscriptionPromptsByTenantRow
|
|
for rows.Next() {
|
|
var i ListSubscriptionPromptsByTenantRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.TenantID,
|
|
&i.SubscriptionID,
|
|
&i.PackageID,
|
|
&i.PromptID,
|
|
&i.Status,
|
|
&i.GrantedAt,
|
|
&i.RevokedAt,
|
|
&i.PromptName,
|
|
&i.PlatformHint,
|
|
&i.PublishedRevisionNo,
|
|
&i.PackageName,
|
|
&i.KolDisplayName,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const revokeKolSubscription = `-- name: RevokeKolSubscription :exec
|
|
UPDATE kol_subscriptions
|
|
SET status = 'revoked', updated_at = NOW()
|
|
WHERE id = $1
|
|
AND tenant_id = $2::bigint
|
|
`
|
|
|
|
type RevokeKolSubscriptionParams struct {
|
|
ID int64 `json:"id"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
}
|
|
|
|
func (q *Queries) RevokeKolSubscription(ctx context.Context, arg RevokeKolSubscriptionParams) error {
|
|
_, err := q.db.Exec(ctx, revokeKolSubscription, arg.ID, arg.TenantID)
|
|
return err
|
|
}
|
|
|
|
const revokeSubscriptionPromptsByPrompt = `-- name: RevokeSubscriptionPromptsByPrompt :exec
|
|
UPDATE kol_subscription_prompts
|
|
SET status = 'revoked', revoked_at = NOW(), updated_at = NOW()
|
|
WHERE prompt_id = $1
|
|
AND tenant_id = $2::bigint
|
|
`
|
|
|
|
type RevokeSubscriptionPromptsByPromptParams struct {
|
|
PromptID int64 `json:"prompt_id"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
}
|
|
|
|
func (q *Queries) RevokeSubscriptionPromptsByPrompt(ctx context.Context, arg RevokeSubscriptionPromptsByPromptParams) error {
|
|
_, err := q.db.Exec(ctx, revokeSubscriptionPromptsByPrompt, arg.PromptID, arg.TenantID)
|
|
return err
|
|
}
|
|
|
|
const revokeSubscriptionPromptsBySubscription = `-- name: RevokeSubscriptionPromptsBySubscription :exec
|
|
UPDATE kol_subscription_prompts
|
|
SET status = 'revoked', revoked_at = NOW(), updated_at = NOW()
|
|
WHERE subscription_id = $1
|
|
AND tenant_id = $2::bigint
|
|
`
|
|
|
|
type RevokeSubscriptionPromptsBySubscriptionParams struct {
|
|
SubscriptionID int64 `json:"subscription_id"`
|
|
TenantID int64 `json:"tenant_id"`
|
|
}
|
|
|
|
func (q *Queries) RevokeSubscriptionPromptsBySubscription(ctx context.Context, arg RevokeSubscriptionPromptsBySubscriptionParams) error {
|
|
_, err := q.db.Exec(ctx, revokeSubscriptionPromptsBySubscription, arg.SubscriptionID, arg.TenantID)
|
|
return err
|
|
}
|