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,83 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from '../users.service';
import { PrismaService } from '../../../prisma/prisma.service';
describe('UsersService', () => {
let service: UsersService;
let prisma: PrismaService;
const mockPrisma = {
user: {
findMany: jest.fn(),
findUnique: jest.fn(),
count: jest.fn(),
update: jest.fn(),
},
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{ provide: PrismaService, useValue: mockPrisma },
],
}).compile();
service = module.get<UsersService>(UsersService);
prisma = module.get<PrismaService>(PrismaService);
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return paginated users', async () => {
const mockUsers = [{ id: 1, nickname: '用户1' }];
mockPrisma.user.findMany.mockResolvedValue(mockUsers);
mockPrisma.user.count.mockResolvedValue(1);
const result = await service.findAll({ page: 1, pageSize: 20 });
expect(result.items).toEqual(mockUsers);
expect(result.total).toBe(1);
});
it('should filter by status', async () => {
mockPrisma.user.findMany.mockResolvedValue([]);
mockPrisma.user.count.mockResolvedValue(0);
await service.findAll({ status: 'ACTIVE' });
expect(mockPrisma.user.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({ status: 'ACTIVE' }),
}),
);
});
});
describe('findById', () => {
it('should return user by id', async () => {
const mockUser = { id: 1, nickname: '用户1' };
mockPrisma.user.findUnique.mockResolvedValue(mockUser);
const result = await service.findById(1);
expect(result).toEqual(mockUser);
});
it('should return null for non-existent user', async () => {
mockPrisma.user.findUnique.mockResolvedValue(null);
const result = await service.findById(999);
expect(result).toBeNull();
});
});
describe('updateProfile', () => {
it('should update user profile', async () => {
mockPrisma.user.update.mockResolvedValue({ id: 1, nickname: '新昵称' });
const result = await service.updateProfile(1, { nickname: '新昵称' });
expect(result).toHaveProperty('nickname', '新昵称');
expect(mockPrisma.user.update).toHaveBeenCalledWith({
where: { id: 1 },
data: { nickname: '新昵称' },
});
});
});
});
@@ -0,0 +1,31 @@
import { Controller, Get, Put, Body, Param, UseGuards, Req, Query } from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
import { UsersService } from './users.service';
@ApiTags('用户')
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth()
async findAll(@Query() query: { page?: number; pageSize?: number; status?: string }) {
return this.usersService.findAll(query);
}
@Get(':id')
@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth()
async findById(@Param('id') id: string) {
return this.usersService.findById(+id);
}
@Put('profile')
@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth()
async updateProfile(@Req() req: any, @Body() body: { nickname?: string; avatar?: string }) {
return this.usersService.updateProfile(req.user.userId, body);
}
}
+10
View File
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@@ -0,0 +1,45 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll(params: { page?: number; pageSize?: number; status?: string }) {
const page = Number(params.page ?? 1);
const pageSize = Number(params.pageSize ?? 20);
const where: any = { deletedAt: null };
if (params.status) where.status = params.status;
const [items, total] = await Promise.all([
this.prisma.user.findMany({
where,
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
select: {
id: true, nickname: true, avatar: true, phone: true, email: true,
status: true, memberPlan: true, createdAt: true, lastLoginAt: true,
},
}),
this.prisma.user.count({ where }),
]);
return { items, total, page, pageSize };
}
async findById(id: number) {
return this.prisma.user.findUnique({
where: { id },
select: {
id: true, nickname: true, avatar: true, phone: true, email: true,
status: true, memberPlan: true, memberExpire: true, sandboxDaily: true,
createdAt: true, lastLoginAt: true,
},
});
}
async updateProfile(id: number, data: { nickname?: string; avatar?: string }) {
return this.prisma.user.update({ where: { id }, data });
}
}