a4d46f2b1e
Add image-quota admin pages and API wiring for plan defaults, ledger, and per-user grants, plus local verify scripts and AGENTS.md note that this repo is the sole admin UI surface. Co-authored-by: Cursor <cursoragent@cursor.com>
93 lines
3.2 KiB
JavaScript
93 lines
3.2 KiB
JavaScript
#!/usr/bin/env node
|
||
/** Portal smoke: /auth/me subscription includes image quota fields */
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const memindRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '../..', 'Memind');
|
||
const portalBase = process.env.PORTAL_BASE ?? 'http://127.0.0.1:8081';
|
||
|
||
function loadEnvFile(filePath) {
|
||
if (!fs.existsSync(filePath)) return;
|
||
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
||
const trimmed = line.trim();
|
||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||
const eq = trimmed.indexOf('=');
|
||
if (eq <= 0) continue;
|
||
const key = trimmed.slice(0, eq).trim();
|
||
let value = trimmed.slice(eq + 1).trim();
|
||
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
||
value = value.slice(1, -1);
|
||
}
|
||
if (!(key in process.env)) process.env[key] = value;
|
||
}
|
||
}
|
||
|
||
loadEnvFile(path.join(memindRoot, '.env'));
|
||
|
||
const username = process.env.H5_TEST_USERNAME?.trim() || process.env.H5_ADMIN_USERNAME?.trim() || 'admin';
|
||
const password = process.env.H5_TEST_PASSWORD?.trim() || process.env.H5_ADMIN_PASSWORD?.trim() || '';
|
||
|
||
async function request(method, urlPath, { body, cookie } = {}) {
|
||
const res = await fetch(`${portalBase}${urlPath}`, {
|
||
method,
|
||
headers: {
|
||
...(body ? { 'Content-Type': 'application/json' } : {}),
|
||
...(cookie ? { Cookie: cookie } : {}),
|
||
},
|
||
body: body ? JSON.stringify(body) : undefined,
|
||
});
|
||
const text = await res.text();
|
||
let json = null;
|
||
try { json = text ? JSON.parse(text) : null; } catch { json = { raw: text.slice(0, 200) }; }
|
||
return { status: res.status, json, setCookie: res.headers.getSetCookie?.() ?? [] };
|
||
}
|
||
|
||
function cookieFromSetCookie(setCookie) {
|
||
return setCookie.map((c) => c.split(';')[0]).join('; ');
|
||
}
|
||
|
||
async function main() {
|
||
console.log(`==> Portal base: ${portalBase}`);
|
||
if (!password) {
|
||
console.error('缺少登录密码(Memind/.env 中 H5_ADMIN_PASSWORD 或 H5_TEST_PASSWORD)');
|
||
process.exit(1);
|
||
}
|
||
|
||
const login = await request('POST', '/auth/login', { body: { username, password } });
|
||
if (login.status !== 200) {
|
||
console.error(`登录失败 HTTP ${login.status}`);
|
||
process.exit(1);
|
||
}
|
||
const cookie = cookieFromSetCookie(login.setCookie);
|
||
console.log(`✓ Portal 登录 (${username})`);
|
||
|
||
const me = await request('GET', '/auth/me', { cookie });
|
||
if (me.status !== 200) {
|
||
console.error(`/auth/me HTTP ${me.status}`);
|
||
process.exit(1);
|
||
}
|
||
const sub = me.json?.user?.subscription;
|
||
if (!sub) {
|
||
console.log('⚠ 当前用户无 active subscription(免费用户可能无订阅记录)');
|
||
process.exit(0);
|
||
}
|
||
|
||
const fields = ['periodImagesLimit', 'periodImagesUsed', 'periodImagesBonus'];
|
||
for (const f of fields) {
|
||
if (!(f in sub)) {
|
||
console.error(`subscription 缺少字段: ${f}`);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
console.log(
|
||
`✓ /auth/me subscription 图片额度: limit=${sub.periodImagesLimit} bonus=${sub.periodImagesBonus ?? 0} used=${sub.periodImagesUsed}`,
|
||
);
|
||
console.log('\nPortal 用户侧 subscription 字段联调通过。');
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error('联调失败:', err.message);
|
||
process.exit(1);
|
||
});
|