import { Injectable } from "@nestjs/common"; import { z } from "zod"; import { router, publicProcedure, protectedProcedure, adminPermissionProcedure, } from "../trpc/trpc.init"; import { NetworkService } from "./network.service"; import { TrustScoreEngine } from "./trust-score.service"; import { VerificationPipeline } from "./verification-pipeline.service"; import { AdminPermission, createGuestPublicProfileSchema, updateGuestPublicProfileSchema, guestNetworkSearchSchema, createNetworkBookingRequestSchema, respondToBookingRequestSchema, createNetworkBookmarkSchema, updateNetworkBookmarkSchema, createHostReviewSchema, trackProfileViewSchema, requestBadgeSchema, revokeBadgeSchema, getVerificationStatusSchema, getTrustScoreSchema, recomputeAllTrustScoresSchema, BookmarkStatus, ProfileViewSource, VerificationBadgeType, } from "@micprotocol/shared-types"; @Injectable() export class NetworkRouter { constructor( private readonly networkService: NetworkService, private readonly trustScoreEngine: TrustScoreEngine, private readonly verificationPipeline: VerificationPipeline, ) {} get router() { return router({ // ─── Profile Management ─────────────────────── createProfile: protectedProcedure .input(createGuestPublicProfileSchema) .mutation(async ({ ctx, input }) => { return this.networkService.createProfile(ctx.userId!, input); }), updateProfile: protectedProcedure .input(updateGuestPublicProfileSchema) .mutation(async ({ ctx, input }) => { return this.networkService.updateProfile(ctx.userId!, input); }), getMyProfile: protectedProcedure .query(async ({ ctx }) => { return this.networkService.getMyProfile(ctx.userId!); }), // Public profile by vanity URL getProfile: publicProcedure .input(z.object({ vanityUrl: z.string() })) .query(async ({ input }) => { return this.networkService.getProfileByVanityUrl(input.vanityUrl); }), getProfileById: publicProcedure .input(z.object({ id: z.string().uuid() })) .query(async ({ input }) => { return this.networkService.getProfileById(input.id); }), // ─── Discovery Search ───────────────────────── search: publicProcedure .input(guestNetworkSearchSchema) .query(async ({ input }) => { return this.networkService.searchProfiles(input); }), trending: publicProcedure .input( z.object({ region: z.string().optional(), limit: z.number().int().min(1).max(50).default(10), }) ) .query(async ({ input }) => { return this.networkService.getTrending(input.region, input.limit); }), // ─── Profile Views ──────────────────────────── trackView: publicProcedure .input(trackProfileViewSchema) .mutation(async ({ input }) => { return this.networkService.trackView(input); }), getViewStats: protectedProcedure .input( z.object({ profileId: z.string().uuid(), days: z.number().int().min(1).max(365).default(30), }) ) .query(async ({ ctx, input }) => { return this.networkService.getProfileViewStats( ctx.userId!, input.profileId, input.days, ); }), // ─── Bookmarks ──────────────────────────────── bookmark: protectedProcedure .input(createNetworkBookmarkSchema) .mutation(async ({ ctx, input }) => { return this.networkService.createBookmark(input, ctx.userId!); }), updateBookmark: protectedProcedure .input( z.object({ id: z.string().uuid(), data: updateNetworkBookmarkSchema, }) ) .mutation(async ({ ctx, input }) => { return this.networkService.updateBookmark(ctx.userId!, input.id, input.data); }), removeBookmark: protectedProcedure .input(z.object({ id: z.string().uuid() })) .mutation(async ({ ctx, input }) => { return this.networkService.removeBookmark(ctx.userId!, input.id); }), listBookmarks: protectedProcedure .input( z.object({ podcastId: z.string().uuid(), status: z.nativeEnum(BookmarkStatus).optional(), }) ) .query(async ({ ctx, input }) => { return this.networkService.listBookmarks( input.podcastId, ctx.userId!, input.status, ); }), // ─── Booking Requests ───────────────────────── createBookingRequest: protectedProcedure .input(createNetworkBookingRequestSchema) .mutation(async ({ ctx, input }) => { return this.networkService.createBookingRequest(input, ctx.userId!); }), respondToBookingRequest: protectedProcedure .input(respondToBookingRequestSchema) .mutation(async ({ ctx, input }) => { return this.networkService.respondToBookingRequest( input.requestId, ctx.userId!, input.status, ); }), listBookingRequests: protectedProcedure .input( z.object({ profileId: z.string().uuid(), status: z.string().optional(), }) ) .query(async ({ ctx, input }) => { return this.networkService.listBookingRequests( ctx.userId!, input.profileId, input.status, ); }), listSentBookingRequests: protectedProcedure .input( z.object({ podcastId: z.string().uuid(), status: z.string().optional(), }) ) .query(async ({ ctx, input }) => { return this.networkService.listSentBookingRequests( ctx.userId!, input.podcastId, input.status, ); }), // ─── Reviews ────────────────────────────────── submitReview: protectedProcedure .input(createHostReviewSchema) .mutation(async ({ ctx, input }) => { return this.networkService.submitReview(ctx.userId!, input); }), // ─── Reputation ─────────────────────────────── getReputationInsights: protectedProcedure .input(z.object({ profileId: z.string().uuid() })) .query(async ({ ctx, input }) => { return this.networkService.getReputationInsights(ctx.userId!, input.profileId); }), recalculateReputation: protectedProcedure .input(z.object({ profileId: z.string().uuid() })) .mutation(async ({ ctx, input }) => { return this.networkService.recalculateReputation(ctx.userId!, input.profileId); }), // ─── Recommendations ────────────────────────── getRecommendations: protectedProcedure .input( z.object({ profileId: z.string().uuid(), limit: z.number().int().min(1).max(20).default(5), }) ) .query(async ({ ctx, input }) => { return this.networkService.getRecommendations( ctx.userId!, input.profileId, input.limit ); }), getProfileNetworkStats: protectedProcedure .input(z.object({ profileId: z.string().uuid() })) .query(async ({ ctx, input }) => { return this.networkService.getProfileNetworkStats(ctx.userId!, input.profileId); }), // ─── Podcast Network Stats ──────────────────── stats: protectedProcedure .input(z.object({ podcastId: z.string().uuid() })) .query(async ({ ctx, input }) => { return this.networkService.getPodcastNetworkStats(input.podcastId, ctx.userId!); }), // ─── Admin ──────────────────────────────────── verifyProfile: adminPermissionProcedure(AdminPermission.MODERATION_RESOLVE) .input( z.object({ profileId: z.string().uuid(), status: z.enum(["basic", "verified", "featured"]), }) ) .mutation(async ({ input, ctx }) => { // Defense in depth: verifyProfile() re-checks the caller inside the // service via assertPlatformAdmin. return this.networkService.verifyProfile( ctx.userId!, // Pass current user for authorization input.profileId, input.status, ); }), // ─── Trust Score ───────────────────────────── getTrustScore: protectedProcedure .input(getTrustScoreSchema) .query(async ({ input }) => { return this.trustScoreEngine.getTrustScore(input.profileId); }), recomputeTrustScore: adminPermissionProcedure(AdminPermission.OPS_HEALTH) .input(z.object({ profileId: z.string().uuid() })) .mutation(async ({ input }) => { return this.trustScoreEngine.computeAndPersist(input.profileId); }), recomputeAllTrustScores: adminPermissionProcedure(AdminPermission.OPS_HEALTH) .input(recomputeAllTrustScoresSchema) .mutation(async () => { return this.trustScoreEngine.recomputeAll(); }), // ─── Verification Pipeline ─────────────────── requestBadge: protectedProcedure .input(requestBadgeSchema) .mutation(async ({ ctx, input }) => { return this.verificationPipeline.requestBadge({ profileId: input.profileId, badgeType: input.badgeType as any, evidence: input.evidence, adminUserId: input.badgeType === "identity" ? ctx.userId! : undefined, }); }), revokeBadge: adminPermissionProcedure(AdminPermission.MODERATION_TAKEDOWN) .input(revokeBadgeSchema) .mutation(async ({ ctx, input }) => { // Defense in depth: revokeBadge() re-checks the caller inside the // service via assertPlatformAdmin. return this.verificationPipeline.revokeBadge( ctx.userId!, input.profileId, input.badgeType as any, ); }), getVerificationStatus: protectedProcedure .input(getVerificationStatusSchema) .query(async ({ input }) => { return this.verificationPipeline.getVerificationStatus(input.profileId); }), // Grants verification badges — a moderation decision surface (and it // targets arbitrary profileIds), so it requires MODERATION_RESOLVE. autoVerifyBadges: adminPermissionProcedure(AdminPermission.MODERATION_RESOLVE) .input(z.object({ profileId: z.string().uuid() })) .mutation(async ({ input }) => { return this.verificationPipeline.autoVerifyEligibleBadges(input.profileId); }), // ─── Network Health ────────────────────────── getNetworkHealth: adminPermissionProcedure(AdminPermission.OPS_READ).query(async () => { return this.networkService.getNetworkHealth(); }), }); } }