import { Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common'; import { StoreService } from './store.service'; import { StoreCategoryService } from './store-category.service'; import { RedeemService } from '../redeem/redeem.service'; import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard'; import { OptionalJwtAuthGuard } from '../../common/guards/optional-jwt-auth.guard'; import { PartnerPrimaryGuard } from '../../common/guards/partner-primary.guard'; import { RequirePartnerPermissions } from '../../common/decorators/partner-permission.decorator'; import { PartnerPermissionGuard } from '../../common/guards/partner-permission.guard'; import { ShopStoreGuard } from '../../common/guards/shop-store.guard'; import { CurrentUser } from '../../common/decorators/current-user.decorator'; @Controller('stores') export class PublicStoreController { constructor( private readonly storeService: StoreService, private readonly redeemService: RedeemService, ) {} @Get() @UseGuards(OptionalJwtAuthGuard) async list( @CurrentUser() user: AuthUser | undefined, @Query('cityCode') cityCode?: string, @Query('lat') lat?: string, @Query('lng') lng?: string, ) { const userLat = lat != null && lat !== '' ? Number(lat) : undefined; const userLng = lng != null && lng !== '' ? Number(lng) : undefined; const viewerPhone = await this.resolveViewerPhone(user); return this.storeService.listOpenStores(cityCode, userLat, userLng, { phone: viewerPhone }); } @Get(':id/recent-redeems') @UseGuards(OptionalJwtAuthGuard) async recentRedeems( @CurrentUser() user: AuthUser | undefined, @Param('id') id: string, @Query('limit') limit?: string, ) { const viewerPhone = await this.resolveViewerPhone(user); // 与详情同权:白名单门店对不可见用户返回空(不泄露存在核销) try { await this.storeService.getStore(BigInt(id), { phone: viewerPhone }); } catch { return []; } const n = limit != null && limit !== '' ? Number(limit) : 20; return this.redeemService.listPublicStoreRecentRedeems(BigInt(id), Number.isFinite(n) ? n : 20); } @Get(':id') @UseGuards(OptionalJwtAuthGuard) async detail(@CurrentUser() user: AuthUser | undefined, @Param('id') id: string) { const viewerPhone = await this.resolveViewerPhone(user); return this.storeService.getStore(BigInt(id), { phone: viewerPhone }); } private async resolveViewerPhone(user?: AuthUser) { if (!user || user.actorType !== 'USER') return null; return this.storeService.resolveUserPhone(user.actorId); } } @Controller('store-categories') export class PublicStoreCategoriesController { constructor(private readonly categories: StoreCategoryService) {} @Get() list() { return this.categories.listTree({ includeDisabled: false, ensure: true }); } } @Controller('partner/store-categories') @UseGuards(JwtAuthGuard) export class PartnerStoreCategoriesController { constructor(private readonly categories: StoreCategoryService) {} @Get() list() { return this.categories.listTree({ includeDisabled: false, ensure: true }); } } @Controller('partner/stores') @UseGuards(JwtAuthGuard) export class PartnerStoreController { constructor(private readonly storeService: StoreService) {} @Get() list(@CurrentUser() user: AuthUser) { return this.storeService.partnerListStores(user.actorId); } @Get('cities') cities(@CurrentUser() user: AuthUser) { return this.storeService.partnerListCities(user.actorId); } @Get('phone-available') phoneAvailable(@Query('phone') phone?: string) { return this.storeService.partnerCheckStorePhone(phone ?? ''); } @Post('send-phone-sms') sendPhoneSms(@Body() body: { phone: string }) { return this.storeService.sendPartnerStorePhoneSms(body.phone); } @Get(':id') detail(@CurrentUser() user: AuthUser, @Param('id') id: string) { return this.storeService.partnerGetStore(user.actorId, BigInt(id)); } @Post() create(@CurrentUser() user: AuthUser, @Body() body: Record) { return this.storeService.createStore(user.actorId, body); } @Put(':id/status') updateStatus( @CurrentUser() user: AuthUser, @Param('id') id: string, @Body() body: { status: 'OPEN' | 'PAUSED' | 'CLOSED' }, ) { return this.storeService.partnerUpdateStoreStatus(user.actorId, BigInt(id), body.status); } @Put(':id/basic') updateBasic( @CurrentUser() user: AuthUser, @Param('id') id: string, @Body() body: Record, ) { return this.storeService.partnerUpdateStoreBasic(user.actorId, BigInt(id), body); } @Put(':id/media') updateMedia( @CurrentUser() user: AuthUser, @Param('id') id: string, @Body() body: Record, ) { return this.storeService.partnerUpdateStoreMedia(user.actorId, BigInt(id), body); } } @Controller('partner/dashboard') @UseGuards(JwtAuthGuard, PartnerPermissionGuard) export class PartnerDashboardController { constructor(private readonly storeService: StoreService) {} @Get() @RequirePartnerPermissions('store:create', 'store:manage', 'order:view', 'warehouse:manage') dashboard(@CurrentUser() user: AuthUser) { return this.storeService.partnerDashboard(user.actorId); } /** 任意合伙人子账号可看同主账号排行(激励),不校验业务权限点 */ @Get('leaderboard') leaderboard( @CurrentUser() user: AuthUser, @Query('period') period?: string, ) { const normalized = period === 'month' || period === 'lastMonth' || period === 'total' ? period : 'total'; return this.storeService.partnerLeaderboard(user.actorId, normalized); } } @Controller('partner/reports') @UseGuards(JwtAuthGuard, PartnerPrimaryGuard) export class PartnerReportController { constructor(private readonly storeService: StoreService) {} @Get('weekly') weekly( @CurrentUser() user: AuthUser, @Query('startDate') startDate?: string, ) { return this.storeService.partnerWeeklyReport(user.actorId, startDate); } } @Controller('shop/store') @UseGuards(JwtAuthGuard, ShopStoreGuard) export class ShopStoreController { constructor(private readonly storeService: StoreService) {} @Get() info(@CurrentUser() user: AuthUser) { return this.storeService.getShopStore(user.actorId, user.storeId!); } @Put('status') status(@CurrentUser() user: AuthUser, @Body() body: { status: 'OPEN' | 'PAUSED' }) { return this.storeService.updateShopStatus(user.actorId, user.storeId!, body.status); } } @Controller('shop/dashboard') @UseGuards(JwtAuthGuard, ShopStoreGuard) export class ShopDashboardController { constructor(private readonly redeemService: RedeemService) {} @Get() async dashboard(@CurrentUser() user: AuthUser) { return this.redeemService.getShopDashboard(user.actorId, user.storeId!); } }