MicProtocol / apps /api /src /network /network.controller.ts
Benaah's picture
Upload folder using huggingface_hub
6abfd9d verified
Raw History Blame Contribute Delete
11.9 kB
import {
Controller,
Get,
Post,
Put,
Delete,
Param,
Body,
Query,
UseGuards,
Req,
ForbiddenException,
} from "@nestjs/common";
import {
ApiTags,
ApiOperation,
ApiParam,
ApiQuery,
ApiResponse,
ApiBearerAuth,
} from "@nestjs/swagger";
import { Throttle } from "@nestjs/throttler";
import { BetterAuthGuard } from "../auth/better-auth.guard";
import { PodcastAccessGuard } from "../common/guards/podcast-access.guard";
import { NetworkService } from "./network.service";
import {
CreateGuestPublicProfileDto,
UpdateGuestPublicProfileDto,
GuestNetworkSearchDto,
CreateNetworkBookingRequestDto,
RespondToBookingRequestDto,
CreateNetworkBookmarkDto,
UpdateNetworkBookmarkDto,
CreateHostReviewDto,
TrackProfileViewDto,
} from "./network.dto";
@ApiTags("Network")
@Controller("network")
export class NetworkController {
constructor(private readonly networkService: NetworkService) {}
// ─── Public Endpoints ────────────────────────────────
@Get("guests/search")
@Throttle({ default: { limit: 30, ttl: 60000 } })
@ApiOperation({ summary: "Search the guest network" })
@ApiResponse({ status: 200, description: "Search results" })
async searchGuests(@Query() query: GuestNetworkSearchDto) {
return this.networkService.searchProfiles(query as any);
}
@Get("guests/trending")
@ApiOperation({ summary: "Get trending guest voices" })
@ApiQuery({ name: "region", required: false, example: "east-africa" })
@ApiQuery({ name: "limit", required: false, example: 10 })
async getTrending(
@Query("region") region?: string,
@Query("limit") limit?: number,
) {
return this.networkService.getTrending(region, limit || 10);
}
@Get("guests/:vanityUrl")
@ApiOperation({ summary: "Get public guest profile by vanity URL" })
@ApiParam({ name: "vanityUrl", example: "john-nduati" })
async getProfile(@Param("vanityUrl") vanityUrl: string) {
return this.networkService.getProfileByVanityUrl(vanityUrl);
}
@Post("views")
@ApiOperation({ summary: "Track a profile view" })
async trackView(@Body() body: TrackProfileViewDto) {
return this.networkService.trackView(body as any);
}
// ─── Protected Endpoints ─────────────────────────────
@Post("profiles")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Create your public guest profile" })
@ApiResponse({ status: 201, description: "Profile created" })
@ApiResponse({ status: 409, description: "Profile already exists" })
async createProfile(
@Req() req: any,
@Body() body: CreateGuestPublicProfileDto,
) {
return this.networkService.createProfile(req.userId, body as any);
}
@Put("profiles/me")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Update your public guest profile" })
async updateMyProfile(
@Req() req: any,
@Body() body: UpdateGuestPublicProfileDto,
) {
return this.networkService.updateProfile(req.userId, body as any);
}
@Get("profiles/me")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Get your public profile with analytics" })
async getMyProfile(@Req() req: any) {
return this.networkService.getMyProfile(req.userId);
}
@Get("profiles/:id/views")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Get profile view statistics" })
@ApiParam({ name: "id" })
@ApiQuery({ name: "days", required: false, example: 30 })
async getViewStats(
@Req() req: any,
@Param("id") id: string,
@Query("days") days?: number,
) {
// SECURITY: `req.userId` is set by BetterAuthGuard from the verified session.
// Never trust client-supplied identity headers (e.g. `x-user-id`) for
// authorization decisions. getProfileViewStats enforces ownership
// server-side.
return this.networkService.getProfileViewStats(req.userId, id, days || 30);
}
// ─── Bookmarks ───────────────────────────────────────
@Post("bookmarks")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Bookmark a guest profile" })
async createBookmark(
@Body() body: CreateNetworkBookmarkDto,
@Req() req: any,
) {
return this.networkService.createBookmark(body as any, req.userId);
}
@Put("bookmarks/:id")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Update a bookmark" })
async updateBookmark(
@Req() req: any,
@Param("id") id: string,
@Body() body: UpdateNetworkBookmarkDto,
) {
return this.networkService.updateBookmark(req.userId, id, body as any);
}
@Delete("bookmarks/:id")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Remove a bookmark" })
async removeBookmark(
@Req() req: any,
@Param("id") id: string,
) {
return this.networkService.removeBookmark(req.userId, id);
}
@Get("bookmarks")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "List bookmarked guest profiles" })
@ApiQuery({ name: "podcastId", required: true })
@ApiQuery({ name: "status", required: false })
async listBookmarks(
@Req() req: any,
@Query("podcastId") podcastId: string,
@Query("status") status?: string,
) {
return this.networkService.listBookmarks(podcastId, req.userId, status);
}
// ─── Booking Requests ────────────────────────────────
@Post("booking-requests")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Send a booking request to a guest" })
@ApiResponse({ status: 201, description: "Booking request created" })
async createBookingRequest(
@Body() body: CreateNetworkBookingRequestDto,
@Req() req: any,
) {
return this.networkService.createBookingRequest(body as any, req.userId);
}
@Put("booking-requests/:id/respond")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Accept or decline a booking request" })
@ApiParam({ name: "id" })
async respondToBookingRequest(
@Req() req: any,
@Param("id") id: string,
@Body() body: RespondToBookingRequestDto,
) {
return this.networkService.respondToBookingRequest(
id,
req.userId,
body.status,
);
}
@Get("booking-requests/received")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "List received booking requests" })
@ApiQuery({ name: "profileId", required: true })
@ApiQuery({ name: "status", required: false })
async listBookingRequests(
@Req() req: any,
@Query("profileId") profileId: string,
@Query("status") status?: string,
) {
return this.networkService.listBookingRequests(req.userId, profileId, status);
}
@Get("booking-requests/sent")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "List sent booking requests" })
@ApiQuery({ name: "podcastId", required: true })
@ApiQuery({ name: "status", required: false })
async listSentBookingRequests(
@Req() req: any,
@Query("podcastId") podcastId: string,
@Query("status") status?: string,
) {
return this.networkService.listSentBookingRequests(req.userId, podcastId, status);
}
// ─── Reviews ─────────────────────────────────────────
@Post("reviews")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Submit a host review for a guest" })
async submitReview(
@Req() req: any,
@Body() body: CreateHostReviewDto,
) {
return this.networkService.submitReview(req.userId, body as any);
}
// ─── Reputation ──────────────────────────────────────
@Get("profiles/:id/reputation")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Get reputation insights for a profile" })
@ApiParam({ name: "id" })
async getReputationInsights(
@Req() req: any,
@Param("id") id: string,
) {
return this.networkService.getReputationInsights(req.userId, id);
}
@Post("profiles/:id/reputation/recalculate")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Recalculate reputation score for a profile" })
@ApiParam({ name: "id" })
async recalculateReputation(
@Req() req: any,
@Param("id") id: string,
) {
return this.networkService.recalculateReputation(req.userId, id);
}
// ─── Recommendations ─────────────────────────────────
@Get("profiles/:id/recommendations")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Get guest recommendations based on network graph" })
@ApiParam({ name: "id" })
@ApiQuery({ name: "limit", required: false, example: 5 })
async getRecommendations(
@Req() req: any,
@Param("id") id: string,
@Query("limit") limit?: number,
) {
return this.networkService.getRecommendations(req.userId, id, limit || 5);
}
@Get("profiles/:id/network-stats")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Get social network statistics for a profile" })
@ApiParam({ name: "id" })
async getProfileNetworkStats(
@Req() req: any,
@Param("id") id: string,
) {
return this.networkService.getProfileNetworkStats(req.userId, id);
}
// ─── Podcast Network Stats ───────────────────────────
@Get("stats/:podcastId")
@UseGuards(BetterAuthGuard, PodcastAccessGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Get network usage statistics for a podcast" })
@ApiParam({ name: "podcastId" })
async getPodcastNetworkStats(
@Param("podcastId") podcastId: string,
@Req() req: any,
) {
return this.networkService.getPodcastNetworkStats(podcastId, req.userId);
}
// ─── Admin ───────────────────────────────────────────
@Put("admin/verify/:profileId")
@UseGuards(BetterAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Verify a guest profile (admin)" })
@ApiParam({ name: "profileId" })
async verifyProfile(
@Req() req: any,
@Param("profileId") profileId: string,
@Body() body: { status: "basic" | "verified" | "featured" },
) {
// SECURITY: `req.userId` is set by BetterAuthGuard from the verified session.
// Using the client-controlled `x-user-id` header here would let any
// authenticated user impersonate an admin for this endpoint.
return this.networkService.verifyProfile(req.userId, profileId, body.status);
}
}