MicProtocol / apps /api /src /network /network.router.ts
Benaah's picture
Upload folder using huggingface_hub
6abfd9d verified
Raw History Blame Contribute Delete
13.6 kB
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();
}),
});
}
}