fix(kol): Phase 1 review — ACL lineage, expiry filters, tighter tenant scope
Addresses findings from consolidated Phase 1 code review: - Add composite unique indexes + composite FKs so subscription_prompts and packages cannot reference mismatched (tenant_id, package_id, prompt_id) tuples, enforcing ACL lineage at the schema level. - Add status CHECK constraints on all KOL tables as defense-in-depth. - ListActiveSubscribersForPackage: drop meaningless tenant_id IS NOT NULL, add end_at expiry filter so fan-out skips expired subscribers. - ApproveKolSubscription: add status='pending' guard to prevent double-approval. - ListSubscriptionPromptsByTenant: join subscription/prompt/package/profile lifecycle so hidden/archived content no longer leaks; surface kol_display_name for UI. - ListPublishedKolPackages: exclude expired subscribers from marketplace count. - KolDashboardTrend: rewrite as CTE with day-series, returning both daily usage and new subscription counts. - Remove cosmetic tenant_id IS NOT NULL from dashboard overview queries (the column is NOT NULL by schema). - check_tenant_scope.sh: require tenant_id = sqlc.(n?)arg filter, reject tenant_id IS NOT NULL as a fake scope, maintain explicit exemption list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -115,15 +115,12 @@ SELECT
|
||||
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,
|
||||
WHERE u.package_id = ANY($1::bigint[])) 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
|
||||
`
|
||||
|
||||
@@ -135,6 +132,7 @@ type KolDashboardOverviewRow struct {
|
||||
FailureCount int64 `json:"failure_count"`
|
||||
}
|
||||
|
||||
// Cross-tenant aggregation: KOL owns packages across subscriber tenants.
|
||||
func (q *Queries) KolDashboardOverview(ctx context.Context, packageIds []int64) (KolDashboardOverviewRow, error) {
|
||||
row := q.db.QueryRow(ctx, kolDashboardOverview, packageIds)
|
||||
var i KolDashboardOverviewRow
|
||||
@@ -149,28 +147,53 @@ func (q *Queries) KolDashboardOverview(ctx context.Context, packageIds []int64)
|
||||
}
|
||||
|
||||
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
|
||||
WITH days AS (
|
||||
SELECT generate_series(
|
||||
date_trunc('day', NOW()) - ($1::int - 1) * INTERVAL '1 day',
|
||||
date_trunc('day', NOW()),
|
||||
INTERVAL '1 day'
|
||||
)::date AS d
|
||||
),
|
||||
u AS (
|
||||
SELECT date_trunc('day', created_at)::date AS d,
|
||||
COUNT(*) FILTER (WHERE status = 'completed') AS usages
|
||||
FROM kol_usage_logs
|
||||
WHERE package_id = ANY($2::bigint[])
|
||||
AND created_at >= NOW() - $1::int * INTERVAL '1 day'
|
||||
GROUP BY 1
|
||||
),
|
||||
s AS (
|
||||
SELECT date_trunc('day', created_at)::date AS d,
|
||||
COUNT(*) AS subscriptions
|
||||
FROM kol_subscriptions
|
||||
WHERE package_id = ANY($2::bigint[])
|
||||
AND status IN ('active', 'expired', 'revoked')
|
||||
AND created_at >= NOW() - $1::int * INTERVAL '1 day'
|
||||
GROUP BY 1
|
||||
)
|
||||
SELECT days.d,
|
||||
COALESCE(u.usages, 0)::bigint AS usages,
|
||||
COALESCE(s.subscriptions, 0)::bigint AS subscriptions
|
||||
FROM days
|
||||
LEFT JOIN u ON u.d = days.d
|
||||
LEFT JOIN s ON s.d = days.d
|
||||
ORDER BY days.d ASC
|
||||
`
|
||||
|
||||
type KolDashboardTrendParams struct {
|
||||
PackageIds []int64 `json:"package_ids"`
|
||||
PeriodDays int32 `json:"period_days"`
|
||||
PackageIds []int64 `json:"package_ids"`
|
||||
}
|
||||
|
||||
type KolDashboardTrendRow struct {
|
||||
D pgtype.Date `json:"d"`
|
||||
Usages int64 `json:"usages"`
|
||||
D pgtype.Date `json:"d"`
|
||||
Usages int64 `json:"usages"`
|
||||
Subscriptions int64 `json:"subscriptions"`
|
||||
}
|
||||
|
||||
// Returns a per-day series combining usage count and new subscription count for KOL dashboard.
|
||||
func (q *Queries) KolDashboardTrend(ctx context.Context, arg KolDashboardTrendParams) ([]KolDashboardTrendRow, error) {
|
||||
rows, err := q.db.Query(ctx, kolDashboardTrend, arg.PackageIds, arg.PeriodDays)
|
||||
rows, err := q.db.Query(ctx, kolDashboardTrend, arg.PeriodDays, arg.PackageIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -178,7 +201,7 @@ func (q *Queries) KolDashboardTrend(ctx context.Context, arg KolDashboardTrendPa
|
||||
var items []KolDashboardTrendRow
|
||||
for rows.Next() {
|
||||
var i KolDashboardTrendRow
|
||||
if err := rows.Scan(&i.D, &i.Usages); err != nil {
|
||||
if err := rows.Scan(&i.D, &i.Usages, &i.Subscriptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
@@ -195,7 +218,6 @@ SELECT package_id, COUNT(*) AS total,
|
||||
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
|
||||
`
|
||||
|
||||
@@ -206,6 +228,7 @@ type KolUsageCountByPackageRow struct {
|
||||
Failed int64 `json:"failed"`
|
||||
}
|
||||
|
||||
// Cross-tenant aggregation for KOL dashboard.
|
||||
func (q *Queries) KolUsageCountByPackage(ctx context.Context, packageIds []int64) ([]KolUsageCountByPackageRow, error) {
|
||||
rows, err := q.db.Query(ctx, kolUsageCountByPackage, packageIds)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user