|
@@ -15,12 +15,12 @@ const CLASS_LABELS = {
|
|
|
GradeCate: '多级字典分类', Grade: '多级字典选项',
|
|
GradeCate: '多级字典分类', Grade: '多级字典选项',
|
|
|
Currency: '货币管理', SysHoliday: '节假日管理',
|
|
Currency: '货币管理', SysHoliday: '节假日管理',
|
|
|
Product: '商品', StoreProduct: '门店商品', StoreApplication: '门店申请', ShopFareTlp: '商城运费模板', ShopMoneyRegular: '金额规则', PayPlat: '支付平台',
|
|
Product: '商品', StoreProduct: '门店商品', StoreApplication: '门店申请', ShopFareTlp: '商城运费模板', ShopMoneyRegular: '金额规则', PayPlat: '支付平台',
|
|
|
- GuestBar: '互动社区', Guestbook: '留言', Guestcate: '留言分类', Feedback: '反馈', Baike: '百科审核', Pub: '互动模块定义', PubTw: '提问互动提交', PubWTHD: '问题回答提交', PubZXDC: '在线调查提交', Search: '后台快捷入口', AdZone: '广告位管理', AdInfo: '广告内容管理', FontPicShape: '字体图形素材', FontPicShapeType: '字体图形分类', DesignAsk: '问卷调查', DesignQuestion: '问卷题目', DesignAnswer: '问卷答卷', DesignRes: '可视化设计资源', ServiceSeat: '客服席位', Temp: '客服欢迎语', StoreApplication: '店铺管理', StoreStyle: '店铺样式', PageStyle: '黄页样式', PlatComp: '协同办公企业', ContentTagKey: '内容标签词库', UserLevel: '积分等级', UserMoneyLog: '资金积分流水', UserPromotion: '用户推广关系', CRMSAttr: 'CRM 客户类型', SysLog: '系统日志', MisType: 'OA 流程类型', Agency: '代理机构', DeliveryCenter: '交付中心'
|
|
|
|
|
|
|
+ GuestBar: '互动社区', Guestbook: '留言', Guestcate: '留言分类', Feedback: '反馈', Baike: '百科审核', Pub: '互动模块定义', PubTw: '提问互动提交', PubWTHD: '问题回答提交', PubZXDC: '在线调查提交', Role: '系统角色', ARoleAuth: '角色权限', Search: '后台快捷入口', AdZone: '广告位管理', AdInfo: '广告内容管理', FontPicShape: '字体图形素材', FontPicShapeType: '字体图形分类', DesignAsk: '问卷调查', DesignQuestion: '问卷题目', DesignAnswer: '问卷答卷', DesignRes: '可视化设计资源', ServiceSeat: '客服席位', Temp: '客服欢迎语', StoreApplication: '店铺管理', StoreStyle: '店铺样式', PageStyle: '黄页样式', PlatComp: '协同办公企业', ContentTagKey: '内容标签词库', UserLevel: '积分等级', UserMoneyLog: '资金积分流水', UserPromotion: '用户推广关系', CRMSAttr: 'CRM 客户类型', SysLog: '系统日志', MisType: 'OA 流程类型', Agency: '代理机构', DeliveryCenter: '交付中心'
|
|
|
};
|
|
};
|
|
|
const ALLOWED_CLASSES = new Set([
|
|
const ALLOWED_CLASSES = new Set([
|
|
|
'PageTemplate','PaperQuestions','PayPlat','Permission','PlatComp','App','DesignAnswer','DesignAsk','Attachment','DesignPage','DesignQuestion','DesignRes','Feedback','DesignScence','StudentAchieve','ContentArticle','DesignSiteInfo','Profile','AdInfo','AdZone','ARoleAuth','Baike','ExamSysQuestions','ContactInfo','VocabularyWord','AssessmentProfile','Agency','MemoryPracticeRecord','DeliveryCenter','CourseBinding','PracticeRecord','CourseAppointment','Account','SurveyItem','SurveyLog','LessonRecord','DailyStudyRecord','CommonModel','ContentPublish','Company','_Role','_User','CRMSAttr','Currency','Datadic','Datadiccategory','DesignTlp','DocModel','DocPermission','ExamClass','ExamSysPapers','ExamType','ExamPoint','ExTeacher','FontPicShape','FontPicShapeType','Grade','GradeCate','Group','GroupModel','GuestBar','Guestbook','Guestcate','MailTemp','Manager','MisProcedure','MisProLevel','MisSign','MisType','PageStyle','Model','ModelField','Node','NodeAuth','NodeModelTemplate','Product','PlatUserRole','Pub','PubTw','PubWTHD','PubZXDC','PublishNode','QuestionsKnowledge','Role','StoreProduct','SafeMobile','Search','SenTask','ServiceSeat','ShopFareTlp','ShopMoneyRegular','Special','StoreApplication','StoreStyle','SysCSSManage','SysHoliday','SysLog','Temp','ThirdPlatInfo','UserCredit','UserDummyPoint','UserFriendGroup','UserLevel','UserMoneyLog','UserPromotion','UserSIcon','UserUserPoint','UserExpDomP','UserExpHis'
|
|
'PageTemplate','PaperQuestions','PayPlat','Permission','PlatComp','App','DesignAnswer','DesignAsk','Attachment','DesignPage','DesignQuestion','DesignRes','Feedback','DesignScence','StudentAchieve','ContentArticle','DesignSiteInfo','Profile','AdInfo','AdZone','ARoleAuth','Baike','ExamSysQuestions','ContactInfo','VocabularyWord','AssessmentProfile','Agency','MemoryPracticeRecord','DeliveryCenter','CourseBinding','PracticeRecord','CourseAppointment','Account','SurveyItem','SurveyLog','LessonRecord','DailyStudyRecord','CommonModel','ContentPublish','Company','_Role','_User','CRMSAttr','Currency','Datadic','Datadiccategory','DesignTlp','DocModel','DocPermission','ExamClass','ExamSysPapers','ExamType','ExamPoint','ExTeacher','FontPicShape','FontPicShapeType','Grade','GradeCate','Group','GroupModel','GuestBar','Guestbook','Guestcate','MailTemp','Manager','MisProcedure','MisProLevel','MisSign','MisType','PageStyle','Model','ModelField','Node','NodeAuth','NodeModelTemplate','Product','PlatUserRole','Pub','PubTw','PubWTHD','PubZXDC','PublishNode','QuestionsKnowledge','Role','StoreProduct','SafeMobile','Search','SenTask','ServiceSeat','ShopFareTlp','ShopMoneyRegular','Special','StoreApplication','StoreStyle','SysCSSManage','SysHoliday','SysLog','Temp','ThirdPlatInfo','UserCredit','UserDummyPoint','UserFriendGroup','UserLevel','UserMoneyLog','UserPromotion','UserSIcon','UserUserPoint','UserExpDomP','UserExpHis'
|
|
|
]);
|
|
]);
|
|
|
-const READ_ONLY_CLASSES = new Set(['_Role','Permission','PayPlat','ThirdPlatInfo','GuestBar','DesignAnswer','Baike','ExamSysPapers','ExamSysQuestions','PaperQuestions','ExamType','ContentPublish','PublishNode','SysLog','Pub','PubTw','PubWTHD','PubZXDC']);
|
|
|
|
|
|
|
+const READ_ONLY_CLASSES = new Set(['_Role','Permission','ARoleAuth','PayPlat','ThirdPlatInfo','GuestBar','DesignAnswer','Baike','ExamSysPapers','ExamSysQuestions','PaperQuestions','ExamType','ContentPublish','PublishNode','SysLog','Pub','PubTw','PubWTHD','PubZXDC']);
|
|
|
const SYSTEM_FIELDS = new Set(['objectId','createdAt','updatedAt','ACL','company','password','authData','sessionToken','legacyPasswordHash','legacyPasswordHashType','appPassword','newapiToken','fmodeApiToken','useMasterKey','adminPassword','randNumber','adminRoleKey','isAdmin','roles','role','isDisabled','legacyUserId','legacyGroupId','legacyUserData','legacyUserPlat','groupId']);
|
|
const SYSTEM_FIELDS = new Set(['objectId','createdAt','updatedAt','ACL','company','password','authData','sessionToken','legacyPasswordHash','legacyPasswordHashType','appPassword','newapiToken','fmodeApiToken','useMasterKey','adminPassword','randNumber','adminRoleKey','isAdmin','roles','role','isDisabled','legacyUserId','legacyGroupId','legacyUserData','legacyUserPlat','groupId']);
|
|
|
const HIDDEN_FIELDS = new Set(['password','authData','sessionToken','legacyPasswordHash','legacyPasswordHashType','appPassword','newapiToken','fmodeApiToken','useMasterKey','adminPassword','randNumber','apiKey','appSecret','secret','token']);
|
|
const HIDDEN_FIELDS = new Set(['password','authData','sessionToken','legacyPasswordHash','legacyPasswordHashType','appPassword','newapiToken','fmodeApiToken','useMasterKey','adminPassword','randNumber','apiKey','appSecret','secret','token']);
|
|
|
const GENERIC_WRITE_TYPES = new Set(['String','Number','Boolean','Date','Pointer','Object','Array']);
|
|
const GENERIC_WRITE_TYPES = new Set(['String','Number','Boolean','Date','Pointer','Object','Array']);
|
|
@@ -52,6 +52,8 @@ const CLASS_SYSTEM_FIELDS = {
|
|
|
DesignQuestion: new Set(['id','askId','cdate','cuser','orderId','sourceKey']),
|
|
DesignQuestion: new Set(['id','askId','cdate','cuser','orderId','sourceKey']),
|
|
|
DesignAnswer: new Set(['id','askId','ip','cdate','userId','sourceKey']),
|
|
DesignAnswer: new Set(['id','askId','ip','cdate','userId','sourceKey']),
|
|
|
DesignRes: new Set(['id','vpath','previewimg','cdate','userid','sourceKey']),
|
|
DesignRes: new Set(['id','vpath','previewimg','cdate','userid','sourceKey']),
|
|
|
|
|
+ Role: new Set(['roleId','ztype','zstatus','nodeId','auth','auth2','auth3','cadminId','cdate','sourceKey']),
|
|
|
|
|
+ ARoleAuth: new Set(['id','rid','adminId','sourceKey']),
|
|
|
ServiceSeat: new Set(['sId','sAdminId','sRemrk','sDateTime','sourceKey']),
|
|
ServiceSeat: new Set(['sId','sAdminId','sRemrk','sDateTime','sourceKey']),
|
|
|
Temp: new Set(['id','useType','str3','str5','str6','describe','cdate','userId','sourceKey']),
|
|
Temp: new Set(['id','useType','str3','str5','str6','describe','cdate','userId','sourceKey']),
|
|
|
StoreApplication: new Set(['id','userId','userName','addTime','storeState','storeCommendState','storeModelId','sourceKey']),
|
|
StoreApplication: new Set(['id','userId','userName','addTime','storeState','storeCommendState','storeModelId','sourceKey']),
|
|
@@ -1147,6 +1149,32 @@ async function handler(request, response) {
|
|
|
if (String(input.className || '') !== 'DesignRes' || String(input.action || '') !== 'delete') fail(400, '不支持的设计资源批量操作'); const requestedIds = Array.isArray(input.objectIds) ? input.objectIds : [input.objectId]; const objectIds = [...new Set(requestedIds.map((value) => String(value || '').trim()).filter(Boolean))]; if (!objectIds.length || objectIds.length > 100) fail(400, '每次请选择 1 至 100 个设计资源');
|
|
if (String(input.className || '') !== 'DesignRes' || String(input.action || '') !== 'delete') fail(400, '不支持的设计资源批量操作'); const requestedIds = Array.isArray(input.objectIds) ? input.objectIds : [input.objectId]; const objectIds = [...new Set(requestedIds.map((value) => String(value || '').trim()).filter(Boolean))]; if (!objectIds.length || objectIds.length > 100) fail(400, '每次请选择 1 至 100 个设计资源');
|
|
|
const fields = await schemaFor('DesignRes'); const query = new Parse.Query('DesignRes'); applyTenant(query, fields, context, input.companyId); query.containedIn('objectId', objectIds); query.limit(objectIds.length); const targets = await query.find({ useMasterKey: true }); if (targets.length !== objectIds.length) fail(404, '部分设计资源不存在或不属于当前帐套'); await Parse.Object.destroyAll(targets, { useMasterKey: true }); for (const target of targets) await audit({ ...context, company: target.get('company') || context.company }, 'delete-design-resource', 'DesignRes', target.id); return response.json({ success: true, data: { action: 'delete', updated: targets.length, results: [] } });
|
|
const fields = await schemaFor('DesignRes'); const query = new Parse.Query('DesignRes'); applyTenant(query, fields, context, input.companyId); query.containedIn('objectId', objectIds); query.limit(objectIds.length); const targets = await query.find({ useMasterKey: true }); if (targets.length !== objectIds.length) fail(404, '部分设计资源不存在或不属于当前帐套'); await Parse.Object.destroyAll(targets, { useMasterKey: true }); for (const target of targets) await audit({ ...context, company: target.get('company') || context.company }, 'delete-design-resource', 'DesignRes', target.id); return response.json({ success: true, data: { action: 'delete', updated: targets.length, results: [] } });
|
|
|
}
|
|
}
|
|
|
|
|
+ if (operation === 'roles') {
|
|
|
|
|
+ const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20)); const type = String(input.type || 'admin').trim().toLowerCase(); const search = String(input.search || '').trim().toLowerCase(); if (!['admin','user','-100'].includes(type) || search.length > 100) fail(400, '角色类型或搜索词无效');
|
|
|
|
|
+ const fields = await schemaFor('Role'); const query = new Parse.Query('Role'); applyTenant(query, fields, context, input.companyId); query.limit(1000); let results = await query.find({ useMasterKey: true }); results = results.filter((entry) => (type === '-100' || String(entry.get('ztype') || 'admin').toLowerCase() === type) && (!search || [entry.get('roleName'),entry.get('description')].some((value) => String(value || '').toLowerCase().includes(search)))); results.sort((left, right) => Number(left.get('roleId') || 0) - Number(right.get('roleId') || 0)); const total = results.length; results = results.slice((page - 1) * pageSize, page * pageSize);
|
|
|
|
|
+ return response.json({ success: true, data: { className: 'Role', page, pageSize, total, results: results.map(serializeObject) } });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (operation === 'saveRole') {
|
|
|
|
|
+ const objectId = String(input.objectId || ''); const payload = input.fields && typeof input.fields === 'object' ? input.fields : {}; const roleName = String(payload.roleName || '').trim(); const description = String(payload.description || '').trim(); const requestedType = String(input.roleType || payload.ztype || 'admin').trim().toLowerCase(); if (!roleName || roleName.length > 100 || description.length > 1000 || !['admin','user'].includes(requestedType)) fail(400, '角色名称、说明或类型无效');
|
|
|
|
|
+ let companyId = pointerId(context.company); if (context.isSuperAdmin && input.companyId) companyId = String(input.companyId); if (!companyId) fail(400, '角色必须指定帐套'); const company = await new Parse.Query('Company').get(companyId, { useMasterKey: true }); const fields = await schemaFor('Role'); let target; let created = false; let roleType = requestedType;
|
|
|
|
|
+ if (objectId) { const query = new Parse.Query('Role'); applyTenant(query, fields, context, input.companyId); target = await query.get(objectId, { useMasterKey: true }); companyId = pointerId(target.get('company')) || companyId; roleType = String(target.get('ztype') || 'admin').toLowerCase(); if (requestedType !== roleType) fail(400, '角色类型不允许在管理员角色与用户角色之间迁移'); } else { target = new Parse.Object('Role'); target.set('company', company); target.set('sourceKey', 'cloud:role:' + companyId + ':' + Date.now() + ':' + Math.random().toString(36).slice(2,10)); target.set('ztype', roleType); target.set('zstatus', 99); target.set('nodeId', 0); target.set('auth', ''); target.set('auth2', ''); target.set('auth3', ''); target.set('cdate', new Date()); target.set('cadminId', Number(context.current.get('legacyUserId') || (context.current.get('legacyUserData') || {}).UserID || 0)); created = true; }
|
|
|
|
|
+ const duplicate = await Psql.oneOrNone('SELECT "objectId" FROM "Role" WHERE "company"=$1 AND "objectId"<>$2 AND LOWER(TRIM(COALESCE("roleName",\'\')))=LOWER($3) AND LOWER(COALESCE("ztype",\'admin\'))=$4 LIMIT 1', [companyId, objectId, roleName, roleType]); if (duplicate) fail(409, '同一帐套同一类型的角色名称不能重复'); target.set('roleName', roleName); target.set('description', description);
|
|
|
|
|
+ try { await target.save(null, { useMasterKey: true }); if (created) { const rows = await Psql.query('WITH lock_row AS MATERIALIZED (SELECT pg_advisory_xact_lock(hashtext(\'xiaoshu-role-id:\'||$1))),next_id AS MATERIALIZED (SELECT COALESCE(MAX("roleId"),0)+1 AS id FROM "Role",lock_row WHERE "company"=$1) UPDATE "Role" SET "roleId"=next_id.id,"sourceKey"=\'[["RoleID",\'||next_id.id::text||\']]\',"updatedAt"=NOW() FROM next_id WHERE "objectId"=$2 RETURNING next_id.id', [companyId, target.id]); if (!Number(rows[0] && rows[0].id)) throw new Error('无法分配角色编号'); } } catch (error) { if (created && target.id) await target.destroy({ useMasterKey: true }).catch(() => undefined); fail(422, '角色保存失败: ' + String(error && error.message || error)); } await target.fetch({ useMasterKey: true }); await audit({ ...context, company: target.get('company') || company }, objectId ? 'update-role' : 'create-role', 'Role', target.id); return response.json({ success: true, data: serializeObject(target) });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (operation === 'roleAuthorization') {
|
|
|
|
|
+ const objectId = String(input.objectId || ''); if (!objectId) fail(400, '缺少角色 objectId'); const roleFields = await schemaFor('Role'); const roleQuery = new Parse.Query('Role'); applyTenant(roleQuery, roleFields, context, input.companyId); const role = await roleQuery.get(objectId, { useMasterKey: true }); const roleId = Number(role.get('roleId')) || 0; if (!roleId) fail(409, '角色缺少旧数字 ID'); const authFields = await schemaFor('ARoleAuth'); const authQuery = new Parse.Query('ARoleAuth'); applyTenant(authQuery, authFields, { ...context, company: role.get('company') || context.company }, pointerId(role.get('company'))); authQuery.equalTo('rid', roleId); authQuery.limit(2); const authRows = await authQuery.find({ useMasterKey: true }); if (authRows.length > 1) fail(409, '角色存在重复权限记录'); const names = ['model','content','shop','page','exam','user','system','office','portable','sites','other','extend']; const authorization = authRows[0] ? serializeObject(authRows[0]) : { rid: roleId }; for (const name of names) if (!Object.prototype.hasOwnProperty.call(authorization, name)) authorization[name] = '';
|
|
|
|
|
+ return response.json({ success: true, data: { ...serializeObject(role), authorization } });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (operation === 'saveRoleAuthorization') {
|
|
|
|
|
+ const roleObjectId = String(input.roleObjectId || input.objectId || ''); const payload = input.authorization && typeof input.authorization === 'object' ? input.authorization : {}; if (!roleObjectId) fail(400, '缺少角色 objectId'); const roleFields = await schemaFor('Role'); const roleQuery = new Parse.Query('Role'); applyTenant(roleQuery, roleFields, context, input.companyId); const role = await roleQuery.get(roleObjectId, { useMasterKey: true }); const roleId = Number(role.get('roleId')) || 0; const companyId = pointerId(role.get('company')) || pointerId(context.company); if (!roleId || !companyId) fail(409, '角色缺少旧编号或帐套'); const names = ['model','content','shop','page','exam','user','system','office','portable','sites','other','extend']; const normalized = {}; let totalLength = 0; for (const name of names) { const values = [...new Set(String(payload[name] || '').split(',').map((value) => value.trim()).filter(Boolean))]; if (values.length > 100 || values.some((value) => !/^[A-Za-z0-9_.:-]{1,64}$/.test(value))) fail(400, '角色权限码格式无效'); normalized[name] = values.join(','); totalLength += normalized[name].length; } if (totalLength > 10000) fail(400, '角色权限码过长');
|
|
|
|
|
+ const authFields = await schemaFor('ARoleAuth'); const authQuery = new Parse.Query('ARoleAuth'); applyTenant(authQuery, authFields, { ...context, company: role.get('company') || context.company }, companyId); authQuery.equalTo('rid', roleId); authQuery.limit(2); const rows = await authQuery.find({ useMasterKey: true }); if (rows.length > 1) fail(409, '角色存在重复权限记录'); let target = rows[0]; const created = !target; if (!target) { target = new Parse.Object('ARoleAuth'); target.set('company', role.get('company')); target.set('rid', roleId); target.set('sourceKey', 'cloud:role-auth:' + companyId + ':' + roleId + ':' + Date.now()); } for (const [name,value] of Object.entries(normalized)) target.set(name, value); target.set('adminId', Number(context.current.get('legacyUserId') || (context.current.get('legacyUserData') || {}).UserID || 0));
|
|
|
|
|
+ try { await target.save(null, { useMasterKey: true }); if (created) { const assigned = await Psql.query('WITH lock_row AS MATERIALIZED (SELECT pg_advisory_xact_lock(hashtext(\'xiaoshu-role-auth-id:\'||$1))),next_id AS MATERIALIZED (SELECT COALESCE(MAX("id"),0)+1 AS id FROM "ARoleAuth",lock_row WHERE "company"=$1) UPDATE "ARoleAuth" SET "id"=next_id.id,"sourceKey"=\'[["ID",\'||next_id.id::text||\']]\',"updatedAt"=NOW() FROM next_id WHERE "objectId"=$2 RETURNING next_id.id', [companyId, target.id]); if (!Number(assigned[0] && assigned[0].id)) throw new Error('无法分配角色权限编号'); } } catch (error) { if (created && target.id) await target.destroy({ useMasterKey: true }).catch(() => undefined); fail(422, '角色权限保存失败: ' + String(error && error.message || error)); } await target.fetch({ useMasterKey: true }); await audit({ ...context, company: role.get('company') || context.company }, created ? 'create-role-authorization' : 'update-role-authorization', 'ARoleAuth', target.id); return response.json({ success: true, data: serializeObject(target) });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (operation === 'roleBatch') {
|
|
|
|
|
+ if (String(input.className || '') !== 'Role' || String(input.action || '') !== 'delete') fail(400, '不支持的角色批量操作'); const objectIds = [...new Set((Array.isArray(input.objectIds) ? input.objectIds : [input.objectId]).map((value) => String(value || '').trim()).filter(Boolean))]; if (!objectIds.length || objectIds.length > 100) fail(400, '每次请选择 1 至 100 个角色'); const fields = await schemaFor('Role'); const query = new Parse.Query('Role'); applyTenant(query, fields, context, input.companyId); query.containedIn('objectId', objectIds); query.limit(objectIds.length); const targets = await query.find({ useMasterKey: true }); if (targets.length !== objectIds.length) fail(404, '部分角色不存在或不属于当前帐套'); const authFields = await schemaFor('ARoleAuth'); const authTargets = [];
|
|
|
|
|
+ for (const target of targets) { const companyId = pointerId(target.get('company')); const roleId = Number(target.get('roleId')) || 0; if (!companyId || !roleId) fail(409, '角色缺少旧编号或帐套'); const refs = await Psql.one('SELECT (SELECT COUNT(*)::int FROM "Manager" WHERE "company"=$1 AND POSITION(\',\'||$2::text||\',\' IN \',\'||REPLACE(COALESCE("adminRole",\'\'),\' \',\'\')||\',\')>0) AS managers,(SELECT COUNT(*)::int FROM "_User" WHERE "company"=$1 AND POSITION(\',\'||$2::text||\',\' IN \',\'||REPLACE(COALESCE("legacyUserData"->>\'UserRole\',\'\'),\' \',\'\')||\',\')>0) AS users', [companyId, roleId]); if (Number(refs.managers) || Number(refs.users)) fail(409, '角色仍被管理员或用户引用,不能删除'); const authQuery = new Parse.Query('ARoleAuth'); applyTenant(authQuery, authFields, { ...context, company: target.get('company') || context.company }, companyId); authQuery.equalTo('rid', roleId); authQuery.limit(100); authTargets.push(...await authQuery.find({ useMasterKey: true })); }
|
|
|
|
|
+ if (authTargets.length) await Parse.Object.destroyAll(authTargets, { useMasterKey: true }); await Parse.Object.destroyAll(targets, { useMasterKey: true }); for (const target of targets) await audit({ ...context, company: target.get('company') || context.company }, 'delete-role', 'Role', target.id); return response.json({ success: true, data: { action: 'delete', updated: targets.length, deletedAuthorizations: authTargets.length } });
|
|
|
|
|
+ }
|
|
|
if (operation === 'legacyFontPicBlocker') {
|
|
if (operation === 'legacyFontPicBlocker') {
|
|
|
const action = String(input.action || ''); const blockers = { Index: '目标 Schema 缺少旧 FontPic 成品字体图主表与历史数据', FontPicInfo: '目标 Schema 缺少旧 FontPic 成品字体图详情数据', FontPicInfo_Submit: '目标 Schema 缺少旧 FontPic 成品字体图持久化模型', FontPic_API: '目标 Schema 缺少旧 FontPic 成品字体图可删除记录', Draft: '目标 Schema 缺少旧 FontPicDraft 草稿表与历史数据', DraftInfo: '目标 Schema 缺少旧 FontPicDraft 草稿详情数据', DraftInfo_Submit: '目标 Schema 缺少旧 FontPicDraft 草稿持久化模型', Draft_API: '目标 Schema 缺少旧 FontPicDraft 可删除记录' }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧字体图动作'); fail(501, 'migration_blocked: ' + blockers[action]);
|
|
const action = String(input.action || ''); const blockers = { Index: '目标 Schema 缺少旧 FontPic 成品字体图主表与历史数据', FontPicInfo: '目标 Schema 缺少旧 FontPic 成品字体图详情数据', FontPicInfo_Submit: '目标 Schema 缺少旧 FontPic 成品字体图持久化模型', FontPic_API: '目标 Schema 缺少旧 FontPic 成品字体图可删除记录', Draft: '目标 Schema 缺少旧 FontPicDraft 草稿表与历史数据', DraftInfo: '目标 Schema 缺少旧 FontPicDraft 草稿详情数据', DraftInfo_Submit: '目标 Schema 缺少旧 FontPicDraft 草稿持久化模型', Draft_API: '目标 Schema 缺少旧 FontPicDraft 可删除记录' }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧字体图动作'); fail(501, 'migration_blocked: ' + blockers[action]);
|
|
|
}
|
|
}
|
|
@@ -1994,6 +2022,7 @@ async function handler(request, response) {
|
|
|
if (className === 'PageStyle') fail(400, '黄页样式必须走专用保存流程');
|
|
if (className === 'PageStyle') fail(400, '黄页样式必须走专用保存流程');
|
|
|
if (className === 'PlatComp') fail(400, '协同办公企业必须走专用保存流程');
|
|
if (className === 'PlatComp') fail(400, '协同办公企业必须走专用保存流程');
|
|
|
if (className === 'DesignRes') fail(400, '设计资源必须走专用保存流程');
|
|
if (className === 'DesignRes') fail(400, '设计资源必须走专用保存流程');
|
|
|
|
|
+ if (className === 'Role') fail(400, '系统角色必须走专用保存流程');
|
|
|
if (className === 'CommonModel' && !objectId) fail(400, '内容新增必须同时写入模型附表,不能走通用保存');
|
|
if (className === 'CommonModel' && !objectId) fail(400, '内容新增必须同时写入模型附表,不能走通用保存');
|
|
|
let object;
|
|
let object;
|
|
|
if (objectId) { const query = new Parse.Query(className); applyTenant(query, fields, context, input.companyId); object = await query.get(objectId, { useMasterKey: true }); } else object = new Parse.Object(className);
|
|
if (objectId) { const query = new Parse.Query(className); applyTenant(query, fields, context, input.companyId); object = await query.get(objectId, { useMasterKey: true }); } else object = new Parse.Object(className);
|
|
@@ -2017,6 +2046,7 @@ async function handler(request, response) {
|
|
|
if (className === 'PageStyle') fail(400, '黄页样式必须走专用批量流程');
|
|
if (className === 'PageStyle') fail(400, '黄页样式必须走专用批量流程');
|
|
|
if (className === 'PlatComp') fail(400, '协同办公企业必须走专用批量流程');
|
|
if (className === 'PlatComp') fail(400, '协同办公企业必须走专用批量流程');
|
|
|
if (className === 'DesignRes') fail(400, '设计资源必须走专用批量流程');
|
|
if (className === 'DesignRes') fail(400, '设计资源必须走专用批量流程');
|
|
|
|
|
+ if (className === 'Role') fail(400, '系统角色必须走专用批量流程');
|
|
|
if (className === 'Guestcate') { const cateid = Number(object.get('cateid')) || 0; const companyId = pointerId(object.get('company')); const refs = await Psql.one('SELECT (SELECT COUNT(*)::int FROM "Guestcate" WHERE "company"=$1 AND "parentId"=$2) AS children,(SELECT COUNT(*)::int FROM "Guestbook" WHERE "company"=$1 AND "cateid"=$2) AS messages,(SELECT COUNT(*)::int FROM "GuestBar" WHERE "company"=$1 AND "cateId"=$2) AS posts', [companyId, cateid]); if (Number(refs.children) || Number(refs.messages) || Number(refs.posts)) fail(409, '分类仍被下级分类、留言或帖子引用,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-guest-category', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|
|
if (className === 'Guestcate') { const cateid = Number(object.get('cateid')) || 0; const companyId = pointerId(object.get('company')); const refs = await Psql.one('SELECT (SELECT COUNT(*)::int FROM "Guestcate" WHERE "company"=$1 AND "parentId"=$2) AS children,(SELECT COUNT(*)::int FROM "Guestbook" WHERE "company"=$1 AND "cateid"=$2) AS messages,(SELECT COUNT(*)::int FROM "GuestBar" WHERE "company"=$1 AND "cateId"=$2) AS posts', [companyId, cateid]); if (Number(refs.children) || Number(refs.messages) || Number(refs.posts)) fail(409, '分类仍被下级分类、留言或帖子引用,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-guest-category', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|
|
|
if (className === 'ExamClass') { const classId = Number(object.get('cId')) || 0; const companyId = pointerId(object.get('company')); const refs = await Psql.one('SELECT (SELECT COUNT(*)::int FROM "ExamClass" WHERE "company"=$1 AND "cClassid"=$2) AS children,(SELECT COUNT(*)::int FROM "ExamSysQuestions" WHERE "company"=$1 AND "pClass"=$2) AS questions', [companyId, classId]); if (Number(refs.children) || Number(refs.questions)) fail(409, '试题分类仍被下级分类或试题引用,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-exam-class', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|
|
if (className === 'ExamClass') { const classId = Number(object.get('cId')) || 0; const companyId = pointerId(object.get('company')); const refs = await Psql.one('SELECT (SELECT COUNT(*)::int FROM "ExamClass" WHERE "company"=$1 AND "cClassid"=$2) AS children,(SELECT COUNT(*)::int FROM "ExamSysQuestions" WHERE "company"=$1 AND "pClass"=$2) AS questions', [companyId, classId]); if (Number(refs.children) || Number(refs.questions)) fail(409, '试题分类仍被下级分类或试题引用,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-exam-class', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|
|
|
if (className === 'ExamPoint') { const pointId = Number(object.get('id')) || 0; const companyId = pointerId(object.get('company')); const children = await Psql.one('SELECT COUNT(*)::int AS count FROM "ExamPoint" WHERE "company"=$1 AND "tid"=$2', [companyId, pointId]); if (Number(children.count)) fail(409, '考点仍有下级考点,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-exam-point', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|
|
if (className === 'ExamPoint') { const pointId = Number(object.get('id')) || 0; const companyId = pointerId(object.get('company')); const children = await Psql.one('SELECT COUNT(*)::int AS count FROM "ExamPoint" WHERE "company"=$1 AND "tid"=$2', [companyId, pointId]); if (Number(children.count)) fail(409, '考点仍有下级考点,不能删除'); await object.destroy({ useMasterKey: true }); await audit(context, 'delete-exam-point', className, objectId); return response.json({ success: true, data: { objectId, softDeleted: false } }); }
|