feat(project): init

This commit is contained in:
zhangheng
2026-04-25 11:26:33 +08:00
commit 9bf212f67c
63 changed files with 11141 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
import { NextResponse, type NextRequest } from "next/server";
import { requireApiAdmin } from "@/lib/api-auth";
import { setDefaultMonthlyQuota } from "@/lib/db";
import { jsonError, normalizeError } from "@/lib/http";
export const runtime = "nodejs";
export async function PUT(request: NextRequest) {
try {
requireApiAdmin(request);
const body = (await request.json()) as {
defaultMonthlyQuota?: unknown;
};
const numericValue = Number.parseInt(
String(body?.defaultMonthlyQuota ?? ""),
10,
);
if (!Number.isFinite(numericValue) || numericValue < 0) {
return jsonError("默认额度必须是大于等于 0 的整数。", 400);
}
const settings = setDefaultMonthlyQuota(numericValue);
return NextResponse.json({
ok: true,
settings,
});
} catch (error) {
const normalizedError = normalizeError(error, "保存默认额度失败。");
return jsonError(normalizedError.message, normalizedError.statusCode);
}
}

View File

@@ -0,0 +1,87 @@
import { NextResponse, type NextRequest } from "next/server";
import { requireApiAdmin } from "@/lib/api-auth";
import { getUserById, updateUserRecord } from "@/lib/db";
import { hashPassword, toPublicUser } from "@/lib/auth";
import { getQuotaSnapshotForUser } from "@/lib/quota";
import { jsonError, normalizeError } from "@/lib/http";
export const runtime = "nodejs";
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
try {
const adminUser = requireApiAdmin(request);
const body = (await request.json()) as {
role?: unknown;
monthlyQuota?: unknown;
isActive?: unknown;
password?: unknown;
};
const { id } = await params;
const targetUser = getUserById(id);
const monthlyQuota =
typeof body?.monthlyQuota === "number" ||
typeof body?.monthlyQuota === "string"
? body.monthlyQuota
: undefined;
const password = typeof body?.password === "string" ? body.password : "";
if (!targetUser) {
return jsonError("用户不存在。", 404);
}
if (targetUser.id === adminUser.id) {
if (body?.role && body.role !== "admin") {
return jsonError("不能移除当前管理员自己的 admin 身份。", 400);
}
if (body?.isActive === false) {
return jsonError("不能禁用当前管理员自己的账号。", 400);
}
}
const updatedUser = updateUserRecord(targetUser.id, {
role: body?.role === "admin" ? "admin" : "user",
monthlyQuota:
body?.role === "admin"
? null
: monthlyQuota === null || monthlyQuota === ""
? null
: monthlyQuota,
isActive:
body?.isActive === undefined
? targetUser.isActive
: Boolean(body.isActive),
passwordHash:
password.trim().length >= 8 ? hashPassword(password) : undefined,
});
if (!updatedUser) {
return jsonError("用户不存在。", 404);
}
const publicUser = toPublicUser(updatedUser);
if (!publicUser) {
return jsonError("保存用户失败。", 500);
}
const quota = getQuotaSnapshotForUser(publicUser);
return NextResponse.json({
ok: true,
user: {
...publicUser,
usedThisPeriod: quota.used,
remaining: quota.remaining,
limit: quota.limit,
isUnlimited: quota.isUnlimited,
},
});
} catch (error) {
const normalizedError = normalizeError(error, "保存用户失败。");
return jsonError(normalizedError.message, normalizedError.statusCode);
}
}

View File

@@ -0,0 +1,72 @@
import { NextResponse, type NextRequest } from "next/server";
import { requireApiAdmin } from "@/lib/api-auth";
import { createUserRecord, getUserByUsername } from "@/lib/db";
import { hashPassword, toPublicUser } from "@/lib/auth";
import { getQuotaSnapshotForUser } from "@/lib/quota";
import { jsonError, normalizeError } from "@/lib/http";
export const runtime = "nodejs";
export async function POST(request: NextRequest) {
try {
requireApiAdmin(request);
const body = (await request.json()) as {
username?: unknown;
password?: unknown;
role?: unknown;
monthlyQuota?: unknown;
};
const username = String(body?.username ?? "")
.trim()
.toLowerCase();
const password = String(body?.password ?? "");
const role = body?.role === "admin" ? "admin" : "user";
const monthlyQuota =
typeof body?.monthlyQuota === "number" ||
typeof body?.monthlyQuota === "string"
? body.monthlyQuota
: undefined;
if (!username) {
return jsonError("用户名不能为空。", 400);
}
if (password.length < 8) {
return jsonError("密码至少需要 8 个字符。", 400);
}
if (getUserByUsername(username)) {
return jsonError("该用户名已存在。", 409);
}
const publicUser = toPublicUser(
createUserRecord({
username,
passwordHash: hashPassword(password),
role,
monthlyQuota: role === "admin" ? null : monthlyQuota,
}),
);
if (!publicUser) {
return jsonError("创建用户失败。", 500);
}
const quota = getQuotaSnapshotForUser(publicUser);
return NextResponse.json({
ok: true,
user: {
...publicUser,
usedThisPeriod: quota.used,
remaining: quota.remaining,
limit: quota.limit,
isUnlimited: quota.isUnlimited,
},
});
} catch (error) {
const normalizedError = normalizeError(error, "创建用户失败。");
return jsonError(normalizedError.message, normalizedError.statusCode);
}
}