Files
mp-pilates/packages/server/src/booking/booking.controller.ts
richarjiang 22407a7ff9 fix(booking): 允许管理员/老师通过同一接口取消任意预约
将 cancelBooking 改为接收 actor({ id, isAdmin }),服务端按 JWT role
分流:普通成员仅能取消自己的预约,管理员跳过 owner check。
history 备注自动按 actor 选择「学员/管理员」前缀,审计可追溯。

客户端无感,仍调 PUT /booking/:id/cancel,前端零改动。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-09 19:50:41 +08:00

159 lines
4.5 KiB
TypeScript

import {
BadRequestException,
Body,
Controller,
Get,
Param,
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common'
import { BookingStatus, UserRole } from '@mp-pilates/shared'
import { JwtAuthGuard } from '../auth/jwt-auth.guard'
import { RolesGuard } from '../auth/roles.guard'
import { Roles } from '../auth/roles.decorator'
import { CurrentUser } from '../common/decorators/current-user.decorator'
import { AuthenticatedUser } from '../auth/jwt.strategy'
import { BookingService } from './booking.service'
import { CreateBookingDto } from './dto/create-booking.dto'
import { AdminArrangeBookingDto } from './dto/admin-arrange-booking.dto'
@Controller()
export class BookingController {
constructor(private readonly bookingService: BookingService) {}
// ─── User Endpoints ───────────────────────────────────────────────────────
@Post('booking')
@UseGuards(JwtAuthGuard)
async createBooking(
@CurrentUser('sub') userId: string,
@Body() dto: CreateBookingDto,
) {
return this.bookingService.createBooking(userId, dto)
}
@Put('booking/:id/cancel')
@UseGuards(JwtAuthGuard)
async cancelBooking(
@CurrentUser() user: AuthenticatedUser,
@Param('id') id: string,
) {
return this.bookingService.cancelBooking(
{ id: user.sub, isAdmin: user.role === UserRole.ADMIN },
id,
)
}
@Get('booking/my/activity')
@UseGuards(JwtAuthGuard)
async getPracticeActivity(@CurrentUser('sub') userId: string) {
return this.bookingService.getPracticeActivity(userId)
}
@Get('booking/my/upcoming')
@UseGuards(JwtAuthGuard)
async getUpcomingBookings(@CurrentUser('sub') userId: string) {
return this.bookingService.getUpcomingBookings(userId)
}
@Get('booking/my')
@UseGuards(JwtAuthGuard)
async getMyBookings(
@CurrentUser('sub') userId: string,
@Query('status') status?: BookingStatus,
@Query('page') page?: string,
@Query('limit') limit?: string,
) {
return this.bookingService.getMyBookings(
userId,
status,
page ? Number(page) : 1,
limit ? Number(limit) : 10,
)
}
@Get('booking/:id/history')
@UseGuards(JwtAuthGuard)
async getBookingStatusHistory(@Param('id') id: string) {
return this.bookingService.getBookingStatusHistory(id)
}
@Get('booking/:id')
@UseGuards(JwtAuthGuard)
async getBookingById(@Param('id') id: string) {
return this.bookingService.getBookingById(id)
}
// ─── Admin Endpoints ──────────────────────────────────────────────────────
@Get('admin/bookings')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
async getAllBookings(
@Query('page') page?: string,
@Query('limit') limit?: string,
@Query('status') status?: BookingStatus,
) {
return this.bookingService.getAllBookings(
page ? Number(page) : 1,
limit ? Number(limit) : 10,
status,
)
}
@Post('admin/bookings')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
async arrangeBooking(
@CurrentUser('sub') operatorId: string,
@Body() dto: AdminArrangeBookingDto,
) {
return this.bookingService.adminArrangeBooking(operatorId, dto)
}
@Get('admin/teaching-schedule')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
async getTeachingSchedule(@Query('date') date?: string) {
if (!date) {
throw new BadRequestException('date is required')
}
return this.bookingService.getTeachingScheduleByDate(date)
}
@Put('booking/:id/confirm')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
async confirmBooking(
@CurrentUser('sub') operatorId: string,
@Param('id') id: string,
@Body() body: { remark?: string },
) {
return this.bookingService.confirmBooking(id, operatorId, body.remark)
}
@Put('booking/:id/complete')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
async completeBooking(
@CurrentUser('sub') operatorId: string,
@Param('id') id: string,
@Body() body: { remark?: string },
) {
return this.bookingService.completeBooking(id, operatorId, body.remark)
}
@Put('booking/:id/noshow')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
async markNoShow(
@CurrentUser('sub') operatorId: string,
@Param('id') id: string,
@Body() body: { remark?: string },
) {
return this.bookingService.markNoShow(id, operatorId, body.remark)
}
}