life-manager/codebase/features/scheduling/backend/scheduling.controller.ts

252 lines
8.2 KiB
TypeScript

import {
BadRequestException,
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
Put,
Query,
} from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { SchedulingService } from './scheduling.service';
import { DailyPlanService } from './daily-plan.service';
import { DailyLogService } from './daily-log.service';
import { CreateTimeBlockDto } from './dto/create-time-block.dto';
import { UpdateTimeBlockDto } from './dto/update-time-block.dto';
import { AddTimeBlockNoteDto } from './dto/add-time-block-note.dto';
import { UpdateDailyPlanDto, UpdateReflectionDto } from './dto/create-daily-plan.dto';
import { CreateDailyLogEntryDto } from './dto/create-daily-log-entry.dto';
import { QueryTimeBlocksDto } from './dto/query-scheduling.dto';
import { AvailabilityConfigDto, GenerateAvailabilityBlocksDto } from './dto/availability-config.dto';
import type { AvailabilityConfig, TimeBlockNote } from '@life-platform/shared';
import { TimeBlock } from './entities/time-block.entity';
import { DailyPlan } from './entities/daily-plan.entity';
import { DailyLogEntry } from './entities/daily-log-entry.entity';
@ApiTags('Scheduling')
@Controller('scheduling')
export class SchedulingController {
constructor(
private readonly schedulingService: SchedulingService,
private readonly dailyPlanService: DailyPlanService,
private readonly dailyLogService: DailyLogService,
) {}
@Get('time-blocks')
@ApiOperation({ summary: 'Query time blocks by date or date range' })
getTimeBlocks(@Query() query: QueryTimeBlocksDto): Promise<TimeBlock[]> {
return this.schedulingService.queryTimeBlocks(query);
}
@Post('time-blocks')
@ApiOperation({ summary: 'Create a time block' })
createTimeBlock(@Body() dto: CreateTimeBlockDto): Promise<TimeBlock> {
return this.schedulingService.createTimeBlock(dto);
}
@Patch('time-blocks/:id')
@ApiOperation({ summary: 'Update a time block' })
updateTimeBlock(
@Param('id') id: string,
@Body() dto: UpdateTimeBlockDto,
): Promise<TimeBlock> {
return this.schedulingService.updateTimeBlock(id, dto);
}
@Delete('time-blocks')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Bulk delete time blocks by date or date range' })
bulkDeleteTimeBlocks(@Query() query: QueryTimeBlocksDto): Promise<void> {
if (!query.date && !query.startDate) {
throw new BadRequestException('Must provide date or startDate for bulk delete');
}
if (query.endDate && !query.startDate && !query.date) {
throw new BadRequestException('Must provide startDate or date when endDate is specified');
}
return this.schedulingService.bulkDeleteTimeBlocks(query);
}
@Delete('time-blocks/:id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a time block' })
deleteTimeBlock(@Param('id') id: string): Promise<void> {
return this.schedulingService.deleteTimeBlock(id);
}
// --- Time Block Notes ---
@Get('time-blocks/current')
@ApiOperation({ summary: 'Get current or most recently ended time block' })
getCurrentBlock(): Promise<TimeBlock | null> {
return this.schedulingService.findCurrentOrRecentBlock();
}
@Get('time-blocks/:id/notes')
@ApiOperation({ summary: 'Get notes for a time block' })
getNotes(@Param('id') id: string): Promise<TimeBlockNote[]> {
return this.schedulingService.getNotes(id);
}
@Post('time-blocks/:id/notes')
@ApiOperation({ summary: 'Add a note to a time block' })
addNote(
@Param('id') id: string,
@Body() dto: AddTimeBlockNoteDto,
): Promise<TimeBlockNote> {
return this.schedulingService.addNote(id, dto);
}
@Delete('time-blocks/:id/notes/:noteId')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Remove a note from a time block' })
removeNote(
@Param('id') id: string,
@Param('noteId') noteId: string,
): Promise<void> {
return this.schedulingService.removeNote(id, noteId);
}
@Get('daily-plan/:date')
@ApiOperation({ summary: 'Get or create daily plan for a date' })
getDailyPlan(@Param('date') date: string): Promise<DailyPlan> {
return this.dailyPlanService.findByDate(date);
}
@Delete('daily-plan/:date')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete daily plan for a date' })
deleteDailyPlan(@Param('date') date: string): Promise<void> {
this.validateDateParam(date);
return this.dailyPlanService.deleteByDate(date);
}
@Put('daily-plan/:date')
@ApiOperation({ summary: 'Upsert daily plan for a date' })
upsertDailyPlan(
@Param('date') date: string,
@Body() dto: UpdateDailyPlanDto,
): Promise<DailyPlan> {
return this.dailyPlanService.upsert(date, dto);
}
@Patch('daily-plan/:date/reflection')
@ApiOperation({ summary: 'Update evening reflection for a daily plan' })
updateReflection(
@Param('date') date: string,
@Body() dto: UpdateReflectionDto,
): Promise<DailyPlan> {
return this.dailyPlanService.updateReflection(date, dto);
}
// --- Daily Log Entries ---
@Get('daily-log')
@ApiOperation({ summary: 'Query log entries by type (cross-date)' })
queryDailyLogs(
@Query('type') type?: string,
@Query('types') types?: string,
@Query('limit') limit?: string,
@Query('offset') offset?: string,
@Query('dateFrom') dateFrom?: string,
@Query('dateTo') dateTo?: string,
): Promise<DailyLogEntry[]> {
const parsedLimit = limit ? parseInt(limit, 10) : 10;
const parsedOffset = offset ? parseInt(offset, 10) : undefined;
if (types) {
return this.dailyLogService.findRecent({
types: types.split(','),
limit: parsedLimit,
offset: parsedOffset,
dateFrom,
dateTo,
});
}
if (type) {
return this.dailyLogService.findByType(type, {
limit: parsedLimit,
offset: parsedOffset,
});
}
return this.dailyLogService.findRecent({
limit: parsedLimit,
offset: parsedOffset,
dateFrom,
dateTo,
});
}
@Post('daily-log')
@ApiOperation({ summary: 'Create a log entry (no date param required)' })
createDailyLog(@Body() dto: CreateDailyLogEntryDto): Promise<DailyLogEntry> {
return this.dailyLogService.create(dto);
}
@Get('daily-plan/:date/log')
@ApiOperation({ summary: 'Get log entries for a date' })
getDailyLog(@Param('date') date: string): Promise<DailyLogEntry[]> {
this.validateDateParam(date);
return this.dailyLogService.findByDate(date);
}
@Post('daily-plan/:date/log')
@ApiOperation({ summary: 'Add a log entry for a date' })
addDailyLogEntry(
@Param('date') date: string,
@Body() dto: CreateDailyLogEntryDto,
): Promise<DailyLogEntry> {
this.validateDateParam(date);
return this.dailyLogService.create({ ...dto, date });
}
@Delete('daily-plan/:date/log/:id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a log entry' })
deleteDailyLogEntry(@Param('id') id: string): Promise<void> {
return this.dailyLogService.delete(id);
}
@Delete('daily-log/:id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a log entry by ID (cross-date)' })
deleteDailyLogById(@Param('id') id: string): Promise<void> {
return this.dailyLogService.delete(id);
}
// --- Availability Windows ---
@Get('availability-windows/config')
@ApiOperation({ summary: 'Get availability window configuration' })
getAvailabilityConfig(): Promise<AvailabilityConfig> {
return this.schedulingService.getAvailabilityConfig();
}
@Put('availability-windows/config')
@ApiOperation({ summary: 'Update availability window configuration' })
updateAvailabilityConfig(
@Body() dto: AvailabilityConfigDto,
): Promise<AvailabilityConfig> {
return this.schedulingService.setAvailabilityConfig(dto);
}
@Post('availability-windows/generate')
@ApiOperation({ summary: 'Generate availability time blocks for a date' })
generateAvailabilityBlocks(
@Body() dto: GenerateAvailabilityBlocksDto,
): Promise<TimeBlock[]> {
return this.schedulingService.generateAvailabilityBlocks(dto.date);
}
private validateDateParam(date: string): void {
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
throw new BadRequestException('date param must be in YYYY-MM-DD format');
}
}
}