Add embedded Express admin API and unified dev workflow.
Move admin backend into this repo (Express + MySQL on :8085) so npm run dev starts both API and Vite, with updated env defaults and Caddy config for gadm.tkmind.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
export function parsePageQuery(query, defaultPageSize = 20) {
|
||||
const page = Math.max(1, Number(query.page) || 1);
|
||||
const pageSize = Math.min(Math.max(Number(query.pageSize) || defaultPageSize, 1), 200);
|
||||
const offset = (page - 1) * pageSize;
|
||||
return { page, pageSize, offset };
|
||||
}
|
||||
|
||||
export function paginateArray(items, query, defaultPageSize = 20) {
|
||||
const { page, pageSize, offset } = parsePageQuery(query, defaultPageSize);
|
||||
const total = items.length;
|
||||
return {
|
||||
items: items.slice(offset, offset + pageSize),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.max(1, Math.ceil(total / pageSize)),
|
||||
};
|
||||
}
|
||||
|
||||
export async function listUsagePaged(pool, query) {
|
||||
const { page, pageSize, offset } = parsePageQuery(query, 20);
|
||||
const userId = typeof query.userId === 'string' && query.userId ? query.userId : null;
|
||||
const params = [];
|
||||
let where = '';
|
||||
if (userId) {
|
||||
where = 'WHERE r.user_id = ?';
|
||||
params.push(userId);
|
||||
}
|
||||
const [[{ total }]] = await pool.query(
|
||||
`SELECT COUNT(*) AS total FROM h5_usage_records r ${where}`,
|
||||
params,
|
||||
);
|
||||
const [rows] = await pool.query(
|
||||
`SELECT r.id, r.user_id, u.username, r.agent_session_id, r.request_id,
|
||||
r.input_tokens, r.output_tokens, r.cost_cents, r.balance_after_cents, r.created_at
|
||||
FROM h5_usage_records r
|
||||
JOIN h5_users u ON u.id = r.user_id
|
||||
${where}
|
||||
ORDER BY r.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...params, pageSize, offset],
|
||||
);
|
||||
return {
|
||||
records: rows.map((row) => ({
|
||||
id: Number(row.id),
|
||||
userId: row.user_id,
|
||||
username: row.username,
|
||||
agentSessionId: row.agent_session_id,
|
||||
requestId: row.request_id,
|
||||
inputTokens: Number(row.input_tokens),
|
||||
outputTokens: Number(row.output_tokens),
|
||||
costCents: Number(row.cost_cents),
|
||||
balanceAfterCents: Number(row.balance_after_cents),
|
||||
createdAt: Number(row.created_at),
|
||||
})),
|
||||
total: Number(total),
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.max(1, Math.ceil(Number(total) / pageSize)),
|
||||
};
|
||||
}
|
||||
|
||||
export async function listLedgerPaged(pool, query) {
|
||||
const { page, pageSize, offset } = parsePageQuery(query, 20);
|
||||
const userId = typeof query.userId === 'string' && query.userId ? query.userId : null;
|
||||
const params = [];
|
||||
const clauses = [];
|
||||
if (userId) {
|
||||
clauses.push('l.user_id = ?');
|
||||
params.push(userId);
|
||||
}
|
||||
const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
|
||||
const [[{ total }]] = await pool.query(
|
||||
`SELECT COUNT(*) AS total FROM h5_billing_ledger l ${where}`,
|
||||
params,
|
||||
);
|
||||
const [rows] = await pool.query(
|
||||
`SELECT l.id, l.user_id, u.username, l.type, l.amount_cents, l.tokens,
|
||||
l.session_id, l.note, l.created_at
|
||||
FROM h5_billing_ledger l
|
||||
JOIN h5_users u ON u.id = l.user_id
|
||||
${where}
|
||||
ORDER BY l.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...params, pageSize, offset],
|
||||
);
|
||||
return {
|
||||
entries: rows.map((row) => ({
|
||||
id: Number(row.id),
|
||||
userId: row.user_id,
|
||||
username: row.username,
|
||||
type: row.type,
|
||||
amountCents: Number(row.amount_cents),
|
||||
tokens: Number(row.tokens),
|
||||
sessionId: row.session_id,
|
||||
note: row.note,
|
||||
createdAt: Number(row.created_at),
|
||||
})),
|
||||
total: Number(total),
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.max(1, Math.ceil(Number(total) / pageSize)),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user