feat: complete image_make admin controls

This commit is contained in:
john
2026-07-21 17:01:35 +08:00
parent 1969071cf4
commit a49bb8c8b0
9 changed files with 845 additions and 96 deletions
+120
View File
@@ -0,0 +1,120 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createAdminApp } from './app.mjs';
function createServices({ role = 'admin', imageMakeAdminConfigService } = {}) {
return {
ready: Promise.resolve(),
parseCookies: () => ({ adm_session: 'test-token' }),
USER_COOKIE: 'adm_session',
userLoginCookies: () => ['adm_session=test-token; Path=/; HttpOnly'],
clearUserSessionCookie: () => ['adm_session=; Path=/; Max-Age=0'],
resolveCookieDomainForRequest: () => null,
userAuth: {
getMe: async () => ({ id: 'admin-user-id', username: 'admin', role }),
},
imageMakeAdminConfigService,
};
}
async function withServer(services, callback) {
const app = createAdminApp(services);
const server = await new Promise((resolve) => {
const instance = app.listen(0, '127.0.0.1', () => resolve(instance));
});
const address = server.address();
const baseUrl = `http://127.0.0.1:${address.port}`;
try {
await callback(baseUrl);
} finally {
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
}
}
const adminHeaders = {
cookie: 'adm_session=test-token',
'content-type': 'application/json',
};
test('image_make admin routes read and update config with the admin identity', async () => {
let updateCall = null;
const service = {
getAdminConfig: async () => ({
source: 'default',
config: {
defaultProvider: 'mock',
jobDefaultTimeoutSeconds: 120,
providers: { mock: { enabled: true }, aliyun_bailian: { enabled: false }, comfyui: { enabled: false } },
},
}),
updateAdminConfig: async (patch, context) => {
updateCall = { patch, context };
return { ok: true, source: 'admin-db', config: patch };
},
getRuntimeConfig: async () => ({
ok: true,
fingerprint: '1234567890abcdef',
defaultProvider: 'aliyun_bailian',
jobDefaultTimeoutSeconds: 120,
providers: {
mock: { enabled: true },
aliyun_bailian: {
enabled: true,
model: 'qwen-image-plus',
apiBase: 'https://dashscope.aliyuncs.com/api/v1',
apiKey: 'must-not-leak',
},
comfyui: { enabled: false },
},
}),
};
await withServer(createServices({ imageMakeAdminConfigService: service }), async (baseUrl) => {
const configResponse = await fetch(`${baseUrl}/admin-api/image-make/config`, { headers: adminHeaders });
assert.equal(configResponse.status, 200);
assert.equal((await configResponse.json()).config.defaultProvider, 'mock');
const patch = { defaultProvider: 'mock', jobDefaultTimeoutSeconds: 180 };
const updateResponse = await fetch(`${baseUrl}/admin-api/image-make/config`, {
method: 'PATCH',
headers: adminHeaders,
body: JSON.stringify(patch),
});
assert.equal(updateResponse.status, 200);
assert.deepEqual(updateCall, { patch, context: { updatedBy: 'admin-user-id' } });
const runtimeResponse = await fetch(`${baseUrl}/admin-api/image-make/runtime`, { headers: adminHeaders });
assert.equal(runtimeResponse.status, 200);
const runtime = await runtimeResponse.json();
assert.equal(runtime.providers.aliyun_bailian.apiKeyConfigured, true);
assert.equal('apiKey' in runtime.providers.aliyun_bailian, false);
assert.equal(JSON.stringify(runtime).includes('must-not-leak'), false);
});
});
test('image_make admin config validation failures are returned as 400', async () => {
const service = {
updateAdminConfig: async () => ({ ok: false, message: '默认 Provider 必须处于启用状态' }),
};
await withServer(createServices({ imageMakeAdminConfigService: service }), async (baseUrl) => {
const response = await fetch(`${baseUrl}/admin-api/image-make/config`, {
method: 'PATCH',
headers: adminHeaders,
body: JSON.stringify({ defaultProvider: 'comfyui' }),
});
assert.equal(response.status, 400);
assert.equal((await response.json()).message, '默认 Provider 必须处于启用状态');
});
});
test('image_make admin routes require an admin user and an enabled service', async () => {
await withServer(createServices({ role: 'user', imageMakeAdminConfigService: {} }), async (baseUrl) => {
const forbidden = await fetch(`${baseUrl}/admin-api/image-make/config`, { headers: adminHeaders });
assert.equal(forbidden.status, 403);
});
await withServer(createServices({ imageMakeAdminConfigService: {} }), async (baseUrl) => {
const unavailable = await fetch(`${baseUrl}/admin-api/image-make/config`, { headers: adminHeaders });
assert.equal(unavailable.status, 503);
});
});
+48
View File
@@ -93,6 +93,7 @@ export function createAdminApp(services) {
userAuth,
llmProviderService,
assetGatewayConfigService,
imageMakeAdminConfigService,
pool,
ready,
wechatAdmin,
@@ -369,6 +370,53 @@ export function createAdminApp(services) {
res.json(result);
});
adminApi.get('/image-make/config', requireAdmin, async (_req, res) => {
if (!imageMakeAdminConfigService?.getAdminConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
res.json(await imageMakeAdminConfigService.getAdminConfig());
});
const updateImageMakeConfig = async (req, res) => {
if (!imageMakeAdminConfigService?.updateAdminConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
const result = await imageMakeAdminConfigService.updateAdminConfig(req.body ?? {}, {
updatedBy: req.currentUser.id,
});
if (result.ok === false) return res.status(400).json({ message: result.message });
res.json(result);
};
adminApi.put('/image-make/config', requireAdmin, updateImageMakeConfig);
adminApi.patch('/image-make/config', requireAdmin, updateImageMakeConfig);
adminApi.get('/image-make/runtime', requireAdmin, async (_req, res) => {
if (!imageMakeAdminConfigService?.getRuntimeConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
const runtime = await imageMakeAdminConfigService.getRuntimeConfig();
if (!runtime.ok) return res.status(503).json({ message: runtime.message ?? 'image_make 运行时配置无效' });
const { providers, ...rest } = runtime;
res.json({
...rest,
providers: {
mock: { enabled: providers.mock.enabled },
aliyun_bailian: providers.aliyun_bailian.enabled
? {
enabled: true,
model: providers.aliyun_bailian.model,
apiBase: providers.aliyun_bailian.apiBase,
apiKeyConfigured: Boolean(providers.aliyun_bailian.apiKey),
}
: { enabled: false },
comfyui: providers.comfyui.enabled
? { enabled: true, ...providers.comfyui }
: { enabled: false },
},
});
});
adminApi.get('/memory-v2/config', requireAdmin, async (_req, res) => {
if (!memoryV2ConfigService) return res.status(503).json({ message: 'Memory V2 配置未启用' });
res.json(await memoryV2ConfigService.getAdminConfig());
+7
View File
@@ -33,6 +33,7 @@ export async function bootstrapAdminServices() {
const { createPersonalMemoryCandidateStore } = await importMemind('memory-v2-personal-store.mjs');
const { createSkillRuntimeAdminConfigService } = await importMemind('skill-runtime-admin-config.mjs');
const { createAssetGatewayConfigService } = await importMemind('asset-gateway.mjs');
const { createImageMakeAdminConfigService } = await importMemind('image-make-admin-config.mjs');
const { ensureAssetGatewaySchema } = await importMemind('db.mjs');
const { createWechatScheduleLlmConfigService } = await importMemind('wechat-schedule-llm-config.mjs');
const { createAdminSystemTestService } = await importMemind('admin-system-tests.mjs');
@@ -93,6 +94,11 @@ export async function bootstrapAdminServices() {
});
await ensureAssetGatewaySchema(pool);
const assetGatewayConfigService = createAssetGatewayConfigService(pool, { llmProviderService });
const imageMakeAdminConfigService = createImageMakeAdminConfigService(pool, {
env: process.env,
llmProviderService,
});
await imageMakeAdminConfigService.ensureSchema();
const memoryV2ConfigService = createMemoryV2AdminConfigService(pool, {
env: process.env,
});
@@ -148,6 +154,7 @@ export async function bootstrapAdminServices() {
userAuth,
llmProviderService,
assetGatewayConfigService,
imageMakeAdminConfigService,
plazaOps,
createOpsApi,
wechatAdmin,
+2
View File
@@ -99,6 +99,7 @@ ready
userAuth,
llmProviderService,
assetGatewayConfigService,
imageMakeAdminConfigService,
plazaOps,
createOpsApi,
wechatAdmin,
@@ -125,6 +126,7 @@ ready
userAuth,
llmProviderService,
assetGatewayConfigService,
imageMakeAdminConfigService,
plazaOps,
createOpsApi,
wechatAdmin,