新增训练计划模块,包括控制器、服务、模型及数据传输对象,更新应用模块以引入新模块,同时在AI教练模块中添加体态评估功能,支持体重识别与更新,优化用户体重历史记录管理。
This commit is contained in:
37
src/articles/articles.controller.ts
Normal file
37
src/articles/articles.controller.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { CreateArticleDto, QueryArticlesDto, CreateArticleResponseDto, QueryArticlesResponseDto } from './dto/article.dto';
|
||||
|
||||
@ApiTags('articles')
|
||||
@Controller('articles')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class ArticlesController {
|
||||
constructor(private readonly articlesService: ArticlesService) { }
|
||||
|
||||
@Post('create')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '创建文章' })
|
||||
@ApiBody({ type: CreateArticleDto })
|
||||
@ApiResponse({ status: 200 })
|
||||
async create(@Body() dto: CreateArticleDto): Promise<CreateArticleResponseDto> {
|
||||
return this.articlesService.create(dto);
|
||||
}
|
||||
|
||||
@Get('list')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '查询文章列表(分页)' })
|
||||
async list(@Query() query: QueryArticlesDto): Promise<QueryArticlesResponseDto> {
|
||||
return this.articlesService.query(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '获取文章详情并增加阅读数' })
|
||||
async getOne(@Param('id') id: string): Promise<CreateArticleResponseDto> {
|
||||
return this.articlesService.getAndIncreaseReadCount(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
src/articles/articles.module.ts
Normal file
16
src/articles/articles.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SequelizeModule } from '@nestjs/sequelize';
|
||||
import { ArticlesService } from './articles.service';
|
||||
import { ArticlesController } from './articles.controller';
|
||||
import { Article } from './models/article.model';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
|
||||
@Module({
|
||||
imports: [SequelizeModule.forFeature([Article]), UsersModule],
|
||||
providers: [ArticlesService],
|
||||
controllers: [ArticlesController],
|
||||
exports: [ArticlesService],
|
||||
})
|
||||
export class ArticlesModule { }
|
||||
|
||||
|
||||
61
src/articles/articles.service.ts
Normal file
61
src/articles/articles.service.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/sequelize';
|
||||
import { Op } from 'sequelize';
|
||||
import { Article } from './models/article.model';
|
||||
import { CreateArticleDto, QueryArticlesDto, ArticleVo } from './dto/article.dto';
|
||||
import { ResponseCode } from '../base.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ArticlesService {
|
||||
private readonly logger = new Logger(ArticlesService.name);
|
||||
constructor(
|
||||
@InjectModel(Article)
|
||||
private readonly articleModel: typeof Article,
|
||||
) { }
|
||||
|
||||
async create(dto: CreateArticleDto) {
|
||||
const article = await this.articleModel.create({
|
||||
title: dto.title,
|
||||
publishedDate: dto.publishedDate as any,
|
||||
htmlContent: dto.htmlContent,
|
||||
});
|
||||
return { code: ResponseCode.SUCCESS, message: 'success', data: article.toJSON() as ArticleVo };
|
||||
}
|
||||
|
||||
async query(params: QueryArticlesDto) {
|
||||
const page = Math.max(1, Number(params.page || 1));
|
||||
const pageSize = Math.min(100, Math.max(1, Number(params.pageSize || 10)));
|
||||
const where: any = {};
|
||||
if (params.keyword) {
|
||||
where.title = { [Op.like]: `%${params.keyword}%` };
|
||||
}
|
||||
if (params.startDate || params.endDate) {
|
||||
where.publishedDate = {} as any;
|
||||
if (params.startDate) (where.publishedDate as any)[Op.gte] = params.startDate as any;
|
||||
if (params.endDate) (where.publishedDate as any)[Op.lte] = params.endDate as any;
|
||||
}
|
||||
|
||||
const { rows, count } = await this.articleModel.findAndCountAll({
|
||||
where,
|
||||
order: [['publishedDate', 'DESC'], ['createdAt', 'DESC']],
|
||||
offset: (page - 1) * pageSize,
|
||||
limit: pageSize,
|
||||
});
|
||||
|
||||
return {
|
||||
code: ResponseCode.SUCCESS,
|
||||
message: 'success',
|
||||
data: { list: rows.map(r => r.toJSON() as ArticleVo), total: count, page, pageSize },
|
||||
};
|
||||
}
|
||||
|
||||
async getAndIncreaseReadCount(id: string) {
|
||||
const article = await this.articleModel.findByPk(id);
|
||||
if (!article) throw new NotFoundException('文章不存在');
|
||||
article.readCount += 1;
|
||||
await article.save();
|
||||
return { code: ResponseCode.SUCCESS, message: 'success', data: article.toJSON() as ArticleVo };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
43
src/articles/dto/article.dto.ts
Normal file
43
src/articles/dto/article.dto.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { BaseResponseDto } from '../../base.dto';
|
||||
|
||||
export class CreateArticleDto {
|
||||
@ApiProperty({ description: '标题' })
|
||||
title!: string;
|
||||
|
||||
@ApiProperty({ description: '发布日期 YYYY-MM-DD' })
|
||||
publishedDate!: string;
|
||||
|
||||
@ApiProperty({ description: 'HTML 富文本内容' })
|
||||
htmlContent!: string;
|
||||
}
|
||||
|
||||
export class QueryArticlesDto {
|
||||
@ApiProperty({ required: false, description: '关键词(匹配标题)' })
|
||||
keyword?: string;
|
||||
|
||||
@ApiProperty({ required: false, description: '起始日期 YYYY-MM-DD' })
|
||||
startDate?: string;
|
||||
|
||||
@ApiProperty({ required: false, description: '结束日期 YYYY-MM-DD' })
|
||||
endDate?: string;
|
||||
|
||||
@ApiProperty({ required: false, description: '分页页码,从1开始', default: 1 })
|
||||
page?: number = 1;
|
||||
|
||||
@ApiProperty({ required: false, description: '分页大小', default: 10 })
|
||||
pageSize?: number = 10;
|
||||
}
|
||||
|
||||
export interface ArticleVo {
|
||||
id: string;
|
||||
title: string;
|
||||
publishedDate: string;
|
||||
readCount: number;
|
||||
htmlContent: string;
|
||||
}
|
||||
|
||||
export type CreateArticleResponseDto = BaseResponseDto<ArticleVo>;
|
||||
export type QueryArticlesResponseDto = BaseResponseDto<{ list: ArticleVo[]; total: number; page: number; pageSize: number }>;
|
||||
|
||||
|
||||
57
src/articles/models/article.model.ts
Normal file
57
src/articles/models/article.model.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Column, Model, Table, DataType, Index } from 'sequelize-typescript';
|
||||
|
||||
@Table({
|
||||
tableName: 't_articles',
|
||||
underscored: true,
|
||||
})
|
||||
export class Article extends Model {
|
||||
@Column({
|
||||
type: DataType.UUID,
|
||||
defaultValue: DataType.UUIDV4,
|
||||
primaryKey: true,
|
||||
})
|
||||
declare id: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '标题',
|
||||
})
|
||||
declare title: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '发布日期(仅日期)',
|
||||
})
|
||||
declare publishedDate: string; // YYYY-MM-DD
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '阅读数',
|
||||
})
|
||||
declare readCount: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.TEXT('long'),
|
||||
allowNull: false,
|
||||
comment: 'HTML 富文本内容',
|
||||
})
|
||||
declare htmlContent: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
defaultValue: DataType.NOW,
|
||||
})
|
||||
declare createdAt: Date;
|
||||
|
||||
@Column({
|
||||
type: DataType.DATE,
|
||||
defaultValue: DataType.NOW,
|
||||
})
|
||||
declare updatedAt: Date;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user