feat(water-records): 新增喝水记录功能模块

新增完整的喝水记录管理功能,支持用户记录每日喝水情况、设置目标和查看统计信息。功能包括:

- 创建、查询、更新和删除喝水记录
- 设置和管理每日喝水目标
- 获取今日喝水统计和完成率分析
- 支持分页查询和日期范围筛选
- 完整的数据验证和错误处理机制

该模块已从用户模块中独立出来,提供REST API接口,包含数据库迁移脚本和详细文档。
This commit is contained in:
richarjiang
2025-09-01 11:02:13 +08:00
parent 0488fe62a1
commit 2c2e964199
13 changed files with 1131 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
import {
Controller,
Get,
Post,
Body,
Param,
HttpCode,
HttpStatus,
Put,
Delete,
Query,
Logger,
UseGuards,
NotFoundException,
} from '@nestjs/common';
import { ApiOperation, ApiBody, ApiResponse, ApiTags, ApiQuery } from '@nestjs/swagger';
import { WaterRecordsService } from './water-records.service';
import {
CreateWaterRecordDto,
UpdateWaterRecordDto,
WaterRecordResponseDto,
GetWaterRecordsQueryDto,
WaterRecordsListResponseDto,
UpdateWaterGoalDto,
WaterGoalResponseDto,
TodayWaterStatsResponseDto
} from './dto/water-record.dto';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser } from '../common/decorators/current-user.decorator';
import { AccessTokenPayload } from '../users/services/apple-auth.service';
@ApiTags('water-records')
@Controller('water-records')
export class WaterRecordsController {
private readonly logger = new Logger(WaterRecordsController.name);
constructor(
private readonly waterRecordsService: WaterRecordsService,
) { }
/**
* 创建喝水记录
*/
@UseGuards(JwtAuthGuard)
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: '创建喝水记录' })
@ApiBody({ type: CreateWaterRecordDto })
@ApiResponse({ status: 201, description: '成功创建喝水记录', type: WaterRecordResponseDto })
async createWaterRecord(
@Body() createDto: CreateWaterRecordDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<WaterRecordResponseDto> {
this.logger.log(`创建喝水记录 - 用户ID: ${user.sub}, 喝水量: ${createDto.amount}ml`);
return this.waterRecordsService.createWaterRecord(user.sub, createDto);
}
/**
* 获取喝水记录列表
*/
@UseGuards(JwtAuthGuard)
@Get()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '获取喝水记录列表' })
@ApiQuery({ name: 'startDate', required: false, description: '开始日期 (YYYY-MM-DD)' })
@ApiQuery({ name: 'endDate', required: false, description: '结束日期 (YYYY-MM-DD)' })
@ApiQuery({ name: 'page', required: false, description: '页码默认1' })
@ApiQuery({ name: 'limit', required: false, description: '每页数量默认20' })
@ApiResponse({ status: 200, description: '成功获取喝水记录列表', type: WaterRecordsListResponseDto })
async getWaterRecords(
@Query() query: GetWaterRecordsQueryDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<WaterRecordsListResponseDto> {
this.logger.log(`获取喝水记录列表 - 用户ID: ${user.sub}`);
return this.waterRecordsService.getWaterRecords(user.sub, query);
}
/**
* 更新喝水记录
*/
@UseGuards(JwtAuthGuard)
@Put(':id')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '更新喝水记录' })
@ApiBody({ type: UpdateWaterRecordDto })
@ApiResponse({ status: 200, description: '成功更新喝水记录', type: WaterRecordResponseDto })
async updateWaterRecord(
@Param('id') recordId: string,
@Body() updateDto: UpdateWaterRecordDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<WaterRecordResponseDto> {
this.logger.log(`更新喝水记录 - 用户ID: ${user.sub}, 记录ID: ${recordId}`);
return this.waterRecordsService.updateWaterRecord(user.sub, parseInt(recordId), updateDto);
}
/**
* 删除喝水记录
*/
@UseGuards(JwtAuthGuard)
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: '删除喝水记录' })
@ApiResponse({ status: 204, description: '成功删除喝水记录' })
async deleteWaterRecord(
@Param('id') recordId: string,
@CurrentUser() user: AccessTokenPayload,
): Promise<void> {
this.logger.log(`删除喝水记录 - 用户ID: ${user.sub}, 记录ID: ${recordId}`);
const success = await this.waterRecordsService.deleteWaterRecord(user.sub, parseInt(recordId));
if (!success) {
throw new NotFoundException('喝水记录不存在');
}
}
/**
* 更新喝水目标
*/
@UseGuards(JwtAuthGuard)
@Put('goal/daily')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '更新每日喝水目标' })
@ApiBody({ type: UpdateWaterGoalDto })
@ApiResponse({ status: 200, description: '成功更新喝水目标', type: WaterGoalResponseDto })
async updateWaterGoal(
@Body() updateDto: UpdateWaterGoalDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<WaterGoalResponseDto> {
this.logger.log(`更新喝水目标 - 用户ID: ${user.sub}, 目标: ${updateDto.dailyWaterGoal}ml`);
return this.waterRecordsService.updateWaterGoal(user.sub, updateDto);
}
/**
* 获取今日喝水统计
*/
@UseGuards(JwtAuthGuard)
@Get('stats/today')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '获取今日喝水统计' })
@ApiResponse({ status: 200, description: '成功获取今日喝水统计', type: TodayWaterStatsResponseDto })
async getTodayWaterStats(
@CurrentUser() user: AccessTokenPayload,
): Promise<TodayWaterStatsResponseDto> {
this.logger.log(`获取今日喝水统计 - 用户ID: ${user.sub}`);
return this.waterRecordsService.getTodayWaterStats(user.sub);
}
}