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,51 @@
import { NextResponse, type NextRequest } from "next/server";
import {
SESSION_COOKIE_NAME,
authenticateUser,
createSessionForUser,
getSessionCookieOptions,
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 {
const body = (await request.json()) as {
username?: unknown;
password?: unknown;
};
const username = String(body?.username ?? "").trim();
const password = String(body?.password ?? "");
if (!username || !password) {
return jsonError("用户名和密码不能为空。", 400);
}
const user = authenticateUser(username, password);
if (!user) {
return jsonError("用户名或密码错误。", 401);
}
const session = createSessionForUser(user.id);
const response = NextResponse.json({
ok: true,
user: toPublicUser(user),
quota: getQuotaSnapshotForUser(user),
});
response.cookies.set(
SESSION_COOKIE_NAME,
session.sessionId,
getSessionCookieOptions(session.expiresAt),
);
return response;
} catch (error) {
const normalizedError = normalizeError(error, "登录失败。");
return jsonError(normalizedError.message, normalizedError.statusCode);
}
}

View File

@@ -0,0 +1,22 @@
import { NextResponse, type NextRequest } from "next/server";
import { SESSION_COOKIE_NAME, clearSessionById } from "@/lib/auth";
export const runtime = "nodejs";
export async function POST(request: NextRequest) {
const sessionId = request.cookies.get(SESSION_COOKIE_NAME)?.value;
clearSessionById(sessionId);
const response = NextResponse.json({ ok: true });
response.cookies.set(SESSION_COOKIE_NAME, "", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
expires: new Date(0),
});
return response;
}