feat(server): add membership and time-slot modules

Membership: card type CRUD, deduction/restore logic, valid card lookup (15 tests)
TimeSlot: slot generation from week templates, availability query with booking
status, admin management, cleanup tasks (26 tests)
65 total tests passing
This commit is contained in:
richarjiang
2026-04-02 12:24:07 +08:00
parent a1a91f96d8
commit 593a6e5453
16 changed files with 1746 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
import {
Controller,
Get,
Post,
Put,
Param,
Body,
Query,
UseGuards,
HttpCode,
HttpStatus,
} from '@nestjs/common'
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 { UserRole } from '@mp-pilates/shared'
import { TimeSlotService } from './time-slot.service'
import { SlotGeneratorService } from './slot-generator.service'
import { QuerySlotsDto } from './dto/query-slots.dto'
import { CreateManualSlotDto } from './dto/create-manual-slot.dto'
import { UpdateWeekTemplateDto } from './dto/week-template.dto'
// ---------------------------------------------------------------------------
// Member endpoints
// ---------------------------------------------------------------------------
@UseGuards(JwtAuthGuard)
@Controller('time-slot')
export class TimeSlotController {
constructor(private readonly timeSlotService: TimeSlotService) {}
@Get('available')
getAvailableSlots(
@Query() query: QuerySlotsDto,
@CurrentUser('sub') userId: string,
) {
return this.timeSlotService.getAvailableSlots(query.date, userId)
}
@Get(':id')
getSlotById(@Param('id') id: string) {
return this.timeSlotService.getSlotById(id)
}
}
// ---------------------------------------------------------------------------
// Admin endpoints
// ---------------------------------------------------------------------------
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
@Controller('admin')
export class AdminTimeSlotController {
constructor(
private readonly timeSlotService: TimeSlotService,
private readonly slotGeneratorService: SlotGeneratorService,
) {}
// Week template management
@Get('week-template')
getWeekTemplates() {
return this.timeSlotService.getWeekTemplates()
}
@Put('week-template')
replaceWeekTemplates(@Body() dto: UpdateWeekTemplateDto) {
return this.timeSlotService.replaceWeekTemplates(dto.templates)
}
// Manual slot management
@Post('time-slot/manual')
createManualSlot(@Body() dto: CreateManualSlotDto) {
return this.timeSlotService.createManualSlot(dto)
}
@Put('time-slot/:id/close')
@HttpCode(HttpStatus.OK)
closeSlot(@Param('id') id: string) {
return this.timeSlotService.closeSlot(id)
}
// Slot generation trigger
@Post('generate-slots')
@HttpCode(HttpStatus.OK)
generateSlots() {
return this.slotGeneratorService.generateSlots()
}
}