Ver código fonte

feat: 完成轮播图接口

IlhamTahir 1 ano atrás
pai
commit
4161fddb31

+ 6 - 3
src/article/article.module.ts

@@ -6,10 +6,13 @@ import { Category } from './entity/category.entity';
 import { ArticleService } from './service/article.service';
 import { Article } from './entity/article.entity';
 import { ArticleController } from './controller/article.controller';
+import { CarousalController } from './controller/carousal.controller';
+import { CarousalService } from './service/carousal.service';
+import { Carousal } from './entity/carousal.entity';
 
 @Module({
-  controllers: [CategoryController, ArticleController],
-  imports: [TypeOrmModule.forFeature([Category, Article])],
-  providers: [CategoryService, ArticleService],
+  controllers: [CategoryController, ArticleController, CarousalController],
+  imports: [TypeOrmModule.forFeature([Category, Article, Carousal])],
+  providers: [CategoryService, ArticleService, CarousalService],
 })
 export class ArticleModule {}

+ 107 - 0
src/article/controller/carousal.controller.ts

@@ -0,0 +1,107 @@
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  Post,
+  Put,
+  Query,
+} from '@nestjs/common';
+import { CarousalService } from '../service/carousal.service';
+import { CreateCarousalRequest } from '../dto/create-carousal.request';
+import { ApiBearerAuth, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';
+import { CarousalVo } from '../vo/carousal.vo';
+import { CarousalMapper } from '../mapper/carousal.mapper';
+import { PageResult } from '../../core/vo/page-result';
+import { PageResultMapper } from '../../core/mapper/page-result.mapper';
+import { SearchCarousalFilter } from '../dto/search-carousal.filter';
+import { UpdateCarousalRequest } from '../dto/update-carousal.request';
+
+@Controller('carousals')
+export class CarousalController {
+  constructor(private readonly carousalService: CarousalService) {}
+
+  @Post()
+  @ApiBearerAuth()
+  @ApiOkResponse({
+    type: CarousalVo,
+  })
+  async create(@Body() createCarousalRequest: CreateCarousalRequest) {
+    return CarousalMapper.toVo(
+      await this.carousalService.create(createCarousalRequest),
+    );
+  }
+
+  @Get()
+  @ApiOkResponse({
+    description: '轮播图分页列表',
+    schema: {
+      allOf: [
+        { $ref: getSchemaPath(PageResult) }, // 引用 PageResult 模型
+        {
+          properties: {
+            data: {
+              type: 'array',
+              items: { $ref: getSchemaPath(CarousalVo) }, // 泛型内容具体化
+            },
+          },
+        },
+      ],
+    },
+  })
+  @ApiBearerAuth()
+  async search(@Query() searchCarousalFilter: SearchCarousalFilter) {
+    const [data, total] =
+      await this.carousalService.search(searchCarousalFilter);
+    return PageResultMapper.toPageResult<CarousalVo>(
+      CarousalMapper.toVos(data),
+      searchCarousalFilter.getPage(),
+      searchCarousalFilter.getSize(),
+      total,
+    );
+  }
+
+  @Get(':id')
+  @ApiBearerAuth()
+  @ApiOkResponse({
+    type: CarousalVo,
+  })
+  async get(id: string) {
+    return CarousalMapper.toVo(await this.carousalService.get(id));
+  }
+
+  @Put(':id')
+  @ApiBearerAuth()
+  @ApiOkResponse({
+    type: CarousalVo,
+  })
+  async update(id: string, updateCarousalRequest: UpdateCarousalRequest) {
+    return CarousalMapper.toVo(
+      await this.carousalService.update(id, updateCarousalRequest),
+    );
+  }
+
+  @Put(':id/active')
+  @ApiBearerAuth()
+  @ApiOkResponse({
+    type: CarousalVo,
+  })
+  async active(id: string) {
+    return CarousalMapper.toVo(await this.carousalService.active(id));
+  }
+
+  @Put(':id/inactive')
+  @ApiBearerAuth()
+  @ApiOkResponse({
+    type: CarousalVo,
+  })
+  async inactive(id: string) {
+    return CarousalMapper.toVo(await this.carousalService.inactive(id));
+  }
+
+  @Delete(':id')
+  @ApiBearerAuth()
+  async delete(id: string) {
+    await this.carousalService.delete(id);
+  }
+}

+ 23 - 0
src/article/dto/create-carousal.request.ts

@@ -0,0 +1,23 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { IsNotEmpty } from 'class-validator';
+import { CarousalTargetType } from '../enum/carousal-target-type.enum';
+
+export class CreateCarousalRequest {
+  @ApiProperty({
+    example: 'https://example.cc/1.jpg',
+  })
+  @IsNotEmpty({ message: '轮播图不能为空' })
+  imageUrl: string;
+
+  @ApiProperty()
+  @IsNotEmpty({ message: '跳转类型不能为空' })
+  targetType: CarousalTargetType;
+
+  @ApiProperty({
+    example: 'https://example.cc',
+  })
+  targetUrl: string;
+
+  @ApiProperty()
+  targetId: string;
+}

+ 19 - 1
src/article/dto/search-article.filter.ts

@@ -1,3 +1,21 @@
 import { BaseFilter } from '../../core/dto/base.filter';
+import { ApiProperty } from '@nestjs/swagger';
+import { IsOptional } from 'class-validator';
+import { Like } from 'typeorm';
 
-export class SearchArticleFilter extends BaseFilter {}
+export class SearchArticleFilter extends BaseFilter {
+  @ApiProperty({ required: false, description: '标题模糊搜索', example: '猫' })
+  @IsOptional()
+  title?: string;
+
+  getConditions(): Record<string, any> {
+    const conditions = super.getConditions();
+
+    // 如果存在 name,则添加模糊查询条件
+    if (this.title) {
+      conditions.title = Like(`%${this.title}%`); // 使用 TypeORM 的 Like 运算符
+    }
+
+    return conditions;
+  }
+}

+ 3 - 0
src/article/dto/search-carousal.filter.ts

@@ -0,0 +1,3 @@
+import { BaseFilter } from '../../core/dto/base.filter';
+
+export class SearchCarousalFilter extends BaseFilter {}

+ 3 - 0
src/article/dto/update-carousal.request.ts

@@ -0,0 +1,3 @@
+import { CreateCarousalRequest } from './create-carousal.request';
+
+export class UpdateCarousalRequest extends CreateCarousalRequest {}

+ 25 - 0
src/article/entity/carousal.entity.ts

@@ -0,0 +1,25 @@
+import { TraceableEntity } from '../../core/entity/traceable.entity';
+import { Column, Entity } from 'typeorm';
+import { CarousalStatus } from '../enum/carousal-status.enum';
+
+@Entity()
+export class Carousal extends TraceableEntity {
+  @Column()
+  imageUrl: string;
+
+  @Column({
+    type: 'enum',
+    enum: CarousalStatus,
+    default: CarousalStatus.ACTIVE,
+  })
+  status: CarousalStatus = CarousalStatus.ACTIVE;
+
+  @Column({ nullable: false })
+  targetType: string;
+
+  @Column({ nullable: true })
+  targetId: string;
+
+  @Column({ nullable: true })
+  targetUrl: string;
+}

+ 4 - 0
src/article/enum/carousal-status.enum.ts

@@ -0,0 +1,4 @@
+export enum CarousalStatus {
+  ACTIVE = 'active',
+  INACTIVE = 'inactive',
+}

+ 4 - 0
src/article/enum/carousal-target-type.enum.ts

@@ -0,0 +1,4 @@
+export enum CarousalTargetType {
+  ARTICLE = 'article',
+  URL = 'url',
+}

+ 24 - 0
src/article/mapper/carousal.mapper.ts

@@ -0,0 +1,24 @@
+import { CarousalVo } from '../vo/carousal.vo';
+import { Carousal } from '../entity/carousal.entity';
+import { UserMapper } from '../../core/mapper/user.mapper';
+import { DateUtil } from '../../core/util/date.util';
+
+export class CarousalMapper {
+  static toVo(entity: Carousal): CarousalVo {
+    return {
+      id: entity.id,
+      imageUrl: entity.imageUrl,
+      targetType: entity.targetType,
+      targetId: entity.targetId,
+      targetUrl: entity.targetUrl,
+      status: entity.status,
+      createBy: UserMapper.toVo(entity.createBy),
+      updateBy: UserMapper.toVo(entity.updateBy),
+      createdTime: DateUtil.format(entity.createdTime),
+      updatedTime: DateUtil.format(entity.updatedTime),
+    };
+  }
+  static toVos(entities: Carousal[]): CarousalVo[] {
+    return entities.map((entity) => this.toVo(entity));
+  }
+}

+ 68 - 0
src/article/service/carousal.service.ts

@@ -0,0 +1,68 @@
+import { HttpException, Injectable } from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Carousal } from '../entity/carousal.entity';
+import { Repository } from 'typeorm';
+import { CreateCarousalRequest } from '../dto/create-carousal.request';
+import { SearchCarousalFilter } from '../dto/search-carousal.filter';
+import { CarousalStatus } from '../enum/carousal-status.enum';
+
+@Injectable()
+export class CarousalService {
+  constructor(
+    @InjectRepository(Carousal)
+    private readonly carousalRepository: Repository<Carousal>,
+  ) {}
+
+  create(createCarousalRequest: CreateCarousalRequest) {
+    const carousal = this.carousalRepository.create({
+      imageUrl: createCarousalRequest.imageUrl,
+      targetType: createCarousalRequest.targetType,
+      targetId: createCarousalRequest.targetId,
+      targetUrl: createCarousalRequest.targetUrl,
+    });
+    return this.carousalRepository.save(carousal);
+  }
+
+  async search(searchCarousalFilter: SearchCarousalFilter) {
+    return this.carousalRepository.findAndCount({
+      where: searchCarousalFilter.getConditions(),
+      skip: searchCarousalFilter.getSkip(),
+      take: searchCarousalFilter.getSize(),
+      order: searchCarousalFilter.getOrderBy(),
+    });
+  }
+
+  async get(id: string) {
+    const carousal = await this.carousalRepository.findOneBy({ id });
+    if (!carousal) {
+      throw new HttpException('Not Found', 404);
+    }
+    return carousal;
+  }
+
+  async update(id: string, updateCarousalRequest: CreateCarousalRequest) {
+    const carousal = await this.get(id);
+    carousal.imageUrl = updateCarousalRequest.imageUrl;
+    carousal.targetType = updateCarousalRequest.targetType;
+    carousal.targetId = updateCarousalRequest.targetId;
+    carousal.targetUrl = updateCarousalRequest.targetUrl;
+    return this.carousalRepository.save(carousal);
+  }
+
+  async active(id: string) {
+    const carousal = await this.get(id);
+    carousal.status = CarousalStatus.ACTIVE;
+    return this.carousalRepository.save(carousal);
+  }
+
+  async inactive(id: string) {
+    const carousal = await this.get(id);
+    carousal.status = CarousalStatus.INACTIVE;
+    return this.carousalRepository.save(carousal);
+  }
+
+  async delete(id: string) {
+    const carousal = await this.get(id);
+    return this.carousalRepository.remove(carousal);
+  }
+}

+ 19 - 0
src/article/vo/carousal.vo.ts

@@ -0,0 +1,19 @@
+import { ApiProperty, ApiSchema } from '@nestjs/swagger';
+import { TraceableVo } from '../../core/vo/traceable.vo';
+import { CarousalStatus } from '../enum/carousal-status.enum';
+
+@ApiSchema({
+  name: 'Carousal',
+})
+export class CarousalVo extends TraceableVo {
+  @ApiProperty()
+  imageUrl: string;
+  @ApiProperty()
+  status: CarousalStatus;
+  @ApiProperty()
+  targetType: string;
+  @ApiProperty()
+  targetId: string;
+  @ApiProperty()
+  targetUrl: string;
+}

+ 0 - 1
src/core/controller/file.controller.ts

@@ -15,7 +15,6 @@ export class FileController {
   constructor(private readonly fileService: FileService) {}
   @Post('upload')
   @ApiConsumes('multipart/form-data')
-  @NoAuth()
   @ApiBody({
     description: '上传文件',
     schema: {