"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.rateLimitedWorkspaceProcedure = exports.rateLimitedProcedure = exports.adminPermissionProcedure = exports.adminProcedure = exports.requireActiveSubscription = exports.requireFeature = exports.studioProcedure = exports.creatorProcedure = exports.hobbyProcedure = exports.workspaceProc = exports.workspaceProcedure = exports.protectedProcedure = exports.middleware = exports.publicProcedure = exports.router = void 0; const server_1 = require("@trpc/server"); const zod_1 = require("zod"); const shared_types_1 = require("@micprotocol/shared-types"); const platform_admin_1 = require("../common/platform-admin"); const subscription_features_1 = require("../billing/subscription-features"); const rate_limit_runtime_1 = require("./rate-limit.runtime"); const t = server_1.initTRPC.context().create({ errorFormatter({ shape, error }) { let zodError = null; if (error.cause instanceof zod_1.ZodError) { zodError = error.cause.flatten(); } else if (error.code === "BAD_REQUEST" && error.cause instanceof Error) { zodError = error.cause.message; } return { ...shape, data: { ...shape.data, zodError, timestamp: new Date().toISOString(), }, }; }, }); exports.router = t.router; exports.publicProcedure = t.procedure; exports.middleware = t.middleware; const isAuthenticated = (0, exports.middleware)(async ({ ctx, next }) => { if (!ctx.userId) { throw new server_1.TRPCError({ code: "UNAUTHORIZED", message: "You must be logged in to perform this action", }); } return next({ ctx: { ...ctx, userId: ctx.userId, }, }); }); exports.protectedProcedure = exports.publicProcedure.use(isAuthenticated); const hasWorkspace = (0, exports.middleware)(async ({ ctx, next }) => { if (!ctx.userId) { throw new server_1.TRPCError({ code: "BAD_REQUEST", message: "Workspace context is required", }); } let workspaceId = ctx.workspaceId; if (!workspaceId && ctx.prisma) { const firstMembership = await ctx.prisma.workspaceMember.findFirst({ where: { userId: ctx.userId }, orderBy: { joinedAt: "asc" }, select: { workspaceId: true, role: true }, }); if (firstMembership) { workspaceId = firstMembership.workspaceId; } } if (!workspaceId) { throw new server_1.TRPCError({ code: "BAD_REQUEST", message: "Workspace context is required. You do not belong to any workspace yet.", }); } if (ctx.prisma) { const membership = await ctx.prisma.workspaceMember.findUnique({ where: { userId_workspaceId: { userId: ctx.userId, workspaceId, }, }, }); if (!membership) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "You do not have access to this workspace", }); } return next({ ctx: { ...ctx, workspaceId, workspaceRole: membership.role, }, }); } return next({ ctx: { ...ctx, workspaceId, }, }); }); const subscriptionGuard = (0, exports.middleware)(async ({ ctx, next }) => { if (!ctx.workspaceId || !ctx.prisma) { return next({ ctx }); } const workspace = await ctx.prisma.workspace.findUnique({ where: { id: ctx.workspaceId }, select: { subscriptionPlanId: true, subscriptionStatus: true, }, }); const planId = workspace?.subscriptionPlanId || shared_types_1.SubscriptionPlanId.HOBBY; const status = workspace?.subscriptionStatus || shared_types_1.SubscriptionStatus.ACTIVE; const hasFeature = (feature) => { if (status !== shared_types_1.SubscriptionStatus.ACTIVE && status !== shared_types_1.SubscriptionStatus.TRIALING) { return subscription_features_1.PLAN_FEATURES[shared_types_1.SubscriptionPlanId.HOBBY].includes(feature); } return (0, subscription_features_1.planHasFeature)(planId, feature); }; const getLimit = (limitKey) => { if (status !== shared_types_1.SubscriptionStatus.ACTIVE && status !== shared_types_1.SubscriptionStatus.TRIALING) { return (0, subscription_features_1.getPlanLimit)(shared_types_1.SubscriptionPlanId.HOBBY, limitKey); } return (0, subscription_features_1.getPlanLimit)(planId, limitKey); }; const isUnlimitedCheck = (limitKey) => { if (status !== shared_types_1.SubscriptionStatus.ACTIVE && status !== shared_types_1.SubscriptionStatus.TRIALING) { return (0, subscription_features_1.isUnlimited)(shared_types_1.SubscriptionPlanId.HOBBY, limitKey); } return (0, subscription_features_1.isUnlimited)(planId, limitKey); }; return next({ ctx: { ...ctx, subscriptionPlanId: planId, subscriptionStatus: status, hasFeature, getLimit, isUnlimited: isUnlimitedCheck, }, }); }); exports.workspaceProcedure = exports.protectedProcedure.use(hasWorkspace).use(subscriptionGuard); exports.workspaceProc = exports.workspaceProcedure; exports.hobbyProcedure = exports.workspaceProc.use(({ ctx, next }) => { return next({ ctx }); }); exports.creatorProcedure = exports.workspaceProc.use(({ ctx, next }) => { if (!ctx.hasFeature?.(subscription_features_1.SubscriptionFeature.AUTOMATED_OUTREACH)) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "This feature requires the Creator plan or higher. Upgrade to access automated outreach sequences.", }); } return next({ ctx }); }); exports.studioProcedure = exports.workspaceProc.use(({ ctx, next }) => { if (!ctx.hasFeature?.(subscription_features_1.SubscriptionFeature.VIRALCLIP_ENGINE)) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "This feature requires the Studio plan. Upgrade to access ViralClip Engine and AI-powered clip detection.", }); } return next({ ctx }); }); const requireFeature = (feature) => exports.workspaceProc.use(({ ctx, next }) => { if (!ctx.hasFeature?.(feature)) { const featureLabel = feature.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); throw new server_1.TRPCError({ code: "FORBIDDEN", message: `This feature requires a higher subscription plan. ` + `The "${featureLabel}" feature is not available on your current plan. ` + `Upgrade your plan to access this feature.`, }); } return next({ ctx }); }); exports.requireFeature = requireFeature; exports.requireActiveSubscription = exports.workspaceProc.use(({ ctx, next }) => { if (ctx.subscriptionStatus === shared_types_1.SubscriptionStatus.PAST_DUE || ctx.subscriptionStatus === shared_types_1.SubscriptionStatus.CANCELED || ctx.subscriptionStatus === shared_types_1.SubscriptionStatus.INCOMPLETE) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "Your subscription is not active. Please update your payment method or upgrade your plan.", }); } return next({ ctx }); }); exports.adminProcedure = exports.protectedProcedure.use(async ({ ctx, next }) => { if (!ctx.prisma) { throw new server_1.TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Prisma not available" }); } const user = await ctx.prisma.user.findUnique({ where: { id: ctx.userId }, select: { isAdmin: true, adminRole: true, suspendedAt: true }, }); if (!user) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "Admin privileges required" }); } if (user.suspendedAt) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "Account is suspended" }); } if (!user.isAdmin && !user.adminRole) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "Admin privileges required" }); } return next({ ctx }); }); const adminPermissionProcedure = (permission) => exports.protectedProcedure.use(async ({ ctx, next }) => { if (!ctx.prisma) { throw new server_1.TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Prisma not available" }); } if (!ctx.userId) { throw new server_1.TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" }); } const user = await ctx.prisma.user.findUnique({ where: { id: ctx.userId }, select: { isAdmin: true, adminRole: true, adminPermissions: true, suspendedAt: true, }, }); if (!user) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "Admin privileges required" }); } if (user.suspendedAt) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "Account is suspended" }); } const role = (0, platform_admin_1.deriveAdminRole)(user); if (!role) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: "Admin privileges required" }); } const permissions = (0, shared_types_1.resolveAdminPermissions)(role, user.adminPermissions); if (!permissions.has(permission)) { throw new server_1.TRPCError({ code: "FORBIDDEN", message: `Insufficient permissions: ${permission} required`, }); } return next({ ctx: { ...ctx, isAdmin: !!user.isAdmin, adminRole: role, adminPermissions: permissions, }, }); }); exports.adminPermissionProcedure = adminPermissionProcedure; const applyRateLimit = (prefix, max, windowMs, keyFn) => (0, exports.middleware)(async ({ ctx, next, input }) => { let identifier = ctx.userId ?? ctx.workspaceId ?? null; if (!identifier && keyFn) { identifier = keyFn(ctx, input) || null; } if (!identifier) { identifier = "anonymous"; } const { allowed, resetAt } = await (0, rate_limit_runtime_1.checkRateLimit)(identifier, prefix, max, windowMs); if (!allowed) { throw new server_1.TRPCError({ code: "TOO_MANY_REQUESTS", message: `Rate limit exceeded for "${prefix}". Retry after ${new Date(resetAt).toISOString()}.`, }); } return next({ ctx }); }); const rateLimitedProcedure = (prefix, max, windowMs, keyFn) => exports.publicProcedure.use(applyRateLimit(prefix, max, windowMs, keyFn)); exports.rateLimitedProcedure = rateLimitedProcedure; const rateLimitedWorkspaceProcedure = (prefix, max, windowMs) => exports.workspaceProcedure.use(applyRateLimit(prefix, max, windowMs)); exports.rateLimitedWorkspaceProcedure = rateLimitedWorkspaceProcedure; //# sourceMappingURL=trpc.init.js.map