feat: Introduce DTOs for API validation and new deployment features, including a Git controller and UI components.
This commit is contained in:
7
apps/server/controllers/auth/dto.ts
Normal file
7
apps/server/controllers/auth/dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const loginSchema = z.object({
|
||||
code: z.string().min(1, { message: 'Code不能为空' }),
|
||||
});
|
||||
|
||||
export type LoginInput = z.infer<typeof loginSchema>;
|
||||
@@ -3,6 +3,7 @@ import { Controller, Get, Post } from '../../decorators/route.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import { gitea } from '../../libs/gitea.ts';
|
||||
import { loginSchema } from './dto.ts';
|
||||
|
||||
@Controller('/auth')
|
||||
export class AuthController {
|
||||
@@ -20,7 +21,7 @@ export class AuthController {
|
||||
if (ctx.session.user) {
|
||||
return ctx.session.user;
|
||||
}
|
||||
const { code } = ctx.request.body as LoginRequestBody;
|
||||
const { code } = loginSchema.parse(ctx.request.body);
|
||||
const { access_token, refresh_token, expires_in } =
|
||||
await gitea.getToken(code);
|
||||
const giteaAuth = {
|
||||
@@ -81,7 +82,3 @@ export class AuthController {
|
||||
return ctx.session?.user;
|
||||
}
|
||||
}
|
||||
|
||||
interface LoginRequestBody {
|
||||
code: string;
|
||||
}
|
||||
|
||||
19
apps/server/controllers/deployment/dto.ts
Normal file
19
apps/server/controllers/deployment/dto.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const listDeploymentsQuerySchema = z.object({
|
||||
page: z.coerce.number().int().min(1).optional().default(1),
|
||||
pageSize: z.coerce.number().int().min(1).max(100).optional().default(10),
|
||||
projectId: z.coerce.number().int().positive().optional(),
|
||||
});
|
||||
|
||||
export const createDeploymentSchema = z.object({
|
||||
projectId: z.number().int().positive({ message: '项目ID必须是正整数' }),
|
||||
pipelineId: z.number().int().positive({ message: '流水线ID必须是正整数' }),
|
||||
branch: z.string().min(1, { message: '分支不能为空' }),
|
||||
commitHash: z.string().min(1, { message: '提交哈希不能为空' }),
|
||||
commitMessage: z.string().min(1, { message: '提交信息不能为空' }),
|
||||
env: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ListDeploymentsQuery = z.infer<typeof listDeploymentsQuerySchema>;
|
||||
export type CreateDeploymentInput = z.infer<typeof createDeploymentSchema>;
|
||||
@@ -1,45 +1,61 @@
|
||||
import { Controller, Get, Post } from '../../decorators/route.ts';
|
||||
import type { Prisma } from '../../generated/prisma/index.js';
|
||||
import type { Prisma } from '../../generated/client.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import type { Context } from 'koa';
|
||||
import { listDeploymentsQuerySchema, createDeploymentSchema } from './dto.ts';
|
||||
|
||||
@Controller('/deployments')
|
||||
export class DeploymentController {
|
||||
@Get('')
|
||||
async list(ctx: Context) {
|
||||
const { page = 1, pageSize = 10 } = ctx.query;
|
||||
const { page, pageSize, projectId } = listDeploymentsQuerySchema.parse(ctx.query);
|
||||
const where: Prisma.DeploymentWhereInput = {
|
||||
valid: 1,
|
||||
};
|
||||
|
||||
if (projectId) {
|
||||
where.projectId = projectId;
|
||||
}
|
||||
|
||||
const result = await prisma.deployment.findMany({
|
||||
where: {
|
||||
valid: 1,
|
||||
},
|
||||
take: Number(pageSize),
|
||||
skip: (Number(page) - 1) * Number(pageSize),
|
||||
where,
|
||||
take: pageSize,
|
||||
skip: (page - 1) * pageSize,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
});
|
||||
const total = await prisma.deployment.count();
|
||||
const total = await prisma.deployment.count({ where });
|
||||
|
||||
return {
|
||||
data: result,
|
||||
page: Number(page),
|
||||
pageSize: Number(pageSize),
|
||||
total: total,
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
@Post('')
|
||||
async create(ctx: Context) {
|
||||
const body = ctx.request.body as Prisma.DeploymentCreateInput;
|
||||
const body = createDeploymentSchema.parse(ctx.request.body);
|
||||
|
||||
prisma.deployment.create({
|
||||
const result = await prisma.deployment.create({
|
||||
data: {
|
||||
branch: body.branch,
|
||||
commitHash: body.commitHash,
|
||||
commitMessage: body.commitMessage,
|
||||
|
||||
status: 'pending',
|
||||
Project: {
|
||||
connect: { id: body.projectId },
|
||||
},
|
||||
pipelineId: body.pipelineId,
|
||||
env: body.env || 'dev',
|
||||
buildLog: '',
|
||||
createdBy: 'system', // TODO: get from user
|
||||
updatedBy: 'system',
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
13
apps/server/controllers/git/dto.ts
Normal file
13
apps/server/controllers/git/dto.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const getCommitsQuerySchema = z.object({
|
||||
projectId: z.coerce.number().int().positive({ message: 'Project ID is required' }),
|
||||
branch: z.string().optional(),
|
||||
});
|
||||
|
||||
export const getBranchesQuerySchema = z.object({
|
||||
projectId: z.coerce.number().int().positive({ message: 'Project ID is required' }),
|
||||
});
|
||||
|
||||
export type GetCommitsQuery = z.infer<typeof getCommitsQuerySchema>;
|
||||
export type GetBranchesQuery = z.infer<typeof getBranchesQuerySchema>;
|
||||
113
apps/server/controllers/git/index.ts
Normal file
113
apps/server/controllers/git/index.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import type { Context } from 'koa';
|
||||
import { Controller, Get } from '../../decorators/route.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { gitea } from '../../libs/gitea.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import { getCommitsQuerySchema, getBranchesQuerySchema } from './dto.ts';
|
||||
|
||||
@Controller('/git')
|
||||
export class GitController {
|
||||
@Get('/commits')
|
||||
async getCommits(ctx: Context) {
|
||||
const { projectId, branch } = getCommitsQuerySchema.parse(ctx.query);
|
||||
|
||||
const project = await prisma.project.findFirst({
|
||||
where: {
|
||||
id: projectId,
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
throw new BusinessError('Project not found', 1002, 404);
|
||||
}
|
||||
|
||||
// Parse repository URL to get owner and repo
|
||||
// Supports:
|
||||
// https://gitea.com/owner/repo.git
|
||||
// http://gitea.com/owner/repo
|
||||
const { owner, repo } = this.parseRepoUrl(project.repository);
|
||||
|
||||
// Get access token from session
|
||||
const accessToken = ctx.session?.gitea?.access_token;
|
||||
console.log('Access token present:', !!accessToken);
|
||||
|
||||
if (!accessToken) {
|
||||
throw new BusinessError('Gitea access token not found. Please login again.', 1004, 401);
|
||||
}
|
||||
|
||||
try {
|
||||
const commits = await gitea.getCommits(owner, repo, accessToken, branch);
|
||||
return commits;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch commits:', error);
|
||||
throw new BusinessError('Failed to fetch commits from Gitea', 1005, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/branches')
|
||||
async getBranches(ctx: Context) {
|
||||
const { projectId } = getBranchesQuerySchema.parse(ctx.query);
|
||||
|
||||
const project = await prisma.project.findFirst({
|
||||
where: {
|
||||
id: projectId,
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
throw new BusinessError('Project not found', 1002, 404);
|
||||
}
|
||||
|
||||
const { owner, repo } = this.parseRepoUrl(project.repository);
|
||||
|
||||
const accessToken = ctx.session?.gitea?.access_token;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new BusinessError('Gitea access token not found. Please login again.', 1004, 401);
|
||||
}
|
||||
|
||||
try {
|
||||
const branches = await gitea.getBranches(owner, repo, accessToken);
|
||||
return branches;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch branches:', error);
|
||||
throw new BusinessError('Failed to fetch branches from Gitea', 1006, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private parseRepoUrl(url: string) {
|
||||
let cleanUrl = url.trim();
|
||||
if (cleanUrl.endsWith('/')) {
|
||||
cleanUrl = cleanUrl.slice(0, -1);
|
||||
}
|
||||
|
||||
// Handle SCP-like syntax: git@host:owner/repo.git
|
||||
if (!cleanUrl.includes('://') && cleanUrl.includes(':')) {
|
||||
const scpMatch = cleanUrl.match(/:([^\/]+)\/([^\/]+?)(\.git)?$/);
|
||||
if (scpMatch) {
|
||||
return { owner: scpMatch[1], repo: scpMatch[2] };
|
||||
}
|
||||
}
|
||||
|
||||
// Handle HTTP/HTTPS/SSH URLs
|
||||
try {
|
||||
const urlObj = new URL(cleanUrl);
|
||||
const parts = urlObj.pathname.split('/').filter(Boolean);
|
||||
if (parts.length >= 2) {
|
||||
const repo = parts.pop()!.replace(/\.git$/, '');
|
||||
const owner = parts.pop()!;
|
||||
return { owner, repo };
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to simple regex
|
||||
const match = cleanUrl.match(/([^\/]+)\/([^\/]+?)(\.git)?$/);
|
||||
if (match) {
|
||||
return { owner: match[1], repo: match[2] };
|
||||
}
|
||||
}
|
||||
|
||||
throw new BusinessError('Invalid repository URL format', 1003, 400);
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,4 @@ export { AuthController } from './auth/index.ts';
|
||||
export { DeploymentController } from './deployment/index.ts';
|
||||
export { PipelineController } from './pipeline/index.ts';
|
||||
export { StepController } from './step/index.ts'
|
||||
export { GitController } from './git/index.ts';
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
updatePipelineSchema,
|
||||
pipelineIdSchema,
|
||||
listPipelinesQuerySchema,
|
||||
} from './schema.ts';
|
||||
} from './dto.ts';
|
||||
|
||||
@Controller('/pipelines')
|
||||
export class PipelineController {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
updateProjectSchema,
|
||||
listProjectQuerySchema,
|
||||
projectIdSchema,
|
||||
} from './schema.ts';
|
||||
} from './dto.ts';
|
||||
|
||||
@Controller('/projects')
|
||||
export class ProjectController {
|
||||
|
||||
103
apps/server/controllers/step/dto.ts
Normal file
103
apps/server/controllers/step/dto.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// 定义验证架构
|
||||
export const createStepSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
message: '步骤名称必须是字符串',
|
||||
})
|
||||
.min(1, { message: '步骤名称不能为空' })
|
||||
.max(100, { message: '步骤名称不能超过100个字符' }),
|
||||
|
||||
description: z
|
||||
.string({
|
||||
message: '步骤描述必须是字符串',
|
||||
})
|
||||
.max(500, { message: '步骤描述不能超过500个字符' })
|
||||
.optional(),
|
||||
|
||||
order: z
|
||||
.number({
|
||||
message: '步骤顺序必须是数字',
|
||||
})
|
||||
.int()
|
||||
.min(0, { message: '步骤顺序必须是非负整数' }),
|
||||
|
||||
script: z
|
||||
.string({
|
||||
message: '脚本命令必须是字符串',
|
||||
})
|
||||
.min(1, { message: '脚本命令不能为空' }),
|
||||
|
||||
pipelineId: z
|
||||
.number({
|
||||
message: '流水线ID必须是数字',
|
||||
})
|
||||
.int()
|
||||
.positive({ message: '流水线ID必须是正整数' }),
|
||||
});
|
||||
|
||||
export const updateStepSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
message: '步骤名称必须是字符串',
|
||||
})
|
||||
.min(1, { message: '步骤名称不能为空' })
|
||||
.max(100, { message: '步骤名称不能超过100个字符' })
|
||||
.optional(),
|
||||
|
||||
description: z
|
||||
.string({
|
||||
message: '步骤描述必须是字符串',
|
||||
})
|
||||
.max(500, { message: '步骤描述不能超过500个字符' })
|
||||
.optional(),
|
||||
|
||||
order: z
|
||||
.number({
|
||||
message: '步骤顺序必须是数字',
|
||||
})
|
||||
.int()
|
||||
.min(0, { message: '步骤顺序必须是非负整数' })
|
||||
.optional(),
|
||||
|
||||
script: z
|
||||
.string({
|
||||
message: '脚本命令必须是字符串',
|
||||
})
|
||||
.min(1, { message: '脚本命令不能为空' })
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const stepIdSchema = z.object({
|
||||
id: z.coerce.number().int().positive({ message: '步骤 ID 必须是正整数' }),
|
||||
});
|
||||
|
||||
export const listStepsQuerySchema = z
|
||||
.object({
|
||||
pipelineId: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive({ message: '流水线ID必须是正整数' })
|
||||
.optional(),
|
||||
page: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1, { message: '页码必须大于0' })
|
||||
.optional()
|
||||
.default(1),
|
||||
limit: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1, { message: '每页数量必须大于0' })
|
||||
.max(100, { message: '每页数量不能超过100' })
|
||||
.optional()
|
||||
.default(10),
|
||||
})
|
||||
.optional();
|
||||
|
||||
// TypeScript 类型
|
||||
export type CreateStepInput = z.infer<typeof createStepSchema>;
|
||||
export type UpdateStepInput = z.infer<typeof updateStepSchema>;
|
||||
export type StepIdParams = z.infer<typeof stepIdSchema>;
|
||||
export type ListStepsQuery = z.infer<typeof listStepsQuerySchema>;
|
||||
@@ -3,109 +3,12 @@ import { prisma } from '../../libs/prisma.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts';
|
||||
import { z } from 'zod';
|
||||
|
||||
// 定义验证架构
|
||||
const createStepSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
message: '步骤名称必须是字符串',
|
||||
})
|
||||
.min(1, { message: '步骤名称不能为空' })
|
||||
.max(100, { message: '步骤名称不能超过100个字符' }),
|
||||
|
||||
description: z
|
||||
.string({
|
||||
message: '步骤描述必须是字符串',
|
||||
})
|
||||
.max(500, { message: '步骤描述不能超过500个字符' })
|
||||
.optional(),
|
||||
|
||||
order: z
|
||||
.number({
|
||||
message: '步骤顺序必须是数字',
|
||||
})
|
||||
.int()
|
||||
.min(0, { message: '步骤顺序必须是非负整数' }),
|
||||
|
||||
script: z
|
||||
.string({
|
||||
message: '脚本命令必须是字符串',
|
||||
})
|
||||
.min(1, { message: '脚本命令不能为空' }),
|
||||
|
||||
pipelineId: z
|
||||
.number({
|
||||
message: '流水线ID必须是数字',
|
||||
})
|
||||
.int()
|
||||
.positive({ message: '流水线ID必须是正整数' }),
|
||||
});
|
||||
|
||||
const updateStepSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
message: '步骤名称必须是字符串',
|
||||
})
|
||||
.min(1, { message: '步骤名称不能为空' })
|
||||
.max(100, { message: '步骤名称不能超过100个字符' })
|
||||
.optional(),
|
||||
|
||||
description: z
|
||||
.string({
|
||||
message: '步骤描述必须是字符串',
|
||||
})
|
||||
.max(500, { message: '步骤描述不能超过500个字符' })
|
||||
.optional(),
|
||||
|
||||
order: z
|
||||
.number({
|
||||
message: '步骤顺序必须是数字',
|
||||
})
|
||||
.int()
|
||||
.min(0, { message: '步骤顺序必须是非负整数' })
|
||||
.optional(),
|
||||
|
||||
script: z
|
||||
.string({
|
||||
message: '脚本命令必须是字符串',
|
||||
})
|
||||
.min(1, { message: '脚本命令不能为空' })
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const stepIdSchema = z.object({
|
||||
id: z.coerce.number().int().positive({ message: '步骤 ID 必须是正整数' }),
|
||||
});
|
||||
|
||||
const listStepsQuerySchema = z
|
||||
.object({
|
||||
pipelineId: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive({ message: '流水线ID必须是正整数' })
|
||||
.optional(),
|
||||
page: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1, { message: '页码必须大于0' })
|
||||
.optional()
|
||||
.default(1),
|
||||
limit: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1, { message: '每页数量必须大于0' })
|
||||
.max(100, { message: '每页数量不能超过100' })
|
||||
.optional()
|
||||
.default(10),
|
||||
})
|
||||
.optional();
|
||||
|
||||
// TypeScript 类型
|
||||
type CreateStepInput = z.infer<typeof createStepSchema>;
|
||||
type UpdateStepInput = z.infer<typeof updateStepSchema>;
|
||||
type StepIdParams = z.infer<typeof stepIdSchema>;
|
||||
type ListStepsQuery = z.infer<typeof listStepsQuerySchema>;
|
||||
import {
|
||||
createStepSchema,
|
||||
updateStepSchema,
|
||||
stepIdSchema,
|
||||
listStepsQuerySchema,
|
||||
} from './dto.ts';
|
||||
|
||||
@Controller('/steps')
|
||||
export class StepController {
|
||||
@@ -185,7 +88,6 @@ export class StepController {
|
||||
const step = await prisma.step.create({
|
||||
data: {
|
||||
name: validatedData.name,
|
||||
description: validatedData.description || '',
|
||||
order: validatedData.order,
|
||||
script: validatedData.script,
|
||||
pipelineId: validatedData.pipelineId,
|
||||
|
||||
26
apps/server/controllers/user/dto.ts
Normal file
26
apps/server/controllers/user/dto.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const userIdSchema = z.object({
|
||||
id: z.coerce.number().int().positive({ message: '用户ID必须是正整数' }),
|
||||
});
|
||||
|
||||
export const createUserSchema = z.object({
|
||||
name: z.string().min(1, { message: '用户名不能为空' }),
|
||||
email: z.string().email({ message: '邮箱格式不正确' }),
|
||||
status: z.enum(['active', 'inactive']).optional().default('active'),
|
||||
});
|
||||
|
||||
export const updateUserSchema = z.object({
|
||||
name: z.string().min(1).optional(),
|
||||
email: z.string().email().optional(),
|
||||
status: z.enum(['active', 'inactive']).optional(),
|
||||
});
|
||||
|
||||
export const searchUserQuerySchema = z.object({
|
||||
keyword: z.string().optional(),
|
||||
status: z.enum(['active', 'inactive']).optional(),
|
||||
});
|
||||
|
||||
export type CreateUserInput = z.infer<typeof createUserSchema>;
|
||||
export type UpdateUserInput = z.infer<typeof updateUserSchema>;
|
||||
export type SearchUserQuery = z.infer<typeof searchUserQuerySchema>;
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { Context } from 'koa';
|
||||
import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import {
|
||||
userIdSchema,
|
||||
createUserSchema,
|
||||
updateUserSchema,
|
||||
searchUserQuerySchema,
|
||||
} from './dto.ts';
|
||||
|
||||
/**
|
||||
* 用户控制器
|
||||
@@ -22,18 +28,18 @@ export class UserController {
|
||||
|
||||
@Get('/detail/:id')
|
||||
async detail(ctx: Context) {
|
||||
const { id } = ctx.params;
|
||||
const { id } = userIdSchema.parse(ctx.params);
|
||||
|
||||
// 模拟根据ID查找用户
|
||||
const user = {
|
||||
id: Number(id),
|
||||
id,
|
||||
name: 'User ' + id,
|
||||
email: `user${id}@example.com`,
|
||||
status: 'active',
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
if (Number(id) > 100) {
|
||||
if (id > 100) {
|
||||
throw new BusinessError('用户不存在', 2001, 404);
|
||||
}
|
||||
|
||||
@@ -42,14 +48,14 @@ export class UserController {
|
||||
|
||||
@Post('')
|
||||
async create(ctx: Context) {
|
||||
const body = (ctx.request as any).body;
|
||||
const body = createUserSchema.parse(ctx.request.body);
|
||||
|
||||
// 模拟创建用户
|
||||
const newUser = {
|
||||
id: Date.now(),
|
||||
...body,
|
||||
createdAt: new Date().toISOString(),
|
||||
status: 'active'
|
||||
status: body.status
|
||||
};
|
||||
|
||||
return newUser;
|
||||
@@ -57,12 +63,12 @@ export class UserController {
|
||||
|
||||
@Put('/:id')
|
||||
async update(ctx: Context) {
|
||||
const { id } = ctx.params;
|
||||
const body = (ctx.request as any).body;
|
||||
const { id } = userIdSchema.parse(ctx.params);
|
||||
const body = updateUserSchema.parse(ctx.request.body);
|
||||
|
||||
// 模拟更新用户
|
||||
const updatedUser = {
|
||||
id: Number(id),
|
||||
id,
|
||||
...body,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
@@ -72,9 +78,9 @@ export class UserController {
|
||||
|
||||
@Delete('/:id')
|
||||
async delete(ctx: Context) {
|
||||
const { id } = ctx.params;
|
||||
const { id } = userIdSchema.parse(ctx.params);
|
||||
|
||||
if (Number(id) === 1) {
|
||||
if (id === 1) {
|
||||
throw new BusinessError('管理员账户不能删除', 2002, 403);
|
||||
}
|
||||
|
||||
@@ -88,7 +94,7 @@ export class UserController {
|
||||
|
||||
@Get('/search')
|
||||
async search(ctx: Context) {
|
||||
const { keyword, status } = ctx.query;
|
||||
const { keyword, status } = searchUserQuerySchema.parse(ctx.query);
|
||||
|
||||
// 模拟搜索逻辑
|
||||
let results = [
|
||||
@@ -98,8 +104,8 @@ export class UserController {
|
||||
|
||||
if (keyword) {
|
||||
results = results.filter(user =>
|
||||
user.name.toLowerCase().includes(String(keyword).toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(String(keyword).toLowerCase())
|
||||
user.name.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(keyword.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user