|
@@ -4,6 +4,8 @@ import { readFileSync } from 'node:fs';
|
|
|
const enrollmentOpsCode = readFileSync(new URL('./cloud/member-enrollment.js', import.meta.url), 'utf8');
|
|
const enrollmentOpsCode = readFileSync(new URL('./cloud/member-enrollment.js', import.meta.url), 'utf8');
|
|
|
const enrollmentAppCode = readFileSync(new URL('./cloud/member-enrollment-app.js', import.meta.url), 'utf8');
|
|
const enrollmentAppCode = readFileSync(new URL('./cloud/member-enrollment-app.js', import.meta.url), 'utf8');
|
|
|
const payrollIntegrityCode = readFileSync(new URL('./cloud/payroll-integrity.js', import.meta.url), 'utf8');
|
|
const payrollIntegrityCode = readFileSync(new URL('./cloud/payroll-integrity.js', import.meta.url), 'utf8');
|
|
|
|
|
+const operatorAccountsCode = readFileSync(new URL('./cloud/operator-accounts.js', import.meta.url), 'utf8');
|
|
|
|
|
+const adminRolePolicyCode = readFileSync(new URL('./cloud/admin-role-policy.js', import.meta.url), 'utf8');
|
|
|
const reviewRewardCode = readFileSync(new URL('./cloud/review-rewards.js', import.meta.url), 'utf8');
|
|
const reviewRewardCode = readFileSync(new URL('./cloud/review-rewards.js', import.meta.url), 'utf8');
|
|
|
|
|
|
|
|
const APP_ID = process.env.XIAOSHU_PARSE_APP_ID || '7pIbDBJmKx_main';
|
|
const APP_ID = process.env.XIAOSHU_PARSE_APP_ID || '7pIbDBJmKx_main';
|
|
@@ -118,20 +120,13 @@ async function requireAdmin(request) {
|
|
|
if (!current) fail(401, '管理员会话已失效');
|
|
if (!current) fail(401, '管理员会话已失效');
|
|
|
await current.fetch({ useMasterKey: true });
|
|
await current.fetch({ useMasterKey: true });
|
|
|
if (current.get('isDisabled') === true || current.get('isDeleted') === true) fail(403, '管理员账号已停用');
|
|
if (current.get('isDisabled') === true || current.get('isDeleted') === true) fail(403, '管理员账号已停用');
|
|
|
- const roles = Array.isArray(current.get('roles')) ? current.get('roles').map(String) : [];
|
|
|
|
|
- const role = String(current.get('role') || '');
|
|
|
|
|
- const roleKey = String(current.get('adminRoleKey') || '');
|
|
|
|
|
- const isSuperAdmin = roleKey === 'super-admin' || roles.includes('super-admin');
|
|
|
|
|
- const isAdmin = current.get('isAdmin') === true || role === 'admin' || roles.includes('admin') || isSuperAdmin;
|
|
|
|
|
|
|
+ const {roles,isSuperAdmin,isAdmin} = adminRoleSnapshot(current);
|
|
|
if (!isAdmin) fail(403, '当前账号未被授权为后台管理员');
|
|
if (!isAdmin) fail(403, '当前账号未被授权为后台管理员');
|
|
|
const company = request.company || current.get('company') || null;
|
|
const company = request.company || current.get('company') || null;
|
|
|
if (!company && !isSuperAdmin) fail(403, '管理员账号尚未分配帐套');
|
|
if (!company && !isSuperAdmin) fail(403, '管理员账号尚未分配帐套');
|
|
|
return { current, roles, isSuperAdmin, company };
|
|
return { current, roles, isSuperAdmin, company };
|
|
|
}
|
|
}
|
|
|
-function isAdminAccount(target) {
|
|
|
|
|
- const roles = Array.isArray(target.get('roles')) ? target.get('roles').map(String) : [];
|
|
|
|
|
- return target.get('isAdmin') === true || target.get('role') === 'admin' || roles.includes('admin') || target.get('adminRoleKey') === 'super-admin' || roles.includes('super-admin');
|
|
|
|
|
-}
|
|
|
|
|
|
|
+function isAdminAccount(target) { return adminRoleSnapshot(target).isAdmin; }
|
|
|
function assertCanManageUser(context, target, action) {
|
|
function assertCanManageUser(context, target, action) {
|
|
|
if (action === 'lock' && target.id === context.current.id) fail(400, '不能停用当前登录账号');
|
|
if (action === 'lock' && target.id === context.current.id) fail(400, '不能停用当前登录账号');
|
|
|
if (isAdminAccount(target) && target.id !== context.current.id && !context.isSuperAdmin) fail(403, '只有超级管理员可以管理其他管理员账号');
|
|
if (isAdminAccount(target) && target.id !== context.current.id && !context.isSuperAdmin) fail(403, '只有超级管理员可以管理其他管理员账号');
|
|
@@ -215,6 +210,7 @@ async function handler(request, response) {
|
|
|
const operation = String(input.operation || 'meta');
|
|
const operation = String(input.operation || 'meta');
|
|
|
const context = await requireAdmin(request);
|
|
const context = await requireAdmin(request);
|
|
|
if (['techDashboard','catalog','apiCatalog','schema','list','get','save','delete'].includes(operation) && !context.isSuperAdmin) fail(403, '只有超级管理员可以使用技术后台和通用数据接口');
|
|
if (['techDashboard','catalog','apiCatalog','schema','list','get','save','delete'].includes(operation) && !context.isSuperAdmin) fail(403, '只有超级管理员可以使用技术后台和通用数据接口');
|
|
|
|
|
+ if (operation.startsWith('operatorAccounts/')) return response.json({success:true,data:await operatorAccounts(context,input)});
|
|
|
if (operation === 'meta') {
|
|
if (operation === 'meta') {
|
|
|
const functionQuery = new Parse.Query('Function');
|
|
const functionQuery = new Parse.Query('Function');
|
|
|
functionQuery.notEqualTo('enabled', false);
|
|
functionQuery.notEqualTo('enabled', false);
|
|
@@ -2954,8 +2950,8 @@ function safe(value, depth = 0) { if (value == null || depth > 4) return value;
|
|
|
async function auth(request) {
|
|
async function auth(request) {
|
|
|
const current = request.user || (typeof user !== 'undefined' ? user : null); if (!current) fail(401, '管理员会话已失效'); await current.fetch({ useMasterKey:true });
|
|
const current = request.user || (typeof user !== 'undefined' ? user : null); if (!current) fail(401, '管理员会话已失效'); await current.fetch({ useMasterKey:true });
|
|
|
if (current.get('isDisabled') === true || current.get('isDeleted') === true) fail(403, '管理员账号已停用');
|
|
if (current.get('isDisabled') === true || current.get('isDeleted') === true) fail(403, '管理员账号已停用');
|
|
|
- const roles = Array.isArray(current.get('roles')) ? current.get('roles').map(String) : []; const role = String(current.get('role') || ''); const roleKey = String(current.get('adminRoleKey') || ''); const isSuperAdmin = roleKey === 'super-admin' || roles.includes('super-admin');
|
|
|
|
|
- if (!(current.get('isAdmin') === true || role === 'admin' || roles.includes('admin') || roles.some((item) => item.startsWith('ops-')) || isSuperAdmin)) fail(403, '当前账号未获运营后台权限');
|
|
|
|
|
|
|
+ const {roles,isSuperAdmin,isAdmin} = adminRoleSnapshot(current);
|
|
|
|
|
+ if (!isAdmin) fail(403, '当前账号未获运营后台权限');
|
|
|
const company = request.company || current.get('company') || null; if (!company && !isSuperAdmin) fail(403, '管理员账号尚未分配帐套'); return { current, roles, isSuperAdmin, company };
|
|
const company = request.company || current.get('company') || null; if (!company && !isSuperAdmin) fail(403, '管理员账号尚未分配帐套'); return { current, roles, isSuperAdmin, company };
|
|
|
}
|
|
}
|
|
|
async function fieldsOf(className) { return (await new Parse.Schema(className).get({ useMasterKey:true })).fields || {}; }
|
|
async function fieldsOf(className) { return (await new Parse.Schema(className).get({ useMasterKey:true })).fields || {}; }
|
|
@@ -3689,9 +3685,9 @@ async function handler(request,response) {
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
const definitions = [
|
|
const definitions = [
|
|
|
- { name: 'xiaoshu.ops.gateway', desc: '小树陪练运营管理后台专用网关(业务投影、分级隐私、三级权限与审计)', path: 'xiaoshu/ops/gateway', code: operationsGatewayCode + reviewRewardCode + enrollmentOpsCode + payrollIntegrityCode, params: [{ name: 'operation', type: 'String', required: true }] },
|
|
|
|
|
- { name: 'xiaoshu.ops.gateway.v3', desc: '小树陪练运营管理后台专用网关 V3(含历史排课投影、上海业务日期与陪练排课汇总)', path: 'xiaoshu/ops/gateway-v3', code: operationsGatewayCode + reviewRewardCode + enrollmentOpsCode + payrollIntegrityCode, params: [{ name: 'operation', type: 'String', required: true }] },
|
|
|
|
|
- { name: 'xiaoshu.admin.gateway', desc: '小树陪练 Angular 管理后台统一数据网关(权限、帐套、CRUD、统计)', path: 'xiaoshu/admin/gateway', code: adminGatewayCode, params: [{ name: 'operation', type: 'String', required: true }] },
|
|
|
|
|
|
|
+ { name: 'xiaoshu.ops.gateway', desc: '小树陪练运营管理后台专用网关(业务投影、分级隐私、三级权限与审计)', path: 'xiaoshu/ops/gateway', code: operationsGatewayCode + adminRolePolicyCode + reviewRewardCode + enrollmentOpsCode + payrollIntegrityCode, params: [{ name: 'operation', type: 'String', required: true }] },
|
|
|
|
|
+ { name: 'xiaoshu.ops.gateway.v3', desc: '小树陪练运营管理后台专用网关 V3(含历史排课投影、上海业务日期与陪练排课汇总)', path: 'xiaoshu/ops/gateway-v3', code: operationsGatewayCode + adminRolePolicyCode + reviewRewardCode + enrollmentOpsCode + payrollIntegrityCode, params: [{ name: 'operation', type: 'String', required: true }] },
|
|
|
|
|
+ { name: 'xiaoshu.admin.gateway', desc: '小树陪练 Angular 管理后台统一数据网关(权限、帐套、CRUD、统计)', path: 'xiaoshu/admin/gateway', code: adminGatewayCode + adminRolePolicyCode + operatorAccountsCode, params: [{ name: 'operation', type: 'String', required: true }] },
|
|
|
{ name: 'cms.content-normalizer', desc: '替代 ZL_StripeHtmlTag/ZL_StripeTrimstr,保守输出纯文本', path: 'xiaoshu/cms/content-normalizer', code: cmsReadCode('content-normalizer'), params: [{ name: 'content', type: 'String', required: false }] },
|
|
{ name: 'cms.content-normalizer', desc: '替代 ZL_StripeHtmlTag/ZL_StripeTrimstr,保守输出纯文本', path: 'xiaoshu/cms/content-normalizer', code: cmsReadCode('content-normalizer'), params: [{ name: 'content', type: 'String', required: false }] },
|
|
|
{ name: 'cms.users.extended', desc: '替代 ZL_EX_UserView,返回安全用户扩展资料', path: 'xiaoshu/cms/users/extended', code: cmsReadCode('user-extended'), params: [{ name: 'userId', type: 'String', required: true }] },
|
|
{ name: 'cms.users.extended', desc: '替代 ZL_EX_UserView,返回安全用户扩展资料', path: 'xiaoshu/cms/users/extended', code: cmsReadCode('user-extended'), params: [{ name: 'userId', type: 'String', required: true }] },
|
|
|
{ name: 'cms.users.platform', desc: '替代 ZL_User_PlatView', path: 'xiaoshu/cms/users/platform', code: cmsReadCode('user-platform'), params: [{ name: 'userId', type: 'String', required: true }] },
|
|
{ name: 'cms.users.platform', desc: '替代 ZL_User_PlatView', path: 'xiaoshu/cms/users/platform', code: cmsReadCode('user-platform'), params: [{ name: 'userId', type: 'String', required: true }] },
|