Remove backup/env files from tracking, update .gitignore
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
PG_HOST=localhost
|
||||
PG_PORT=5432
|
||||
PG_DATABASE=omniai
|
||||
PG_USER=omniai
|
||||
PG_PASSWORD=bybyby@123
|
||||
PG_POOL_MAX=10
|
||||
PORT=3600
|
||||
HOST=0.0.0.0
|
||||
JWT_SECRET=499808ef76791e59ab1019f8fbb86d2b
|
||||
DEFAULT_ADMIN_PASSWORD=bybyby@123BY
|
||||
JWT_EXPIRES_IN=7d
|
||||
CORS_ORIGINS=*
|
||||
@@ -1,17 +0,0 @@
|
||||
PG_HOST=localhost
|
||||
PG_PORT=5432
|
||||
PG_DATABASE=omniai
|
||||
PG_USER=omniai
|
||||
PG_PASSWORD=bybyby@123
|
||||
PG_POOL_MAX=10
|
||||
PORT=3600
|
||||
HOST=0.0.0.0
|
||||
JWT_SECRET=499808ef76791e59ab1019f8fbb86d2b
|
||||
DEFAULT_ADMIN_PASSWORD=bybyby@123BY
|
||||
JWT_EXPIRES_IN=7d
|
||||
CORS_ORIGINS=*
|
||||
STS_ACCESS_KEY_ID=LTAI5t7qL3iR9dchydHQ3cmT
|
||||
STS_ACCESS_KEY_SECRET=ssywO1bUwu2pPZaq3KugXbaE0Za9gi
|
||||
OSS_ROLE_ARN=acs:ram::1582660594690998:role/omniai-oss-role
|
||||
OSS_BUCKET=stringtest
|
||||
OSS_REGION=oss-cn-hangzhou
|
||||
@@ -1,17 +0,0 @@
|
||||
PG_HOST=localhost
|
||||
PG_PORT=5432
|
||||
PG_DATABASE=omniai
|
||||
PG_USER=omniai
|
||||
PG_PASSWORD=bybyby@123
|
||||
PG_POOL_MAX=10
|
||||
PORT=3600
|
||||
HOST=0.0.0.0
|
||||
JWT_SECRET=499808ef76791e59ab1019f8fbb86d2b
|
||||
DEFAULT_ADMIN_PASSWORD=bybyby@123BY
|
||||
JWT_EXPIRES_IN=7d
|
||||
CORS_ORIGINS=*
|
||||
STS_ACCESS_KEY_ID=LTAI5t7qL3iR9dchydHQ3cmT
|
||||
STS_ACCESS_KEY_SECRET=ssywO1bUwu2pPZaq3KugXbaE0Za9gi
|
||||
OSS_ROLE_ARN=acs:ram::1582660594690998:role/OmniAI-OSS-Upload
|
||||
OSS_BUCKET=stringtest
|
||||
OSS_REGION=oss-cn-hangzhou
|
||||
@@ -8,3 +8,8 @@ config/internal-beta-codes.md
|
||||
data/
|
||||
*.tmp
|
||||
.DS_Store
|
||||
*.env.backup*
|
||||
*.env.rolefix*
|
||||
*.env.enterprise*
|
||||
src/routes/ai.js.bak*
|
||||
src/routes/enterprise.js.enterprise-usage-fix*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,456 +0,0 @@
|
||||
const {
|
||||
requireAuth,
|
||||
requireEnterpriseAdmin,
|
||||
distributeCredits,
|
||||
getEnterpriseFinancials,
|
||||
getEnterpriseName,
|
||||
pool,
|
||||
getPeriodStart,
|
||||
generateOrderNo,
|
||||
clampPositiveInteger,
|
||||
clampNonNegativeInteger,
|
||||
} = require("./context");
|
||||
|
||||
function registerEnterpriseRoutes(router) {
|
||||
// ── Enterprise: Dashboard & Financials ────────────────────────────────
|
||||
|
||||
router.get("/enterprise/dashboard", requireAuth, requireEnterpriseAdmin, async (req, res) => {
|
||||
const financials = await getEnterpriseFinancials(req.user.enterpriseId);
|
||||
const {
|
||||
rows: [countRow],
|
||||
} = await pool.query(
|
||||
"SELECT COUNT(*) AS count FROM users WHERE enterprise_id = $1 AND enabled = 1 AND is_enterprise_admin = 0",
|
||||
[req.user.enterpriseId],
|
||||
);
|
||||
|
||||
res.json({
|
||||
enterpriseName: req.user.enterpriseName,
|
||||
enterpriseCode: req.user.enterpriseCode,
|
||||
balanceCents: financials.balanceCents,
|
||||
activePackages: financials.activePackages,
|
||||
subAccountCount: Number(countRow?.count || 0),
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/enterprise/financials", requireAuth, requireEnterpriseAdmin, async (req, res) => {
|
||||
const financials = await getEnterpriseFinancials(req.user.enterpriseId);
|
||||
res.json({
|
||||
balanceCents: financials.balanceCents,
|
||||
activePackages: financials.activePackages.map((p) => ({
|
||||
id: p.id,
|
||||
packageName: p.package_name,
|
||||
remainingImage: p.remaining_image,
|
||||
remainingVideo: p.remaining_video,
|
||||
remainingText: p.remaining_text,
|
||||
expiresAt: p.expires_at,
|
||||
activatedAt: p.activated_at,
|
||||
})),
|
||||
recentTransactions: financials.recentTransactions.map((t) => ({
|
||||
id: t.id,
|
||||
type: t.type,
|
||||
amountCents: t.amount_cents,
|
||||
balanceAfterCents: t.balance_after_cents,
|
||||
description: t.description,
|
||||
createdAt: t.created_at,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/enterprise/usage/summary", requireAuth, requireEnterpriseAdmin, async (req, res) => {
|
||||
const { period = "30d" } = req.query;
|
||||
const periodStart = getPeriodStart(period);
|
||||
const ledgerDateJoin = periodStart ? `AND cl.created_at >= ${periodStart}` : "";
|
||||
const ledgerDateWhere = periodStart ? `AND created_at >= ${periodStart}` : "";
|
||||
const recordsLimit = clampPositiveInteger(req.query.limit, 50, 200);
|
||||
|
||||
try {
|
||||
const {
|
||||
rows: [enterprise],
|
||||
} = await pool.query(
|
||||
"SELECT id, name, balance_cents FROM enterprises WHERE id = $1 AND enabled = 1 LIMIT 1",
|
||||
[req.user.enterpriseId],
|
||||
);
|
||||
if (!enterprise) return res.status(404).json({ error: "企业不存在或已禁用" });
|
||||
|
||||
const { rows: members } = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
u.username,
|
||||
COALESCE(em.role, CASE WHEN u.is_enterprise_admin = 1 THEN 'admin' ELSE 'employee' END) AS role,
|
||||
COALESCE(SUM(CASE WHEN cl.status IN ('reserved', 'charged') THEN cl.amount_cents ELSE 0 END), 0) AS used_cents,
|
||||
COUNT(CASE WHEN cl.status IN ('reserved', 'charged') THEN 1 END) AS task_count,
|
||||
MAX(cl.created_at) AS last_used_at
|
||||
FROM users u
|
||||
LEFT JOIN enterprise_members em ON em.enterprise_id = u.enterprise_id AND em.user_id = u.id
|
||||
LEFT JOIN credit_ledger cl ON cl.enterprise_id = u.enterprise_id AND cl.user_id = u.id ${ledgerDateJoin}
|
||||
WHERE u.enterprise_id = $1 AND u.enabled = 1
|
||||
GROUP BY u.id, u.username, role
|
||||
ORDER BY used_cents DESC, u.id ASC
|
||||
`,
|
||||
[req.user.enterpriseId],
|
||||
);
|
||||
|
||||
const { rows: modelBreakdown } = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
COALESCE(model, 'unknown') AS model,
|
||||
COALESCE(SUM(amount_cents), 0) AS used_cents,
|
||||
COUNT(*) AS task_count
|
||||
FROM credit_ledger
|
||||
WHERE enterprise_id = $1
|
||||
AND status IN ('reserved', 'charged')
|
||||
${ledgerDateWhere}
|
||||
GROUP BY COALESCE(model, 'unknown')
|
||||
ORDER BY used_cents DESC
|
||||
LIMIT 50
|
||||
`,
|
||||
[req.user.enterpriseId],
|
||||
);
|
||||
|
||||
const {
|
||||
rows: [totalRow],
|
||||
} = await pool.query(
|
||||
`
|
||||
SELECT COALESCE(SUM(amount_cents), 0) AS total_used_cents
|
||||
FROM credit_ledger
|
||||
WHERE enterprise_id = $1
|
||||
AND status IN ('reserved', 'charged')
|
||||
${ledgerDateWhere}
|
||||
`,
|
||||
[req.user.enterpriseId],
|
||||
);
|
||||
|
||||
const { rows: records } = await pool.query(
|
||||
`
|
||||
SELECT cl.*, u.username
|
||||
FROM credit_ledger cl
|
||||
LEFT JOIN users u ON u.id = cl.user_id
|
||||
WHERE cl.enterprise_id = $1
|
||||
AND cl.status IN ('reserved', 'charged')
|
||||
${periodStart ? `AND cl.created_at >= ${periodStart}` : ""}
|
||||
ORDER BY cl.created_at DESC
|
||||
LIMIT $2
|
||||
`,
|
||||
[req.user.enterpriseId, recordsLimit],
|
||||
);
|
||||
|
||||
res.json({
|
||||
enterpriseId: String(enterprise.id),
|
||||
enterpriseName: enterprise.name,
|
||||
balanceCents: Number(enterprise.balance_cents || 0),
|
||||
totalUsedCents: Number(totalRow?.total_used_cents || 0),
|
||||
members: members.map((row) => ({
|
||||
userId: Number(row.user_id),
|
||||
username: row.username,
|
||||
role: row.role || "employee",
|
||||
usedCents: Number(row.used_cents || 0),
|
||||
taskCount: Number(row.task_count || 0),
|
||||
lastUsedAt: row.last_used_at || null,
|
||||
})),
|
||||
modelBreakdown: modelBreakdown.map((row) => ({
|
||||
model: row.model,
|
||||
usedCents: Number(row.used_cents || 0),
|
||||
taskCount: Number(row.task_count || 0),
|
||||
})),
|
||||
records: records.map((row) => ({
|
||||
id: String(row.id),
|
||||
userId: row.user_id == null ? "" : Number(row.user_id),
|
||||
username: row.username || "",
|
||||
model: row.model || "",
|
||||
taskType: row.task_type,
|
||||
resolution: row.resolution || null,
|
||||
durationSeconds: row.duration_seconds == null ? null : Number(row.duration_seconds),
|
||||
amountCents: Number(row.amount_cents || 0),
|
||||
status: row.status,
|
||||
createdAt: row.created_at,
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[enterprise/usage/summary] failed", error);
|
||||
res.status(500).json({ error: "企业用量汇总加载失败" });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/enterprise/usage/records", requireAuth, requireEnterpriseAdmin, async (req, res) => {
|
||||
const limit = clampPositiveInteger(req.query.limit, 50, 200);
|
||||
const offset = clampNonNegativeInteger(req.query.offset, 0, 100000);
|
||||
const userId = req.query.userId || req.query.user_id;
|
||||
const model = String(req.query.model || "").trim();
|
||||
const dateFrom = req.query.from || req.query.date_from;
|
||||
const dateTo = req.query.to || req.query.date_to;
|
||||
const where = ["cl.enterprise_id = $1"];
|
||||
const params = [req.user.enterpriseId];
|
||||
|
||||
if (userId) {
|
||||
params.push(userId);
|
||||
where.push(`cl.user_id = $${params.length}`);
|
||||
}
|
||||
if (model) {
|
||||
params.push(model);
|
||||
where.push(`cl.model = $${params.length}`);
|
||||
}
|
||||
if (dateFrom) {
|
||||
params.push(`${dateFrom}T00:00:00.000Z`);
|
||||
where.push(`cl.created_at >= $${params.length}`);
|
||||
}
|
||||
if (dateTo) {
|
||||
params.push(`${dateTo}T23:59:59.999Z`);
|
||||
where.push(`cl.created_at <= $${params.length}`);
|
||||
}
|
||||
|
||||
const whereSql = `WHERE ${where.join(" AND ")}`;
|
||||
|
||||
try {
|
||||
const {
|
||||
rows: [countRow],
|
||||
} = await pool.query(`SELECT COUNT(*) AS total FROM credit_ledger cl ${whereSql}`, params);
|
||||
const { rows } = await pool.query(
|
||||
`
|
||||
SELECT cl.*, u.username
|
||||
FROM credit_ledger cl
|
||||
LEFT JOIN users u ON u.id = cl.user_id
|
||||
${whereSql}
|
||||
ORDER BY cl.created_at DESC
|
||||
LIMIT $${params.length + 1}
|
||||
OFFSET $${params.length + 2}
|
||||
`,
|
||||
[...params, limit, offset],
|
||||
);
|
||||
|
||||
res.json({
|
||||
items: rows.map((row) => ({
|
||||
id: String(row.id),
|
||||
userId: row.user_id == null ? "" : Number(row.user_id),
|
||||
username: row.username || "",
|
||||
model: row.model || "",
|
||||
taskType: row.task_type,
|
||||
resolution: row.resolution || null,
|
||||
durationSeconds: row.duration_seconds == null ? null : Number(row.duration_seconds),
|
||||
amountCents: Number(row.amount_cents || 0),
|
||||
status: row.status,
|
||||
createdAt: row.created_at,
|
||||
})),
|
||||
total: Number(countRow?.total || 0),
|
||||
limit,
|
||||
offset,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[enterprise/usage/records] failed", error);
|
||||
res.status(500).json({ error: "企业用量记录加载失败" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/enterprise/recharge", requireAuth, requireEnterpriseAdmin, async (req, res) => {
|
||||
const { amountCents, paymentMethod = "wechat" } = req.body;
|
||||
if (!amountCents || amountCents <= 0)
|
||||
return res.status(400).json({ error: "充值金额必须大于0" });
|
||||
|
||||
const orderNo = generateOrderNo();
|
||||
const enterpriseName = await getEnterpriseName(req.user.enterpriseId);
|
||||
|
||||
try {
|
||||
await pool.query(
|
||||
`
|
||||
INSERT INTO payment_orders (order_no, enterprise_id, enterprise_name, type, amount_cents, payment_method, status)
|
||||
VALUES ($1, $2, $3, 'recharge', $4, $5, 'pending')
|
||||
`,
|
||||
[orderNo, req.user.enterpriseId, enterpriseName, Number(amountCents), paymentMethod],
|
||||
);
|
||||
|
||||
res.json({ orderNo, amountCents, paymentMethod });
|
||||
} catch (error) {
|
||||
console.error("[enterprise/recharge] failed", error);
|
||||
res.status(500).json({ error: "创建充值订单失败" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/enterprise/distribute", requireAuth, requireEnterpriseAdmin, async (req, res) => {
|
||||
const { userId, amountCents, distributions } = req.body;
|
||||
|
||||
try {
|
||||
if (distributions && Array.isArray(distributions)) {
|
||||
for (const d of distributions) {
|
||||
if (!d.userId || !d.amountCents || d.amountCents <= 0) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "每条分发记录必须包含有效的 userId 和 amountCents" });
|
||||
}
|
||||
await distributeCredits(req.user.enterpriseId, d.userId, d.amountCents, req.user.id);
|
||||
}
|
||||
res.json({ success: true, count: distributions.length });
|
||||
} else if (userId && amountCents) {
|
||||
if (amountCents <= 0) return res.status(400).json({ error: "分发积分必须大于0" });
|
||||
const result = await distributeCredits(
|
||||
req.user.enterpriseId,
|
||||
userId,
|
||||
amountCents,
|
||||
req.user.id,
|
||||
);
|
||||
res.json({ success: true, ...result });
|
||||
} else {
|
||||
return res.status(400).json({ error: "缺少分发参数" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[enterprise/distribute] failed", error);
|
||||
res.status(400).json({ error: "分发参数处理失败" });
|
||||
}
|
||||
});
|
||||
|
||||
router.get(
|
||||
"/enterprise/employee-consumption",
|
||||
requireAuth,
|
||||
requireEnterpriseAdmin,
|
||||
async (req, res) => {
|
||||
const { period = "30d" } = req.query;
|
||||
const periodStart = getPeriodStart(period);
|
||||
const whereClause = periodStart ? `AND acl.created_at >= ${periodStart}` : "";
|
||||
|
||||
const { rows } = await pool.query(
|
||||
`
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
u.username,
|
||||
u.balance_cents AS current_balance_cents,
|
||||
COUNT(acl.id) AS total_calls,
|
||||
COALESCE(SUM(CASE WHEN acl.cost_estimate IS NOT NULL THEN CAST(ROUND((acl.cost_estimate * 100)::numeric) AS INTEGER) ELSE 0 END), 0) AS total_cost_cents,
|
||||
MAX(acl.created_at) AS last_active
|
||||
FROM users u
|
||||
LEFT JOIN api_call_logs acl ON acl.user_id = u.id AND acl.status = 'success'
|
||||
WHERE u.enterprise_id = $1 AND u.enabled = 1 AND u.is_enterprise_admin = 0 ${whereClause}
|
||||
GROUP BY u.id, u.username, u.balance_cents
|
||||
ORDER BY total_cost_cents DESC
|
||||
`,
|
||||
[req.user.enterpriseId],
|
||||
);
|
||||
|
||||
res.json(
|
||||
rows.map((r) => ({
|
||||
userId: Number(r.user_id),
|
||||
username: r.username,
|
||||
currentBalanceCents: Number(r.current_balance_cents),
|
||||
totalCalls: Number(r.total_calls),
|
||||
totalCostCents: Number(r.total_cost_cents),
|
||||
lastActive: r.last_active,
|
||||
})),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/enterprise/purchase-package",
|
||||
requireAuth,
|
||||
requireEnterpriseAdmin,
|
||||
async (req, res) => {
|
||||
const { packageId, paymentMethod = "wechat" } = req.body;
|
||||
if (!packageId) return res.status(400).json({ error: "缺少套餐ID" });
|
||||
|
||||
const {
|
||||
rows: [pkg],
|
||||
} = await pool.query("SELECT * FROM packages WHERE id = $1 AND enabled = 1", [packageId]);
|
||||
if (!pkg) return res.status(404).json({ error: "套餐不存在或已下架" });
|
||||
|
||||
const orderNo = generateOrderNo();
|
||||
const enterpriseName = await getEnterpriseName(req.user.enterpriseId);
|
||||
|
||||
try {
|
||||
await pool.query(
|
||||
`
|
||||
INSERT INTO payment_orders (order_no, enterprise_id, enterprise_name, type, amount_cents, package_id, payment_method, status)
|
||||
VALUES ($1, $2, $3, 'package', $4, $5, $6, 'pending')
|
||||
`,
|
||||
[
|
||||
orderNo,
|
||||
req.user.enterpriseId,
|
||||
enterpriseName,
|
||||
pkg.price_cents,
|
||||
packageId,
|
||||
paymentMethod,
|
||||
],
|
||||
);
|
||||
|
||||
res.json({ orderNo, amountCents: pkg.price_cents, packageId, paymentMethod });
|
||||
} catch (error) {
|
||||
console.error("[enterprise/purchase-package] failed", error);
|
||||
res.status(500).json({ error: "创建套餐订单失败" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ── Enterprise: Invoices ──────────────────────────────────────────────
|
||||
|
||||
router.post(
|
||||
"/enterprise/invoice-apply",
|
||||
requireAuth,
|
||||
requireEnterpriseAdmin,
|
||||
async (req, res) => {
|
||||
const { paymentOrderId, type = "general", title, taxNo } = req.body;
|
||||
if (!title) return res.status(400).json({ error: "缺少发票抬头" });
|
||||
|
||||
let amountCents = 0;
|
||||
if (paymentOrderId) {
|
||||
const {
|
||||
rows: [order],
|
||||
} = await pool.query(
|
||||
"SELECT * FROM payment_orders WHERE id = $1 AND enterprise_id = $2 AND status = $3",
|
||||
[paymentOrderId, req.user.enterpriseId, "paid"],
|
||||
);
|
||||
if (!order) return res.status(404).json({ error: "支付订单不存在或未支付" });
|
||||
amountCents = order.amount_cents;
|
||||
}
|
||||
|
||||
const enterpriseName = await getEnterpriseName(req.user.enterpriseId);
|
||||
|
||||
try {
|
||||
const {
|
||||
rows: [row],
|
||||
} = await pool.query(
|
||||
`
|
||||
INSERT INTO invoices (enterprise_id, enterprise_name, payment_order_id, type, title, tax_no, amount_cents, status)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, 'pending')
|
||||
RETURNING id
|
||||
`,
|
||||
[
|
||||
req.user.enterpriseId,
|
||||
enterpriseName,
|
||||
paymentOrderId || null,
|
||||
type,
|
||||
title,
|
||||
taxNo || null,
|
||||
amountCents,
|
||||
],
|
||||
);
|
||||
|
||||
res.json({ id: row.id, success: true });
|
||||
} catch (error) {
|
||||
console.error("[enterprise/invoice-apply] failed", error);
|
||||
res.status(500).json({ error: "申请发票失败" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.get("/enterprise/invoices", requireAuth, requireEnterpriseAdmin, async (req, res) => {
|
||||
const { rows } = await pool.query(
|
||||
"SELECT * FROM invoices WHERE enterprise_id = $1 ORDER BY id DESC",
|
||||
[req.user.enterpriseId],
|
||||
);
|
||||
res.json(
|
||||
rows.map((row) => ({
|
||||
id: Number(row.id),
|
||||
type: row.type,
|
||||
title: row.title,
|
||||
taxNo: row.tax_no,
|
||||
amountCents: row.amount_cents,
|
||||
status: row.status,
|
||||
invoiceNo: row.invoice_no,
|
||||
invoiceUrl: row.invoice_url,
|
||||
issuedAt: row.issued_at,
|
||||
createdAt: row.created_at,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerEnterpriseRoutes,
|
||||
};
|
||||
Reference in New Issue
Block a user