Introduce plan and content categories across programs and exam-prep catalog roots, wire category-aware checkout and access checks, and keep learner gating temporarily bypassed until data migration is ready. Co-authored-by: Cursor <cursoragent@cursor.com>
207 lines
5.8 KiB
SQL
207 lines
5.8 KiB
SQL
-- =====================
|
|
-- Subscription Plans
|
|
-- =====================
|
|
|
|
-- name: CreateSubscriptionPlan :one
|
|
INSERT INTO subscription_plans (
|
|
name, description, category, duration_value, duration_unit, price, currency, is_active
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, COALESCE($8, true))
|
|
RETURNING *;
|
|
|
|
-- name: GetSubscriptionPlanByID :one
|
|
SELECT * FROM subscription_plans WHERE id = $1;
|
|
|
|
-- name: ListSubscriptionPlans :many
|
|
SELECT * FROM subscription_plans
|
|
WHERE ($1::BOOLEAN IS NULL OR $1 = true AND is_active = true OR $1 = false)
|
|
ORDER BY price ASC;
|
|
|
|
-- name: ListActiveSubscriptionPlans :many
|
|
SELECT * FROM subscription_plans
|
|
WHERE is_active = true
|
|
ORDER BY price ASC;
|
|
|
|
-- name: UpdateSubscriptionPlan :exec
|
|
UPDATE subscription_plans
|
|
SET
|
|
name = COALESCE(sqlc.narg('name')::varchar, name),
|
|
description = COALESCE(sqlc.narg('description')::text, description),
|
|
category = COALESCE(sqlc.narg('category')::varchar, category),
|
|
duration_value = COALESCE(sqlc.narg('duration_value')::int, duration_value),
|
|
duration_unit = COALESCE(sqlc.narg('duration_unit')::varchar, duration_unit),
|
|
price = COALESCE(sqlc.narg('price')::numeric, price),
|
|
currency = COALESCE(sqlc.narg('currency')::varchar, currency),
|
|
is_active = COALESCE(sqlc.narg('is_active')::boolean, is_active),
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = sqlc.arg('id');
|
|
|
|
-- name: DeleteSubscriptionPlan :exec
|
|
DELETE FROM subscription_plans WHERE id = $1;
|
|
|
|
-- =====================
|
|
-- User Subscriptions
|
|
-- =====================
|
|
|
|
-- name: CreateUserSubscription :one
|
|
INSERT INTO user_subscriptions (
|
|
user_id, plan_id, starts_at, expires_at, status, payment_reference, payment_method, auto_renew
|
|
)
|
|
VALUES ($1, $2, COALESCE($3, CURRENT_TIMESTAMP), $4, COALESCE($5, 'ACTIVE'), $6, $7, COALESCE($8, false))
|
|
RETURNING *;
|
|
|
|
-- name: GetUserSubscriptionByID :one
|
|
SELECT
|
|
us.*,
|
|
sp.name AS plan_name,
|
|
sp.duration_value,
|
|
sp.duration_unit,
|
|
sp.price,
|
|
sp.currency
|
|
FROM user_subscriptions us
|
|
JOIN subscription_plans sp ON sp.id = us.plan_id
|
|
WHERE us.id = $1;
|
|
|
|
-- Display status for admin user lists: ACTIVE (non-expired), else latest PENDING, else Unsubscribed.
|
|
-- name: ListSubscriptionDisplayStatusesByUserIDs :many
|
|
WITH input AS (
|
|
SELECT unnest($1::bigint[])::bigint AS user_id
|
|
)
|
|
SELECT
|
|
input.user_id,
|
|
COALESCE(
|
|
(SELECT us.status::text FROM user_subscriptions us
|
|
WHERE us.user_id = input.user_id
|
|
AND us.status = 'ACTIVE' AND us.expires_at > CURRENT_TIMESTAMP
|
|
ORDER BY us.expires_at DESC LIMIT 1),
|
|
(SELECT us.status::text FROM user_subscriptions us
|
|
WHERE us.user_id = input.user_id
|
|
AND us.status = 'PENDING'
|
|
ORDER BY us.created_at DESC LIMIT 1),
|
|
'Unsubscribed'
|
|
)::text AS subscription_status
|
|
FROM input;
|
|
|
|
-- name: GetSubscriptionDisplayStatusByUserID :one
|
|
SELECT COALESCE(
|
|
(SELECT us.status::text FROM user_subscriptions us
|
|
WHERE us.user_id = $1
|
|
AND us.status = 'ACTIVE' AND us.expires_at > CURRENT_TIMESTAMP
|
|
ORDER BY us.expires_at DESC LIMIT 1),
|
|
(SELECT us.status::text FROM user_subscriptions us
|
|
WHERE us.user_id = $1
|
|
AND us.status = 'PENDING'
|
|
ORDER BY us.created_at DESC LIMIT 1),
|
|
'Unsubscribed'
|
|
)::text AS subscription_status;
|
|
|
|
-- name: GetActiveSubscriptionByUserID :one
|
|
SELECT
|
|
us.*,
|
|
sp.name AS plan_name,
|
|
sp.duration_value,
|
|
sp.duration_unit,
|
|
sp.price,
|
|
sp.currency
|
|
FROM user_subscriptions us
|
|
JOIN subscription_plans sp ON sp.id = us.plan_id
|
|
WHERE us.user_id = $1
|
|
AND us.status = 'ACTIVE'
|
|
AND us.expires_at > CURRENT_TIMESTAMP
|
|
ORDER BY us.expires_at DESC
|
|
LIMIT 1;
|
|
|
|
-- name: GetUserSubscriptionHistory :many
|
|
SELECT
|
|
us.*,
|
|
sp.name AS plan_name,
|
|
sp.duration_value,
|
|
sp.duration_unit,
|
|
sp.price,
|
|
sp.currency
|
|
FROM user_subscriptions us
|
|
JOIN subscription_plans sp ON sp.id = us.plan_id
|
|
WHERE us.user_id = $1
|
|
ORDER BY us.created_at DESC
|
|
LIMIT sqlc.narg('limit')::INT
|
|
OFFSET sqlc.narg('offset')::INT;
|
|
|
|
-- name: CountUserSubscriptions :one
|
|
SELECT COUNT(*) FROM user_subscriptions WHERE user_id = $1;
|
|
|
|
-- name: UpdateUserSubscriptionStatus :exec
|
|
UPDATE user_subscriptions
|
|
SET
|
|
status = $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $2;
|
|
|
|
-- name: CancelUserSubscription :exec
|
|
UPDATE user_subscriptions
|
|
SET
|
|
status = 'CANCELLED',
|
|
cancelled_at = CURRENT_TIMESTAMP,
|
|
auto_renew = false,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $1;
|
|
|
|
-- name: ExpireUserSubscription :exec
|
|
UPDATE user_subscriptions
|
|
SET
|
|
status = 'EXPIRED',
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $1;
|
|
|
|
-- name: UpdateAutoRenew :exec
|
|
UPDATE user_subscriptions
|
|
SET
|
|
auto_renew = $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $2;
|
|
|
|
-- name: GetExpiredSubscriptions :many
|
|
SELECT us.*, sp.name AS plan_name
|
|
FROM user_subscriptions us
|
|
JOIN subscription_plans sp ON sp.id = us.plan_id
|
|
WHERE us.status = 'ACTIVE'
|
|
AND us.expires_at <= CURRENT_TIMESTAMP;
|
|
|
|
-- name: GetExpiringSubscriptions :many
|
|
SELECT
|
|
us.*,
|
|
sp.name AS plan_name,
|
|
u.email,
|
|
u.first_name
|
|
FROM user_subscriptions us
|
|
JOIN subscription_plans sp ON sp.id = us.plan_id
|
|
JOIN users u ON u.id = us.user_id
|
|
WHERE us.status = 'ACTIVE'
|
|
AND us.expires_at > CURRENT_TIMESTAMP
|
|
AND us.expires_at <= CURRENT_TIMESTAMP + INTERVAL '7 days';
|
|
|
|
-- name: HasActiveSubscription :one
|
|
SELECT EXISTS(
|
|
SELECT 1 FROM user_subscriptions
|
|
WHERE user_id = $1
|
|
AND status = 'ACTIVE'
|
|
AND expires_at > CURRENT_TIMESTAMP
|
|
) AS has_subscription;
|
|
|
|
-- name: HasActiveSubscriptionByCategory :one
|
|
SELECT EXISTS(
|
|
SELECT 1
|
|
FROM user_subscriptions us
|
|
JOIN subscription_plans sp ON sp.id = us.plan_id
|
|
WHERE us.user_id = $1
|
|
AND sp.category = $2
|
|
AND us.status = 'ACTIVE'
|
|
AND us.expires_at > CURRENT_TIMESTAMP
|
|
) AS has_subscription;
|
|
|
|
-- name: ExtendSubscription :exec
|
|
UPDATE user_subscriptions
|
|
SET
|
|
expires_at = $1,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $2;
|