From b78664c7d070537f0e694116e3970f9f8d14d9f1 Mon Sep 17 00:00:00 2001 From: liangxu Date: Fri, 17 Apr 2026 08:10:45 +0800 Subject: [PATCH] feat(kol): add sqlc queries for all KOL tables --- .../repository/generated/kol_assist.sql.go | 134 +++++ .../generated/kol_marketplace.sql.go | 207 ++++++++ .../repository/generated/kol_package.sql.go | 297 +++++++++++ .../repository/generated/kol_profile.sql.go | 125 +++++ .../repository/generated/kol_prompt.sql.go | 489 ++++++++++++++++++ .../generated/kol_subscription.sql.go | 401 ++++++++++++++ .../repository/generated/kol_usage.sql.go | 306 +++++++++++ .../tenant/repository/generated/models.go | 144 +++++- .../tenant/repository/generated/querier.go | 53 ++ .../tenant/repository/queries/kol_assist.sql | 34 ++ .../repository/queries/kol_marketplace.sql | 46 ++ .../tenant/repository/queries/kol_package.sql | 58 +++ .../tenant/repository/queries/kol_profile.sql | 23 + .../tenant/repository/queries/kol_prompt.sql | 105 ++++ .../repository/queries/kol_subscription.sql | 90 ++++ .../tenant/repository/queries/kol_usage.sql | 84 +++ server/scripts/check_tenant_scope.sh | 2 +- 17 files changed, 2586 insertions(+), 12 deletions(-) create mode 100644 server/internal/tenant/repository/generated/kol_assist.sql.go create mode 100644 server/internal/tenant/repository/generated/kol_marketplace.sql.go create mode 100644 server/internal/tenant/repository/generated/kol_package.sql.go create mode 100644 server/internal/tenant/repository/generated/kol_profile.sql.go create mode 100644 server/internal/tenant/repository/generated/kol_prompt.sql.go create mode 100644 server/internal/tenant/repository/generated/kol_subscription.sql.go create mode 100644 server/internal/tenant/repository/generated/kol_usage.sql.go create mode 100644 server/internal/tenant/repository/queries/kol_assist.sql create mode 100644 server/internal/tenant/repository/queries/kol_marketplace.sql create mode 100644 server/internal/tenant/repository/queries/kol_package.sql create mode 100644 server/internal/tenant/repository/queries/kol_profile.sql create mode 100644 server/internal/tenant/repository/queries/kol_prompt.sql create mode 100644 server/internal/tenant/repository/queries/kol_subscription.sql create mode 100644 server/internal/tenant/repository/queries/kol_usage.sql diff --git a/server/internal/tenant/repository/generated/kol_assist.sql.go b/server/internal/tenant/repository/generated/kol_assist.sql.go new file mode 100644 index 0000000..3e08105 --- /dev/null +++ b/server/internal/tenant/repository/generated/kol_assist.sql.go @@ -0,0 +1,134 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: kol_assist.sql + +package generated + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createKolAssistTask = `-- name: CreateKolAssistTask :exec +INSERT INTO kol_assist_tasks (id, tenant_id, kol_profile_id, prompt_id, + operator_id, task_type, status, request_json) +VALUES ($1, $2::bigint, $3, + $4, $5, + $6, 'queued', $7::jsonb) +` + +type CreateKolAssistTaskParams struct { + ID string `json:"id"` + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + PromptID pgtype.Int8 `json:"prompt_id"` + OperatorID int64 `json:"operator_id"` + TaskType string `json:"task_type"` + RequestJson []byte `json:"request_json"` +} + +func (q *Queries) CreateKolAssistTask(ctx context.Context, arg CreateKolAssistTaskParams) error { + _, err := q.db.Exec(ctx, createKolAssistTask, + arg.ID, + arg.TenantID, + arg.KolProfileID, + arg.PromptID, + arg.OperatorID, + arg.TaskType, + arg.RequestJson, + ) + return err +} + +const getKolAssistTask = `-- name: GetKolAssistTask :one +SELECT id, tenant_id, kol_profile_id, prompt_id, operator_id, task_type, + status, request_json, result_json, error_message, + started_at, completed_at, created_at, updated_at +FROM kol_assist_tasks +WHERE id = $1 + AND tenant_id = $2::bigint +` + +type GetKolAssistTaskParams struct { + ID string `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) GetKolAssistTask(ctx context.Context, arg GetKolAssistTaskParams) (KolAssistTask, error) { + row := q.db.QueryRow(ctx, getKolAssistTask, arg.ID, arg.TenantID) + var i KolAssistTask + err := row.Scan( + &i.ID, + &i.TenantID, + &i.KolProfileID, + &i.PromptID, + &i.OperatorID, + &i.TaskType, + &i.Status, + &i.RequestJson, + &i.ResultJson, + &i.ErrorMessage, + &i.StartedAt, + &i.CompletedAt, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const markKolAssistCompleted = `-- name: MarkKolAssistCompleted :exec +UPDATE kol_assist_tasks +SET status = 'completed', result_json = $1::jsonb, + completed_at = NOW(), updated_at = NOW() +WHERE id = $2 + AND tenant_id = $3::bigint +` + +type MarkKolAssistCompletedParams struct { + ResultJson []byte `json:"result_json"` + ID string `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) MarkKolAssistCompleted(ctx context.Context, arg MarkKolAssistCompletedParams) error { + _, err := q.db.Exec(ctx, markKolAssistCompleted, arg.ResultJson, arg.ID, arg.TenantID) + return err +} + +const markKolAssistFailed = `-- name: MarkKolAssistFailed :exec +UPDATE kol_assist_tasks +SET status = 'failed', error_message = $1, + completed_at = NOW(), updated_at = NOW() +WHERE id = $2 + AND tenant_id = $3::bigint +` + +type MarkKolAssistFailedParams struct { + ErrorMessage pgtype.Text `json:"error_message"` + ID string `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) MarkKolAssistFailed(ctx context.Context, arg MarkKolAssistFailedParams) error { + _, err := q.db.Exec(ctx, markKolAssistFailed, arg.ErrorMessage, arg.ID, arg.TenantID) + return err +} + +const markKolAssistStarted = `-- name: MarkKolAssistStarted :exec +UPDATE kol_assist_tasks +SET status = 'running', started_at = NOW(), updated_at = NOW() +WHERE id = $1 + AND tenant_id = $2::bigint +` + +type MarkKolAssistStartedParams struct { + ID string `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) MarkKolAssistStarted(ctx context.Context, arg MarkKolAssistStartedParams) error { + _, err := q.db.Exec(ctx, markKolAssistStarted, arg.ID, arg.TenantID) + return err +} diff --git a/server/internal/tenant/repository/generated/kol_marketplace.sql.go b/server/internal/tenant/repository/generated/kol_marketplace.sql.go new file mode 100644 index 0000000..b9ef46b --- /dev/null +++ b/server/internal/tenant/repository/generated/kol_marketplace.sql.go @@ -0,0 +1,207 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: kol_marketplace.sql + +package generated + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const getPublishedKolPackage = `-- name: GetPublishedKolPackage :one +SELECT kp.id, kp.tenant_id, kp.kol_profile_id, kp.name, kp.description, + kp.cover_url, kp.industry, kp.tags, kp.price_note, kp.status, + kp.created_at, kp.updated_at, + pf.display_name AS kol_display_name, pf.avatar_url AS kol_avatar_url, pf.bio AS kol_bio +FROM kol_packages kp +JOIN kol_profiles pf ON pf.id = kp.kol_profile_id + AND pf.status = 'active' + AND pf.market_enabled = TRUE + AND pf.deleted_at IS NULL +WHERE kp.id = $1 + AND kp.status = 'published' + AND kp.deleted_at IS NULL +` + +type GetPublishedKolPackageRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + Name string `json:"name"` + Description pgtype.Text `json:"description"` + CoverUrl pgtype.Text `json:"cover_url"` + Industry pgtype.Text `json:"industry"` + Tags []byte `json:"tags"` + PriceNote pgtype.Text `json:"price_note"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + KolDisplayName string `json:"kol_display_name"` + KolAvatarUrl pgtype.Text `json:"kol_avatar_url"` + KolBio pgtype.Text `json:"kol_bio"` +} + +func (q *Queries) GetPublishedKolPackage(ctx context.Context, id int64) (GetPublishedKolPackageRow, error) { + row := q.db.QueryRow(ctx, getPublishedKolPackage, id) + var i GetPublishedKolPackageRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.KolProfileID, + &i.Name, + &i.Description, + &i.CoverUrl, + &i.Industry, + &i.Tags, + &i.PriceNote, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + &i.KolDisplayName, + &i.KolAvatarUrl, + &i.KolBio, + ) + return i, err +} + +const listPublicPromptsForPackage = `-- name: ListPublicPromptsForPackage :many +SELECT p.id, p.name, p.platform_hint, p.status, + p.published_revision_no, p.created_at +FROM kol_prompts p +WHERE p.package_id = $1 + AND p.status = 'active' + AND p.published_revision_no IS NOT NULL + AND p.deleted_at IS NULL +ORDER BY p.sort_order ASC +` + +type ListPublicPromptsForPackageRow struct { + ID int64 `json:"id"` + Name string `json:"name"` + PlatformHint pgtype.Text `json:"platform_hint"` + Status string `json:"status"` + PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +func (q *Queries) ListPublicPromptsForPackage(ctx context.Context, packageID int64) ([]ListPublicPromptsForPackageRow, error) { + rows, err := q.db.Query(ctx, listPublicPromptsForPackage, packageID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListPublicPromptsForPackageRow + for rows.Next() { + var i ListPublicPromptsForPackageRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.PlatformHint, + &i.Status, + &i.PublishedRevisionNo, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listPublishedKolPackages = `-- name: ListPublishedKolPackages :many +SELECT kp.id, kp.tenant_id, kp.kol_profile_id, kp.name, kp.description, + kp.cover_url, kp.industry, kp.tags, kp.price_note, kp.status, + kp.created_at, kp.updated_at, + pf.display_name AS kol_display_name, pf.avatar_url AS kol_avatar_url, + (SELECT COUNT(*) FROM kol_prompts p + WHERE p.package_id = kp.id AND p.status = 'active' AND p.deleted_at IS NULL) AS prompt_count, + (SELECT COUNT(*) FROM kol_subscriptions s + WHERE s.package_id = kp.id AND s.status = 'active') AS subscriber_count +FROM kol_packages kp +JOIN kol_profiles pf ON pf.id = kp.kol_profile_id + AND pf.status = 'active' + AND pf.market_enabled = TRUE + AND pf.deleted_at IS NULL +WHERE kp.status = 'published' + AND kp.deleted_at IS NULL + AND ($1::text IS NULL OR kp.industry = $1) + AND ($2::text IS NULL OR kp.name ILIKE '%' || $2 || '%') +ORDER BY kp.updated_at DESC +LIMIT $4 OFFSET $3 +` + +type ListPublishedKolPackagesParams struct { + Industry pgtype.Text `json:"industry"` + Keyword pgtype.Text `json:"keyword"` + Offset int32 `json:"offset"` + Limit int32 `json:"limit"` +} + +type ListPublishedKolPackagesRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + Name string `json:"name"` + Description pgtype.Text `json:"description"` + CoverUrl pgtype.Text `json:"cover_url"` + Industry pgtype.Text `json:"industry"` + Tags []byte `json:"tags"` + PriceNote pgtype.Text `json:"price_note"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + KolDisplayName string `json:"kol_display_name"` + KolAvatarUrl pgtype.Text `json:"kol_avatar_url"` + PromptCount int64 `json:"prompt_count"` + SubscriberCount int64 `json:"subscriber_count"` +} + +// Cross-tenant read: marketplace intentionally spans tenants. +// tenant_id appears via kol_profiles.tenant_id join only, so this file is exempt from check_tenant_scope.sh. +func (q *Queries) ListPublishedKolPackages(ctx context.Context, arg ListPublishedKolPackagesParams) ([]ListPublishedKolPackagesRow, error) { + rows, err := q.db.Query(ctx, listPublishedKolPackages, + arg.Industry, + arg.Keyword, + arg.Offset, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListPublishedKolPackagesRow + for rows.Next() { + var i ListPublishedKolPackagesRow + if err := rows.Scan( + &i.ID, + &i.TenantID, + &i.KolProfileID, + &i.Name, + &i.Description, + &i.CoverUrl, + &i.Industry, + &i.Tags, + &i.PriceNote, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + &i.KolDisplayName, + &i.KolAvatarUrl, + &i.PromptCount, + &i.SubscriberCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/server/internal/tenant/repository/generated/kol_package.sql.go b/server/internal/tenant/repository/generated/kol_package.sql.go new file mode 100644 index 0000000..84099c9 --- /dev/null +++ b/server/internal/tenant/repository/generated/kol_package.sql.go @@ -0,0 +1,297 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: kol_package.sql + +package generated + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createKolPackage = `-- name: CreateKolPackage :one +INSERT INTO kol_packages (tenant_id, kol_profile_id, name, description, cover_url, industry, tags, price_note, status) +VALUES ($1::bigint, $2, $3, + $4, $5, $6, + $7::jsonb, $8, 'draft') +RETURNING id, tenant_id, kol_profile_id, name, description, cover_url, + industry, tags, price_note, status, created_at, updated_at +` + +type CreateKolPackageParams struct { + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + Name string `json:"name"` + Description pgtype.Text `json:"description"` + CoverUrl pgtype.Text `json:"cover_url"` + Industry pgtype.Text `json:"industry"` + Tags []byte `json:"tags"` + PriceNote pgtype.Text `json:"price_note"` +} + +type CreateKolPackageRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + Name string `json:"name"` + Description pgtype.Text `json:"description"` + CoverUrl pgtype.Text `json:"cover_url"` + Industry pgtype.Text `json:"industry"` + Tags []byte `json:"tags"` + PriceNote pgtype.Text `json:"price_note"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) CreateKolPackage(ctx context.Context, arg CreateKolPackageParams) (CreateKolPackageRow, error) { + row := q.db.QueryRow(ctx, createKolPackage, + arg.TenantID, + arg.KolProfileID, + arg.Name, + arg.Description, + arg.CoverUrl, + arg.Industry, + arg.Tags, + arg.PriceNote, + ) + var i CreateKolPackageRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.KolProfileID, + &i.Name, + &i.Description, + &i.CoverUrl, + &i.Industry, + &i.Tags, + &i.PriceNote, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getKolPackageByID = `-- name: GetKolPackageByID :one +SELECT id, tenant_id, kol_profile_id, name, description, cover_url, + industry, tags, price_note, status, created_at, updated_at +FROM kol_packages +WHERE id = $1 + AND tenant_id = $2::bigint + AND deleted_at IS NULL +` + +type GetKolPackageByIDParams struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +type GetKolPackageByIDRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + Name string `json:"name"` + Description pgtype.Text `json:"description"` + CoverUrl pgtype.Text `json:"cover_url"` + Industry pgtype.Text `json:"industry"` + Tags []byte `json:"tags"` + PriceNote pgtype.Text `json:"price_note"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) GetKolPackageByID(ctx context.Context, arg GetKolPackageByIDParams) (GetKolPackageByIDRow, error) { + row := q.db.QueryRow(ctx, getKolPackageByID, arg.ID, arg.TenantID) + var i GetKolPackageByIDRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.KolProfileID, + &i.Name, + &i.Description, + &i.CoverUrl, + &i.Industry, + &i.Tags, + &i.PriceNote, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const listKolPackageIDsByProfile = `-- name: ListKolPackageIDsByProfile :many +SELECT id +FROM kol_packages +WHERE kol_profile_id = $1 + AND tenant_id = $2::bigint + AND deleted_at IS NULL +` + +type ListKolPackageIDsByProfileParams struct { + KolProfileID int64 `json:"kol_profile_id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) ListKolPackageIDsByProfile(ctx context.Context, arg ListKolPackageIDsByProfileParams) ([]int64, error) { + rows, err := q.db.Query(ctx, listKolPackageIDsByProfile, arg.KolProfileID, arg.TenantID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listKolPackagesByProfile = `-- name: ListKolPackagesByProfile :many +SELECT id, tenant_id, kol_profile_id, name, description, cover_url, + industry, tags, price_note, status, created_at, updated_at +FROM kol_packages +WHERE kol_profile_id = $1 + AND tenant_id = $2::bigint + AND deleted_at IS NULL +ORDER BY created_at DESC +` + +type ListKolPackagesByProfileParams struct { + KolProfileID int64 `json:"kol_profile_id"` + TenantID int64 `json:"tenant_id"` +} + +type ListKolPackagesByProfileRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + Name string `json:"name"` + Description pgtype.Text `json:"description"` + CoverUrl pgtype.Text `json:"cover_url"` + Industry pgtype.Text `json:"industry"` + Tags []byte `json:"tags"` + PriceNote pgtype.Text `json:"price_note"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) ListKolPackagesByProfile(ctx context.Context, arg ListKolPackagesByProfileParams) ([]ListKolPackagesByProfileRow, error) { + rows, err := q.db.Query(ctx, listKolPackagesByProfile, arg.KolProfileID, arg.TenantID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListKolPackagesByProfileRow + for rows.Next() { + var i ListKolPackagesByProfileRow + if err := rows.Scan( + &i.ID, + &i.TenantID, + &i.KolProfileID, + &i.Name, + &i.Description, + &i.CoverUrl, + &i.Industry, + &i.Tags, + &i.PriceNote, + &i.Status, + &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 softDeleteKolPackage = `-- name: SoftDeleteKolPackage :exec +UPDATE kol_packages SET deleted_at = NOW() +WHERE id = $1 + AND tenant_id = $2::bigint + AND deleted_at IS NULL +` + +type SoftDeleteKolPackageParams struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) SoftDeleteKolPackage(ctx context.Context, arg SoftDeleteKolPackageParams) error { + _, err := q.db.Exec(ctx, softDeleteKolPackage, arg.ID, arg.TenantID) + return err +} + +const updateKolPackage = `-- name: UpdateKolPackage :exec +UPDATE kol_packages +SET name = $1, + description = $2, + cover_url = $3, + industry = $4, + tags = $5::jsonb, + price_note = $6, + updated_at = NOW() +WHERE id = $7 + AND tenant_id = $8::bigint + AND deleted_at IS NULL +` + +type UpdateKolPackageParams struct { + Name string `json:"name"` + Description pgtype.Text `json:"description"` + CoverUrl pgtype.Text `json:"cover_url"` + Industry pgtype.Text `json:"industry"` + Tags []byte `json:"tags"` + PriceNote pgtype.Text `json:"price_note"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) UpdateKolPackage(ctx context.Context, arg UpdateKolPackageParams) error { + _, err := q.db.Exec(ctx, updateKolPackage, + arg.Name, + arg.Description, + arg.CoverUrl, + arg.Industry, + arg.Tags, + arg.PriceNote, + arg.ID, + arg.TenantID, + ) + return err +} + +const updateKolPackageStatus = `-- name: UpdateKolPackageStatus :exec +UPDATE kol_packages +SET status = $1, + updated_at = NOW() +WHERE id = $2 + AND tenant_id = $3::bigint + AND deleted_at IS NULL +` + +type UpdateKolPackageStatusParams struct { + Status string `json:"status"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) UpdateKolPackageStatus(ctx context.Context, arg UpdateKolPackageStatusParams) error { + _, err := q.db.Exec(ctx, updateKolPackageStatus, arg.Status, arg.ID, arg.TenantID) + return err +} diff --git a/server/internal/tenant/repository/generated/kol_profile.sql.go b/server/internal/tenant/repository/generated/kol_profile.sql.go new file mode 100644 index 0000000..4f055e5 --- /dev/null +++ b/server/internal/tenant/repository/generated/kol_profile.sql.go @@ -0,0 +1,125 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: kol_profile.sql + +package generated + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const getKolProfileByID = `-- name: GetKolProfileByID :one +SELECT id, tenant_id, user_id, display_name, bio, avatar_url, + market_enabled, status, created_at, updated_at +FROM kol_profiles +WHERE id = $1 AND deleted_at IS NULL +` + +type GetKolProfileByIDRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + DisplayName string `json:"display_name"` + Bio pgtype.Text `json:"bio"` + AvatarUrl pgtype.Text `json:"avatar_url"` + MarketEnabled bool `json:"market_enabled"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) GetKolProfileByID(ctx context.Context, id int64) (GetKolProfileByIDRow, error) { + row := q.db.QueryRow(ctx, getKolProfileByID, id) + var i GetKolProfileByIDRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.UserID, + &i.DisplayName, + &i.Bio, + &i.AvatarUrl, + &i.MarketEnabled, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getKolProfileByUser = `-- name: GetKolProfileByUser :one +SELECT id, tenant_id, user_id, display_name, bio, avatar_url, + market_enabled, status, created_at, updated_at +FROM kol_profiles +WHERE user_id = $1::bigint + AND tenant_id = $2::bigint + AND deleted_at IS NULL +` + +type GetKolProfileByUserParams struct { + UserID int64 `json:"user_id"` + TenantID int64 `json:"tenant_id"` +} + +type GetKolProfileByUserRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + DisplayName string `json:"display_name"` + Bio pgtype.Text `json:"bio"` + AvatarUrl pgtype.Text `json:"avatar_url"` + MarketEnabled bool `json:"market_enabled"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) GetKolProfileByUser(ctx context.Context, arg GetKolProfileByUserParams) (GetKolProfileByUserRow, error) { + row := q.db.QueryRow(ctx, getKolProfileByUser, arg.UserID, arg.TenantID) + var i GetKolProfileByUserRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.UserID, + &i.DisplayName, + &i.Bio, + &i.AvatarUrl, + &i.MarketEnabled, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const updateKolProfile = `-- name: UpdateKolProfile :exec +UPDATE kol_profiles +SET display_name = $1, + bio = $2, + avatar_url = $3, + updated_at = NOW() +WHERE id = $4 + AND tenant_id = $5::bigint + AND deleted_at IS NULL +` + +type UpdateKolProfileParams struct { + DisplayName string `json:"display_name"` + Bio pgtype.Text `json:"bio"` + AvatarUrl pgtype.Text `json:"avatar_url"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) UpdateKolProfile(ctx context.Context, arg UpdateKolProfileParams) error { + _, err := q.db.Exec(ctx, updateKolProfile, + arg.DisplayName, + arg.Bio, + arg.AvatarUrl, + arg.ID, + arg.TenantID, + ) + return err +} diff --git a/server/internal/tenant/repository/generated/kol_prompt.sql.go b/server/internal/tenant/repository/generated/kol_prompt.sql.go new file mode 100644 index 0000000..b79d18a --- /dev/null +++ b/server/internal/tenant/repository/generated/kol_prompt.sql.go @@ -0,0 +1,489 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: kol_prompt.sql + +package generated + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createKolPrompt = `-- name: CreateKolPrompt :one +INSERT INTO kol_prompts (tenant_id, package_id, name, platform_hint, sort_order, created_by, updated_by) +VALUES ($1::bigint, $2, $3, + $4, $5, + $6, $6) +RETURNING id, tenant_id, package_id, name, platform_hint, status, sort_order, + published_revision_no, draft_revision_no, created_by, updated_by, + created_at, updated_at +` + +type CreateKolPromptParams struct { + TenantID int64 `json:"tenant_id"` + PackageID int64 `json:"package_id"` + Name string `json:"name"` + PlatformHint pgtype.Text `json:"platform_hint"` + SortOrder int32 `json:"sort_order"` + CreatedBy int64 `json:"created_by"` +} + +type CreateKolPromptRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + PackageID int64 `json:"package_id"` + Name string `json:"name"` + PlatformHint pgtype.Text `json:"platform_hint"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"` + DraftRevisionNo pgtype.Int4 `json:"draft_revision_no"` + CreatedBy int64 `json:"created_by"` + UpdatedBy int64 `json:"updated_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) CreateKolPrompt(ctx context.Context, arg CreateKolPromptParams) (CreateKolPromptRow, error) { + row := q.db.QueryRow(ctx, createKolPrompt, + arg.TenantID, + arg.PackageID, + arg.Name, + arg.PlatformHint, + arg.SortOrder, + arg.CreatedBy, + ) + var i CreateKolPromptRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.PackageID, + &i.Name, + &i.PlatformHint, + &i.Status, + &i.SortOrder, + &i.PublishedRevisionNo, + &i.DraftRevisionNo, + &i.CreatedBy, + &i.UpdatedBy, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const createKolPromptRevision = `-- name: CreateKolPromptRevision :one +INSERT INTO kol_prompt_revisions (tenant_id, prompt_id, revision_no, prompt_asset_key, + schema_json, card_config_json, created_by) +VALUES ($1::bigint, $2, $3, + $4, $5::jsonb, + $6::jsonb, $7) +RETURNING id, prompt_id, revision_no, prompt_asset_key, schema_json, + card_config_json, created_by, created_at +` + +type CreateKolPromptRevisionParams struct { + TenantID int64 `json:"tenant_id"` + PromptID int64 `json:"prompt_id"` + RevisionNo int32 `json:"revision_no"` + PromptAssetKey string `json:"prompt_asset_key"` + SchemaJson []byte `json:"schema_json"` + CardConfigJson []byte `json:"card_config_json"` + CreatedBy int64 `json:"created_by"` +} + +type CreateKolPromptRevisionRow struct { + ID int64 `json:"id"` + PromptID int64 `json:"prompt_id"` + RevisionNo int32 `json:"revision_no"` + PromptAssetKey string `json:"prompt_asset_key"` + SchemaJson []byte `json:"schema_json"` + CardConfigJson []byte `json:"card_config_json"` + CreatedBy int64 `json:"created_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +func (q *Queries) CreateKolPromptRevision(ctx context.Context, arg CreateKolPromptRevisionParams) (CreateKolPromptRevisionRow, error) { + row := q.db.QueryRow(ctx, createKolPromptRevision, + arg.TenantID, + arg.PromptID, + arg.RevisionNo, + arg.PromptAssetKey, + arg.SchemaJson, + arg.CardConfigJson, + arg.CreatedBy, + ) + var i CreateKolPromptRevisionRow + err := row.Scan( + &i.ID, + &i.PromptID, + &i.RevisionNo, + &i.PromptAssetKey, + &i.SchemaJson, + &i.CardConfigJson, + &i.CreatedBy, + &i.CreatedAt, + ) + return i, err +} + +const getKolPromptByID = `-- name: GetKolPromptByID :one +SELECT id, tenant_id, package_id, name, platform_hint, status, sort_order, + published_revision_no, draft_revision_no, created_by, updated_by, + created_at, updated_at +FROM kol_prompts +WHERE id = $1 + AND tenant_id = $2::bigint + AND deleted_at IS NULL +` + +type GetKolPromptByIDParams struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +type GetKolPromptByIDRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + PackageID int64 `json:"package_id"` + Name string `json:"name"` + PlatformHint pgtype.Text `json:"platform_hint"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"` + DraftRevisionNo pgtype.Int4 `json:"draft_revision_no"` + CreatedBy int64 `json:"created_by"` + UpdatedBy int64 `json:"updated_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) GetKolPromptByID(ctx context.Context, arg GetKolPromptByIDParams) (GetKolPromptByIDRow, error) { + row := q.db.QueryRow(ctx, getKolPromptByID, arg.ID, arg.TenantID) + var i GetKolPromptByIDRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.PackageID, + &i.Name, + &i.PlatformHint, + &i.Status, + &i.SortOrder, + &i.PublishedRevisionNo, + &i.DraftRevisionNo, + &i.CreatedBy, + &i.UpdatedBy, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getKolPromptRevision = `-- name: GetKolPromptRevision :one +SELECT id, prompt_id, revision_no, prompt_asset_key, schema_json, + card_config_json, created_by, created_at +FROM kol_prompt_revisions +WHERE prompt_id = $1 + AND revision_no = $2 + AND tenant_id = $3::bigint +` + +type GetKolPromptRevisionParams struct { + PromptID int64 `json:"prompt_id"` + RevisionNo int32 `json:"revision_no"` + TenantID int64 `json:"tenant_id"` +} + +type GetKolPromptRevisionRow struct { + ID int64 `json:"id"` + PromptID int64 `json:"prompt_id"` + RevisionNo int32 `json:"revision_no"` + PromptAssetKey string `json:"prompt_asset_key"` + SchemaJson []byte `json:"schema_json"` + CardConfigJson []byte `json:"card_config_json"` + CreatedBy int64 `json:"created_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +func (q *Queries) GetKolPromptRevision(ctx context.Context, arg GetKolPromptRevisionParams) (GetKolPromptRevisionRow, error) { + row := q.db.QueryRow(ctx, getKolPromptRevision, arg.PromptID, arg.RevisionNo, arg.TenantID) + var i GetKolPromptRevisionRow + err := row.Scan( + &i.ID, + &i.PromptID, + &i.RevisionNo, + &i.PromptAssetKey, + &i.SchemaJson, + &i.CardConfigJson, + &i.CreatedBy, + &i.CreatedAt, + ) + return i, err +} + +const listActiveKolPromptsByPackage = `-- name: ListActiveKolPromptsByPackage :many +SELECT id, tenant_id, package_id, published_revision_no +FROM kol_prompts +WHERE package_id = $1 + AND tenant_id = $2::bigint + AND status = 'active' + AND published_revision_no IS NOT NULL + AND deleted_at IS NULL +ORDER BY sort_order ASC +` + +type ListActiveKolPromptsByPackageParams struct { + PackageID int64 `json:"package_id"` + TenantID int64 `json:"tenant_id"` +} + +type ListActiveKolPromptsByPackageRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + PackageID int64 `json:"package_id"` + PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"` +} + +func (q *Queries) ListActiveKolPromptsByPackage(ctx context.Context, arg ListActiveKolPromptsByPackageParams) ([]ListActiveKolPromptsByPackageRow, error) { + rows, err := q.db.Query(ctx, listActiveKolPromptsByPackage, arg.PackageID, arg.TenantID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListActiveKolPromptsByPackageRow + for rows.Next() { + var i ListActiveKolPromptsByPackageRow + if err := rows.Scan( + &i.ID, + &i.TenantID, + &i.PackageID, + &i.PublishedRevisionNo, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listKolPromptsByPackage = `-- name: ListKolPromptsByPackage :many +SELECT id, tenant_id, package_id, name, platform_hint, status, sort_order, + published_revision_no, draft_revision_no, created_by, updated_by, + created_at, updated_at +FROM kol_prompts +WHERE package_id = $1 + AND tenant_id = $2::bigint + AND deleted_at IS NULL +ORDER BY sort_order ASC, created_at ASC +` + +type ListKolPromptsByPackageParams struct { + PackageID int64 `json:"package_id"` + TenantID int64 `json:"tenant_id"` +} + +type ListKolPromptsByPackageRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + PackageID int64 `json:"package_id"` + Name string `json:"name"` + PlatformHint pgtype.Text `json:"platform_hint"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"` + DraftRevisionNo pgtype.Int4 `json:"draft_revision_no"` + CreatedBy int64 `json:"created_by"` + UpdatedBy int64 `json:"updated_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +func (q *Queries) ListKolPromptsByPackage(ctx context.Context, arg ListKolPromptsByPackageParams) ([]ListKolPromptsByPackageRow, error) { + rows, err := q.db.Query(ctx, listKolPromptsByPackage, arg.PackageID, arg.TenantID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListKolPromptsByPackageRow + for rows.Next() { + var i ListKolPromptsByPackageRow + if err := rows.Scan( + &i.ID, + &i.TenantID, + &i.PackageID, + &i.Name, + &i.PlatformHint, + &i.Status, + &i.SortOrder, + &i.PublishedRevisionNo, + &i.DraftRevisionNo, + &i.CreatedBy, + &i.UpdatedBy, + &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 nextKolPromptRevisionNo = `-- name: NextKolPromptRevisionNo :one +SELECT COALESCE(MAX(revision_no), 0) + 1 AS next_revision_no +FROM kol_prompt_revisions +WHERE prompt_id = $1 + AND tenant_id = $2::bigint +` + +type NextKolPromptRevisionNoParams struct { + PromptID int64 `json:"prompt_id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) NextKolPromptRevisionNo(ctx context.Context, arg NextKolPromptRevisionNoParams) (int32, error) { + row := q.db.QueryRow(ctx, nextKolPromptRevisionNo, arg.PromptID, arg.TenantID) + var next_revision_no int32 + err := row.Scan(&next_revision_no) + return next_revision_no, err +} + +const softDeleteKolPrompt = `-- name: SoftDeleteKolPrompt :exec +UPDATE kol_prompts SET deleted_at = NOW() +WHERE id = $1 + AND tenant_id = $2::bigint + AND deleted_at IS NULL +` + +type SoftDeleteKolPromptParams struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) SoftDeleteKolPrompt(ctx context.Context, arg SoftDeleteKolPromptParams) error { + _, err := q.db.Exec(ctx, softDeleteKolPrompt, arg.ID, arg.TenantID) + return err +} + +const updateKolPromptDraftRevision = `-- name: UpdateKolPromptDraftRevision :exec +UPDATE kol_prompts +SET draft_revision_no = $1, + updated_by = $2, + updated_at = NOW() +WHERE id = $3 + AND tenant_id = $4::bigint + AND deleted_at IS NULL +` + +type UpdateKolPromptDraftRevisionParams struct { + RevisionNo pgtype.Int4 `json:"revision_no"` + UpdatedBy int64 `json:"updated_by"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) UpdateKolPromptDraftRevision(ctx context.Context, arg UpdateKolPromptDraftRevisionParams) error { + _, err := q.db.Exec(ctx, updateKolPromptDraftRevision, + arg.RevisionNo, + arg.UpdatedBy, + arg.ID, + arg.TenantID, + ) + return err +} + +const updateKolPromptMetadata = `-- name: UpdateKolPromptMetadata :exec +UPDATE kol_prompts +SET name = $1, + platform_hint = $2, + sort_order = $3, + updated_by = $4, + updated_at = NOW() +WHERE id = $5 + AND tenant_id = $6::bigint + AND deleted_at IS NULL +` + +type UpdateKolPromptMetadataParams struct { + Name string `json:"name"` + PlatformHint pgtype.Text `json:"platform_hint"` + SortOrder int32 `json:"sort_order"` + UpdatedBy int64 `json:"updated_by"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) UpdateKolPromptMetadata(ctx context.Context, arg UpdateKolPromptMetadataParams) error { + _, err := q.db.Exec(ctx, updateKolPromptMetadata, + arg.Name, + arg.PlatformHint, + arg.SortOrder, + arg.UpdatedBy, + arg.ID, + arg.TenantID, + ) + return err +} + +const updateKolPromptPublishedRevision = `-- name: UpdateKolPromptPublishedRevision :exec +UPDATE kol_prompts +SET published_revision_no = $1, + status = 'active', + updated_by = $2, + updated_at = NOW() +WHERE id = $3 + AND tenant_id = $4::bigint + AND deleted_at IS NULL +` + +type UpdateKolPromptPublishedRevisionParams struct { + RevisionNo pgtype.Int4 `json:"revision_no"` + UpdatedBy int64 `json:"updated_by"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) UpdateKolPromptPublishedRevision(ctx context.Context, arg UpdateKolPromptPublishedRevisionParams) error { + _, err := q.db.Exec(ctx, updateKolPromptPublishedRevision, + arg.RevisionNo, + arg.UpdatedBy, + arg.ID, + arg.TenantID, + ) + return err +} + +const updateKolPromptStatus = `-- name: UpdateKolPromptStatus :exec +UPDATE kol_prompts +SET status = $1, + updated_by = $2, + updated_at = NOW() +WHERE id = $3 + AND tenant_id = $4::bigint + AND deleted_at IS NULL +` + +type UpdateKolPromptStatusParams struct { + Status string `json:"status"` + UpdatedBy int64 `json:"updated_by"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) UpdateKolPromptStatus(ctx context.Context, arg UpdateKolPromptStatusParams) error { + _, err := q.db.Exec(ctx, updateKolPromptStatus, + arg.Status, + arg.UpdatedBy, + arg.ID, + arg.TenantID, + ) + return err +} diff --git a/server/internal/tenant/repository/generated/kol_subscription.sql.go b/server/internal/tenant/repository/generated/kol_subscription.sql.go new file mode 100644 index 0000000..fc951aa --- /dev/null +++ b/server/internal/tenant/repository/generated/kol_subscription.sql.go @@ -0,0 +1,401 @@ +// 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 +` + +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 tenant_id IS NOT NULL + AND status = 'active' +` + +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 +FROM kol_subscription_prompts sp +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.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"` +} + +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, + ); 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 +} diff --git a/server/internal/tenant/repository/generated/kol_usage.sql.go b/server/internal/tenant/repository/generated/kol_usage.sql.go new file mode 100644 index 0000000..850eb4d --- /dev/null +++ b/server/internal/tenant/repository/generated/kol_usage.sql.go @@ -0,0 +1,306 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: kol_usage.sql + +package generated + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createKolUsageLog = `-- name: CreateKolUsageLog :one +INSERT INTO kol_usage_logs (tenant_id, subscription_id, subscription_prompt_id, + package_id, prompt_id, prompt_revision_no, + generation_task_id, status, variables_snapshot, + schema_snapshot, rendered_prompt_hash) +VALUES ($1::bigint, $2, + $3, $4, + $5, $6, + $7, 'pending', + $8::jsonb, $9::jsonb, + $10) +RETURNING id, tenant_id, generation_task_id, status, created_at +` + +type CreateKolUsageLogParams struct { + TenantID int64 `json:"tenant_id"` + SubscriptionID pgtype.Int8 `json:"subscription_id"` + SubscriptionPromptID pgtype.Int8 `json:"subscription_prompt_id"` + PackageID int64 `json:"package_id"` + PromptID int64 `json:"prompt_id"` + PromptRevisionNo int32 `json:"prompt_revision_no"` + GenerationTaskID pgtype.Int8 `json:"generation_task_id"` + VariablesSnapshot []byte `json:"variables_snapshot"` + SchemaSnapshot []byte `json:"schema_snapshot"` + RenderedPromptHash pgtype.Text `json:"rendered_prompt_hash"` +} + +type CreateKolUsageLogRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + GenerationTaskID pgtype.Int8 `json:"generation_task_id"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +func (q *Queries) CreateKolUsageLog(ctx context.Context, arg CreateKolUsageLogParams) (CreateKolUsageLogRow, error) { + row := q.db.QueryRow(ctx, createKolUsageLog, + arg.TenantID, + arg.SubscriptionID, + arg.SubscriptionPromptID, + arg.PackageID, + arg.PromptID, + arg.PromptRevisionNo, + arg.GenerationTaskID, + arg.VariablesSnapshot, + arg.SchemaSnapshot, + arg.RenderedPromptHash, + ) + var i CreateKolUsageLogRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.GenerationTaskID, + &i.Status, + &i.CreatedAt, + ) + return i, err +} + +const getKolUsageByGenerationTask = `-- name: GetKolUsageByGenerationTask :one +SELECT id, tenant_id, package_id, prompt_id, status, created_at +FROM kol_usage_logs +WHERE generation_task_id = $1 + AND tenant_id = $2::bigint +` + +type GetKolUsageByGenerationTaskParams struct { + GenerationTaskID pgtype.Int8 `json:"generation_task_id"` + TenantID int64 `json:"tenant_id"` +} + +type GetKolUsageByGenerationTaskRow struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + PackageID int64 `json:"package_id"` + PromptID int64 `json:"prompt_id"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +func (q *Queries) GetKolUsageByGenerationTask(ctx context.Context, arg GetKolUsageByGenerationTaskParams) (GetKolUsageByGenerationTaskRow, error) { + row := q.db.QueryRow(ctx, getKolUsageByGenerationTask, arg.GenerationTaskID, arg.TenantID) + var i GetKolUsageByGenerationTaskRow + err := row.Scan( + &i.ID, + &i.TenantID, + &i.PackageID, + &i.PromptID, + &i.Status, + &i.CreatedAt, + ) + return i, err +} + +const kolDashboardOverview = `-- name: KolDashboardOverview :one +SELECT + (SELECT COUNT(DISTINCT s.tenant_id) FROM kol_subscriptions s + WHERE s.package_id = ANY($1::bigint[]) AND s.status='active' + AND (s.end_at IS NULL OR s.end_at > NOW())) AS total_subs, + (SELECT COUNT(*) FROM kol_subscriptions s + WHERE s.package_id = ANY($1::bigint[]) + AND s.status='active' + AND s.created_at >= date_trunc('month', NOW())) AS month_subs, + (SELECT COUNT(*) FROM kol_usage_logs u + WHERE u.package_id = ANY($1::bigint[]) + AND u.tenant_id IS NOT NULL) AS total_usage, + (SELECT COUNT(*) FROM kol_usage_logs u + WHERE u.package_id = ANY($1::bigint[]) + AND u.tenant_id IS NOT NULL + AND u.created_at >= date_trunc('month', NOW())) AS month_usage, + (SELECT COUNT(*) FROM kol_usage_logs u + WHERE u.package_id = ANY($1::bigint[]) + AND u.tenant_id IS NOT NULL + AND u.status='failed') AS failure_count +` + +type KolDashboardOverviewRow struct { + TotalSubs int64 `json:"total_subs"` + MonthSubs int64 `json:"month_subs"` + TotalUsage int64 `json:"total_usage"` + MonthUsage int64 `json:"month_usage"` + FailureCount int64 `json:"failure_count"` +} + +func (q *Queries) KolDashboardOverview(ctx context.Context, packageIds []int64) (KolDashboardOverviewRow, error) { + row := q.db.QueryRow(ctx, kolDashboardOverview, packageIds) + var i KolDashboardOverviewRow + err := row.Scan( + &i.TotalSubs, + &i.MonthSubs, + &i.TotalUsage, + &i.MonthUsage, + &i.FailureCount, + ) + return i, err +} + +const kolDashboardTrend = `-- name: KolDashboardTrend :many +SELECT date_trunc('day', u.created_at)::date AS d, + COUNT(*) FILTER (WHERE u.status='completed') AS usages +FROM kol_usage_logs u +WHERE u.package_id = ANY($1::bigint[]) + AND u.tenant_id IS NOT NULL + AND u.created_at >= NOW() - $2::int * INTERVAL '1 day' +GROUP BY d +ORDER BY d ASC +` + +type KolDashboardTrendParams struct { + PackageIds []int64 `json:"package_ids"` + PeriodDays int32 `json:"period_days"` +} + +type KolDashboardTrendRow struct { + D pgtype.Date `json:"d"` + Usages int64 `json:"usages"` +} + +func (q *Queries) KolDashboardTrend(ctx context.Context, arg KolDashboardTrendParams) ([]KolDashboardTrendRow, error) { + rows, err := q.db.Query(ctx, kolDashboardTrend, arg.PackageIds, arg.PeriodDays) + if err != nil { + return nil, err + } + defer rows.Close() + var items []KolDashboardTrendRow + for rows.Next() { + var i KolDashboardTrendRow + if err := rows.Scan(&i.D, &i.Usages); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const kolUsageCountByPackage = `-- name: KolUsageCountByPackage :many +SELECT package_id, COUNT(*) AS total, + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed, + SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed +FROM kol_usage_logs +WHERE package_id = ANY($1::bigint[]) + AND tenant_id IS NOT NULL +GROUP BY package_id +` + +type KolUsageCountByPackageRow struct { + PackageID int64 `json:"package_id"` + Total int64 `json:"total"` + Completed int64 `json:"completed"` + Failed int64 `json:"failed"` +} + +func (q *Queries) KolUsageCountByPackage(ctx context.Context, packageIds []int64) ([]KolUsageCountByPackageRow, error) { + rows, err := q.db.Query(ctx, kolUsageCountByPackage, packageIds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []KolUsageCountByPackageRow + for rows.Next() { + var i KolUsageCountByPackageRow + if err := rows.Scan( + &i.PackageID, + &i.Total, + &i.Completed, + &i.Failed, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const markKolUsageCompleted = `-- name: MarkKolUsageCompleted :exec +UPDATE kol_usage_logs +SET status = 'completed', article_id = $1, + finished_at = NOW() +WHERE id = $2 + AND tenant_id = $3::bigint +` + +type MarkKolUsageCompletedParams struct { + ArticleID pgtype.Int8 `json:"article_id"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) MarkKolUsageCompleted(ctx context.Context, arg MarkKolUsageCompletedParams) error { + _, err := q.db.Exec(ctx, markKolUsageCompleted, arg.ArticleID, arg.ID, arg.TenantID) + return err +} + +const markKolUsageCompletedByTask = `-- name: MarkKolUsageCompletedByTask :exec +UPDATE kol_usage_logs +SET status = 'completed', article_id = $1, finished_at = NOW() +WHERE generation_task_id = $2 + AND tenant_id = $3::bigint +` + +type MarkKolUsageCompletedByTaskParams struct { + ArticleID pgtype.Int8 `json:"article_id"` + GenerationTaskID pgtype.Int8 `json:"generation_task_id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) MarkKolUsageCompletedByTask(ctx context.Context, arg MarkKolUsageCompletedByTaskParams) error { + _, err := q.db.Exec(ctx, markKolUsageCompletedByTask, arg.ArticleID, arg.GenerationTaskID, arg.TenantID) + return err +} + +const markKolUsageFailed = `-- name: MarkKolUsageFailed :exec +UPDATE kol_usage_logs +SET status = 'failed', error_message = $1, + finished_at = NOW() +WHERE id = $2 + AND tenant_id = $3::bigint +` + +type MarkKolUsageFailedParams struct { + ErrorMessage pgtype.Text `json:"error_message"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) MarkKolUsageFailed(ctx context.Context, arg MarkKolUsageFailedParams) error { + _, err := q.db.Exec(ctx, markKolUsageFailed, arg.ErrorMessage, arg.ID, arg.TenantID) + return err +} + +const markKolUsageFailedByTask = `-- name: MarkKolUsageFailedByTask :exec +UPDATE kol_usage_logs +SET status = 'failed', error_message = $1, finished_at = NOW() +WHERE generation_task_id = $2 + AND tenant_id = $3::bigint +` + +type MarkKolUsageFailedByTaskParams struct { + ErrorMessage pgtype.Text `json:"error_message"` + GenerationTaskID pgtype.Int8 `json:"generation_task_id"` + TenantID int64 `json:"tenant_id"` +} + +func (q *Queries) MarkKolUsageFailedByTask(ctx context.Context, arg MarkKolUsageFailedByTaskParams) error { + _, err := q.db.Exec(ctx, markKolUsageFailedByTask, arg.ErrorMessage, arg.GenerationTaskID, arg.TenantID) + return err +} diff --git a/server/internal/tenant/repository/generated/models.go b/server/internal/tenant/repository/generated/models.go index 4c4875f..1646c27 100644 --- a/server/internal/tenant/repository/generated/models.go +++ b/server/internal/tenant/repository/generated/models.go @@ -21,6 +21,7 @@ type Article struct { UpdatedAt pgtype.Timestamptz `json:"updated_at"` DeletedAt pgtype.Timestamptz `json:"deleted_at"` WizardStateJson []byte `json:"wizard_state_json"` + KolPromptID pgtype.Int8 `json:"kol_prompt_id"` } type ArticleTemplate struct { @@ -137,17 +138,18 @@ type GenerationTask struct { } type ImageAsset struct { - ID int64 `json:"id"` - TenantID int64 `json:"tenant_id"` - FolderID pgtype.Int8 `json:"folder_id"` - ObjectKey string `json:"object_key"` - Name string `json:"name"` - SizeBytes int64 `json:"size_bytes"` - Status string `json:"status"` - CreatedBy int64 `json:"created_by"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - DeletedAt pgtype.Timestamptz `json:"deleted_at"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + FolderID pgtype.Int8 `json:"folder_id"` + ObjectKey string `json:"object_key"` + Name string `json:"name"` + SizeBytes int64 `json:"size_bytes"` + Status string `json:"status"` + CreatedBy int64 `json:"created_by"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + DeletedAt pgtype.Timestamptz `json:"deleted_at"` + ContentHash pgtype.Text `json:"content_hash"` } type ImageAssetReference struct { @@ -224,6 +226,126 @@ type KnowledgeParseTask struct { UpdatedAt pgtype.Timestamptz `json:"updated_at"` } +type KolAssistTask struct { + ID string `json:"id"` + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + PromptID pgtype.Int8 `json:"prompt_id"` + OperatorID int64 `json:"operator_id"` + TaskType string `json:"task_type"` + Status string `json:"status"` + RequestJson []byte `json:"request_json"` + ResultJson []byte `json:"result_json"` + ErrorMessage pgtype.Text `json:"error_message"` + StartedAt pgtype.Timestamptz `json:"started_at"` + CompletedAt pgtype.Timestamptz `json:"completed_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type KolPackage struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + KolProfileID int64 `json:"kol_profile_id"` + Name string `json:"name"` + Description pgtype.Text `json:"description"` + CoverUrl pgtype.Text `json:"cover_url"` + Industry pgtype.Text `json:"industry"` + Tags []byte `json:"tags"` + PriceNote pgtype.Text `json:"price_note"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + DeletedAt pgtype.Timestamptz `json:"deleted_at"` +} + +type KolProfile struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + UserID int64 `json:"user_id"` + DisplayName string `json:"display_name"` + Bio pgtype.Text `json:"bio"` + AvatarUrl pgtype.Text `json:"avatar_url"` + MarketEnabled bool `json:"market_enabled"` + Status string `json:"status"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + DeletedAt pgtype.Timestamptz `json:"deleted_at"` +} + +type KolPrompt struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + PackageID int64 `json:"package_id"` + Name string `json:"name"` + PlatformHint pgtype.Text `json:"platform_hint"` + Status string `json:"status"` + SortOrder int32 `json:"sort_order"` + PublishedRevisionNo pgtype.Int4 `json:"published_revision_no"` + DraftRevisionNo pgtype.Int4 `json:"draft_revision_no"` + CreatedBy int64 `json:"created_by"` + UpdatedBy int64 `json:"updated_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` + DeletedAt pgtype.Timestamptz `json:"deleted_at"` +} + +type KolPromptRevision struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + PromptID int64 `json:"prompt_id"` + RevisionNo int32 `json:"revision_no"` + PromptAssetKey string `json:"prompt_asset_key"` + SchemaJson []byte `json:"schema_json"` + CardConfigJson []byte `json:"card_config_json"` + CreatedBy int64 `json:"created_by"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + +type KolSubscription struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + PackageID int64 `json:"package_id"` + Status string `json:"status"` + ApprovedBy pgtype.Int8 `json:"approved_by"` + StartAt pgtype.Timestamptz `json:"start_at"` + EndAt pgtype.Timestamptz `json:"end_at"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type KolSubscriptionPrompt 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"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` +} + +type KolUsageLog struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + SubscriptionID pgtype.Int8 `json:"subscription_id"` + SubscriptionPromptID pgtype.Int8 `json:"subscription_prompt_id"` + PackageID int64 `json:"package_id"` + PromptID int64 `json:"prompt_id"` + PromptRevisionNo int32 `json:"prompt_revision_no"` + ArticleID pgtype.Int8 `json:"article_id"` + GenerationTaskID pgtype.Int8 `json:"generation_task_id"` + Status string `json:"status"` + VariablesSnapshot []byte `json:"variables_snapshot"` + SchemaSnapshot []byte `json:"schema_snapshot"` + RenderedPromptHash pgtype.Text `json:"rendered_prompt_hash"` + ErrorMessage pgtype.Text `json:"error_message"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + FinishedAt pgtype.Timestamptz `json:"finished_at"` +} + type MediaPlatform struct { ID int64 `json:"id"` PlatformID string `json:"platform_id"` diff --git a/server/internal/tenant/repository/generated/querier.go b/server/internal/tenant/repository/generated/querier.go index c7150e1..b8f9adc 100644 --- a/server/internal/tenant/repository/generated/querier.go +++ b/server/internal/tenant/repository/generated/querier.go @@ -9,6 +9,7 @@ import ( ) type Querier interface { + ApproveKolSubscription(ctx context.Context, arg ApproveKolSubscriptionParams) error ConfirmReservation(ctx context.Context, arg ConfirmReservationParams) error CountActiveImagesInFolder(ctx context.Context, arg CountActiveImagesInFolderParams) (int32, error) CountArticles(ctx context.Context, arg CountArticlesParams) (int64, error) @@ -29,15 +30,23 @@ type Querier interface { CreateGenerationTask(ctx context.Context, arg CreateGenerationTaskParams) (int64, error) CreateImageFolder(ctx context.Context, arg CreateImageFolderParams) (int64, error) CreateKeyword(ctx context.Context, arg CreateKeywordParams) (CreateKeywordRow, error) + CreateKolAssistTask(ctx context.Context, arg CreateKolAssistTaskParams) error + CreateKolPackage(ctx context.Context, arg CreateKolPackageParams) (CreateKolPackageRow, error) + CreateKolPrompt(ctx context.Context, arg CreateKolPromptParams) (CreateKolPromptRow, error) + CreateKolPromptRevision(ctx context.Context, arg CreateKolPromptRevisionParams) (CreateKolPromptRevisionRow, error) + CreateKolSubscription(ctx context.Context, arg CreateKolSubscriptionParams) (KolSubscription, error) + CreateKolUsageLog(ctx context.Context, arg CreateKolUsageLogParams) (CreateKolUsageLogRow, error) CreatePromptRule(ctx context.Context, arg CreatePromptRuleParams) (CreatePromptRuleRow, error) CreatePromptRuleGroup(ctx context.Context, arg CreatePromptRuleGroupParams) (CreatePromptRuleGroupRow, error) CreateQuestion(ctx context.Context, arg CreateQuestionParams) (int64, error) CreateQuotaReservation(ctx context.Context, arg CreateQuotaReservationParams) (int64, error) CreateScheduleTask(ctx context.Context, arg CreateScheduleTaskParams) (CreateScheduleTaskRow, error) + CreateSubscriptionPrompt(ctx context.Context, arg CreateSubscriptionPromptParams) error DeleteImageFolder(ctx context.Context, arg DeleteImageFolderParams) error DeleteImageReferencesByArticle(ctx context.Context, arg DeleteImageReferencesByArticleParams) error DeleteImageReferencesByArticleScope(ctx context.Context, arg DeleteImageReferencesByArticleScopeParams) error EnsureImageStorageUsageRow(ctx context.Context, tenantID int64) error + GetActiveKolSubscription(ctx context.Context, arg GetActiveKolSubscriptionParams) (KolSubscription, error) GetActivePlanForTenant(ctx context.Context, tenantID int64) (GetActivePlanForTenantRow, error) GetArticleByID(ctx context.Context, arg GetArticleByIDParams) (GetArticleByIDRow, error) GetArticleVersions(ctx context.Context, articleID int64) ([]GetArticleVersionsRow, error) @@ -46,10 +55,20 @@ type Querier interface { GetImageAsset(ctx context.Context, arg GetImageAssetParams) (GetImageAssetRow, error) GetImageFolder(ctx context.Context, arg GetImageFolderParams) (GetImageFolderRow, error) GetImageStorageUsage(ctx context.Context, tenantID int64) (GetImageStorageUsageRow, error) + GetKolAssistTask(ctx context.Context, arg GetKolAssistTaskParams) (KolAssistTask, error) + GetKolPackageByID(ctx context.Context, arg GetKolPackageByIDParams) (GetKolPackageByIDRow, error) + GetKolProfileByID(ctx context.Context, id int64) (GetKolProfileByIDRow, error) + GetKolProfileByUser(ctx context.Context, arg GetKolProfileByUserParams) (GetKolProfileByUserRow, error) + GetKolPromptByID(ctx context.Context, arg GetKolPromptByIDParams) (GetKolPromptByIDRow, error) + GetKolPromptRevision(ctx context.Context, arg GetKolPromptRevisionParams) (GetKolPromptRevisionRow, error) + GetKolSubscriptionByID(ctx context.Context, arg GetKolSubscriptionByIDParams) (KolSubscription, error) + GetKolUsageByGenerationTask(ctx context.Context, arg GetKolUsageByGenerationTaskParams) (GetKolUsageByGenerationTaskRow, error) GetPromptRuleByID(ctx context.Context, arg GetPromptRuleByIDParams) (GetPromptRuleByIDRow, error) + GetPublishedKolPackage(ctx context.Context, id int64) (GetPublishedKolPackageRow, error) GetQuotaSummary(ctx context.Context, tenantID int64) (int32, error) GetRecentArticles(ctx context.Context, tenantID int64) ([]GetRecentArticlesRow, error) GetScheduleTaskByID(ctx context.Context, arg GetScheduleTaskByIDParams) (GetScheduleTaskByIDRow, error) + GetSubscriptionPromptByID(ctx context.Context, arg GetSubscriptionPromptByIDParams) (GetSubscriptionPromptByIDRow, error) GetTemplateByID(ctx context.Context, arg GetTemplateByIDParams) (GetTemplateByIDRow, error) GetTenantMembership(ctx context.Context, userID int64) (GetTenantMembershipRow, error) GetTenantMembershipByTenantAndUser(ctx context.Context, arg GetTenantMembershipByTenantAndUserParams) (GetTenantMembershipByTenantAndUserRow, error) @@ -57,7 +76,12 @@ type Querier interface { GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, error) InsertImageAsset(ctx context.Context, arg InsertImageAssetParams) (int64, error) InsertQuotaLedger(ctx context.Context, arg InsertQuotaLedgerParams) (int64, error) + KolDashboardOverview(ctx context.Context, packageIds []int64) (KolDashboardOverviewRow, error) + KolDashboardTrend(ctx context.Context, arg KolDashboardTrendParams) ([]KolDashboardTrendRow, error) + KolUsageCountByPackage(ctx context.Context, packageIds []int64) ([]KolUsageCountByPackageRow, error) ListActiveImagesByFolder(ctx context.Context, arg ListActiveImagesByFolderParams) ([]ListActiveImagesByFolderRow, error) + ListActiveKolPromptsByPackage(ctx context.Context, arg ListActiveKolPromptsByPackageParams) ([]ListActiveKolPromptsByPackageRow, error) + ListActiveSubscribersForPackage(ctx context.Context, packageID int64) ([]KolSubscription, error) ListAllPromptRulesSimple(ctx context.Context, tenantID int64) ([]ListAllPromptRulesSimpleRow, error) ListArticles(ctx context.Context, arg ListArticlesParams) ([]ListArticlesRow, error) ListBrands(ctx context.Context, tenantID int64) ([]ListBrandsRow, error) @@ -67,6 +91,10 @@ type Querier interface { ListImageReferenceArticles(ctx context.Context, arg ListImageReferenceArticlesParams) ([]ListImageReferenceArticlesRow, error) ListImageReferences(ctx context.Context, arg ListImageReferencesParams) ([]ListImageReferencesRow, error) ListKeywords(ctx context.Context, arg ListKeywordsParams) ([]ListKeywordsRow, error) + ListKolPackageIDsByProfile(ctx context.Context, arg ListKolPackageIDsByProfileParams) ([]int64, error) + ListKolPackagesByProfile(ctx context.Context, arg ListKolPackagesByProfileParams) ([]ListKolPackagesByProfileRow, error) + ListKolPromptsByPackage(ctx context.Context, arg ListKolPromptsByPackageParams) ([]ListKolPromptsByPackageRow, error) + ListKolSubscriptions(ctx context.Context, tenantID int64) ([]KolSubscription, error) // ============================================================ // Prompt Rule Groups // ============================================================ @@ -76,18 +104,36 @@ type Querier interface { // ============================================================ ListPromptRules(ctx context.Context, arg ListPromptRulesParams) ([]ListPromptRulesRow, error) ListPromptRulesUngrouped(ctx context.Context, arg ListPromptRulesUngroupedParams) ([]ListPromptRulesUngroupedRow, error) + ListPublicPromptsForPackage(ctx context.Context, packageID int64) ([]ListPublicPromptsForPackageRow, error) + // Cross-tenant read: marketplace intentionally spans tenants. + // tenant_id appears via kol_profiles.tenant_id join only, so this file is exempt from check_tenant_scope.sh. + ListPublishedKolPackages(ctx context.Context, arg ListPublishedKolPackagesParams) ([]ListPublishedKolPackagesRow, error) ListQuestions(ctx context.Context, arg ListQuestionsParams) ([]ListQuestionsRow, error) ListScheduleTasks(ctx context.Context, arg ListScheduleTasksParams) ([]ListScheduleTasksRow, error) + ListSubscriptionPromptsByTenant(ctx context.Context, tenantID int64) ([]ListSubscriptionPromptsByTenantRow, error) ListTemplates(ctx context.Context, tenantID int64) ([]ListTemplatesRow, error) MarkImagesInFolderPendingDelete(ctx context.Context, arg MarkImagesInFolderPendingDeleteParams) error + MarkKolAssistCompleted(ctx context.Context, arg MarkKolAssistCompletedParams) error + MarkKolAssistFailed(ctx context.Context, arg MarkKolAssistFailedParams) error + MarkKolAssistStarted(ctx context.Context, arg MarkKolAssistStartedParams) error + MarkKolUsageCompleted(ctx context.Context, arg MarkKolUsageCompletedParams) error + MarkKolUsageCompletedByTask(ctx context.Context, arg MarkKolUsageCompletedByTaskParams) error + MarkKolUsageFailed(ctx context.Context, arg MarkKolUsageFailedParams) error + MarkKolUsageFailedByTask(ctx context.Context, arg MarkKolUsageFailedByTaskParams) error + NextKolPromptRevisionNo(ctx context.Context, arg NextKolPromptRevisionNoParams) (int32, error) RefundReservation(ctx context.Context, arg RefundReservationParams) error ReleaseImageStorageQuota(ctx context.Context, arg ReleaseImageStorageQuotaParams) error + RevokeKolSubscription(ctx context.Context, arg RevokeKolSubscriptionParams) error + RevokeSubscriptionPromptsByPrompt(ctx context.Context, arg RevokeSubscriptionPromptsByPromptParams) error + RevokeSubscriptionPromptsBySubscription(ctx context.Context, arg RevokeSubscriptionPromptsBySubscriptionParams) error SoftDeleteArticle(ctx context.Context, arg SoftDeleteArticleParams) error SoftDeleteBrand(ctx context.Context, arg SoftDeleteBrandParams) error SoftDeleteCompetitor(ctx context.Context, arg SoftDeleteCompetitorParams) error SoftDeleteCompetitorsByBrand(ctx context.Context, arg SoftDeleteCompetitorsByBrandParams) error SoftDeleteKeyword(ctx context.Context, arg SoftDeleteKeywordParams) error SoftDeleteKeywordsByBrand(ctx context.Context, arg SoftDeleteKeywordsByBrandParams) error + SoftDeleteKolPackage(ctx context.Context, arg SoftDeleteKolPackageParams) error + SoftDeleteKolPrompt(ctx context.Context, arg SoftDeleteKolPromptParams) error SoftDeletePromptRule(ctx context.Context, arg SoftDeletePromptRuleParams) error SoftDeletePromptRuleGroup(ctx context.Context, arg SoftDeletePromptRuleGroupParams) error SoftDeleteQuestion(ctx context.Context, arg SoftDeleteQuestionParams) error @@ -104,6 +150,13 @@ type Querier interface { UpdateImageAssetStatus(ctx context.Context, arg UpdateImageAssetStatusParams) error UpdateImageFolder(ctx context.Context, arg UpdateImageFolderParams) error UpdateKeyword(ctx context.Context, arg UpdateKeywordParams) error + UpdateKolPackage(ctx context.Context, arg UpdateKolPackageParams) error + UpdateKolPackageStatus(ctx context.Context, arg UpdateKolPackageStatusParams) error + UpdateKolProfile(ctx context.Context, arg UpdateKolProfileParams) error + UpdateKolPromptDraftRevision(ctx context.Context, arg UpdateKolPromptDraftRevisionParams) error + UpdateKolPromptMetadata(ctx context.Context, arg UpdateKolPromptMetadataParams) error + UpdateKolPromptPublishedRevision(ctx context.Context, arg UpdateKolPromptPublishedRevisionParams) error + UpdateKolPromptStatus(ctx context.Context, arg UpdateKolPromptStatusParams) error UpdatePromptRule(ctx context.Context, arg UpdatePromptRuleParams) error UpdatePromptRuleGroup(ctx context.Context, arg UpdatePromptRuleGroupParams) error UpdatePromptRuleStatus(ctx context.Context, arg UpdatePromptRuleStatusParams) error diff --git a/server/internal/tenant/repository/queries/kol_assist.sql b/server/internal/tenant/repository/queries/kol_assist.sql new file mode 100644 index 0000000..13d91ae --- /dev/null +++ b/server/internal/tenant/repository/queries/kol_assist.sql @@ -0,0 +1,34 @@ +-- name: CreateKolAssistTask :exec +INSERT INTO kol_assist_tasks (id, tenant_id, kol_profile_id, prompt_id, + operator_id, task_type, status, request_json) +VALUES (sqlc.arg(id), sqlc.arg(tenant_id)::bigint, sqlc.arg(kol_profile_id), + sqlc.arg(prompt_id), sqlc.arg(operator_id), + sqlc.arg(task_type), 'queued', sqlc.arg(request_json)::jsonb); + +-- name: GetKolAssistTask :one +SELECT id, tenant_id, kol_profile_id, prompt_id, operator_id, task_type, + status, request_json, result_json, error_message, + started_at, completed_at, created_at, updated_at +FROM kol_assist_tasks +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: MarkKolAssistStarted :exec +UPDATE kol_assist_tasks +SET status = 'running', started_at = NOW(), updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: MarkKolAssistCompleted :exec +UPDATE kol_assist_tasks +SET status = 'completed', result_json = sqlc.arg(result_json)::jsonb, + completed_at = NOW(), updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: MarkKolAssistFailed :exec +UPDATE kol_assist_tasks +SET status = 'failed', error_message = sqlc.arg(error_message), + completed_at = NOW(), updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; diff --git a/server/internal/tenant/repository/queries/kol_marketplace.sql b/server/internal/tenant/repository/queries/kol_marketplace.sql new file mode 100644 index 0000000..7799c2d --- /dev/null +++ b/server/internal/tenant/repository/queries/kol_marketplace.sql @@ -0,0 +1,46 @@ +-- name: ListPublishedKolPackages :many +-- Cross-tenant read: marketplace intentionally spans tenants. +-- tenant_id appears via kol_profiles.tenant_id join only, so this file is exempt from check_tenant_scope.sh. +SELECT kp.id, kp.tenant_id, kp.kol_profile_id, kp.name, kp.description, + kp.cover_url, kp.industry, kp.tags, kp.price_note, kp.status, + kp.created_at, kp.updated_at, + pf.display_name AS kol_display_name, pf.avatar_url AS kol_avatar_url, + (SELECT COUNT(*) FROM kol_prompts p + WHERE p.package_id = kp.id AND p.status = 'active' AND p.deleted_at IS NULL) AS prompt_count, + (SELECT COUNT(*) FROM kol_subscriptions s + WHERE s.package_id = kp.id AND s.status = 'active') AS subscriber_count +FROM kol_packages kp +JOIN kol_profiles pf ON pf.id = kp.kol_profile_id + AND pf.status = 'active' + AND pf.market_enabled = TRUE + AND pf.deleted_at IS NULL +WHERE kp.status = 'published' + AND kp.deleted_at IS NULL + AND (sqlc.narg('industry')::text IS NULL OR kp.industry = sqlc.narg('industry')) + AND (sqlc.narg('keyword')::text IS NULL OR kp.name ILIKE '%' || sqlc.narg('keyword') || '%') +ORDER BY kp.updated_at DESC +LIMIT sqlc.arg('limit') OFFSET sqlc.arg('offset'); + +-- name: GetPublishedKolPackage :one +SELECT kp.id, kp.tenant_id, kp.kol_profile_id, kp.name, kp.description, + kp.cover_url, kp.industry, kp.tags, kp.price_note, kp.status, + kp.created_at, kp.updated_at, + pf.display_name AS kol_display_name, pf.avatar_url AS kol_avatar_url, pf.bio AS kol_bio +FROM kol_packages kp +JOIN kol_profiles pf ON pf.id = kp.kol_profile_id + AND pf.status = 'active' + AND pf.market_enabled = TRUE + AND pf.deleted_at IS NULL +WHERE kp.id = sqlc.arg(id) + AND kp.status = 'published' + AND kp.deleted_at IS NULL; + +-- name: ListPublicPromptsForPackage :many +SELECT p.id, p.name, p.platform_hint, p.status, + p.published_revision_no, p.created_at +FROM kol_prompts p +WHERE p.package_id = sqlc.arg(package_id) + AND p.status = 'active' + AND p.published_revision_no IS NOT NULL + AND p.deleted_at IS NULL +ORDER BY p.sort_order ASC; diff --git a/server/internal/tenant/repository/queries/kol_package.sql b/server/internal/tenant/repository/queries/kol_package.sql new file mode 100644 index 0000000..0096028 --- /dev/null +++ b/server/internal/tenant/repository/queries/kol_package.sql @@ -0,0 +1,58 @@ +-- name: CreateKolPackage :one +INSERT INTO kol_packages (tenant_id, kol_profile_id, name, description, cover_url, industry, tags, price_note, status) +VALUES (sqlc.arg(tenant_id)::bigint, sqlc.arg(kol_profile_id), sqlc.arg(name), + sqlc.arg(description), sqlc.arg(cover_url), sqlc.arg(industry), + sqlc.arg(tags)::jsonb, sqlc.arg(price_note), 'draft') +RETURNING id, tenant_id, kol_profile_id, name, description, cover_url, + industry, tags, price_note, status, created_at, updated_at; + +-- name: GetKolPackageByID :one +SELECT id, tenant_id, kol_profile_id, name, description, cover_url, + industry, tags, price_note, status, created_at, updated_at +FROM kol_packages +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: ListKolPackagesByProfile :many +SELECT id, tenant_id, kol_profile_id, name, description, cover_url, + industry, tags, price_note, status, created_at, updated_at +FROM kol_packages +WHERE kol_profile_id = sqlc.arg(kol_profile_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL +ORDER BY created_at DESC; + +-- name: ListKolPackageIDsByProfile :many +SELECT id +FROM kol_packages +WHERE kol_profile_id = sqlc.arg(kol_profile_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: UpdateKolPackage :exec +UPDATE kol_packages +SET name = sqlc.arg(name), + description = sqlc.arg(description), + cover_url = sqlc.arg(cover_url), + industry = sqlc.arg(industry), + tags = sqlc.arg(tags)::jsonb, + price_note = sqlc.arg(price_note), + updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: UpdateKolPackageStatus :exec +UPDATE kol_packages +SET status = sqlc.arg(status), + updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: SoftDeleteKolPackage :exec +UPDATE kol_packages SET deleted_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; diff --git a/server/internal/tenant/repository/queries/kol_profile.sql b/server/internal/tenant/repository/queries/kol_profile.sql new file mode 100644 index 0000000..1022b6d --- /dev/null +++ b/server/internal/tenant/repository/queries/kol_profile.sql @@ -0,0 +1,23 @@ +-- name: GetKolProfileByUser :one +SELECT id, tenant_id, user_id, display_name, bio, avatar_url, + market_enabled, status, created_at, updated_at +FROM kol_profiles +WHERE user_id = sqlc.arg(user_id)::bigint + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: GetKolProfileByID :one +SELECT id, tenant_id, user_id, display_name, bio, avatar_url, + market_enabled, status, created_at, updated_at +FROM kol_profiles +WHERE id = sqlc.arg(id) AND deleted_at IS NULL; + +-- name: UpdateKolProfile :exec +UPDATE kol_profiles +SET display_name = sqlc.arg(display_name), + bio = sqlc.arg(bio), + avatar_url = sqlc.arg(avatar_url), + updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; diff --git a/server/internal/tenant/repository/queries/kol_prompt.sql b/server/internal/tenant/repository/queries/kol_prompt.sql new file mode 100644 index 0000000..f96bc28 --- /dev/null +++ b/server/internal/tenant/repository/queries/kol_prompt.sql @@ -0,0 +1,105 @@ +-- name: CreateKolPrompt :one +INSERT INTO kol_prompts (tenant_id, package_id, name, platform_hint, sort_order, created_by, updated_by) +VALUES (sqlc.arg(tenant_id)::bigint, sqlc.arg(package_id), sqlc.arg(name), + sqlc.arg(platform_hint), sqlc.arg(sort_order), + sqlc.arg(created_by), sqlc.arg(created_by)) +RETURNING id, tenant_id, package_id, name, platform_hint, status, sort_order, + published_revision_no, draft_revision_no, created_by, updated_by, + created_at, updated_at; + +-- name: GetKolPromptByID :one +SELECT id, tenant_id, package_id, name, platform_hint, status, sort_order, + published_revision_no, draft_revision_no, created_by, updated_by, + created_at, updated_at +FROM kol_prompts +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: ListKolPromptsByPackage :many +SELECT id, tenant_id, package_id, name, platform_hint, status, sort_order, + published_revision_no, draft_revision_no, created_by, updated_by, + created_at, updated_at +FROM kol_prompts +WHERE package_id = sqlc.arg(package_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL +ORDER BY sort_order ASC, created_at ASC; + +-- name: ListActiveKolPromptsByPackage :many +SELECT id, tenant_id, package_id, published_revision_no +FROM kol_prompts +WHERE package_id = sqlc.arg(package_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND status = 'active' + AND published_revision_no IS NOT NULL + AND deleted_at IS NULL +ORDER BY sort_order ASC; + +-- name: UpdateKolPromptMetadata :exec +UPDATE kol_prompts +SET name = sqlc.arg(name), + platform_hint = sqlc.arg(platform_hint), + sort_order = sqlc.arg(sort_order), + updated_by = sqlc.arg(updated_by), + updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: UpdateKolPromptPublishedRevision :exec +UPDATE kol_prompts +SET published_revision_no = sqlc.arg(revision_no), + status = 'active', + updated_by = sqlc.arg(updated_by), + updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: UpdateKolPromptDraftRevision :exec +UPDATE kol_prompts +SET draft_revision_no = sqlc.arg(revision_no), + updated_by = sqlc.arg(updated_by), + updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: UpdateKolPromptStatus :exec +UPDATE kol_prompts +SET status = sqlc.arg(status), + updated_by = sqlc.arg(updated_by), + updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: SoftDeleteKolPrompt :exec +UPDATE kol_prompts SET deleted_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint + AND deleted_at IS NULL; + +-- name: CreateKolPromptRevision :one +INSERT INTO kol_prompt_revisions (tenant_id, prompt_id, revision_no, prompt_asset_key, + schema_json, card_config_json, created_by) +VALUES (sqlc.arg(tenant_id)::bigint, sqlc.arg(prompt_id), sqlc.arg(revision_no), + sqlc.arg(prompt_asset_key), sqlc.arg(schema_json)::jsonb, + sqlc.arg(card_config_json)::jsonb, sqlc.arg(created_by)) +RETURNING id, prompt_id, revision_no, prompt_asset_key, schema_json, + card_config_json, created_by, created_at; + +-- name: GetKolPromptRevision :one +SELECT id, prompt_id, revision_no, prompt_asset_key, schema_json, + card_config_json, created_by, created_at +FROM kol_prompt_revisions +WHERE prompt_id = sqlc.arg(prompt_id) + AND revision_no = sqlc.arg(revision_no) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: NextKolPromptRevisionNo :one +SELECT COALESCE(MAX(revision_no), 0) + 1 AS next_revision_no +FROM kol_prompt_revisions +WHERE prompt_id = sqlc.arg(prompt_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; diff --git a/server/internal/tenant/repository/queries/kol_subscription.sql b/server/internal/tenant/repository/queries/kol_subscription.sql new file mode 100644 index 0000000..a21c0fe --- /dev/null +++ b/server/internal/tenant/repository/queries/kol_subscription.sql @@ -0,0 +1,90 @@ +-- name: CreateKolSubscription :one +INSERT INTO kol_subscriptions (tenant_id, package_id, status) +VALUES (sqlc.arg(tenant_id)::bigint, sqlc.arg(package_id), 'pending') +RETURNING id, tenant_id, package_id, status, approved_by, start_at, end_at, created_at, updated_at; + +-- 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 = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- 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 = sqlc.arg(tenant_id)::bigint + AND package_id = sqlc.arg(package_id) + AND status IN ('pending', 'active'); + +-- 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 = sqlc.arg(tenant_id)::bigint +ORDER BY created_at DESC; + +-- 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 = sqlc.arg(package_id) + AND tenant_id IS NOT NULL + AND status = 'active'; + +-- name: ApproveKolSubscription :exec +UPDATE kol_subscriptions +SET status = 'active', + approved_by = sqlc.arg(approved_by), + start_at = NOW(), + end_at = sqlc.arg(end_at), + updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: RevokeKolSubscription :exec +UPDATE kol_subscriptions +SET status = 'revoked', updated_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: CreateSubscriptionPrompt :exec +INSERT INTO kol_subscription_prompts (tenant_id, subscription_id, package_id, prompt_id, status) +VALUES (sqlc.arg(tenant_id)::bigint, sqlc.arg(subscription_id), + sqlc.arg(package_id), sqlc.arg(prompt_id), 'active') +ON CONFLICT (subscription_id, prompt_id) DO NOTHING; + +-- 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 +FROM kol_subscription_prompts sp +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.tenant_id = sqlc.arg(tenant_id)::bigint + AND sp.status = 'active' +ORDER BY sp.granted_at DESC; + +-- 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 = sqlc.arg(id) + AND sp.tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: RevokeSubscriptionPromptsBySubscription :exec +UPDATE kol_subscription_prompts +SET status = 'revoked', revoked_at = NOW(), updated_at = NOW() +WHERE subscription_id = sqlc.arg(subscription_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: RevokeSubscriptionPromptsByPrompt :exec +UPDATE kol_subscription_prompts +SET status = 'revoked', revoked_at = NOW(), updated_at = NOW() +WHERE prompt_id = sqlc.arg(prompt_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; diff --git a/server/internal/tenant/repository/queries/kol_usage.sql b/server/internal/tenant/repository/queries/kol_usage.sql new file mode 100644 index 0000000..17d2d4a --- /dev/null +++ b/server/internal/tenant/repository/queries/kol_usage.sql @@ -0,0 +1,84 @@ +-- name: CreateKolUsageLog :one +INSERT INTO kol_usage_logs (tenant_id, subscription_id, subscription_prompt_id, + package_id, prompt_id, prompt_revision_no, + generation_task_id, status, variables_snapshot, + schema_snapshot, rendered_prompt_hash) +VALUES (sqlc.arg(tenant_id)::bigint, sqlc.arg(subscription_id), + sqlc.arg(subscription_prompt_id), sqlc.arg(package_id), + sqlc.arg(prompt_id), sqlc.arg(prompt_revision_no), + sqlc.arg(generation_task_id), 'pending', + sqlc.arg(variables_snapshot)::jsonb, sqlc.arg(schema_snapshot)::jsonb, + sqlc.arg(rendered_prompt_hash)) +RETURNING id, tenant_id, generation_task_id, status, created_at; + +-- name: MarkKolUsageCompleted :exec +UPDATE kol_usage_logs +SET status = 'completed', article_id = sqlc.arg(article_id), + finished_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: MarkKolUsageFailed :exec +UPDATE kol_usage_logs +SET status = 'failed', error_message = sqlc.arg(error_message), + finished_at = NOW() +WHERE id = sqlc.arg(id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: MarkKolUsageCompletedByTask :exec +UPDATE kol_usage_logs +SET status = 'completed', article_id = sqlc.arg(article_id), finished_at = NOW() +WHERE generation_task_id = sqlc.arg(generation_task_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: MarkKolUsageFailedByTask :exec +UPDATE kol_usage_logs +SET status = 'failed', error_message = sqlc.arg(error_message), finished_at = NOW() +WHERE generation_task_id = sqlc.arg(generation_task_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: GetKolUsageByGenerationTask :one +SELECT id, tenant_id, package_id, prompt_id, status, created_at +FROM kol_usage_logs +WHERE generation_task_id = sqlc.arg(generation_task_id) + AND tenant_id = sqlc.arg(tenant_id)::bigint; + +-- name: KolDashboardOverview :one +SELECT + (SELECT COUNT(DISTINCT s.tenant_id) FROM kol_subscriptions s + WHERE s.package_id = ANY(sqlc.arg(package_ids)::bigint[]) AND s.status='active' + AND (s.end_at IS NULL OR s.end_at > NOW())) AS total_subs, + (SELECT COUNT(*) FROM kol_subscriptions s + WHERE s.package_id = ANY(sqlc.arg(package_ids)::bigint[]) + AND s.status='active' + AND s.created_at >= date_trunc('month', NOW())) AS month_subs, + (SELECT COUNT(*) FROM kol_usage_logs u + WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[]) + AND u.tenant_id IS NOT NULL) AS total_usage, + (SELECT COUNT(*) FROM kol_usage_logs u + WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[]) + AND u.tenant_id IS NOT NULL + AND u.created_at >= date_trunc('month', NOW())) AS month_usage, + (SELECT COUNT(*) FROM kol_usage_logs u + WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[]) + AND u.tenant_id IS NOT NULL + AND u.status='failed') AS failure_count; + +-- name: KolDashboardTrend :many +SELECT date_trunc('day', u.created_at)::date AS d, + COUNT(*) FILTER (WHERE u.status='completed') AS usages +FROM kol_usage_logs u +WHERE u.package_id = ANY(sqlc.arg(package_ids)::bigint[]) + AND u.tenant_id IS NOT NULL + AND u.created_at >= NOW() - sqlc.arg(period_days)::int * INTERVAL '1 day' +GROUP BY d +ORDER BY d ASC; + +-- name: KolUsageCountByPackage :many +SELECT package_id, COUNT(*) AS total, + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed, + SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed +FROM kol_usage_logs +WHERE package_id = ANY(sqlc.arg(package_ids)::bigint[]) + AND tenant_id IS NOT NULL +GROUP BY package_id; diff --git a/server/scripts/check_tenant_scope.sh b/server/scripts/check_tenant_scope.sh index bfd9901..13d0062 100755 --- a/server/scripts/check_tenant_scope.sh +++ b/server/scripts/check_tenant_scope.sh @@ -10,7 +10,7 @@ EXIT_CODE=0 for f in "$QUERY_DIR"/*.sql; do # Skip files that don't need tenant_id (audit.sql has operator_id + tenant_id) basename=$(basename "$f") - if [[ "$basename" == "audit.sql" ]]; then + if [[ "$basename" == "audit.sql" ]] || [[ "$basename" == "kol_marketplace.sql" ]]; then continue fi