feat: 更新目标管理模块,优化数据库表结构和API逻辑

- 修改目标表和目标完成记录表的字段类型,增强数据一致性和查询性能。
- 移除不必要的搜索字段,简化目标查询DTO,提升查询效率。
- 引入目标状态枚举,增强代码可读性和维护性。
- 添加复合索引以优化查询性能,提升系统响应速度。
- 更新目标管理控制器和服务逻辑,确保与新数据库结构的兼容性。
This commit is contained in:
richarjiang
2025-08-22 11:22:27 +08:00
parent ffc0cd1d13
commit acf8d0c48c
8 changed files with 132 additions and 134 deletions

View File

@@ -1,83 +1,60 @@
-- 创建目标表 -- 创建目标表
CREATE TABLE IF NOT EXISTS t_goals ( CREATE TABLE IF NOT EXISTS `t_goals` (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), `id` char(36) NOT NULL COMMENT '主键ID',
user_id VARCHAR(255) NOT NULL, `user_id` varchar(255) NOT NULL COMMENT '用户ID',
title VARCHAR(255) NOT NULL, `title` varchar(255) NOT NULL COMMENT '目标标题',
description TEXT, `description` text COMMENT '目标描述',
repeat_type VARCHAR(20) NOT NULL DEFAULT 'daily' CHECK (repeat_type IN ('daily', 'weekly', 'monthly', 'custom')), `repeat_type` enum('daily','weekly','monthly','custom') NOT NULL DEFAULT 'daily' COMMENT '重复周期类型daily-每日weekly-每周monthly-每月custom-自定义',
frequency INTEGER NOT NULL DEFAULT 1 CHECK (frequency > 0 AND frequency <= 100), `frequency` int NOT NULL DEFAULT 1 COMMENT '频率(每天/每周/每月多少次)',
custom_repeat_rule JSONB, `custom_repeat_rule` json DEFAULT NULL COMMENT '自定义重复规则(如每周几)',
start_date DATE NOT NULL, `start_date` date NOT NULL COMMENT '目标开始日期',
end_date DATE, `end_date` date DEFAULT NULL COMMENT '目标结束日期',
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'paused', 'completed', 'cancelled')), `status` enum('active','paused','completed','cancelled') NOT NULL DEFAULT 'active' COMMENT '目标状态:active-激活paused-暂停completed-已完成cancelled-已取消',
completed_count INTEGER NOT NULL DEFAULT 0, `completed_count` int NOT NULL DEFAULT 0 COMMENT '已完成次数',
target_count INTEGER CHECK (target_count > 0), `target_count` int DEFAULT NULL COMMENT '目标总次数null表示无限制',
category VARCHAR(100), `category` varchar(100) DEFAULT NULL COMMENT '目标分类标签',
priority INTEGER NOT NULL DEFAULT 0 CHECK (priority >= 0 AND priority <= 10), `priority` int NOT NULL DEFAULT 0 COMMENT '优先级(数字越大优先级越高)',
has_reminder BOOLEAN NOT NULL DEFAULT false, `has_reminder` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否提醒',
reminder_time TIME, `reminder_time` time DEFAULT NULL COMMENT '提醒时间',
reminder_settings JSONB, `reminder_settings` json DEFAULT NULL COMMENT '提醒设置(如每周几提醒)',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted BOOLEAN NOT NULL DEFAULT false `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已删除',
); PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_repeat_type` (`repeat_type`),
KEY `idx_category` (`category`),
KEY `idx_start_date` (`start_date`),
KEY `idx_deleted` (`deleted`),
KEY `idx_user_status` (`user_id`, `status`, `deleted`),
CONSTRAINT `chk_frequency` CHECK (`frequency` > 0 AND `frequency` <= 100),
CONSTRAINT `chk_priority` CHECK (`priority` >= 0 AND `priority` <= 10),
CONSTRAINT `chk_target_count` CHECK (`target_count` IS NULL OR `target_count` > 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户目标表';
-- 创建目标完成记录表 -- 创建目标完成记录表
CREATE TABLE IF NOT EXISTS t_goal_completions ( CREATE TABLE IF NOT EXISTS `t_goal_completions` (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), `id` char(36) NOT NULL COMMENT '主键ID',
goal_id UUID NOT NULL REFERENCES t_goals(id) ON DELETE CASCADE, `goal_id` char(36) NOT NULL COMMENT '目标ID',
user_id VARCHAR(255) NOT NULL, `user_id` varchar(255) NOT NULL COMMENT '用户ID',
completed_at TIMESTAMP WITH TIME ZONE NOT NULL, `completed_at` datetime NOT NULL COMMENT '完成日期',
completion_count INTEGER NOT NULL DEFAULT 1 CHECK (completion_count > 0), `completion_count` int NOT NULL DEFAULT 1 COMMENT '完成次数',
notes TEXT, `notes` text COMMENT '完成备注',
metadata JSONB, `metadata` json DEFAULT NULL COMMENT '完成时的额外数据',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted BOOLEAN NOT NULL DEFAULT false `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已删除',
); PRIMARY KEY (`id`),
KEY `idx_goal_id` (`goal_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_completed_at` (`completed_at`),
KEY `idx_deleted` (`deleted`),
KEY `idx_goal_completed` (`goal_id`, `completed_at`, `deleted`),
CONSTRAINT `fk_goal_completions_goal` FOREIGN KEY (`goal_id`) REFERENCES `t_goals` (`id`) ON DELETE CASCADE,
CONSTRAINT `chk_completion_count` CHECK (`completion_count` > 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='目标完成记录表';
-- 创建索引 -- 创建额外的复合索引以优化查询性能
CREATE INDEX IF NOT EXISTS idx_goals_user_id ON t_goals(user_id); CREATE INDEX IF NOT EXISTS `idx_goals_user_date` ON `t_goals` (`user_id`, `start_date`, `deleted`);
CREATE INDEX IF NOT EXISTS idx_goals_status ON t_goals(status); CREATE INDEX IF NOT EXISTS `idx_goal_completions_user_date` ON `t_goal_completions` (`user_id`, `completed_at`, `deleted`);
CREATE INDEX IF NOT EXISTS idx_goals_repeat_type ON t_goals(repeat_type);
CREATE INDEX IF NOT EXISTS idx_goals_category ON t_goals(category);
CREATE INDEX IF NOT EXISTS idx_goals_start_date ON t_goals(start_date);
CREATE INDEX IF NOT EXISTS idx_goals_deleted ON t_goals(deleted);
CREATE INDEX IF NOT EXISTS idx_goal_completions_goal_id ON t_goal_completions(goal_id);
CREATE INDEX IF NOT EXISTS idx_goal_completions_user_id ON t_goal_completions(user_id);
CREATE INDEX IF NOT EXISTS idx_goal_completions_completed_at ON t_goal_completions(completed_at);
CREATE INDEX IF NOT EXISTS idx_goal_completions_deleted ON t_goal_completions(deleted);
-- 创建复合索引
CREATE INDEX IF NOT EXISTS idx_goals_user_status ON t_goals(user_id, status);
CREATE INDEX IF NOT EXISTS idx_goal_completions_goal_completed ON t_goal_completions(goal_id, completed_at);
-- 添加注释
COMMENT ON TABLE t_goals IS '用户目标表';
COMMENT ON COLUMN t_goals.id IS '目标ID';
COMMENT ON COLUMN t_goals.user_id IS '用户ID';
COMMENT ON COLUMN t_goals.title IS '目标标题';
COMMENT ON COLUMN t_goals.description IS '目标描述';
COMMENT ON COLUMN t_goals.repeat_type IS '重复周期类型daily-每日weekly-每周monthly-每月custom-自定义';
COMMENT ON COLUMN t_goals.frequency IS '频率(每天/每周/每月多少次)';
COMMENT ON COLUMN t_goals.custom_repeat_rule IS '自定义重复规则(如每周几)';
COMMENT ON COLUMN t_goals.start_date IS '目标开始日期';
COMMENT ON COLUMN t_goals.end_date IS '目标结束日期';
COMMENT ON COLUMN t_goals.status IS '目标状态active-激活paused-暂停completed-已完成cancelled-已取消';
COMMENT ON COLUMN t_goals.completed_count IS '已完成次数';
COMMENT ON COLUMN t_goals.target_count IS '目标总次数null表示无限制';
COMMENT ON COLUMN t_goals.category IS '目标分类标签';
COMMENT ON COLUMN t_goals.priority IS '优先级(数字越大优先级越高)';
COMMENT ON COLUMN t_goals.has_reminder IS '是否提醒';
COMMENT ON COLUMN t_goals.reminder_time IS '提醒时间';
COMMENT ON COLUMN t_goals.reminder_settings IS '提醒设置(如每周几提醒)';
COMMENT ON TABLE t_goal_completions IS '目标完成记录表';
COMMENT ON COLUMN t_goal_completions.id IS '完成记录ID';
COMMENT ON COLUMN t_goal_completions.goal_id IS '目标ID';
COMMENT ON COLUMN t_goal_completions.user_id IS '用户ID';
COMMENT ON COLUMN t_goal_completions.completed_at IS '完成日期';
COMMENT ON COLUMN t_goal_completions.completion_count IS '完成次数';
COMMENT ON COLUMN t_goal_completions.notes IS '完成备注';
COMMENT ON COLUMN t_goal_completions.metadata IS '完成时的额外数据';

View File

@@ -59,12 +59,21 @@ export class CreateGoalDto {
customRepeatRule?: CustomRepeatRuleDto; customRepeatRule?: CustomRepeatRuleDto;
@IsDateString({}, { message: '开始日期格式无效' }) @IsDateString({}, { message: '开始日期格式无效' })
startDate: string; @IsOptional()
startDate?: string;
@IsOptional() @IsOptional()
@IsDateString({}, { message: '结束日期格式无效' }) @IsDateString({}, { message: '结束日期格式无效' })
endDate?: string; endDate?: string;
@IsOptional()
@IsInt()
startTime: number;
@IsOptional()
@IsInt()
endTime: number;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@Min(1, { message: '目标总次数必须大于0' }) @Min(1, { message: '目标总次数必须大于0' })

View File

@@ -12,9 +12,9 @@ export class GoalQueryDto {
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@Min(1) @Min(1)
@Max(100) @Max(500)
@Transform(({ value }) => parseInt(value)) @Transform(({ value }) => parseInt(value))
pageSize?: number = 20; pageSize?: number = 50;
@IsOptional() @IsOptional()
@IsEnum(GoalStatus) @IsEnum(GoalStatus)
@@ -28,10 +28,6 @@ export class GoalQueryDto {
@IsString() @IsString()
category?: string; category?: string;
@IsOptional()
@IsString()
search?: string; // 搜索标题和描述
@IsOptional() @IsOptional()
@IsString() @IsString()
startDate?: string; // 开始日期范围 startDate?: string; // 开始日期范围

View File

@@ -8,7 +8,6 @@ import {
Param, Param,
Query, Query,
UseGuards, UseGuards,
Request,
HttpCode, HttpCode,
HttpStatus, HttpStatus,
} from '@nestjs/common'; } from '@nestjs/common';
@@ -19,21 +18,24 @@ import { GoalQueryDto } from './dto/goal-query.dto';
import { CreateGoalCompletionDto } from './dto/goal-completion.dto'; import { CreateGoalCompletionDto } from './dto/goal-completion.dto';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { BaseResponseDto, ResponseCode } from '../base.dto'; import { BaseResponseDto, ResponseCode } from '../base.dto';
import { GoalStatus } from './models/goal.model';
import { CurrentUser } from 'src/common/decorators/current-user.decorator';
import { AccessTokenPayload } from 'src/users/services/apple-auth.service';
@Controller('goals') @Controller('goals')
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
export class GoalsController { export class GoalsController {
constructor(private readonly goalsService: GoalsService) {} constructor(private readonly goalsService: GoalsService) { }
/** /**
* 创建目标 * 创建目标
*/ */
@Post() @Post()
async createGoal( async createGoal(
@Request() req,
@Body() createGoalDto: CreateGoalDto, @Body() createGoalDto: CreateGoalDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<any>> { ): Promise<BaseResponseDto<any>> {
const goal = await this.goalsService.createGoal(req.user.id, createGoalDto); const goal = await this.goalsService.createGoal(user.sub, createGoalDto);
return { return {
code: ResponseCode.SUCCESS, code: ResponseCode.SUCCESS,
message: '目标创建成功', message: '目标创建成功',
@@ -46,10 +48,10 @@ export class GoalsController {
*/ */
@Get() @Get()
async getGoals( async getGoals(
@Request() req,
@Query() query: GoalQueryDto, @Query() query: GoalQueryDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<any>> { ): Promise<BaseResponseDto<any>> {
const result = await this.goalsService.getGoals(req.user.id, query); const result = await this.goalsService.getGoals(user.sub, query);
return { return {
code: ResponseCode.SUCCESS, code: ResponseCode.SUCCESS,
message: '获取目标列表成功', message: '获取目标列表成功',
@@ -62,10 +64,10 @@ export class GoalsController {
*/ */
@Get(':id') @Get(':id')
async getGoal( async getGoal(
@Request() req,
@Param('id') id: string, @Param('id') id: string,
@CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<any>> { ): Promise<BaseResponseDto<any>> {
const goal = await this.goalsService.getGoal(req.user.id, id); const goal = await this.goalsService.getGoal(user.sub, id);
return { return {
code: ResponseCode.SUCCESS, code: ResponseCode.SUCCESS,
message: '获取目标详情成功', message: '获取目标详情成功',
@@ -78,11 +80,11 @@ export class GoalsController {
*/ */
@Put(':id') @Put(':id')
async updateGoal( async updateGoal(
@Request() req,
@Param('id') id: string, @Param('id') id: string,
@Body() updateGoalDto: UpdateGoalDto, @Body() updateGoalDto: UpdateGoalDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<any>> { ): Promise<BaseResponseDto<any>> {
const goal = await this.goalsService.updateGoal(req.user.id, id, updateGoalDto); const goal = await this.goalsService.updateGoal(user.sub, id, updateGoalDto);
return { return {
code: ResponseCode.SUCCESS, code: ResponseCode.SUCCESS,
message: '目标更新成功', message: '目标更新成功',
@@ -96,10 +98,10 @@ export class GoalsController {
@Delete(':id') @Delete(':id')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
async deleteGoal( async deleteGoal(
@Request() req,
@Param('id') id: string, @Param('id') id: string,
@CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<boolean>> { ): Promise<BaseResponseDto<boolean>> {
const result = await this.goalsService.deleteGoal(req.user.id, id); const result = await this.goalsService.deleteGoal(user.sub, id);
return { return {
code: ResponseCode.SUCCESS, code: ResponseCode.SUCCESS,
message: '目标删除成功', message: '目标删除成功',
@@ -112,13 +114,13 @@ export class GoalsController {
*/ */
@Post(':id/complete') @Post(':id/complete')
async completeGoal( async completeGoal(
@Request() req,
@Param('id') id: string, @Param('id') id: string,
@Body() createCompletionDto: CreateGoalCompletionDto, @Body() createCompletionDto: CreateGoalCompletionDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<any>> { ): Promise<BaseResponseDto<any>> {
// 确保完成记录的目标ID与路径参数一致 // 确保完成记录的目标ID与路径参数一致
createCompletionDto.goalId = id; createCompletionDto.goalId = id;
const completion = await this.goalsService.completeGoal(req.user.id, createCompletionDto); const completion = await this.goalsService.completeGoal(user.sub, createCompletionDto);
return { return {
code: ResponseCode.SUCCESS, code: ResponseCode.SUCCESS,
message: '目标完成记录成功', message: '目标完成记录成功',
@@ -131,11 +133,11 @@ export class GoalsController {
*/ */
@Get(':id/completions') @Get(':id/completions')
async getGoalCompletions( async getGoalCompletions(
@Request() req,
@Param('id') id: string, @Param('id') id: string,
@Query() query: any, @Query() query: any,
@CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<any>> { ): Promise<BaseResponseDto<any>> {
const result = await this.goalsService.getGoalCompletions(req.user.id, id, query); const result = await this.goalsService.getGoalCompletions(user.sub, id, query);
return { return {
code: ResponseCode.SUCCESS, code: ResponseCode.SUCCESS,
message: '获取目标完成记录成功', message: '获取目标完成记录成功',
@@ -147,8 +149,10 @@ export class GoalsController {
* 获取目标统计信息 * 获取目标统计信息
*/ */
@Get('stats/overview') @Get('stats/overview')
async getGoalStats(@Request() req): Promise<BaseResponseDto<any>> { async getGoalStats(
const stats = await this.goalsService.getGoalStats(req.user.id); @CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<any>> {
const stats = await this.goalsService.getGoalStats(user.sub);
return { return {
code: ResponseCode.SUCCESS, code: ResponseCode.SUCCESS,
message: '获取目标统计成功', message: '获取目标统计成功',
@@ -161,30 +165,30 @@ export class GoalsController {
*/ */
@Post('batch') @Post('batch')
async batchUpdateGoals( async batchUpdateGoals(
@Request() req,
@Body() body: { @Body() body: {
goalIds: string[]; goalIds: string[];
action: 'pause' | 'resume' | 'complete' | 'delete'; action: 'pause' | 'resume' | 'complete' | 'delete';
data?: any; data?: any;
}, },
@CurrentUser() user: AccessTokenPayload,
): Promise<BaseResponseDto<any>> { ): Promise<BaseResponseDto<any>> {
const { goalIds, action, data } = body; const { goalIds, action, data } = body;
const results = []; const results: { goalId: string; success: boolean; error?: string }[] = [];
for (const goalId of goalIds) { for (const goalId of goalIds) {
try { try {
switch (action) { switch (action) {
case 'pause': case 'pause':
await this.goalsService.updateGoal(req.user.id, goalId, { status: 'paused' }); await this.goalsService.updateGoal(user.sub, goalId, { status: GoalStatus.PAUSED });
break; break;
case 'resume': case 'resume':
await this.goalsService.updateGoal(req.user.id, goalId, { status: 'active' }); await this.goalsService.updateGoal(user.sub, goalId, { status: GoalStatus.ACTIVE });
break; break;
case 'complete': case 'complete':
await this.goalsService.updateGoal(req.user.id, goalId, { status: 'completed' }); await this.goalsService.updateGoal(user.sub, goalId, { status: GoalStatus.COMPLETED });
break; break;
case 'delete': case 'delete':
await this.goalsService.deleteGoal(req.user.id, goalId); await this.goalsService.deleteGoal(user.sub, goalId);
break; break;
} }
results.push({ goalId, success: true }); results.push({ goalId, success: true });

View File

@@ -4,10 +4,12 @@ import { GoalsController } from './goals.controller';
import { GoalsService } from './goals.service'; import { GoalsService } from './goals.service';
import { Goal } from './models/goal.model'; import { Goal } from './models/goal.model';
import { GoalCompletion } from './models/goal-completion.model'; import { GoalCompletion } from './models/goal-completion.model';
import { UsersModule } from '../users/users.module';
@Module({ @Module({
imports: [ imports: [
SequelizeModule.forFeature([Goal, GoalCompletion]), SequelizeModule.forFeature([Goal, GoalCompletion]),
UsersModule,
], ],
controllers: [GoalsController], controllers: [GoalsController],
providers: [GoalsService], providers: [GoalsService],

View File

@@ -32,6 +32,8 @@ export class GoalsService {
...createGoalDto, ...createGoalDto,
startDate: new Date(createGoalDto.startDate), startDate: new Date(createGoalDto.startDate),
endDate: createGoalDto.endDate ? new Date(createGoalDto.endDate) : null, endDate: createGoalDto.endDate ? new Date(createGoalDto.endDate) : null,
startTime: createGoalDto.startTime,
endTime: createGoalDto.endTime,
}); });
this.logger.log(`用户 ${userId} 创建了目标: ${goal.title}`); this.logger.log(`用户 ${userId} 创建了目标: ${goal.title}`);
@@ -47,7 +49,7 @@ export class GoalsService {
*/ */
async getGoals(userId: string, query: GoalQueryDto) { async getGoals(userId: string, query: GoalQueryDto) {
try { try {
const { page = 1, pageSize = 20, status, repeatType, category, search, startDate, endDate, sortBy = 'createdAt', sortOrder = 'desc' } = query; const { page = 1, pageSize = 20, status, repeatType, category, startDate, endDate, sortBy = 'createdAt', sortOrder = 'desc' } = query;
const offset = (page - 1) * pageSize; const offset = (page - 1) * pageSize;
// 构建查询条件 // 构建查询条件
@@ -68,13 +70,6 @@ export class GoalsService {
where.category = category; where.category = category;
} }
if (search) {
where[Op.or] = [
{ title: { [Op.like]: `%${search}%` } },
{ description: { [Op.like]: `%${search}%` } },
];
}
if (startDate || endDate) { if (startDate || endDate) {
where.startDate = {}; where.startDate = {};
if (startDate) { if (startDate) {
@@ -85,6 +80,8 @@ export class GoalsService {
} }
} }
this.logger.log(`查询条件: ${JSON.stringify(where)}`);
// 构建排序条件 // 构建排序条件
const order: Order = [[sortBy, sortOrder.toUpperCase()]]; const order: Order = [[sortBy, sortOrder.toUpperCase()]];
@@ -107,7 +104,7 @@ export class GoalsService {
page, page,
pageSize, pageSize,
total: count, total: count,
items: goals.map(goal => this.formatGoalResponse(goal)), list: goals.map(goal => this.formatGoalResponse(goal)),
}; };
} catch (error) { } catch (error) {
this.logger.error(`获取目标列表失败: ${error.message}`); this.logger.error(`获取目标列表失败: ${error.message}`);

View File

@@ -1,4 +1,4 @@
import { Column, DataType, Model, Table, Index, ForeignKey, BelongsTo } from 'sequelize-typescript'; import { Column, DataType, Model, Table, ForeignKey, BelongsTo } from 'sequelize-typescript';
import { Goal } from './goal.model'; import { Goal } from './goal.model';
@Table({ @Table({
@@ -7,16 +7,15 @@ import { Goal } from './goal.model';
}) })
export class GoalCompletion extends Model { export class GoalCompletion extends Model {
@Column({ @Column({
type: DataType.UUID, type: DataType.CHAR(36),
defaultValue: DataType.UUIDV4, defaultValue: DataType.UUIDV4,
primaryKey: true, primaryKey: true,
}) })
declare id: string; declare id: string;
@Index
@ForeignKey(() => Goal) @ForeignKey(() => Goal)
@Column({ @Column({
type: DataType.UUID, type: DataType.CHAR(36),
allowNull: false, allowNull: false,
comment: '目标ID', comment: '目标ID',
}) })
@@ -25,9 +24,8 @@ export class GoalCompletion extends Model {
@BelongsTo(() => Goal) @BelongsTo(() => Goal)
declare goal: Goal; declare goal: Goal;
@Index
@Column({ @Column({
type: DataType.STRING, type: DataType.STRING(255),
allowNull: false, allowNull: false,
comment: '用户ID', comment: '用户ID',
}) })

View File

@@ -1,4 +1,4 @@
import { Column, DataType, Model, Table, Index, HasMany } from 'sequelize-typescript'; import { Column, DataType, Model, Table, HasMany } from 'sequelize-typescript';
import { GoalCompletion } from './goal-completion.model'; import { GoalCompletion } from './goal-completion.model';
export enum GoalRepeatType { export enum GoalRepeatType {
@@ -21,22 +21,21 @@ export enum GoalStatus {
}) })
export class Goal extends Model { export class Goal extends Model {
@Column({ @Column({
type: DataType.UUID, type: DataType.CHAR(36),
defaultValue: DataType.UUIDV4, defaultValue: DataType.UUIDV4,
primaryKey: true, primaryKey: true,
}) })
declare id: string; declare id: string;
@Index
@Column({ @Column({
type: DataType.STRING, type: DataType.STRING(255),
allowNull: false, allowNull: false,
comment: '用户ID', comment: '用户ID',
}) })
declare userId: string; declare userId: string;
@Column({ @Column({
type: DataType.STRING, type: DataType.STRING(255),
allowNull: false, allowNull: false,
comment: '目标标题', comment: '目标标题',
}) })
@@ -73,19 +72,35 @@ export class Goal extends Model {
declare customRepeatRule: any; declare customRepeatRule: any;
@Column({ @Column({
type: DataType.DATE, type: DataType.DATEONLY,
allowNull: false, allowNull: false,
comment: '目标开始日期', comment: '目标开始日期',
}) })
declare startDate: Date; declare startDate: Date;
@Column({ @Column({
type: DataType.DATE, type: DataType.DATEONLY,
allowNull: true, allowNull: true,
comment: '目标结束日期', comment: '目标结束日期',
}) })
declare endDate: Date; declare endDate: Date;
// 开始时间,分钟
@Column({
type: DataType.INTEGER,
allowNull: true,
comment: '开始时间,分钟',
})
declare startTime: number;
// 结束时间,分钟
@Column({
type: DataType.INTEGER,
allowNull: true,
comment: '结束时间,分钟',
})
declare endTime: number;
@Column({ @Column({
type: DataType.ENUM('active', 'paused', 'completed', 'cancelled'), type: DataType.ENUM('active', 'paused', 'completed', 'cancelled'),
allowNull: false, allowNull: false,
@@ -110,7 +125,7 @@ export class Goal extends Model {
declare targetCount: number; declare targetCount: number;
@Column({ @Column({
type: DataType.STRING, type: DataType.STRING(100),
allowNull: true, allowNull: true,
comment: '目标分类标签', comment: '目标分类标签',
}) })