NAC_Blockchain/ops/nac-admin/dist_backup_20260226_222327/index.js

1174 lines
50 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// server/_core/vite.ts
var vite_exports = {};
__export(vite_exports, {
serveStatic: () => serveStatic,
setupVite: () => setupVite
});
import express from "express";
import fs from "fs";
import { nanoid } from "nanoid";
import path from "path";
import { fileURLToPath } from "url";
import { createServer as createViteServer } from "vite";
async function setupVite(app, server) {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const serverOptions = {
middlewareMode: true,
hmr: { server },
allowedHosts: true
};
const vite = await createViteServer({
configFile: path.resolve(__dirname, "../../vite.config.ts"),
server: serverOptions,
appType: "custom"
});
app.use(vite.middlewares);
app.use("*", async (req, res, next) => {
const url = req.originalUrl;
try {
const clientTemplate = path.resolve(
__dirname,
"../..",
"client",
"index.html"
);
let template = await fs.promises.readFile(clientTemplate, "utf-8");
template = template.replace(
`src="/src/main.tsx"`,
`src="/src/main.tsx?v=${nanoid()}"`
);
const page = await vite.transformIndexHtml(url, template);
res.status(200).set({ "Content-Type": "text/html" }).end(page);
} catch (e) {
vite.ssrFixStacktrace(e);
next(e);
}
});
}
function serveStatic(app) {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const distPath = process.env.NODE_ENV === "development" ? path.resolve(__dirname, "../..", "dist", "public") : path.resolve(__dirname, "public");
if (!fs.existsSync(distPath)) {
console.error(
`Could not find the build directory: ${distPath}, make sure to build the client first`
);
}
app.use(express.static(distPath));
app.use("*", (_req, res) => {
res.sendFile(path.resolve(distPath, "index.html"));
});
}
var init_vite = __esm({
"server/_core/vite.ts"() {
"use strict";
}
});
// server/_core/static.ts
var static_exports = {};
__export(static_exports, {
serveStatic: () => serveStatic2
});
import express2 from "express";
import fs2 from "fs";
import path2 from "path";
import { fileURLToPath as fileURLToPath2 } from "url";
function serveStatic2(app) {
const __filename = fileURLToPath2(import.meta.url);
const __dirname = path2.dirname(__filename);
const distPath = path2.resolve(__dirname, "public");
if (!fs2.existsSync(distPath)) {
console.error(
`[Static] Could not find build directory: ${distPath}`
);
console.error(`[Static] Make sure to run 'pnpm build' first`);
app.use("*", (_req, res) => {
res.status(503).send("Service starting up, please wait...");
});
return;
}
console.log(`[Static] Serving files from: ${distPath}`);
app.use(express2.static(distPath));
app.use("*", (_req, res) => {
res.sendFile(path2.resolve(distPath, "index.html"));
});
}
var init_static = __esm({
"server/_core/static.ts"() {
"use strict";
}
});
// server/_core/index.ts
import "dotenv/config";
import express3 from "express";
import cookieParser from "cookie-parser";
import { createServer } from "http";
import net from "net";
import { createExpressMiddleware } from "@trpc/server/adapters/express";
// shared/const.ts
var COOKIE_NAME = "app_session_id";
var ONE_YEAR_MS = 1e3 * 60 * 60 * 24 * 365;
var AXIOS_TIMEOUT_MS = 3e4;
var UNAUTHED_ERR_MSG = "Please login (10001)";
var NOT_ADMIN_ERR_MSG = "You do not have required permission (10002)";
// server/db.ts
import { eq } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
// drizzle/schema.ts
import { int, mysqlEnum, mysqlTable, text, timestamp, varchar } from "drizzle-orm/mysql-core";
var users = mysqlTable("users", {
/**
* Surrogate primary key. Auto-incremented numeric value managed by the database.
* Use this for relations between tables.
*/
id: int("id").autoincrement().primaryKey(),
/** NAC_AI OAuth identifier (openId) returned from the OAuth callback. Unique per user. */
openId: varchar("openId", { length: 64 }).notNull().unique(),
name: text("name"),
email: varchar("email", { length: 320 }),
loginMethod: varchar("loginMethod", { length: 64 }),
role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull()
});
// server/_core/env.ts
var ENV = {
appId: process.env.VITE_APP_ID ?? "",
cookieSecret: process.env.JWT_SECRET ?? "",
databaseUrl: process.env.DATABASE_URL ?? "",
oAuthServerUrl: process.env.OAUTH_SERVER_URL ?? "",
ownerOpenId: process.env.OWNER_OPEN_ID ?? "",
isProduction: process.env.NODE_ENV === "production",
forgeApiUrl: process.env.BUILT_IN_FORGE_API_URL ?? "",
forgeApiKey: process.env.BUILT_IN_FORGE_API_KEY ?? ""
// 注意NAC数据库密钥请通过 server/secrets.ts 中的函数访问
// 例如getNacMysqlUrl(), getNacMongoUrl(), getNacJwtSecret()
};
// server/db.ts
var _db = null;
async function getDb() {
if (!_db && process.env.DATABASE_URL) {
try {
_db = drizzle(process.env.DATABASE_URL);
} catch (error) {
console.warn("[Database] Failed to connect:", error);
_db = null;
}
}
return _db;
}
async function upsertUser(user) {
if (!user.openId) {
throw new Error("User openId is required for upsert");
}
const db2 = await getDb();
if (!db2) {
console.warn("[Database] Cannot upsert user: database not available");
return;
}
try {
const values = {
openId: user.openId
};
const updateSet = {};
const textFields = ["name", "email", "loginMethod"];
const assignNullable = (field) => {
const value = user[field];
if (value === void 0) return;
const normalized = value ?? null;
values[field] = normalized;
updateSet[field] = normalized;
};
textFields.forEach(assignNullable);
if (user.lastSignedIn !== void 0) {
values.lastSignedIn = user.lastSignedIn;
updateSet.lastSignedIn = user.lastSignedIn;
}
if (user.role !== void 0) {
values.role = user.role;
updateSet.role = user.role;
} else if (user.openId === ENV.ownerOpenId) {
values.role = "admin";
updateSet.role = "admin";
}
if (!values.lastSignedIn) {
values.lastSignedIn = /* @__PURE__ */ new Date();
}
if (Object.keys(updateSet).length === 0) {
updateSet.lastSignedIn = /* @__PURE__ */ new Date();
}
await db2.insert(users).values(values).onDuplicateKeyUpdate({
set: updateSet
});
} catch (error) {
console.error("[Database] Failed to upsert user:", error);
throw error;
}
}
async function getUserByOpenId(openId) {
const db2 = await getDb();
if (!db2) {
console.warn("[Database] Cannot get user: database not available");
return void 0;
}
const result = await db2.select().from(users).where(eq(users.openId, openId)).limit(1);
return result.length > 0 ? result[0] : void 0;
}
// server/_core/cookies.ts
function isSecureRequest(req) {
if (req.protocol === "https") return true;
const forwardedProto = req.headers["x-forwarded-proto"];
if (!forwardedProto) return false;
const protoList = Array.isArray(forwardedProto) ? forwardedProto : forwardedProto.split(",");
return protoList.some((proto) => proto.trim().toLowerCase() === "https");
}
function getSessionCookieOptions(req) {
return {
httpOnly: true,
path: "/",
sameSite: "none",
secure: isSecureRequest(req)
};
}
// shared/_core/errors.ts
var HttpError = class extends Error {
constructor(statusCode, message) {
super(message);
this.statusCode = statusCode;
this.name = "HttpError";
}
};
var ForbiddenError = (msg) => new HttpError(403, msg);
// server/_core/sdk.ts
import axios from "axios";
import { parse as parseCookieHeader } from "cookie";
import { SignJWT, jwtVerify } from "jose";
var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
var EXCHANGE_TOKEN_PATH = `/webdev.v1.WebDevAuthPublicService/ExchangeToken`;
var GET_USER_INFO_PATH = `/webdev.v1.WebDevAuthPublicService/GetUserInfo`;
var GET_USER_INFO_WITH_JWT_PATH = `/webdev.v1.WebDevAuthPublicService/GetUserInfoWithJwt`;
var OAuthService = class {
constructor(client2) {
this.client = client2;
console.log("[OAuth] Initialized with baseURL:", ENV.oAuthServerUrl);
if (!ENV.oAuthServerUrl) {
console.error(
"[OAuth] ERROR: OAUTH_SERVER_URL is not configured! Set OAUTH_SERVER_URL environment variable."
);
}
}
decodeState(state) {
const redirectUri = atob(state);
return redirectUri;
}
async getTokenByCode(code, state) {
const payload = {
clientId: ENV.appId,
grantType: "authorization_code",
code,
redirectUri: this.decodeState(state)
};
const { data } = await this.client.post(
EXCHANGE_TOKEN_PATH,
payload
);
return data;
}
async getUserInfoByToken(token) {
const { data } = await this.client.post(
GET_USER_INFO_PATH,
{
accessToken: token.accessToken
}
);
return data;
}
};
var createOAuthHttpClient = () => axios.create({
baseURL: ENV.oAuthServerUrl,
timeout: AXIOS_TIMEOUT_MS
});
var SDKServer = class {
client;
oauthService;
constructor(client2 = createOAuthHttpClient()) {
this.client = client2;
this.oauthService = new OAuthService(this.client);
}
deriveLoginMethod(platforms, fallback) {
if (fallback && fallback.length > 0) return fallback;
if (!Array.isArray(platforms) || platforms.length === 0) return null;
const set = new Set(
platforms.filter((p) => typeof p === "string")
);
if (set.has("REGISTERED_PLATFORM_EMAIL")) return "email";
if (set.has("REGISTERED_PLATFORM_GOOGLE")) return "google";
if (set.has("REGISTERED_PLATFORM_APPLE")) return "apple";
if (set.has("REGISTERED_PLATFORM_MICROSOFT") || set.has("REGISTERED_PLATFORM_AZURE"))
return "microsoft";
if (set.has("REGISTERED_PLATFORM_GITHUB")) return "github";
const first = Array.from(set)[0];
return first ? first.toLowerCase() : null;
}
/**
* Exchange OAuth authorization code for access token
* @example
* const tokenResponse = await sdk.exchangeCodeForToken(code, state);
*/
async exchangeCodeForToken(code, state) {
return this.oauthService.getTokenByCode(code, state);
}
/**
* Get user information using access token
* @example
* const userInfo = await sdk.getUserInfo(tokenResponse.accessToken);
*/
async getUserInfo(accessToken) {
const data = await this.oauthService.getUserInfoByToken({
accessToken
});
const loginMethod = this.deriveLoginMethod(
data?.platforms,
data?.platform ?? data.platform ?? null
);
return {
...data,
platform: loginMethod,
loginMethod
};
}
parseCookies(cookieHeader) {
if (!cookieHeader) {
return /* @__PURE__ */ new Map();
}
const parsed = parseCookieHeader(cookieHeader);
return new Map(Object.entries(parsed));
}
getSessionSecret() {
const secret = ENV.cookieSecret;
return new TextEncoder().encode(secret);
}
/**
* Create a session token for a NAC_AI user openId
* @example
* const sessionToken = await sdk.createSessionToken(userInfo.openId);
*/
async createSessionToken(openId, options = {}) {
return this.signSession(
{
openId,
appId: ENV.appId,
name: options.name || ""
},
options
);
}
async signSession(payload, options = {}) {
const issuedAt = Date.now();
const expiresInMs = options.expiresInMs ?? ONE_YEAR_MS;
const expirationSeconds = Math.floor((issuedAt + expiresInMs) / 1e3);
const secretKey = this.getSessionSecret();
return new SignJWT({
openId: payload.openId,
appId: payload.appId,
name: payload.name
}).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setExpirationTime(expirationSeconds).sign(secretKey);
}
async verifySession(cookieValue) {
if (!cookieValue) {
console.warn("[Auth] Missing session cookie");
return null;
}
try {
const secretKey = this.getSessionSecret();
const { payload } = await jwtVerify(cookieValue, secretKey, {
algorithms: ["HS256"]
});
const { openId, appId, name } = payload;
if (!isNonEmptyString(openId) || !isNonEmptyString(appId) || !isNonEmptyString(name)) {
console.warn("[Auth] Session payload missing required fields");
return null;
}
return {
openId,
appId,
name
};
} catch (error) {
console.warn("[Auth] Session verification failed", String(error));
return null;
}
}
async getUserInfoWithJwt(jwtToken) {
const payload = {
jwtToken,
projectId: ENV.appId
};
const { data } = await this.client.post(
GET_USER_INFO_WITH_JWT_PATH,
payload
);
const loginMethod = this.deriveLoginMethod(
data?.platforms,
data?.platform ?? data.platform ?? null
);
return {
...data,
platform: loginMethod,
loginMethod
};
}
async authenticateRequest(req) {
const cookies = this.parseCookies(req.headers.cookie);
const sessionCookie = cookies.get(COOKIE_NAME);
const session = await this.verifySession(sessionCookie);
if (!session) {
throw ForbiddenError("Invalid session cookie");
}
const sessionUserId = session.openId;
const signedInAt = /* @__PURE__ */ new Date();
let user = await getUserByOpenId(sessionUserId);
if (!user) {
try {
const userInfo = await this.getUserInfoWithJwt(sessionCookie ?? "");
await upsertUser({
openId: userInfo.openId,
name: userInfo.name || null,
email: userInfo.email ?? null,
loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
lastSignedIn: signedInAt
});
user = await getUserByOpenId(userInfo.openId);
} catch (error) {
console.error("[Auth] Failed to sync user from OAuth:", error);
throw ForbiddenError("Failed to sync user info");
}
}
if (!user) {
throw ForbiddenError("User not found");
}
await upsertUser({
openId: user.openId,
lastSignedIn: signedInAt
});
return user;
}
};
var sdk = new SDKServer();
// server/_core/oauth.ts
function getQueryParam(req, key) {
const value = req.query[key];
return typeof value === "string" ? value : void 0;
}
function registerOAuthRoutes(app) {
app.get("/api/oauth/callback", async (req, res) => {
const code = getQueryParam(req, "code");
const state = getQueryParam(req, "state");
if (!code || !state) {
res.status(400).json({ error: "code and state are required" });
return;
}
try {
const tokenResponse = await sdk.exchangeCodeForToken(code, state);
const userInfo = await sdk.getUserInfo(tokenResponse.accessToken);
if (!userInfo.openId) {
res.status(400).json({ error: "openId missing from user info" });
return;
}
await upsertUser({
openId: userInfo.openId,
name: userInfo.name || null,
email: userInfo.email ?? null,
loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
lastSignedIn: /* @__PURE__ */ new Date()
});
const sessionToken = await sdk.createSessionToken(userInfo.openId, {
name: userInfo.name || "",
expiresInMs: ONE_YEAR_MS
});
const cookieOptions = getSessionCookieOptions(req);
res.cookie(COOKIE_NAME, sessionToken, { ...cookieOptions, maxAge: ONE_YEAR_MS });
res.redirect(302, "/");
} catch (error) {
console.error("[OAuth] Callback failed", error);
res.status(500).json({ error: "OAuth callback failed" });
}
});
}
// server/routers.ts
import { z as z2 } from "zod";
import { TRPCError as TRPCError3 } from "@trpc/server";
// server/_core/trpc.ts
import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson";
var t = initTRPC.context().create({
transformer: superjson
});
var router = t.router;
var publicProcedure = t.procedure;
var requireUser = t.middleware(async (opts) => {
const { ctx, next } = opts;
if (!ctx.user) {
throw new TRPCError({ code: "UNAUTHORIZED", message: UNAUTHED_ERR_MSG });
}
return next({
ctx: {
...ctx,
user: ctx.user
}
});
});
var protectedProcedure = t.procedure.use(requireUser);
var adminProcedure = t.procedure.use(
t.middleware(async (opts) => {
const { ctx, next } = opts;
if (!ctx.user || ctx.user.role !== "admin") {
throw new TRPCError({ code: "FORBIDDEN", message: NOT_ADMIN_ERR_MSG });
}
return next({
ctx: {
...ctx,
user: ctx.user
}
});
})
);
// server/_core/systemRouter.ts
import { z } from "zod";
// server/_core/notification.ts
import { TRPCError as TRPCError2 } from "@trpc/server";
var TITLE_MAX_LENGTH = 1200;
var CONTENT_MAX_LENGTH = 2e4;
var trimValue = (value) => value.trim();
var isNonEmptyString2 = (value) => typeof value === "string" && value.trim().length > 0;
var buildEndpointUrl = (baseUrl) => {
const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
return new URL(
"webdevtoken.v1.WebDevService/SendNotification",
normalizedBase
).toString();
};
var validatePayload = (input) => {
if (!isNonEmptyString2(input.title)) {
throw new TRPCError2({
code: "BAD_REQUEST",
message: "Notification title is required."
});
}
if (!isNonEmptyString2(input.content)) {
throw new TRPCError2({
code: "BAD_REQUEST",
message: "Notification content is required."
});
}
const title = trimValue(input.title);
const content = trimValue(input.content);
if (title.length > TITLE_MAX_LENGTH) {
throw new TRPCError2({
code: "BAD_REQUEST",
message: `Notification title must be at most ${TITLE_MAX_LENGTH} characters.`
});
}
if (content.length > CONTENT_MAX_LENGTH) {
throw new TRPCError2({
code: "BAD_REQUEST",
message: `Notification content must be at most ${CONTENT_MAX_LENGTH} characters.`
});
}
return { title, content };
};
async function notifyOwner(payload) {
const { title, content } = validatePayload(payload);
if (!ENV.forgeApiUrl) {
throw new TRPCError2({
code: "INTERNAL_SERVER_ERROR",
message: "Notification service URL is not configured."
});
}
if (!ENV.forgeApiKey) {
throw new TRPCError2({
code: "INTERNAL_SERVER_ERROR",
message: "Notification service API key is not configured."
});
}
const endpoint = buildEndpointUrl(ENV.forgeApiUrl);
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
accept: "application/json",
authorization: `Bearer ${ENV.forgeApiKey}`,
"content-type": "application/json",
"connect-protocol-version": "1"
},
body: JSON.stringify({ title, content })
});
if (!response.ok) {
const detail = await response.text().catch(() => "");
console.warn(
`[Notification] Failed to notify owner (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`
);
return false;
}
return true;
} catch (error) {
console.warn("[Notification] Error calling notification service:", error);
return false;
}
}
// server/_core/systemRouter.ts
var systemRouter = router({
health: publicProcedure.input(
z.object({
timestamp: z.number().min(0, "timestamp cannot be negative")
})
).query(() => ({
ok: true
})),
notifyOwner: adminProcedure.input(
z.object({
title: z.string().min(1, "title is required"),
content: z.string().min(1, "content is required")
})
).mutation(async ({ input }) => {
const delivered = await notifyOwner(input);
return {
success: delivered
};
})
});
// server/nacAuth.ts
import mysql from "mysql2/promise";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
// server/secrets.ts
function readSecret(key) {
return process.env[key];
}
function getNacMysqlUrl() {
const val = readSecret("NAC_MYSQL_URL");
if (!val) throw new Error("[Secrets] NAC_MYSQL_URL \u672A\u914D\u7F6E");
return val;
}
function getNacMongoUrl() {
const val = readSecret("NAC_MONGO_URL");
if (!val) throw new Error("[Secrets] NAC_MONGO_URL \u672A\u914D\u7F6E");
return val;
}
function getNacJwtSecret() {
const val = readSecret("NAC_JWT_SECRET");
if (!val) throw new Error("[Secrets] NAC_JWT_SECRET \u672A\u914D\u7F6E");
return val;
}
// server/nacAuth.ts
var pool = null;
function getNacMysqlPool() {
if (pool) return pool;
const url = new URL(getNacMysqlUrl());
pool = mysql.createPool({
host: url.hostname,
port: parseInt(url.port || "3306"),
user: url.username,
password: url.password,
database: url.pathname.slice(1),
waitForConnections: true,
connectionLimit: 5,
connectTimeout: 1e4
});
return pool;
}
function resolveRole(kyc_level, node_status) {
if (kyc_level >= 2 && node_status === "constitutional") return "admin";
if (kyc_level >= 1) return "legal";
return "reviewer";
}
async function loginWithNacCredentials(email, password) {
const pool2 = getNacMysqlPool();
const [rows] = await pool2.execute(
"SELECT id, name, email, password, kyc_level, node_status, is_active FROM users WHERE email = ? AND is_active = 1",
[email]
);
if (rows.length === 0) return null;
const dbUser = rows[0];
const valid = await bcrypt.compare(password, dbUser.password);
if (!valid) return null;
const role = resolveRole(dbUser.kyc_level, dbUser.node_status);
const user = {
id: dbUser.id,
name: dbUser.name,
email: dbUser.email,
kyc_level: dbUser.kyc_level,
node_status: dbUser.node_status,
is_active: dbUser.is_active,
role
};
const token = jwt.sign(
{ id: user.id, email: user.email, role: user.role },
getNacJwtSecret(),
{ expiresIn: "24h" }
);
return { user, token };
}
function verifyNacToken(token) {
try {
return jwt.verify(token, getNacJwtSecret());
} catch {
return null;
}
}
async function listNacUsers(limit = 50, offset = 0) {
const pool2 = getNacMysqlPool();
const [rows] = await pool2.execute(
"SELECT id, name, email, kyc_level, node_status, is_active, created_at, last_login_at FROM users ORDER BY id DESC LIMIT ? OFFSET ?",
[limit, offset]
);
return rows.map((r) => ({ ...r, role: resolveRole(r.kyc_level, r.node_status) }));
}
async function getNacUserCount() {
const pool2 = getNacMysqlPool();
const [rows] = await pool2.execute("SELECT COUNT(*) as cnt FROM users");
return rows[0]?.cnt || 0;
}
// server/mongodb.ts
import { MongoClient } from "mongodb";
var client = null;
var db = null;
async function getMongoDb() {
if (db) return db;
try {
client = new MongoClient(getNacMongoUrl(), {
serverSelectionTimeoutMS: 5e3,
connectTimeoutMS: 5e3
});
await client.connect();
db = client.db("nac_knowledge_engine");
console.log("[MongoDB] Connected to nac_knowledge_engine");
return db;
} catch (error) {
console.error("[MongoDB] Connection failed:", error.message);
return null;
}
}
var COLLECTIONS = {
COMPLIANCE_RULES: "compliance_rules",
CRAWLERS: "crawlers",
CRAWLER_LOGS: "crawler_logs",
APPROVAL_CASES: "approval_cases",
TAG_RULES: "tag_rules",
PROTOCOL_REGISTRY: "protocol_registry",
AUDIT_LOGS: "audit_logs",
KNOWLEDGE_STATS: "knowledge_stats"
};
// server/routers.ts
import { ObjectId } from "mongodb";
var nacAuthProcedure = publicProcedure.use(async ({ ctx, next }) => {
const token = ctx.req.cookies?.["nac_admin_token"] || ctx.req.headers["x-nac-token"];
if (!token) throw new TRPCError3({ code: "UNAUTHORIZED", message: "\u8BF7\u5148\u767B\u5F55" });
const payload = verifyNacToken(token);
if (!payload) throw new TRPCError3({ code: "UNAUTHORIZED", message: "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" });
return next({ ctx: { ...ctx, nacUser: payload } });
});
var nacAdminProcedure = nacAuthProcedure.use(async ({ ctx, next }) => {
if (ctx.nacUser?.role !== "admin") {
throw new TRPCError3({ code: "FORBIDDEN", message: "\u9700\u8981\u7BA1\u7406\u5458\u6743\u9650" });
}
return next({ ctx });
});
async function writeAuditLog(action, userId, email, detail) {
try {
const db2 = await getMongoDb();
if (!db2) return;
await db2.collection(COLLECTIONS.AUDIT_LOGS).insertOne({
action,
userId,
email,
detail,
timestamp: /* @__PURE__ */ new Date(),
immutable: true
});
} catch (e) {
console.error("[AuditLog] Failed:", e.message);
}
}
async function ensureKnowledgeBaseData() {
const db2 = await getMongoDb();
if (!db2) return;
const protocolCount = await db2.collection(COLLECTIONS.PROTOCOL_REGISTRY).countDocuments();
if (protocolCount === 0) {
await db2.collection(COLLECTIONS.PROTOCOL_REGISTRY).insertMany([
{ name: "nac-charter-compiler", type: "contract_validation", version: "1.0.0", endpoint: "charter.newassetchain.io", trigger: "asset_type in ALL", status: "active", createdAt: /* @__PURE__ */ new Date() },
{ name: "nac-cnnl-validator", type: "constitutional_check", version: "1.0.0", endpoint: "cnnl.newassetchain.io", trigger: "asset_type in ALL", status: "active", createdAt: /* @__PURE__ */ new Date() },
{ name: "nac-acc20-engine", type: "compliance_approval", version: "1.0.0", endpoint: "acc20.newassetchain.io", trigger: "asset_type in ALL", status: "active", createdAt: /* @__PURE__ */ new Date() },
{ name: "nac-gnacs-classifier", type: "asset_classification", version: "1.0.0", endpoint: "gnacs.newassetchain.io", trigger: "asset_type in ALL", status: "active", createdAt: /* @__PURE__ */ new Date() },
{ name: "nac-valuation-ai", type: "valuation_model", version: "0.9.0", endpoint: "valuation.newassetchain.io", trigger: "asset_type is RealEstate", status: "pending", createdAt: /* @__PURE__ */ new Date() }
]);
}
const crawlerCount = await db2.collection(COLLECTIONS.CRAWLERS).countDocuments();
if (crawlerCount === 0) {
await db2.collection(COLLECTIONS.CRAWLERS).insertMany([
{ name: "CN-CSRC\u6CD5\u89C4\u91C7\u96C6\u5668", jurisdiction: "CN", type: "external", source: "http://www.csrc.gov.cn", category: "regulation", frequency: "daily", status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() },
{ name: "HK-SFC\u6CD5\u89C4\u91C7\u96C6\u5668", jurisdiction: "HK", type: "external", source: "https://www.sfc.hk", category: "regulation", frequency: "daily", status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() },
{ name: "US-SEC\u6CD5\u89C4\u91C7\u96C6\u5668", jurisdiction: "US", type: "external", source: "https://www.sec.gov", category: "regulation", frequency: "daily", status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() },
{ name: "EU-ESMA\u6CD5\u89C4\u91C7\u96C6\u5668", jurisdiction: "EU", type: "external", source: "https://www.esma.europa.eu", category: "regulation", frequency: "weekly", status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() },
{ name: "SG-MAS\u6CD5\u89C4\u91C7\u96C6\u5668", jurisdiction: "SG", type: "external", source: "https://www.mas.gov.sg", category: "regulation", frequency: "daily", status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() },
{ name: "AE-DFSA\u6CD5\u89C4\u91C7\u96C6\u5668", jurisdiction: "AE", type: "external", source: "https://www.dfsa.ae", category: "regulation", frequency: "weekly", status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() },
{ name: "CN-\u88C1\u5224\u6587\u4E66\u7F51\u91C7\u96C6\u5668", jurisdiction: "CN", type: "external", source: "https://wenshu.court.gov.cn", category: "credit", frequency: "weekly", status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() },
{ name: "\u5185\u90E8\u4E0A\u94FE\u6587\u4EF6\u91C7\u96C6\u5668", jurisdiction: "ALL", type: "internal", source: "internal://onboarding", category: "asset_document", frequency: "realtime", status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() }
]);
}
const ruleCount = await db2.collection(COLLECTIONS.COMPLIANCE_RULES).countDocuments();
if (ruleCount === 0) {
await db2.collection(COLLECTIONS.COMPLIANCE_RULES).insertMany([
{ jurisdiction: "CN", assetType: "RealEstate", ruleName: "\u4E0D\u52A8\u4EA7\u767B\u8BB0\u8BC1\u8981\u6C42", description: "\u4E2D\u56FD\u5883\u5185\u623F\u5730\u4EA7\u4E0A\u94FE\u5FC5\u987B\u63D0\u4F9B\u4E0D\u52A8\u4EA7\u767B\u8BB0\u8BC1", required: true, status: "active", tags: ["CN", "RealEstate", "Document", "Required"], createdAt: /* @__PURE__ */ new Date() },
{ jurisdiction: "HK", assetType: "Securities", ruleName: "SFC\u6301\u724C\u8981\u6C42", description: "\u9999\u6E2F\u8BC1\u5238\u7C7B\u8D44\u4EA7\u4E0A\u94FE\u987B\u7ECFSFC\u6301\u724C\u673A\u6784\u5BA1\u6838", required: true, status: "active", tags: ["HK", "Securities", "License", "SFC"], createdAt: /* @__PURE__ */ new Date() },
{ jurisdiction: "US", assetType: "Securities", ruleName: "Reg D\u8C41\u514D\u7533\u62A5", description: "\u7F8E\u56FD\u8BC1\u5238\u7C7B\u8D44\u4EA7\u987B\u6EE1\u8DB3Reg D/S\u8C41\u514D\u6761\u4EF6", required: true, status: "active", tags: ["US", "Securities", "RegD", "SEC"], createdAt: /* @__PURE__ */ new Date() },
{ jurisdiction: "EU", assetType: "ALL", ruleName: "MiCA\u5408\u89C4\u8981\u6C42", description: "\u6B27\u76DF\u5883\u5185\u6240\u6709\u52A0\u5BC6\u8D44\u4EA7\u987B\u7B26\u5408MiCA\u6CD5\u89C4", required: true, status: "active", tags: ["EU", "ALL", "MiCA", "ESMA"], createdAt: /* @__PURE__ */ new Date() },
{ jurisdiction: "SG", assetType: "DigitalToken", ruleName: "MAS\u6570\u5B57\u4EE3\u5E01\u670D\u52A1\u724C\u7167", description: "\u65B0\u52A0\u5761\u6570\u5B57\u4EE3\u5E01\u670D\u52A1\u987B\u6301MAS\u724C\u7167", required: true, status: "active", tags: ["SG", "DigitalToken", "MAS", "License"], createdAt: /* @__PURE__ */ new Date() },
{ jurisdiction: "AE", assetType: "RealEstate", ruleName: "DLD\u4EA7\u6743\u8BC1\u4E66\u8981\u6C42", description: "\u8FEA\u62DC\u623F\u5730\u4EA7\u4E0A\u94FE\u987B\u63D0\u4F9BDLD\u9881\u53D1\u7684\u4EA7\u6743\u8BC1\u4E66", required: true, status: "active", tags: ["AE", "RealEstate", "DLD", "Document"], createdAt: /* @__PURE__ */ new Date() }
]);
}
}
var appRouter = router({
system: systemRouter,
// ─── NAC原生认证不使用NAC_AI OAuth────────────────────────────
nacAuth: router({
login: publicProcedure.input(z2.object({ email: z2.string().email(), password: z2.string().min(1) })).mutation(async ({ input, ctx }) => {
try {
const result = await loginWithNacCredentials(input.email, input.password);
if (!result) throw new TRPCError3({ code: "UNAUTHORIZED", message: "\u90AE\u7BB1\u6216\u5BC6\u7801\u9519\u8BEF" });
const cookieOptions = getSessionCookieOptions(ctx.req);
ctx.res.cookie("nac_admin_token", result.token, { ...cookieOptions, maxAge: 24 * 60 * 60 * 1e3 });
await writeAuditLog("LOGIN", result.user.id, result.user.email, { ip: ctx.req.ip });
return { success: true, user: { id: result.user.id, name: result.user.name, email: result.user.email, role: result.user.role, kyc_level: result.user.kyc_level } };
} catch (e) {
if (e instanceof TRPCError3) throw e;
throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR", message: "\u767B\u5F55\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528" });
}
}),
logout: publicProcedure.mutation(({ ctx }) => {
ctx.res.clearCookie("nac_admin_token");
return { success: true };
}),
me: nacAuthProcedure.query(async ({ ctx }) => {
const nacUser = ctx.nacUser;
return { id: nacUser.id, email: nacUser.email, role: nacUser.role };
})
}),
// ─── 全局态势感知仪表盘 ──────────────────────────────────────────
dashboard: router({
stats: nacAuthProcedure.query(async () => {
const db2 = await getMongoDb();
if (!db2) return { error: "\u6570\u636E\u5E93\u8FDE\u63A5\u5931\u8D25" };
await ensureKnowledgeBaseData();
const [ruleCount, crawlerCount, caseCount, protocolCount, userCount, auditCount] = await Promise.all([
db2.collection(COLLECTIONS.COMPLIANCE_RULES).countDocuments(),
db2.collection(COLLECTIONS.CRAWLERS).countDocuments(),
db2.collection(COLLECTIONS.APPROVAL_CASES).countDocuments(),
db2.collection(COLLECTIONS.PROTOCOL_REGISTRY).countDocuments(),
getNacUserCount(),
db2.collection(COLLECTIONS.AUDIT_LOGS).countDocuments()
]);
const [activeCrawlers, pendingCases, approvedCases, activeProtocols] = await Promise.all([
db2.collection(COLLECTIONS.CRAWLERS).countDocuments({ status: "active" }),
db2.collection(COLLECTIONS.APPROVAL_CASES).countDocuments({ status: "pending_review" }),
db2.collection(COLLECTIONS.APPROVAL_CASES).countDocuments({ decision: "approved" }),
db2.collection(COLLECTIONS.PROTOCOL_REGISTRY).countDocuments({ status: "active" })
]);
const jurisdictionStats = await db2.collection(COLLECTIONS.COMPLIANCE_RULES).aggregate([
{ $group: { _id: "$jurisdiction", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]).toArray();
return {
knowledgeBase: { totalRules: ruleCount, activeProtocols, totalProtocols: protocolCount },
crawlers: { total: crawlerCount, active: activeCrawlers },
approvals: { total: caseCount, pending: pendingCases, approved: approvedCases, approvalRate: caseCount > 0 ? Math.round(approvedCases / caseCount * 100) : 0 },
users: { total: userCount },
audit: { total: auditCount },
jurisdictionCoverage: jurisdictionStats,
systemStatus: { mongodb: "connected", mysql: "connected", timestamp: /* @__PURE__ */ new Date() }
};
}),
recentActivity: nacAuthProcedure.query(async () => {
const db2 = await getMongoDb();
if (!db2) return [];
return db2.collection(COLLECTIONS.AUDIT_LOGS).find({}).sort({ timestamp: -1 }).limit(20).toArray();
})
}),
// ─── 知识库管理 ──────────────────────────────────────────────────
knowledgeBase: router({
list: nacAuthProcedure.input(z2.object({ jurisdiction: z2.string().optional(), assetType: z2.string().optional(), status: z2.string().optional(), page: z2.number().default(1), pageSize: z2.number().default(20) })).query(async ({ input }) => {
const db2 = await getMongoDb();
if (!db2) return { items: [], total: 0 };
const filter = {};
if (input.jurisdiction) filter.jurisdiction = input.jurisdiction;
if (input.assetType) filter.assetType = input.assetType;
if (input.status) filter.status = input.status;
const skip = (input.page - 1) * input.pageSize;
const [items, total] = await Promise.all([
db2.collection(COLLECTIONS.COMPLIANCE_RULES).find(filter).sort({ createdAt: -1 }).skip(skip).limit(input.pageSize).toArray(),
db2.collection(COLLECTIONS.COMPLIANCE_RULES).countDocuments(filter)
]);
return { items, total };
}),
create: nacAdminProcedure.input(z2.object({ jurisdiction: z2.string(), assetType: z2.string(), ruleName: z2.string(), description: z2.string(), required: z2.boolean(), tags: z2.array(z2.string()) })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
const result = await db2.collection(COLLECTIONS.COMPLIANCE_RULES).insertOne({ ...input, status: "active", createdAt: /* @__PURE__ */ new Date() });
await writeAuditLog("CREATE_RULE", ctx.nacUser.id, ctx.nacUser.email, { ruleId: result.insertedId, ruleName: input.ruleName });
return { id: result.insertedId };
}),
update: nacAdminProcedure.input(z2.object({ id: z2.string(), data: z2.object({ ruleName: z2.string().optional(), description: z2.string().optional(), status: z2.enum(["active", "disabled"]).optional(), tags: z2.array(z2.string()).optional() }) })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
await db2.collection(COLLECTIONS.COMPLIANCE_RULES).updateOne({ _id: new ObjectId(input.id) }, { $set: { ...input.data, updatedAt: /* @__PURE__ */ new Date() } });
await writeAuditLog("UPDATE_RULE", ctx.nacUser.id, ctx.nacUser.email, { ruleId: input.id });
return { success: true };
}),
toggleStatus: nacAdminProcedure.input(z2.object({ id: z2.string(), status: z2.enum(["active", "disabled"]) })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
await db2.collection(COLLECTIONS.COMPLIANCE_RULES).updateOne({ _id: new ObjectId(input.id) }, { $set: { status: input.status, updatedAt: /* @__PURE__ */ new Date() } });
await writeAuditLog("TOGGLE_RULE", ctx.nacUser.id, ctx.nacUser.email, { ruleId: input.id, newStatus: input.status });
return { success: true };
}),
delete: nacAdminProcedure.input(z2.object({ id: z2.string() })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
await db2.collection(COLLECTIONS.COMPLIANCE_RULES).deleteOne({ _id: new ObjectId(input.id) });
await writeAuditLog("DELETE_RULE", ctx.nacUser.id, ctx.nacUser.email, { ruleId: input.id });
return { success: true };
})
}),
// ─── 采集器监控与管理 ────────────────────────────────────────────
crawler: router({
list: nacAuthProcedure.query(async () => {
const db2 = await getMongoDb();
if (!db2) return [];
return db2.collection(COLLECTIONS.CRAWLERS).find({}).sort({ createdAt: -1 }).toArray();
}),
logs: nacAuthProcedure.input(z2.object({ crawlerId: z2.string().optional(), limit: z2.number().default(50) })).query(async ({ input }) => {
const db2 = await getMongoDb();
if (!db2) return [];
const filter = {};
if (input.crawlerId) filter.crawlerId = input.crawlerId;
return db2.collection(COLLECTIONS.CRAWLER_LOGS).find(filter).sort({ timestamp: -1 }).limit(input.limit).toArray();
}),
trigger: nacAdminProcedure.input(z2.object({ crawlerId: z2.string() })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
const crawler = await db2.collection(COLLECTIONS.CRAWLERS).findOne({ _id: new ObjectId(input.crawlerId) });
if (!crawler) throw new TRPCError3({ code: "NOT_FOUND", message: "\u91C7\u96C6\u5668\u4E0D\u5B58\u5728" });
await db2.collection(COLLECTIONS.CRAWLER_LOGS).insertOne({ crawlerId: input.crawlerId, crawlerName: crawler.name, action: "manual_trigger", status: "triggered", message: "\u7BA1\u7406\u5458\u624B\u52A8\u89E6\u53D1\u91C7\u96C6\u4EFB\u52A1", timestamp: /* @__PURE__ */ new Date() });
await db2.collection(COLLECTIONS.CRAWLERS).updateOne({ _id: new ObjectId(input.crawlerId) }, { $set: { lastRun: /* @__PURE__ */ new Date() } });
await writeAuditLog("TRIGGER_CRAWLER", ctx.nacUser.id, ctx.nacUser.email, { crawlerId: input.crawlerId, crawlerName: crawler.name });
return { success: true, message: `\u91C7\u96C6\u5668 "${crawler.name}" \u5DF2\u89E6\u53D1` };
}),
create: nacAdminProcedure.input(z2.object({ name: z2.string(), jurisdiction: z2.string(), type: z2.enum(["internal", "external"]), source: z2.string(), category: z2.string(), frequency: z2.string() })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
const result = await db2.collection(COLLECTIONS.CRAWLERS).insertOne({ ...input, status: "active", lastRun: null, successRate: 0, totalCollected: 0, createdAt: /* @__PURE__ */ new Date() });
await writeAuditLog("CREATE_CRAWLER", ctx.nacUser.id, ctx.nacUser.email, { crawlerName: input.name });
return { id: result.insertedId };
})
}),
// ─── AI审批案例审查 ──────────────────────────────────────────────
approvalCase: router({
list: nacAuthProcedure.input(z2.object({ status: z2.string().optional(), riskLevel: z2.string().optional(), page: z2.number().default(1), pageSize: z2.number().default(20) })).query(async ({ input }) => {
const db2 = await getMongoDb();
if (!db2) return { items: [], total: 0 };
const filter = {};
if (input.status) filter.status = input.status;
if (input.riskLevel) filter.riskLevel = input.riskLevel;
const skip = (input.page - 1) * input.pageSize;
const [items, total] = await Promise.all([
db2.collection(COLLECTIONS.APPROVAL_CASES).find(filter).sort({ createdAt: -1 }).skip(skip).limit(input.pageSize).toArray(),
db2.collection(COLLECTIONS.APPROVAL_CASES).countDocuments(filter)
]);
return { items, total };
}),
review: nacAuthProcedure.input(z2.object({ id: z2.string(), decision: z2.enum(["approved", "rejected"]), comment: z2.string().optional() })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
const nacUser = ctx.nacUser;
await db2.collection(COLLECTIONS.APPROVAL_CASES).updateOne(
{ _id: new ObjectId(input.id) },
{ $set: { status: "reviewed", decision: input.decision, reviewComment: input.comment, reviewedBy: nacUser.email, reviewedAt: /* @__PURE__ */ new Date() } }
);
await writeAuditLog("REVIEW_CASE", nacUser.id, nacUser.email, { caseId: input.id, decision: input.decision });
return { success: true };
})
}),
// ─── 标签与规则引擎治理 ──────────────────────────────────────────
tagEngine: router({
listRules: nacAuthProcedure.query(async () => {
const db2 = await getMongoDb();
if (!db2) return [];
return db2.collection(COLLECTIONS.TAG_RULES).find({}).sort({ createdAt: -1 }).toArray();
}),
correctTag: nacAuthProcedure.input(z2.object({ documentId: z2.string(), originalTags: z2.array(z2.string()), correctedTags: z2.array(z2.string()), reason: z2.string() })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
const nacUser = ctx.nacUser;
await db2.collection(COLLECTIONS.TAG_RULES).insertOne({ type: "correction", documentId: input.documentId, originalTags: input.originalTags, correctedTags: input.correctedTags, reason: input.reason, correctedBy: nacUser.email, isTrainingData: true, createdAt: /* @__PURE__ */ new Date() });
await writeAuditLog("CORRECT_TAG", nacUser.id, nacUser.email, { documentId: input.documentId });
return { success: true };
}),
createRule: nacAdminProcedure.input(z2.object({ keyword: z2.string(), tags: z2.array(z2.string()), dimension: z2.string(), description: z2.string() })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
const result = await db2.collection(COLLECTIONS.TAG_RULES).insertOne({ ...input, type: "rule", status: "active", createdAt: /* @__PURE__ */ new Date() });
await writeAuditLog("CREATE_TAG_RULE", ctx.nacUser.id, ctx.nacUser.email, { keyword: input.keyword });
return { id: result.insertedId };
})
}),
// ─── 协议族注册表管理 ────────────────────────────────────────────
protocolRegistry: router({
list: nacAuthProcedure.query(async () => {
const db2 = await getMongoDb();
if (!db2) return [];
return db2.collection(COLLECTIONS.PROTOCOL_REGISTRY).find({}).sort({ createdAt: -1 }).toArray();
}),
register: nacAdminProcedure.input(z2.object({ name: z2.string(), type: z2.string(), version: z2.string(), endpoint: z2.string(), trigger: z2.string(), description: z2.string().optional() })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
const result = await db2.collection(COLLECTIONS.PROTOCOL_REGISTRY).insertOne({ ...input, status: "active", createdAt: /* @__PURE__ */ new Date() });
await writeAuditLog("REGISTER_PROTOCOL", ctx.nacUser.id, ctx.nacUser.email, { protocolName: input.name });
return { id: result.insertedId };
}),
toggleStatus: nacAdminProcedure.input(z2.object({ id: z2.string(), status: z2.enum(["active", "disabled", "deprecated"]) })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
await db2.collection(COLLECTIONS.PROTOCOL_REGISTRY).updateOne({ _id: new ObjectId(input.id) }, { $set: { status: input.status, updatedAt: /* @__PURE__ */ new Date() } });
await writeAuditLog("TOGGLE_PROTOCOL", ctx.nacUser.id, ctx.nacUser.email, { protocolId: input.id, newStatus: input.status });
return { success: true };
}),
updateVersion: nacAdminProcedure.input(z2.object({ id: z2.string(), version: z2.string(), trigger: z2.string().optional() })).mutation(async ({ input, ctx }) => {
const db2 = await getMongoDb();
if (!db2) throw new TRPCError3({ code: "INTERNAL_SERVER_ERROR" });
const update = { version: input.version, updatedAt: /* @__PURE__ */ new Date() };
if (input.trigger) update.trigger = input.trigger;
await db2.collection(COLLECTIONS.PROTOCOL_REGISTRY).updateOne({ _id: new ObjectId(input.id) }, { $set: update });
await writeAuditLog("UPDATE_PROTOCOL_VERSION", ctx.nacUser.id, ctx.nacUser.email, { protocolId: input.id, version: input.version });
return { success: true };
})
}),
// ─── 权限与审计管理 ──────────────────────────────────────────────
rbac: router({
listUsers: nacAdminProcedure.input(z2.object({ page: z2.number().default(1), pageSize: z2.number().default(20) })).query(async ({ input }) => {
const offset = (input.page - 1) * input.pageSize;
const [users2, total] = await Promise.all([listNacUsers(input.pageSize, offset), getNacUserCount()]);
return { users: users2, total };
}),
auditLogs: nacAdminProcedure.input(z2.object({ action: z2.string().optional(), userId: z2.number().optional(), page: z2.number().default(1), pageSize: z2.number().default(50) })).query(async ({ input }) => {
const db2 = await getMongoDb();
if (!db2) return { items: [], total: 0 };
const filter = {};
if (input.action) filter.action = input.action;
if (input.userId) filter.userId = input.userId;
const skip = (input.page - 1) * input.pageSize;
const [items, total] = await Promise.all([
db2.collection(COLLECTIONS.AUDIT_LOGS).find(filter).sort({ timestamp: -1 }).skip(skip).limit(input.pageSize).toArray(),
db2.collection(COLLECTIONS.AUDIT_LOGS).countDocuments(filter)
]);
return { items, total };
})
})
});
// server/_core/context.ts
async function createContext(opts) {
let user = null;
try {
user = await sdk.authenticateRequest(opts.req);
} catch (error) {
user = null;
}
return {
req: opts.req,
res: opts.res,
user
};
}
// server/_core/index.ts
function isPortAvailable(port) {
return new Promise((resolve) => {
const server = net.createServer();
server.listen(port, () => {
server.close(() => resolve(true));
});
server.on("error", () => resolve(false));
});
}
async function findAvailablePort(startPort = 3e3) {
for (let port = startPort; port < startPort + 20; port++) {
if (await isPortAvailable(port)) {
return port;
}
}
throw new Error(`No available port found starting from ${startPort}`);
}
async function startServer() {
const app = express3();
const server = createServer(app);
app.set("trust proxy", 1);
app.use(cookieParser());
app.use(express3.json({ limit: "50mb" }));
app.use(express3.urlencoded({ limit: "50mb", extended: true }));
registerOAuthRoutes(app);
app.use(
"/api/trpc",
createExpressMiddleware({
router: appRouter,
createContext
})
);
if (process.env.NODE_ENV === "development") {
const { setupVite: setupVite2 } = await Promise.resolve().then(() => (init_vite(), vite_exports));
await setupVite2(app, server);
} else {
const { serveStatic: serveStatic3 } = await Promise.resolve().then(() => (init_static(), static_exports));
serveStatic3(app);
}
const preferredPort = parseInt(process.env.PORT || "3000");
const port = await findAvailablePort(preferredPort);
if (port !== preferredPort) {
console.log(`Port ${preferredPort} is busy, using port ${port} instead`);
}
server.listen(port, () => {
console.log(`Server running on http://localhost:${port}/`);
});
}
startServer().catch(console.error);