feat: Phase 1-3 全部完成 — 沙盒增强、学情分析、学习路径

This commit is contained in:
yuzhiran-dev
2026-05-18 09:48:51 +08:00
commit 11bb86854c
277 changed files with 37755 additions and 0 deletions
@@ -0,0 +1,78 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ToolsService } from '../tools.service';
import { PrismaService } from '../../../prisma/prisma.service';
describe('ToolsService', () => {
let service: ToolsService;
let prisma: PrismaService;
const mockPrisma = {
tool: {
findMany: jest.fn(),
count: jest.fn(),
create: jest.fn(),
},
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ToolsService,
{ provide: PrismaService, useValue: mockPrisma },
],
}).compile();
service = module.get<ToolsService>(ToolsService);
prisma = module.get<PrismaService>(PrismaService);
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated tools', async () => {
const mockTools = [{ id: 1, name: 'ChatGPT', category: { id: 1, name: '聊天' } }];
mockPrisma.tool.findMany.mockResolvedValue(mockTools);
mockPrisma.tool.count.mockResolvedValue(1);
const result = await service.findAll({ page: 1, pageSize: 20 });
expect(result.items).toEqual(mockTools);
expect(result.total).toBe(1);
expect(result.page).toBe(1);
expect(result.pageSize).toBe(20);
});
it('should filter by categoryId', async () => {
mockPrisma.tool.findMany.mockResolvedValue([]);
mockPrisma.tool.count.mockResolvedValue(0);
await service.findAll({ categoryId: 2 });
expect(mockPrisma.tool.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ categoryId: 2 }),
}),
);
});
it('should filter by isFeatured', async () => {
mockPrisma.tool.findMany.mockResolvedValue([]);
mockPrisma.tool.count.mockResolvedValue(0);
await service.findAll({ isFeatured: true });
expect(mockPrisma.tool.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ isFeatured: true }),
}),
);
});
});
describe('create', () => {
it('should create a tool', async () => {
const data = { name: 'New Tool', url: 'https://example.com' };
mockPrisma.tool.create.mockResolvedValue({ id: 1, ...data });
const result = await service.create(data);
expect(result).toHaveProperty('id', 1);
expect(mockPrisma.tool.create).toHaveBeenCalledWith({ data });
});
});
});
@@ -0,0 +1,19 @@
import { Controller, Get, Post, Body, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { ToolsService } from './tools.service';
@ApiTags('AI工具')
@Controller('tools')
export class ToolsController {
constructor(private toolsService: ToolsService) {}
@Get()
async findAll(@Query() query: { page?: number; pageSize?: number; categoryId?: number; isFeatured?: boolean }) {
return this.toolsService.findAll(query);
}
@Post()
async create(@Body() body: { name: string; description?: string; url: string; icon?: string; categoryId?: number; tags?: string }) {
return this.toolsService.create(body);
}
}
+10
View File
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ToolsController } from './tools.controller';
import { ToolsService } from './tools.service';
@Module({
controllers: [ToolsController],
providers: [ToolsService],
exports: [ToolsService],
})
export class ToolsModule {}
@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class ToolsService {
constructor(private prisma: PrismaService) {}
async findAll(params: { page?: number; pageSize?: number; categoryId?: number; isFeatured?: boolean }) {
const page = Number(params.page ?? 1);
const pageSize = Number(params.pageSize ?? 20);
const { categoryId, isFeatured } = params;
const where: any = { status: 'PUBLISHED', deletedAt: null };
if (categoryId) where.categoryId = categoryId;
if (isFeatured !== undefined) where.isFeatured = isFeatured;
const [items, total] = await Promise.all([
this.prisma.tool.findMany({
where,
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { viewCount: 'desc' },
include: { category: true },
}),
this.prisma.tool.count({ where }),
]);
return { items, total, page, pageSize };
}
async create(data: { name: string; description?: string; url: string; icon?: string; categoryId?: number; tags?: string }) {
return this.prisma.tool.create({ data });
}
}