大修改

This commit is contained in:
Yeomai committed 2026-09-30 03:38:10 +08:00
1 parent a1cbc9404a
commit e81481623c
47 files changed
+1453 -285

No files matched your search

@@ -1,6 +1,7 @@
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Put, Query } from '@nestjs/common';
import { z } from 'zod';
import { CurrentUser, Public, RequiresAudience, type AuthPrincipal } from '../../common/auth';
import { AdminEnvParam, type DataSourceEnv } from '../../common/admin-env';
import { AppError } from '../../common/errors';
import { AuthService } from '../auth/auth.service';
import { AdminService } from './admin.service';
@@ -22,6 +23,7 @@ const grantSchema = z.object({
note: z.string().max(255).optional(),
});
const refundSchema = z.object({ reason: z.string().min(1).max(100) });
const dataSourceSchema = z.object({ env: z.enum(['production', 'develop']) });
/** 管理员登录。必须公开,否则登录接口本身会被守卫拦住。 */
@Public()
@@ -59,32 +61,49 @@ export class AdminController {
constructor(private readonly admin: AdminService) {}
@Get('stats')
stats() {
return this.admin.stats();
stats(@AdminEnvParam() env: DataSourceEnv) {
return this.admin.stats(env);
}
/**
* 切换**线上小程序**的数据源。
*
* 与管理端左侧的「查看哪个库」完全不同:那个只改自己的视角,
* 这个会真实影响所有用户在小程序里看到的内容,所以前端必须二次确认。
*
* 开关存在生产库,因此切到开发库后依然能切回来。
*/
@Put('data-source')
switchMiniProgramDataSource(@Body() body: unknown) {
const parsed = dataSourceSchema.safeParse(body);
if (!parsed.success) throw AppError.badRequest('参数错误:env 只能是 production 或 develop');
return this.admin.switchMiniProgramDataSource(parsed.data.env);
}
// ── 用户 ───────────────────────────────────────────────────────────────
@Get('users')
listUsers(
@AdminEnvParam() env: DataSourceEnv,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@Query('keyword') keyword?: string,
) {
return this.admin.listUsers(Number(page) || 1, Number(pageSize) || 20, keyword ?? '');
return this.admin.listUsers(env, Number(page) || 1, Number(pageSize) || 20, keyword ?? '');
}
@Patch('users/:id')
updateUser(@Param('id', ParseIntPipe) id: number, @Body() body: unknown) {
updateUser(@AdminEnvParam() env: DataSourceEnv, @Param('id', ParseIntPipe) id: number, @Body() body: unknown) {
const parsed = updateUserSchema.safeParse(body);
if (!parsed.success) throw AppError.badRequest('参数错误');
return this.admin.updateUser(id, parsed.data);
return this.admin.updateUser(env, id, parsed.data);
}
// ── 通用内容 CRUD ──────────────────────────────────────────────────────
@Get('data/:resource')
listResource(
@AdminEnvParam() env: DataSourceEnv,
@Param('resource') resource: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@@ -92,67 +111,77 @@ export class AdminController {
@Query('courseId') courseId?: string,
) {
const filter = courseId ? { course_id: Number(courseId) } : {};
return this.admin.listResource(resource, Number(page) || 1, Number(pageSize) || 20, keyword ?? '', filter);
return this.admin.listResource(env, resource, Number(page) || 1, Number(pageSize) || 20, keyword ?? '', filter);
}
@Post('data/:resource')
createResource(@Param('resource') resource: string, @Body() body: unknown) {
return this.admin.createResource(resource, body);
createResource(@AdminEnvParam() env: DataSourceEnv, @Param('resource') resource: string, @Body() body: unknown) {
return this.admin.createResource(env, resource, body);
}
@Patch('data/:resource/:id')
updateResource(
@AdminEnvParam() env: DataSourceEnv,
@Param('resource') resource: string,
@Param('id', ParseIntPipe) id: number,
@Body() body: unknown,
) {
return this.admin.updateResource(resource, id, body);
return this.admin.updateResource(env, resource, id, body);
}
@Delete('data/:resource/:id')
deleteResource(@Param('resource') resource: string, @Param('id', ParseIntPipe) id: number) {
return this.admin.deleteResource(resource, id);
deleteResource(
@AdminEnvParam() env: DataSourceEnv,
@Param('resource') resource: string,
@Param('id', ParseIntPipe) id: number,
) {
return this.admin.deleteResource(env, resource, id);
}
// ── 配置 ───────────────────────────────────────────────────────────────
@Get('settings')
listSettings() {
return this.admin.listSettings();
listSettings(@AdminEnvParam() env: DataSourceEnv) {
return this.admin.listSettings(env);
}
@Put('settings')
upsertSetting(@Body() body: unknown) {
upsertSetting(@AdminEnvParam() env: DataSourceEnv, @Body() body: unknown) {
const parsed = settingSchema.safeParse(body);
if (!parsed.success) throw AppError.badRequest('参数错误');
return this.admin.upsertSetting(parsed.data.key, parsed.data.value, parsed.data.remark);
return this.admin.upsertSetting(env, parsed.data.key, parsed.data.value, parsed.data.remark);
}
// ── 订单 / 权益 ────────────────────────────────────────────────────────
@Get('orders')
listOrders(@Query('page') page?: string, @Query('pageSize') pageSize?: string, @Query('status') status?: string) {
listOrders(
@AdminEnvParam() env: DataSourceEnv,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@Query('status') status?: string,
) {
const s = status === undefined || status === '' ? undefined : Number(status);
return this.admin.listOrders(Number(page) || 1, Number(pageSize) || 20, Number.isFinite(s) ? s : undefined);
return this.admin.listOrders(env, Number(page) || 1, Number(pageSize) || 20, Number.isFinite(s) ? s : undefined);
}
@Post('orders/:id/refund')
refundOrder(@Param('id', ParseIntPipe) id: number, @Body() body: unknown) {
refundOrder(@AdminEnvParam() env: DataSourceEnv, @Param('id', ParseIntPipe) id: number, @Body() body: unknown) {
const parsed = refundSchema.safeParse(body);
if (!parsed.success) throw AppError.badRequest('请填写退款原因');
return this.admin.refundOrder(id, parsed.data.reason);
return this.admin.refundOrder(env, id, parsed.data.reason);
}
@Get('entitlements')
listEntitlements(@Query('userId', ParseIntPipe) userId: number) {
return this.admin.listEntitlements(userId);
listEntitlements(@AdminEnvParam() env: DataSourceEnv, @Query('userId', ParseIntPipe) userId: number) {
return this.admin.listEntitlements(env, userId);
}
@Post('entitlements')
grantEntitlement(@Body() body: unknown) {
grantEntitlement(@AdminEnvParam() env: DataSourceEnv, @Body() body: unknown) {
const parsed = grantSchema.safeParse(body);
if (!parsed.success) throw AppError.badRequest('参数错误');
return this.admin.grantEntitlement({
return this.admin.grantEntitlement(env, {
userId: parsed.data.userId,
courseId: parsed.data.courseId,
expireAt: parsed.data.expireAt ?? null,
@@ -161,8 +190,12 @@ export class AdminController {
}
@Delete('entitlements')
revokeEntitlement(@Query('userId', ParseIntPipe) userId: number, @Query('courseId', ParseIntPipe) courseId: number) {
return this.admin.revokeEntitlement(userId, courseId);
revokeEntitlement(
@AdminEnvParam() env: DataSourceEnv,
@Query('userId', ParseIntPipe) userId: number,
@Query('courseId', ParseIntPipe) courseId: number,
) {
return this.admin.revokeEntitlement(env, userId, courseId);
}
@Get('me')