import { corsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts' import { requireAdmin, requireManager } from '../_shared/admin-auth.ts' import { createServiceRoleClient } from '../_shared/quota.ts' import { verifyCurrentAdminActor } from '../_shared/admin-current-role.ts' import { AdminPublicError, type SubscriptionAction, actorEmail, adminErrorResponse, adminJsonResponse, adminRpcError, parsePagination, parseSubscriptionMutationRequest, readAdminJson, requireIdempotencyKey, requireUuid, subscriptionStatusFilter, subscriptionTierFilter, validateQueryKeys, validateSubscriptionRpcResult, } from '../_shared/admin-contract.ts' const SUBSCRIPTION_COLUMNS = 'id, user_id, tier, status, provider, payment_provider, current_period_start, current_period_end, overage_credits, admin_note, cancel_at, created_at, updated_at' async function readSubscriptions(req: Request): Promise { const admin = await requireManager(req) const url = new URL(req.url) validateQueryKeys(url, ['userId', 'page', 'limit', 'status', 'tier']) const serviceClient = createServiceRoleClient() await verifyCurrentAdminActor(serviceClient, admin) const userIdValue = url.searchParams.get('userId') if (userIdValue) { const userId = requireUuid(userIdValue, 'invalid_userId') const [{ data: subscription, error: subscriptionError }, { data: profile, error: profileError }] = await Promise.all([ serviceClient.from('subscriptions').select(SUBSCRIPTION_COLUMNS).eq('user_id', userId).maybeSingle(), serviceClient.from('profiles').select('id, name, tier, role').eq('id', userId).maybeSingle(), ]) if (subscriptionError || profileError) throw new AdminPublicError(500, 'admin_subscription_read_failed') if (!subscription) throw new AdminPublicError(404, 'subscription_not_found') return adminJsonResponse({ subscription, profile: profile ?? null }, corsHeaders) } const { page, limit, from, to } = parsePagination(url) const status = subscriptionStatusFilter(url.searchParams.get('status')) const tier = subscriptionTierFilter(url.searchParams.get('tier')) let query = serviceClient .from('subscriptions') .select(`${SUBSCRIPTION_COLUMNS}, profiles!subscriptions_user_id_fkey(name, avatar_url)`, { count: 'exact' }) if (status) query = query.eq('status', status) if (tier) query = query.eq('tier', tier) const { data, count, error } = await query .order('updated_at', { ascending: false }) .range(from, to) if (error) throw new AdminPublicError(500, 'admin_subscription_read_failed') return adminJsonResponse({ subscriptions: data ?? [], total: count ?? 0, page, limit }, corsHeaders) } async function mutateSubscription(req: Request, action: SubscriptionAction): Promise { const admin = action === 'update' ? await requireManager(req) : await requireAdmin(req) const url = new URL(req.url) validateQueryKeys(url, action === 'create' ? [] : ['userId']) const body = await readAdminJson(req) const mutation = parseSubscriptionMutationRequest( action, body, requireIdempotencyKey(req), url.searchParams.get('userId'), ) const serviceClient = createServiceRoleClient() const { data, error } = await serviceClient.rpc('admin_mutate_subscription_v1', { p_actor_email: actorEmail(admin), p_idempotency_key: mutation.idempotencyKey, p_action: mutation.action, p_user_id: mutation.userId, p_tier: mutation.tier ?? null, p_status: mutation.status ?? null, p_current_period_end: mutation.currentPeriodEnd ?? null, p_overage_credits: mutation.overageCredits ?? null, p_admin_note: mutation.adminNote ?? null, p_memo: mutation.memo, }) if (error) throw adminRpcError(error) const response = validateSubscriptionRpcResult(data, mutation) return adminJsonResponse(response, corsHeaders, action === 'create' ? 201 : 200) } export async function handleAdminSubscriptions(req: Request): Promise { const preflight = handleCorsPreflightRequest(req) if (preflight) return preflight try { if (req.method === 'GET') return await readSubscriptions(req) if (req.method === 'POST') return await mutateSubscription(req, 'create') if (req.method === 'PATCH') return await mutateSubscription(req, 'update') if (req.method === 'DELETE') return await mutateSubscription(req, 'delete') return adminJsonResponse({ error: 'method_not_allowed' }, corsHeaders, 405) } catch (error) { console.error('admin-subscriptions request failed', error) return adminErrorResponse(error, corsHeaders) } } if (import.meta.main) Deno.serve(handleAdminSubscriptions)