feat(wechat-game): add level list API endpoints
Add CRUD endpoints for game levels with level field in response: - GET /v1/wechat-game/levels - list all levels ordered by sort_order - GET /v1/wechat-game/levels/:id - get single level by ID New files: - Level entity mapping to levels table - LevelRepository with ordered query support - LevelResponseDto with level field (1-based index from sort_order)
This commit is contained in:
41
src/modules/wechat-game/dto/level-response.dto.ts
Normal file
41
src/modules/wechat-game/dto/level-response.dto.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class LevelResponseDto {
|
||||||
|
@ApiProperty({ description: '关卡编号' })
|
||||||
|
level: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '关卡ID' })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片URL' })
|
||||||
|
imageUrl: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '答案' })
|
||||||
|
answer: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '提示1', nullable: true })
|
||||||
|
hint1: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '提示2', nullable: true })
|
||||||
|
hint2: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '提示3', nullable: true })
|
||||||
|
hint3: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序顺序' })
|
||||||
|
sortOrder: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LevelListResponseDto {
|
||||||
|
@ApiProperty({ type: [LevelResponseDto], description: '关卡列表' })
|
||||||
|
levels: LevelResponseDto[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '关卡总数' })
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
37
src/modules/wechat-game/entities/level.entity.ts
Normal file
37
src/modules/wechat-game/entities/level.entity.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('levels')
|
||||||
|
export class Level {
|
||||||
|
@PrimaryColumn({ type: 'varchar', length: 191 })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 191, name: 'image_url' })
|
||||||
|
imageUrl: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 191 })
|
||||||
|
answer: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 191, nullable: true })
|
||||||
|
hint1: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 191, nullable: true })
|
||||||
|
hint2: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 191, nullable: true })
|
||||||
|
hint3: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'int', name: 'sort_order', default: 0 })
|
||||||
|
sortOrder: number;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Level } from '../entities/level.entity';
|
||||||
|
|
||||||
|
export interface ILevelRepository {
|
||||||
|
findAll(): Promise<Level[]>;
|
||||||
|
findById(id: string): Promise<Level | null>;
|
||||||
|
findAllOrdered(): Promise<Level[]>;
|
||||||
|
}
|
||||||
27
src/modules/wechat-game/repositories/level.repository.ts
Normal file
27
src/modules/wechat-game/repositories/level.repository.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { Level } from '../entities/level.entity';
|
||||||
|
import { ILevelRepository } from './level.repository.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LevelRepository implements ILevelRepository {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Level)
|
||||||
|
private readonly repository: Repository<Level>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async findAll(): Promise<Level[]> {
|
||||||
|
return this.repository.find();
|
||||||
|
}
|
||||||
|
|
||||||
|
async findById(id: string): Promise<Level | null> {
|
||||||
|
return this.repository.findOne({ where: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllOrdered(): Promise<Level[]> {
|
||||||
|
return this.repository.find({
|
||||||
|
order: { sortOrder: 'ASC' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,10 @@ import {
|
|||||||
GameConfigResponseDto,
|
GameConfigResponseDto,
|
||||||
GameConfigListResponseDto,
|
GameConfigListResponseDto,
|
||||||
} from './dto/game-config-response.dto';
|
} from './dto/game-config-response.dto';
|
||||||
|
import {
|
||||||
|
LevelResponseDto,
|
||||||
|
LevelListResponseDto,
|
||||||
|
} from './dto/level-response.dto';
|
||||||
import { ApiResponseDto } from '../../common/dto/api-response.dto';
|
import { ApiResponseDto } from '../../common/dto/api-response.dto';
|
||||||
|
|
||||||
@ApiTags('微信小游戏')
|
@ApiTags('微信小游戏')
|
||||||
@@ -13,7 +17,10 @@ export class WechatGameController {
|
|||||||
constructor(private readonly wechatGameService: WechatGameService) {}
|
constructor(private readonly wechatGameService: WechatGameService) {}
|
||||||
|
|
||||||
@Get('configs')
|
@Get('configs')
|
||||||
@ApiOperation({ summary: '获取所有游戏配置', description: '获取所有激活的游戏配置列表' })
|
@ApiOperation({
|
||||||
|
summary: '获取所有游戏配置',
|
||||||
|
description: '获取所有激活的游戏配置列表',
|
||||||
|
})
|
||||||
@ApiResponse({ status: 200, description: '成功获取配置列表' })
|
@ApiResponse({ status: 200, description: '成功获取配置列表' })
|
||||||
async getAllConfigs(): Promise<ApiResponseDto<GameConfigListResponseDto>> {
|
async getAllConfigs(): Promise<ApiResponseDto<GameConfigListResponseDto>> {
|
||||||
const data = await this.wechatGameService.getAllConfigs();
|
const data = await this.wechatGameService.getAllConfigs();
|
||||||
@@ -21,7 +28,10 @@ export class WechatGameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('configs/:key')
|
@Get('configs/:key')
|
||||||
@ApiOperation({ summary: '根据key获取配置', description: '根据配置键名获取单个游戏配置' })
|
@ApiOperation({
|
||||||
|
summary: '根据key获取配置',
|
||||||
|
description: '根据配置键名获取单个游戏配置',
|
||||||
|
})
|
||||||
@ApiResponse({ status: 200, description: '成功获取配置' })
|
@ApiResponse({ status: 200, description: '成功获取配置' })
|
||||||
@ApiResponse({ status: 404, description: '配置不存在' })
|
@ApiResponse({ status: 404, description: '配置不存在' })
|
||||||
async getConfigByKey(
|
async getConfigByKey(
|
||||||
@@ -30,4 +40,29 @@ export class WechatGameController {
|
|||||||
const data = await this.wechatGameService.getConfigByKey(key);
|
const data = await this.wechatGameService.getConfigByKey(key);
|
||||||
return ApiResponseDto.success(data);
|
return ApiResponseDto.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('levels')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '获取所有关卡',
|
||||||
|
description: '获取所有关卡列表,按sort_order排序',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: '成功获取关卡列表' })
|
||||||
|
async getAllLevels(): Promise<ApiResponseDto<LevelListResponseDto>> {
|
||||||
|
const data = await this.wechatGameService.getAllLevels();
|
||||||
|
return ApiResponseDto.success(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('levels/:id')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '根据ID获取关卡',
|
||||||
|
description: '根据关卡ID获取单个关卡信息',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: '成功获取关卡' })
|
||||||
|
@ApiResponse({ status: 404, description: '关卡不存在' })
|
||||||
|
async getLevelById(
|
||||||
|
@Param('id') id: string,
|
||||||
|
): Promise<ApiResponseDto<LevelResponseDto>> {
|
||||||
|
const data = await this.wechatGameService.getLevelById(id);
|
||||||
|
return ApiResponseDto.success(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
import { WechatGameController } from './wechat-game.controller';
|
import { WechatGameController } from './wechat-game.controller';
|
||||||
import { WechatGameService } from './wechat-game.service';
|
import { WechatGameService } from './wechat-game.service';
|
||||||
import { GameConfig } from './entities/game-config.entity';
|
import { GameConfig } from './entities/game-config.entity';
|
||||||
|
import { Level } from './entities/level.entity';
|
||||||
import { GameConfigRepository } from './repositories/game-config.repository';
|
import { GameConfigRepository } from './repositories/game-config.repository';
|
||||||
|
import { LevelRepository } from './repositories/level.repository';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([GameConfig])],
|
imports: [TypeOrmModule.forFeature([GameConfig, Level])],
|
||||||
controllers: [WechatGameController],
|
controllers: [WechatGameController],
|
||||||
providers: [WechatGameService, GameConfigRepository],
|
providers: [WechatGameService, GameConfigRepository, LevelRepository],
|
||||||
exports: [WechatGameService],
|
exports: [WechatGameService],
|
||||||
})
|
})
|
||||||
export class WechatGameModule {}
|
export class WechatGameModule {}
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { GameConfigRepository } from './repositories/game-config.repository';
|
import { GameConfigRepository } from './repositories/game-config.repository';
|
||||||
|
import { LevelRepository } from './repositories/level.repository';
|
||||||
import {
|
import {
|
||||||
GameConfigResponseDto,
|
GameConfigResponseDto,
|
||||||
GameConfigListResponseDto,
|
GameConfigListResponseDto,
|
||||||
} from './dto/game-config-response.dto';
|
} from './dto/game-config-response.dto';
|
||||||
|
import {
|
||||||
|
LevelResponseDto,
|
||||||
|
LevelListResponseDto,
|
||||||
|
} from './dto/level-response.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WechatGameService {
|
export class WechatGameService {
|
||||||
constructor(private readonly gameConfigRepository: GameConfigRepository) {}
|
constructor(
|
||||||
|
private readonly gameConfigRepository: GameConfigRepository,
|
||||||
|
private readonly levelRepository: LevelRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
async getAllConfigs(): Promise<GameConfigListResponseDto> {
|
async getAllConfigs(): Promise<GameConfigListResponseDto> {
|
||||||
const configs = await this.gameConfigRepository.findActiveConfigs();
|
const configs = await this.gameConfigRepository.findActiveConfigs();
|
||||||
@@ -28,7 +36,31 @@ export class WechatGameService {
|
|||||||
return this.toResponseDto(config);
|
return this.toResponseDto(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private toResponseDto(config: import('./entities/game-config.entity').GameConfig): GameConfigResponseDto {
|
async getAllLevels(): Promise<LevelListResponseDto> {
|
||||||
|
const levels = await this.levelRepository.findAllOrdered();
|
||||||
|
|
||||||
|
return {
|
||||||
|
levels: levels.map((level, index) =>
|
||||||
|
this.toLevelResponseDto(level, index + 1),
|
||||||
|
),
|
||||||
|
total: levels.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLevelById(id: string): Promise<LevelResponseDto> {
|
||||||
|
const levels = await this.levelRepository.findAllOrdered();
|
||||||
|
const levelIndex = levels.findIndex((l) => l.id === id);
|
||||||
|
|
||||||
|
if (levelIndex === -1) {
|
||||||
|
throw new NotFoundException(`Level with id "${id}" not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.toLevelResponseDto(levels[levelIndex], levelIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private toResponseDto(
|
||||||
|
config: import('./entities/game-config.entity').GameConfig,
|
||||||
|
): GameConfigResponseDto {
|
||||||
return {
|
return {
|
||||||
id: config.id,
|
id: config.id,
|
||||||
configKey: config.configKey,
|
configKey: config.configKey,
|
||||||
@@ -39,4 +71,22 @@ export class WechatGameService {
|
|||||||
updatedAt: config.updatedAt,
|
updatedAt: config.updatedAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toLevelResponseDto(
|
||||||
|
level: import('./entities/level.entity').Level,
|
||||||
|
levelNumber: number,
|
||||||
|
): LevelResponseDto {
|
||||||
|
return {
|
||||||
|
level: levelNumber,
|
||||||
|
id: level.id,
|
||||||
|
imageUrl: level.imageUrl,
|
||||||
|
answer: level.answer,
|
||||||
|
hint1: level.hint1,
|
||||||
|
hint2: level.hint2,
|
||||||
|
hint3: level.hint3,
|
||||||
|
sortOrder: level.sortOrder,
|
||||||
|
createdAt: level.createdAt,
|
||||||
|
updatedAt: level.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user