import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common"; import type { CharacterAgeEvidenceType } from "@kokorone/db"; import { PrismaService } from "../prisma/prisma.service.js"; import { AgeDeterminationService } from "./age-determination.service.js"; import { RomanceEligibilityService } from "./romance-eligibility.service.js"; import { RomanceService, type RomanceCurveEventType } from "./romance.service.js"; import { ConfessionService } from "./confession.service.js"; import { MilestoneService } from "./milestone.service.js"; import type { MilestoneType } from "./constants.js"; interface AgeEvidenceBody { evidenceType: CharacterAgeEvidenceType; impliesAdult: boolean; isEpilogue?: boolean; description: string; } interface RomanceEventBody { userId: string; eventType: RomanceCurveEventType; severe?: boolean; now?: string; } interface UserOnlyBody { userId: string; now?: string; } interface MilestoneBody { userId: string; milestoneType: MilestoneType; now?: string; } interface AdultConsentBody { userId: string; verifiedAdult: boolean; optedIn: boolean; } @Controller("romance") export class RomanceController { constructor( private readonly prisma: PrismaService, private readonly ageDetermination: AgeDeterminationService, private readonly eligibility: RomanceEligibilityService, private readonly romance: RomanceService, private readonly confession: ConfessionService, private readonly milestone: MilestoneService, ) {} // L-6 年齡證據登錄:結論永遠是重新算出來的,不直接寫最終結論欄位。 @Post(":characterId/age-evidence") async addAgeEvidence(@Param("characterId") characterId: string, @Body() body: AgeEvidenceBody) { return this.prisma.client.characterAgeEvidence.create({ data: { characterId, evidenceType: body.evidenceType, impliesAdult: body.impliesAdult, isEpilogue: body.isEpilogue ?? false, description: body.description, }, }); } @Get(":characterId/age-determination") async getAgeDetermination(@Param("characterId") characterId: string) { return this.ageDetermination.determine(characterId); } @Get(":characterId/eligibility") async getEligibility(@Param("characterId") characterId: string) { return this.eligibility.getEligibility(characterId); } // L-7 引擎層唯一入口:任何嘗試皆記錄,不論放行與否。 @Post(":characterId/adult-content-check") async checkAdultContent(@Param("characterId") characterId: string, @Body() body: UserOnlyBody) { return this.eligibility.checkAdultContentAllowed(characterId, body.userId); } @Post("adult-mode/consent") async setAdultModeConsent(@Body() body: AdultConsentBody) { return this.prisma.client.adultModeConsent.upsert({ where: { userId: body.userId }, update: { verifiedAdult: body.verifiedAdult, optedIn: body.optedIn }, create: { userId: body.userId, verifiedAdult: body.verifiedAdult, optedIn: body.optedIn }, }); } @Get(":characterId/:userId/state") async getState( @Param("characterId") characterId: string, @Param("userId") userId: string, @Query("now") now?: string, ) { return this.romance.getState(characterId, userId, now ? new Date(now) : undefined); } @Post(":characterId/event") async recordEvent(@Param("characterId") characterId: string, @Body() body: RomanceEventBody) { return this.romance.recordEvent(characterId, body.userId, body.eventType as RomanceCurveEventType, { now: body.now ? new Date(body.now) : undefined, severe: body.severe, }); } @Post(":characterId/confess") async confess(@Param("characterId") characterId: string, @Body() body: UserOnlyBody) { return this.confession.confess(characterId, body.userId, body.now ? new Date(body.now) : undefined); } @Post(":characterId/milestone") async attemptMilestone(@Param("characterId") characterId: string, @Body() body: MilestoneBody) { return this.milestone.attemptMilestone( characterId, body.userId, body.milestoneType, body.now ? new Date(body.now) : undefined, ); } }