-
Notifications
You must be signed in to change notification settings - Fork 117
/
schema_utils.ts
222 lines (202 loc) · 7.41 KB
/
schema_utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*
* Schema converter/getters
*/
import { userCanCreateField, userCanUpdateField } from '../vulcan-users/permissions';
import _keys from 'lodash/keys';
import _filter from 'lodash/filter';
import * as _ from 'underscore';
/* getters */
// filter out fields with "." or "$"
export const getValidFields = <T extends DbObject>(schema: SchemaType<T>) => {
return Object.keys(schema).filter(fieldName => !fieldName.includes('$') && !fieldName.includes('.'));
};
export const getReadableFields = <T extends DbObject>(schema: SchemaType<T>) => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canRead || schema[fieldName].viewableBy);
};
export const getCreateableFields = <T extends DbObject>(schema: SchemaType<T>) => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canCreate || schema[fieldName].insertableBy);
};
export const getUpdateableFields = <T extends DbObject>(schema: SchemaType<T>) => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canUpdate || schema[fieldName].editableBy);
};
/* permissions */
/**
* @method Mongo.Collection.getInsertableFields
* Get an array of all fields editable by a specific user for a given collection
* @param {Object} user – the user for which to check field permissions
*/
export const getInsertableFields = function<T extends DbObject>(schema: SchemaType<T>, user: UsersCurrent|null): Array<string> {
const fields: Array<string> = _filter(_keys(schema), function(fieldName: string): boolean {
var field = schema[fieldName];
return userCanCreateField(user, field);
});
return fields;
};
/**
* @method Mongo.Collection.getEditableFields
* Get an array of all fields editable by a specific user for a given collection (and optionally document)
* @param {Object} user – the user for which to check field permissions
*/
export const getEditableFields = function<T extends DbObject>(schema: SchemaType<T>, user: UsersCurrent|null, document): Array<string> {
const fields = _.filter(_.keys(schema), function(fieldName: string): boolean {
var field = schema[fieldName];
return userCanUpdateField(user, field, document);
});
return fields;
};
/**
* Convert a nested SimpleSchema schema into a JSON object
* If flatten = true, will create a flat object instead of nested tree
*/
export const convertSchema = <T extends DbObject>(schema: SimpleSchemaType<T>, flatten = false) => {
if (schema._schema) {
let jsonSchema = {};
Object.keys(schema._schema).forEach(fieldName => {
// exclude array fields
if (fieldName.includes('$')) {
return;
}
// extract schema
jsonSchema[fieldName] = getFieldSchema(fieldName, schema);
// check for existence of nested field
// and get its schema if possible or its type otherwise
const subSchemaOrType = getNestedFieldSchemaOrType(fieldName, schema);
if (subSchemaOrType) {
// if nested field exists, call convertSchema recursively
const convertedSubSchema = convertSchema(subSchemaOrType);
// nested schema can be a field schema ({type, canRead, etc.}) (convertedSchema will be null)
// or a schema on its own with subfields (convertedSchema will return smth)
if (!convertedSubSchema) {
// subSchema is a simple field in this case (eg array of numbers)
jsonSchema[fieldName].field = getFieldSchema(`${fieldName}.$`, schema);
} else {
// subSchema is a full schema with multiple fields (eg array of objects)
if (flatten) {
jsonSchema = { ...jsonSchema, ...convertedSubSchema };
} else {
jsonSchema[fieldName].schema = convertedSubSchema;
}
}
}
});
return jsonSchema;
} else {
return null;
}
};
/*
Get a JSON object representing a field's schema
*/
export const getFieldSchema = <T extends DbObject>(fieldName: string, schema: SimpleSchemaType<T>) => {
let fieldSchema = {};
schemaProperties.forEach(property => {
const propertyValue = schema.get(fieldName, property);
if (propertyValue) {
fieldSchema[property] = propertyValue;
}
});
return fieldSchema;
};
// type is an array due to the possibility of using SimpleSchema.oneOf
// right now we support only fields with one type
export const getSchemaType = schema => schema.type.definitions[0].type;
const getArrayNestedSchema = <T extends DbObject>(fieldName: string&keyof T, schema: SimpleSchemaType<T>) => {
const arrayItemSchema = schema._schema[`${fieldName}.$`];
const nestedSchema = arrayItemSchema && getSchemaType(arrayItemSchema);
return nestedSchema;
};
// nested object fields type is of the form "type: new SimpleSchema({...})"
// so they should possess a "_schema" prop
const isNestedSchemaField = fieldSchema => {
const fieldType = getSchemaType(fieldSchema);
//console.log('fieldType', typeof fieldType, fieldType._schema)
return fieldType && !!fieldType._schema;
};
const getObjectNestedSchema = (fieldName, schema) => {
const fieldSchema = schema._schema[fieldName];
if (!isNestedSchemaField(fieldSchema)) return null;
const nestedSchema = fieldSchema && getSchemaType(fieldSchema);
return nestedSchema;
};
/*
Given an array field, get its nested schema
If the field is not an object, this will return the subfield type instead
*/
export const getNestedFieldSchemaOrType = (fieldName, schema) => {
const arrayItemSchema = getArrayNestedSchema(fieldName, schema);
if (!arrayItemSchema) {
// look for an object schema
const objectItemSchema = getObjectNestedSchema(fieldName, schema);
// no schema was found
if (!objectItemSchema) return null;
return objectItemSchema;
}
return arrayItemSchema;
};
export const schemaProperties = [
'type',
'label',
'optional',
'min',
'max',
'minCount',
'maxCount',
'allowedValues',
'regEx',
'blackbox',
'defaultValue',
'hidden', // hidden: true means the field is never shown in a form no matter what
'form', // form placeholder
'inputProperties', // form placeholder
'control', // SmartForm control (String or React component)
'input', // SmartForm control (String or React component)
'autoform', // legacy form placeholder; backward compatibility (not used anymore)
'order', // position in the form
'group', // form fieldset group
'onCreate', // field insert callback
'onUpdate', // field edit callback
'onDelete', // field remove callback
'onInsert', // OpenCRUD backwards compatibility
'onEdit', // OpenCRUD backwards compatibility
'canRead',
'canCreate',
'canUpdate',
'viewableBy', // OpenCRUD backwards compatibility
'insertableBy', // OpenCRUD backwards compatibility
'editableBy', // OpenCRUD backwards compatibility
'resolveAs',
'description',
'beforeComponent',
'afterComponent',
'placeholder',
'options',
'query',
'tooltip'
] as const;
export const formProperties = [
'optional',
'min',
'max',
'minCount',
'maxCount',
'allowedValues',
'regEx',
'blackbox',
'defaultValue',
'form', // form placeholder
'inputProperties', // form placeholder
'control', // SmartForm control (String or React component)
'input', // SmartForm control (String or React component)
'order', // position in the form
'group', // form fieldset group
'description',
'beforeComponent',
'afterComponent',
'placeholder',
'options',
'query',
'tooltip'
] as const;