2026-06-02 13:14:10 +08:00
const {
bcrypt ,
requireAuth ,
requireAdmin ,
requireEnterpriseAdmin ,
listModelPrices ,
loadPriceCache ,
creditUserBalance ,
2026-06-08 15:46:28 +08:00
creditsToCreditUnits ,
formatCreditsFromCents ,
2026-06-02 13:14:10 +08:00
pool ,
validateUsername ,
validatePassword ,
validateEnterpriseName ,
ensureEnterpriseExists ,
formatUserRow ,
readModelPricePayload ,
getModelPriceById ,
} = require ( "./context" ) ;
function registerAdminRoutes ( router ) {
// ── Admin: Users ─────────────────────────────────────────────────────
router . get ( "/admin/users" , requireAuth , requireAdmin , async ( _req , res ) => {
const { rows } = await pool . query ( `
SELECT
u.id, u.username, u.role, u.max_concurrency, u.enabled,
u.avatar_url, u.enterprise_id, u.is_enterprise_admin, u.balance_cents,
u.billing_mode, u.beta_expires_at, u.created_at,
e.name AS enterprise_name
FROM users u
LEFT JOIN enterprises e ON e.id = u.enterprise_id
ORDER BY u.id
` ) ;
res . json ( rows . map ( formatUserRow ) ) ;
} ) ;
router . post ( "/admin/users" , requireAuth , requireAdmin , async ( req , res ) => {
const {
username ,
password ,
role = "user" ,
maxConcurrency = 30 ,
enterpriseId = null ,
isEnterpriseAdmin = false ,
} = req . body ;
const usernameError = validateUsername ( username ) ;
if ( usernameError ) return res . status ( 400 ) . json ( { error : usernameError } ) ;
const passwordError = validatePassword ( password ) ;
if ( passwordError ) return res . status ( 400 ) . json ( { error : passwordError } ) ;
if ( enterpriseId != null && ! ( await ensureEnterpriseExists ( enterpriseId ) ) ) {
return res . status ( 400 ) . json ( { error : "企业不存在" } ) ;
}
try {
const hash = await bcrypt . hash ( password , 10 ) ;
const {
rows : [ row ] ,
} = await pool . query (
`
INSERT INTO users (username, password_hash, role, max_concurrency, enterprise_id, is_enterprise_admin, balance_cents)
VALUES ( $ 1, $ 2, $ 3, $ 4, $ 5, $ 6, 0)
RETURNING id
` ,
[
username ,
hash ,
role ,
Number ( maxConcurrency ) || 30 ,
enterpriseId ,
isEnterpriseAdmin ? 1 : 0 ,
] ,
) ;
const {
rows : [ userRow ] ,
} = await pool . query (
`
SELECT u.*, e.name AS enterprise_name
FROM users u LEFT JOIN enterprises e ON e.id = u.enterprise_id
WHERE u.id = $ 1
` ,
[ row . id ] ,
) ;
res . json ( formatUserRow ( userRow ) ) ;
} catch ( error ) {
console . error ( "[admin/users:create] failed" , error ) ;
res . status ( 409 ) . json ( { error : "用户名已存在" } ) ;
}
} ) ;
router . put ( "/admin/users/:id" , requireAuth , requireAdmin , async ( req , res ) => {
const {
enabled ,
role ,
maxConcurrency ,
password ,
enterpriseId ,
isEnterpriseAdmin ,
billingMode ,
betaExpiresAt ,
} = req . body ;
const updates = [ ] ;
const params = [ ] ;
let idx = 1 ;
if ( enabled !== undefined ) {
updates . push ( ` enabled = $ ${ idx ++ } ` ) ;
params . push ( enabled ? 1 : 0 ) ;
}
if ( role ) {
updates . push ( ` role = $ ${ idx ++ } ` ) ;
params . push ( role ) ;
}
if ( maxConcurrency !== undefined ) {
updates . push ( ` max_concurrency = $ ${ idx ++ } ` ) ;
params . push ( Number ( maxConcurrency ) || 30 ) ;
}
if ( password ) {
const passwordError = validatePassword ( password ) ;
if ( passwordError ) return res . status ( 400 ) . json ( { error : passwordError } ) ;
updates . push ( ` password_hash = $ ${ idx ++ } ` ) ;
params . push ( await bcrypt . hash ( password , 10 ) ) ;
}
if ( enterpriseId !== undefined ) {
if ( enterpriseId != null && ! ( await ensureEnterpriseExists ( enterpriseId ) ) )
return res . status ( 400 ) . json ( { error : "企业不存在" } ) ;
updates . push ( ` enterprise_id = $ ${ idx ++ } ` ) ;
params . push ( enterpriseId ) ;
}
if ( isEnterpriseAdmin !== undefined ) {
updates . push ( ` is_enterprise_admin = $ ${ idx ++ } ` ) ;
params . push ( isEnterpriseAdmin ? 1 : 0 ) ;
}
if ( billingMode !== undefined ) {
const mode = String ( billingMode || "credits" ) . trim ( ) ;
if ( ! [ "credits" , "beta_unlimited" ] . includes ( mode ) ) {
return res . status ( 400 ) . json ( { error : "billingMode 无效" } ) ;
}
updates . push ( ` billing_mode = $ ${ idx ++ } ` ) ;
params . push ( mode ) ;
}
if ( betaExpiresAt !== undefined ) {
updates . push ( ` beta_expires_at = $ ${ idx ++ } ` ) ;
params . push ( betaExpiresAt || null ) ;
}
if ( updates . length === 0 ) return res . status ( 400 ) . json ( { error : "没有可更新内容" } ) ;
updates . push ( ` updated_at = NOW() ` ) ;
params . push ( req . params . id ) ;
await pool . query ( ` UPDATE users SET ${ updates . join ( ", " ) } WHERE id = $ ${ idx } ` , params ) ;
res . json ( { success : true } ) ;
} ) ;
router . post ( "/admin/users/:id/credit" , requireAuth , requireAdmin , async ( req , res ) => {
const targetUserId = Number ( req . params . id ) ;
2026-06-08 15:46:28 +08:00
const { amountCredits , amountCents } = req . body ;
const creditUnits =
amountCredits !== undefined && amountCredits !== null && amountCredits !== ""
? creditsToCreditUnits ( amountCredits )
: Number ( amountCents ) ;
if ( ! creditUnits || creditUnits <= 0 ) return res . status ( 400 ) . json ( { error : "积分必须大于 0" } ) ;
2026-06-02 13:14:10 +08:00
try {
const newBalance = await creditUserBalance (
targetUserId ,
2026-06-08 15:46:28 +08:00
creditUnits ,
` 管理员 ${ req . user . username } 发放 ${ formatCreditsFromCents ( creditUnits ) } 积分 ` ,
2026-06-02 13:14:10 +08:00
) ;
res . json ( { success : true , newBalanceCents : newBalance } ) ;
} catch ( err ) {
res . status ( 400 ) . json ( { error : err . message || "发放积分失败" } ) ;
}
} ) ;
// ── Admin: Sub-accounts (enterprise admin) ───────────────────────────
router . get ( "/admin/sub-accounts" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const { rows } = await pool . query (
`
SELECT u.id, u.username, u.role, u.max_concurrency, u.enabled,
u.avatar_url, u.enterprise_id, u.is_enterprise_admin, u.balance_cents,
u.billing_mode, u.beta_expires_at, u.created_at,
e.name AS enterprise_name
FROM users u LEFT JOIN enterprises e ON e.id = u.enterprise_id
WHERE u.enterprise_id = $ 1
ORDER BY u.is_enterprise_admin DESC, u.id ASC
` ,
[ req . user . enterpriseId ] ,
) ;
res . json ( rows . map ( formatUserRow ) ) ;
} ) ;
router . post ( "/admin/sub-accounts" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const { username , password , maxConcurrency = 30 } = req . body ;
const usernameError = validateUsername ( username ) ;
if ( usernameError ) return res . status ( 400 ) . json ( { error : usernameError } ) ;
const passwordError = validatePassword ( password ) ;
if ( passwordError ) return res . status ( 400 ) . json ( { error : passwordError } ) ;
try {
const hash = await bcrypt . hash ( password , 10 ) ;
const {
rows : [ row ] ,
} = await pool . query (
`
INSERT INTO users (username, password_hash, role, max_concurrency, enterprise_id, is_enterprise_admin, balance_cents)
VALUES ( $ 1, $ 2, 'user', $ 3, $ 4, 0, 0)
RETURNING id
` ,
[ username , hash , Number ( maxConcurrency ) || 30 , req . user . enterpriseId ] ,
) ;
const {
rows : [ userRow ] ,
} = await pool . query (
`
SELECT u.id, u.username, u.role, u.max_concurrency, u.enabled,
u.avatar_url, u.enterprise_id, u.is_enterprise_admin, u.balance_cents,
u.billing_mode, u.beta_expires_at, u.created_at,
e.name AS enterprise_name
FROM users u LEFT JOIN enterprises e ON e.id = u.enterprise_id
WHERE u.id = $ 1
` ,
[ row . id ] ,
) ;
res . json ( formatUserRow ( userRow ) ) ;
} catch ( error ) {
console . error ( "[admin/sub-accounts:create] failed" , error ) ;
res . status ( 409 ) . json ( { error : "用户名已存在" } ) ;
}
} ) ;
router . put ( "/admin/sub-accounts/:id" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const {
rows : [ target ] ,
} = await pool . query ( "SELECT id, enterprise_id, is_enterprise_admin FROM users WHERE id = $1" , [
req . params . id ,
] ) ;
if ( ! target || Number ( target . enterprise _id || 0 ) !== Number ( req . user . enterpriseId || 0 ) ) {
return res . status ( 404 ) . json ( { error : "子账号不存在" } ) ;
}
const { enabled , maxConcurrency , password } = req . body ;
const updates = [ ] ;
const params = [ ] ;
let idx = 1 ;
if ( enabled !== undefined ) {
if ( Number ( target . id ) === Number ( req . user . id ) && ! enabled )
return res . status ( 400 ) . json ( { error : "不能禁用当前企业管理员账号" } ) ;
updates . push ( ` enabled = $ ${ idx ++ } ` ) ;
params . push ( enabled ? 1 : 0 ) ;
}
if ( maxConcurrency !== undefined ) {
updates . push ( ` max_concurrency = $ ${ idx ++ } ` ) ;
params . push ( Number ( maxConcurrency ) || 30 ) ;
}
if ( password ) {
const passwordError = validatePassword ( password ) ;
if ( passwordError ) return res . status ( 400 ) . json ( { error : passwordError } ) ;
updates . push ( ` password_hash = $ ${ idx ++ } ` ) ;
params . push ( await bcrypt . hash ( password , 10 ) ) ;
}
if ( updates . length === 0 ) return res . status ( 400 ) . json ( { error : "没有可更新内容" } ) ;
updates . push ( "updated_at = NOW()" ) ;
params . push ( req . params . id ) ;
await pool . query ( ` UPDATE users SET ${ updates . join ( ", " ) } WHERE id = $ ${ idx } ` , params ) ;
res . json ( { success : true } ) ;
} ) ;
// ── Admin: Provider Health ────────────────────────────────────────────
router . get ( '/admin/providers/status' , requireAuth , requireAdmin , async ( _req , res ) => {
const { getProviderHealthCache } = require ( '../providerHealthMonitor' ) ;
const { pool } = require ( './context' ) ;
const health = getProviderHealthCache ( ) ;
const { rows : callStats } = await pool . query (
'SELECT provider, model, status, COUNT(*) AS count, AVG(duration_ms) AS avg_ms, SUM(cost_estimate) AS total_cost FROM api_call_logs WHERE created_at > NOW() - INTERVAL \$1 GROUP BY provider, model, status ORDER BY provider, model' ,
[ '1 hour' ]
) ;
const { rows : keyStats } = await pool . query (
'SELECT provider, COUNT(*) AS total_keys, SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) AS active_keys, SUM(active_count) AS current_load FROM api_keys GROUP BY provider ORDER BY provider'
) ;
res . json ( { health , callStats , keyStats , checkedAt : new Date ( ) . toISOString ( ) } ) ;
} ) ;
// ── Admin: Prices ────────────────────────────────────────────────────
router . get ( "/admin/prices" , requireAuth , requireAdmin , async ( _req , res ) => {
const prices = await listModelPrices ( ) ;
res . json ( prices ) ;
} ) ;
router . post ( "/admin/prices" , requireAuth , requireAdmin , async ( req , res ) => {
const payload = readModelPricePayload ( req . body ) ;
if ( payload . error ) return res . status ( 400 ) . json ( { error : payload . error } ) ;
try {
const {
rows : [ row ] ,
} = await pool . query (
`
INSERT INTO model_prices (model_key, display_name, category, pricing_type, input_price_mills, output_price_mills, flat_price_mills, currency, enabled)
VALUES ( $ 1, $ 2, $ 3, $ 4, $ 5, $ 6, $ 7, $ 8, $ 9)
RETURNING id
` ,
[
payload . value . modelKey ,
payload . value . displayName ,
payload . value . category ,
payload . value . pricingType ,
payload . value . inputPriceMills ,
payload . value . outputPriceMills ,
payload . value . flatPriceMills ,
payload . value . currency ,
payload . value . enabled ? 1 : 0 ,
] ,
) ;
await loadPriceCache ( ) ;
res . json ( await getModelPriceById ( row . id ) ) ;
} catch ( error ) {
console . error ( "[admin/prices:create] failed" , error ) ;
res . status ( 409 ) . json ( { error : "模型标识已存在" } ) ;
}
} ) ;
router . put ( "/admin/prices/:id" , requireAuth , requireAdmin , async ( req , res ) => {
const existing = await getModelPriceById ( req . params . id ) ;
if ( ! existing ) return res . status ( 404 ) . json ( { error : "定价不存在" } ) ;
const payload = readModelPricePayload ( req . body , existing ) ;
if ( payload . error ) return res . status ( 400 ) . json ( { error : payload . error } ) ;
try {
await pool . query (
`
UPDATE model_prices SET model_key = $ 1, display_name = $ 2, category = $ 3, pricing_type = $ 4,
input_price_mills = $ 5, output_price_mills = $ 6, flat_price_mills = $ 7, currency = $ 8, enabled = $ 9
WHERE id = $ 10
` ,
[
payload . value . modelKey ,
payload . value . displayName ,
payload . value . category ,
payload . value . pricingType ,
payload . value . inputPriceMills ,
payload . value . outputPriceMills ,
payload . value . flatPriceMills ,
payload . value . currency ,
payload . value . enabled ? 1 : 0 ,
req . params . id ,
] ,
) ;
await loadPriceCache ( ) ;
res . json ( await getModelPriceById ( req . params . id ) ) ;
} catch ( error ) {
console . error ( "[admin/prices:update] failed" , error ) ;
res . status ( 409 ) . json ( { error : "模型标识已存在" } ) ;
}
} ) ;
// ── Admin: Keys ──────────────────────────────────────────────────────
router . get ( "/admin/keys" , requireAuth , requireAdmin , async ( _req , res ) => {
const { rows } = await pool . query (
"SELECT id, provider, label, max_concurrency, active_count, total_used, enabled, created_at FROM api_keys ORDER BY provider, id" ,
) ;
res . json ( rows ) ;
} ) ;
router . post ( "/admin/keys" , requireAuth , requireAdmin , async ( req , res ) => {
const { provider , api _key , label = "" , max _concurrency = 10 } = req . body ;
if ( ! provider || ! api _key ) return res . status ( 400 ) . json ( { error : "缺少 provider 或 api_key" } ) ;
const {
rows : [ row ] ,
} = await pool . query (
"INSERT INTO api_keys (provider, api_key, label, max_concurrency) VALUES ($1, $2, $3, $4) RETURNING id" ,
[ provider , api _key , label , Number ( max _concurrency ) || 10 ] ,
) ;
res . json ( { id : row . id , provider , label } ) ;
} ) ;
router . put ( "/admin/keys/:id" , requireAuth , requireAdmin , async ( req , res ) => {
const { enabled , label , max _concurrency , api _key } = req . body ;
const updates = [ ] ;
const params = [ ] ;
let idx = 1 ;
if ( enabled !== undefined ) {
updates . push ( ` enabled = $ ${ idx ++ } ` ) ;
params . push ( enabled ? 1 : 0 ) ;
}
if ( label !== undefined ) {
updates . push ( ` label = $ ${ idx ++ } ` ) ;
params . push ( label ) ;
}
if ( max _concurrency !== undefined ) {
updates . push ( ` max_concurrency = $ ${ idx ++ } ` ) ;
params . push ( Number ( max _concurrency ) || 10 ) ;
}
if ( api _key ) {
updates . push ( ` api_key = $ ${ idx ++ } ` ) ;
params . push ( api _key ) ;
}
if ( updates . length === 0 ) return res . status ( 400 ) . json ( { error : "没有可更新内容" } ) ;
params . push ( req . params . id ) ;
await pool . query ( ` UPDATE api_keys SET ${ updates . join ( ", " ) } WHERE id = $ ${ idx } ` , params ) ;
res . json ( { success : true } ) ;
} ) ;
router . delete ( "/admin/keys/:id" , requireAuth , requireAdmin , async ( req , res ) => {
await pool . query ( "DELETE FROM api_keys WHERE id = $1" , [ req . params . id ] ) ;
res . json ( { success : true } ) ;
} ) ;
// ── Admin: Enterprises ───────────────────────────────────────────────
router . get ( "/admin/enterprises" , requireAuth , requireAdmin , async ( _req , res ) => {
const { rows } = await pool . query ( `
SELECT e.*, COUNT(u.id) AS user_count
FROM enterprises e
LEFT JOIN users u ON u.enterprise_id = e.id AND u.enabled = 1
GROUP BY e.id
ORDER BY e.id
` ) ;
res . json (
rows . map ( ( row ) => ( {
id : Number ( row . id ) ,
name : row . name ,
contactName : row . contact _name ,
contactPhone : row . contact _phone ,
taxId : row . tax _id ,
legalPersonName : row . legal _person _name ,
legalPersonPhone : row . legal _person _phone ,
enterpriseCode : row . enterprise _code ,
balanceCents : row . balance _cents ,
enabled : ! ! row . enabled ,
userCount : Number ( row . user _count ) ,
createdAt : row . created _at ,
updatedAt : row . updated _at ,
} ) ) ,
) ;
} ) ;
router . get ( "/admin/enterprises/:id" , requireAuth , requireAdmin , async ( req , res ) => {
const {
rows : [ row ] ,
} = await pool . query ( "SELECT * FROM enterprises WHERE id = $1" , [ req . params . id ] ) ;
if ( ! row ) return res . status ( 404 ) . json ( { error : "企业不存在" } ) ;
res . json ( {
id : Number ( row . id ) ,
name : row . name ,
contactName : row . contact _name ,
contactPhone : row . contact _phone ,
taxId : row . tax _id ,
legalPersonName : row . legal _person _name ,
legalPersonPhone : row . legal _person _phone ,
enterpriseCode : row . enterprise _code ,
balanceCents : row . balance _cents ,
enabled : ! ! row . enabled ,
createdAt : row . created _at ,
updatedAt : row . updated _at ,
} ) ;
} ) ;
router . put ( "/admin/enterprises/:id" , requireAuth , requireAdmin , async ( req , res ) => {
const { name , contactName , contactPhone , taxId , legalPersonName , legalPersonPhone , enabled } =
req . body ;
const updates = [ ] ;
const params = [ ] ;
let idx = 1 ;
if ( name !== undefined ) {
const enterpriseError = validateEnterpriseName ( name ) ;
if ( enterpriseError ) return res . status ( 400 ) . json ( { error : enterpriseError } ) ;
updates . push ( ` name = $ ${ idx ++ } ` ) ;
params . push ( name . trim ( ) ) ;
}
if ( contactName !== undefined ) {
updates . push ( ` contact_name = $ ${ idx ++ } ` ) ;
params . push ( String ( contactName || "" ) . trim ( ) ) ;
}
if ( contactPhone !== undefined ) {
updates . push ( ` contact_phone = $ ${ idx ++ } ` ) ;
params . push ( String ( contactPhone || "" ) . trim ( ) ) ;
}
if ( taxId !== undefined ) {
updates . push ( ` tax_id = $ ${ idx ++ } ` ) ;
params . push ( String ( taxId || "" ) . trim ( ) || null ) ;
}
if ( legalPersonName !== undefined ) {
updates . push ( ` legal_person_name = $ ${ idx ++ } ` ) ;
params . push ( String ( legalPersonName || "" ) . trim ( ) || null ) ;
}
if ( legalPersonPhone !== undefined ) {
updates . push ( ` legal_person_phone = $ ${ idx ++ } ` ) ;
params . push ( String ( legalPersonPhone || "" ) . trim ( ) || null ) ;
}
if ( enabled !== undefined ) {
updates . push ( ` enabled = $ ${ idx ++ } ` ) ;
params . push ( enabled ? 1 : 0 ) ;
}
if ( updates . length === 0 ) return res . status ( 400 ) . json ( { error : "没有可更新内容" } ) ;
updates . push ( "updated_at = NOW()" ) ;
params . push ( req . params . id ) ;
await pool . query ( ` UPDATE enterprises SET ${ updates . join ( ", " ) } WHERE id = $ ${ idx } ` , params ) ;
res . json ( { success : true } ) ;
} ) ;
// ── Admin: Packages ──────────────────────────────────────────────────
router . get ( "/admin/packages" , requireAuth , requireAdmin , async ( _req , res ) => {
const { rows } = await pool . query ( "SELECT * FROM packages ORDER BY sort_order, id" ) ;
res . json (
rows . map ( ( row ) => ( {
id : Number ( row . id ) ,
name : row . name ,
description : row . description ,
priceCents : row . price _cents ,
creditsCents : row . credits _cents ,
imageQuota : row . image _quota ,
videoQuota : row . video _quota ,
textQuota : row . text _quota ,
durationDays : row . duration _days ,
enabled : ! ! row . enabled ,
sortOrder : row . sort _order ,
createdAt : row . created _at ,
} ) ) ,
) ;
} ) ;
router . post ( "/admin/packages" , requireAuth , requireAdmin , async ( req , res ) => {
const {
name ,
description = "" ,
priceCents ,
2026-06-08 15:46:28 +08:00
credits ,
amountCredits ,
2026-06-02 13:14:10 +08:00
creditsCents = 0 ,
imageQuota = 0 ,
videoQuota = 0 ,
textQuota = 0 ,
durationDays = 365 ,
enabled = true ,
sortOrder = 0 ,
} = req . body ;
if ( ! name ) return res . status ( 400 ) . json ( { error : "缺少套餐名称" } ) ;
if ( priceCents == null || priceCents <= 0 )
return res . status ( 400 ) . json ( { error : "售价必须大于0" } ) ;
try {
const {
rows : [ row ] ,
} = await pool . query (
`
INSERT INTO packages (name, description, price_cents, credits_cents, image_quota, video_quota, text_quota, duration_days, enabled, sort_order)
VALUES ( $ 1, $ 2, $ 3, $ 4, $ 5, $ 6, $ 7, $ 8, $ 9, $ 10)
RETURNING id
` ,
[
name ,
description ,
Number ( priceCents ) ,
2026-06-08 15:46:28 +08:00
credits !== undefined || amountCredits !== undefined
? creditsToCreditUnits ( credits ? ? amountCredits )
: Number ( creditsCents || 0 ) ,
2026-06-02 13:14:10 +08:00
Number ( imageQuota || 0 ) ,
Number ( videoQuota || 0 ) ,
Number ( textQuota || 0 ) ,
Number ( durationDays || 365 ) ,
enabled ? 1 : 0 ,
Number ( sortOrder || 0 ) ,
] ,
) ;
res . json ( { id : row . id , success : true } ) ;
} catch ( error ) {
console . error ( "[admin/packages:create] failed" , error ) ;
res . status ( 500 ) . json ( { error : "创建套餐失败" } ) ;
}
} ) ;
router . put ( "/admin/packages/:id" , requireAuth , requireAdmin , async ( req , res ) => {
const {
rows : [ pkg ] ,
} = await pool . query ( "SELECT * FROM packages WHERE id = $1" , [ req . params . id ] ) ;
if ( ! pkg ) return res . status ( 404 ) . json ( { error : "套餐不存在" } ) ;
const {
name ,
description ,
priceCents ,
2026-06-08 15:46:28 +08:00
credits ,
amountCredits ,
2026-06-02 13:14:10 +08:00
creditsCents ,
imageQuota ,
videoQuota ,
textQuota ,
durationDays ,
enabled ,
sortOrder ,
} = req . body ;
const updates = [ ] ;
const params = [ ] ;
let idx = 1 ;
if ( name !== undefined ) {
updates . push ( ` name = $ ${ idx ++ } ` ) ;
params . push ( name ) ;
}
if ( description !== undefined ) {
updates . push ( ` description = $ ${ idx ++ } ` ) ;
params . push ( description ) ;
}
if ( priceCents !== undefined ) {
updates . push ( ` price_cents = $ ${ idx ++ } ` ) ;
params . push ( Number ( priceCents ) ) ;
}
2026-06-08 15:46:28 +08:00
if ( credits !== undefined || amountCredits !== undefined ) {
updates . push ( ` credits_cents = $ ${ idx ++ } ` ) ;
params . push ( creditsToCreditUnits ( credits ? ? amountCredits ) ) ;
} else if ( creditsCents !== undefined ) {
2026-06-02 13:14:10 +08:00
updates . push ( ` credits_cents = $ ${ idx ++ } ` ) ;
params . push ( Number ( creditsCents ) ) ;
}
if ( imageQuota !== undefined ) {
updates . push ( ` image_quota = $ ${ idx ++ } ` ) ;
params . push ( Number ( imageQuota ) ) ;
}
if ( videoQuota !== undefined ) {
updates . push ( ` video_quota = $ ${ idx ++ } ` ) ;
params . push ( Number ( videoQuota ) ) ;
}
if ( textQuota !== undefined ) {
updates . push ( ` text_quota = $ ${ idx ++ } ` ) ;
params . push ( Number ( textQuota ) ) ;
}
if ( durationDays !== undefined ) {
updates . push ( ` duration_days = $ ${ idx ++ } ` ) ;
params . push ( Number ( durationDays ) ) ;
}
if ( enabled !== undefined ) {
updates . push ( ` enabled = $ ${ idx ++ } ` ) ;
params . push ( enabled ? 1 : 0 ) ;
}
if ( sortOrder !== undefined ) {
updates . push ( ` sort_order = $ ${ idx ++ } ` ) ;
params . push ( Number ( sortOrder ) ) ;
}
if ( updates . length === 0 ) return res . status ( 400 ) . json ( { error : "没有可更新内容" } ) ;
params . push ( req . params . id ) ;
await pool . query ( ` UPDATE packages SET ${ updates . join ( ", " ) } WHERE id = $ ${ idx } ` , params ) ;
res . json ( { success : true } ) ;
} ) ;
router . delete ( "/admin/packages/:id" , requireAuth , requireAdmin , async ( req , res ) => {
await pool . query ( "UPDATE packages SET enabled = 0 WHERE id = $1" , [ req . params . id ] ) ;
res . json ( { success : true } ) ;
} ) ;
}
function registerAdminInvoiceRoutes ( router ) {
// ── Admin: Invoices ──────────────────────────────────────────────────
router . get ( "/admin/invoices" , requireAuth , requireAdmin , async ( _req , res ) => {
const { rows } = await pool . query ( `
SELECT i.*, e.name AS enterprise_name
FROM invoices i LEFT JOIN enterprises e ON e.id = i.enterprise_id
ORDER BY i.id DESC
` ) ;
res . json (
rows . map ( ( row ) => ( {
id : Number ( row . id ) ,
enterpriseId : Number ( row . enterprise _id ) ,
enterpriseName : row . enterprise _name ,
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 ,
} ) ) ,
) ;
} ) ;
router . put ( "/admin/invoices/:id" , requireAuth , requireAdmin , async ( req , res ) => {
const { invoiceNo , invoiceUrl , status } = req . body ;
const updates = [ ] ;
const params = [ ] ;
let idx = 1 ;
if ( invoiceNo !== undefined ) {
updates . push ( ` invoice_no = $ ${ idx ++ } ` ) ;
params . push ( invoiceNo ) ;
}
if ( invoiceUrl !== undefined ) {
updates . push ( ` invoice_url = $ ${ idx ++ } ` ) ;
params . push ( invoiceUrl ) ;
}
if ( status ) {
updates . push ( ` status = $ ${ idx ++ } ` ) ;
params . push ( status ) ;
}
if ( status === "issued" ) {
updates . push ( "issued_at = NOW()" ) ;
}
if ( updates . length === 0 ) return res . status ( 400 ) . json ( { error : "没有可更新内容" } ) ;
params . push ( req . params . id ) ;
await pool . query ( ` UPDATE invoices SET ${ updates . join ( ", " ) } WHERE id = $ ${ idx } ` , params ) ;
res . json ( { success : true } ) ;
} ) ;
}
module . exports = {
registerAdminRoutes ,
registerAdminInvoiceRoutes ,
} ;