import { Plan } from "@prisma/client"; import { getPlanLimits } from "./plans"; import { hasProFeatures } from "./edition"; import { requiresArtTrackLayoutEntitlement, type LayoutSettings, } from "./layout"; import { PREMIUM_REQUIRED_CODE, requiresCustomWatermarkEntitlement, type WatermarkSettings, } from "./watermark"; export class PremiumRequiredError extends Error { readonly code = PREMIUM_REQUIRED_CODE; readonly status = 403; constructor(message: string) { super(message); this.name = "PremiumRequiredError"; } } export function assertCustomWatermarkAllowed(plan: Plan, settings: WatermarkSettings) { if (!requiresCustomWatermarkEntitlement(settings)) return; if (getPlanLimits(plan).customWatermark || hasProFeatures(plan)) return; throw new PremiumRequiredError( "Custom watermark, typography, logo overlay, and position controls require Pro.", ); } export function assertArtTrackLayoutAllowed(plan: Plan, settings: LayoutSettings) { if (!requiresArtTrackLayoutEntitlement(settings)) return; if (getPlanLimits(plan).artTrackLayouts || hasProFeatures(plan)) return; throw new PremiumRequiredError( "Blurred backgrounds and art-track layout templates require Pro.", ); } export function assertPerItemImagesAllowed( plan: Plan, sharedImagePath: string, itemImagePaths: Array, ) { const unique = new Set( itemImagePaths .map((p) => (p && p.trim() ? p.trim() : sharedImagePath)) .filter(Boolean), ); // More than one distinct cover in the batch → Pro if (unique.size <= 1) return; if (getPlanLimits(plan).perItemImages || hasProFeatures(plan)) return; throw new PremiumRequiredError( "Matching a unique image to each audio file requires Pro. Free plan uses one shared cover image.", ); } export function premiumRequiredResponse(message: string) { return { error: message, code: PREMIUM_REQUIRED_CODE, }; }