|
|
@@ -20,6 +20,7 @@ const ALLOWED_CLASSES = new Set([
|
|
|
const READ_ONLY_CLASSES = new Set(['_Role','Permission','PayPlat','ThirdPlatInfo']);
|
|
|
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']);
|
|
|
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']);
|
|
|
|
|
|
function inputOf(request) {
|
|
|
const body = request.body || {};
|
|
|
@@ -92,6 +93,9 @@ function toParseValue(field, value) {
|
|
|
return number;
|
|
|
}
|
|
|
if (field.type === 'Boolean') return value === true || value === 'true';
|
|
|
+ if (field.type === 'Array') { if (!Array.isArray(value)) fail(400, '数组字段格式无效'); return value; }
|
|
|
+ if (field.type === 'Object') { if (!value || typeof value !== 'object' || Array.isArray(value)) fail(400, '对象字段格式无效'); return value; }
|
|
|
+ if (!GENERIC_WRITE_TYPES.has(field.type)) fail(400, '该字段类型不允许通用编辑: ' + field.type);
|
|
|
return value;
|
|
|
}
|
|
|
async function countClass(className, context) {
|
|
|
@@ -148,7 +152,7 @@ async function handler(request, response) {
|
|
|
const fields = await schemaFor(className);
|
|
|
const classWritable = !READ_ONLY_CLASSES.has(className);
|
|
|
if (operation === 'schema') {
|
|
|
- const fieldList = Object.entries(fields).filter(([name]) => !HIDDEN_FIELDS.has(name) && !/(?:password|secret|sessiontoken|masterkey|privatekey)/i.test(name)).map(([name, field]) => ({ name, type: field.type, targetClass: field.targetClass, required: field.required === true, writable: classWritable && !SYSTEM_FIELDS.has(name) }));
|
|
|
+ const fieldList = Object.entries(fields).filter(([name]) => !HIDDEN_FIELDS.has(name) && !/(?:password|secret|sessiontoken|masterkey|privatekey)/i.test(name)).map(([name, field]) => ({ name, type: field.type, targetClass: field.targetClass, required: field.required === true, writable: classWritable && !SYSTEM_FIELDS.has(name) && GENERIC_WRITE_TYPES.has(field.type) }));
|
|
|
return response.json({ success: true, data: { className, label: CLASS_LABELS[className] || className, fields: fieldList, writable: classWritable, supportsSoftDelete: Boolean(fields.isDeleted) } });
|
|
|
}
|
|
|
if (operation === 'list') {
|
|
|
@@ -172,7 +176,7 @@ async function handler(request, response) {
|
|
|
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);
|
|
|
const payload = input.fields && typeof input.fields === 'object' ? input.fields : {};
|
|
|
- for (const [name, value] of Object.entries(payload)) { if (!fields[name] || SYSTEM_FIELDS.has(name)) continue; if (value === null) object.unset(name); else object.set(name, toParseValue(fields[name], value)); }
|
|
|
+ for (const [name, value] of Object.entries(payload)) { if (!fields[name] || SYSTEM_FIELDS.has(name)) continue; if (!GENERIC_WRITE_TYPES.has(fields[name].type)) fail(400, '字段不允许通用编辑: ' + name); if (value === null) object.unset(name); else object.set(name, toParseValue(fields[name], value)); }
|
|
|
if (fields.company && context.company) object.set('company', context.company); if (fields.isDeleted && !objectId) object.set('isDeleted', false);
|
|
|
await object.save(null, { useMasterKey: true }); await audit(context, objectId ? 'update' : 'create', className, object.id);
|
|
|
return response.json({ success: true, data: serializeObject(object) });
|