-
-
Save gaupoit/6b4512527bb127cb360e5f35b0065494 to your computer and use it in GitHub Desktop.
Static Schema for Parse Server (TS/JS) (tested, and production ready)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This function update, migrate and create Classes | |
export const buildSchemas = async (localSchemas: any[]) => { | |
try { | |
const timeout = setTimeout(() => { | |
if (process.env.NODE_ENV === 'production') process.exit(1) | |
}, 20000) | |
const allCloudSchema = (await Parse.Schema.all()).filter( | |
(s: any) => !lib.isDefaultSchema(s.className), | |
) | |
clearTimeout(timeout) | |
// Hack to force session schema to be created | |
await lib.createDeleteSession() | |
await Promise.all( | |
localSchemas.map(async (localSchema) => lib.saveOrUpdate(allCloudSchema, localSchema)), | |
) | |
} catch (e) { | |
if (process.env.NODE_ENV === 'production') process.exit(1) | |
} | |
} | |
export const lib = { | |
createDeleteSession: async () => { | |
const session = new Parse.Session() | |
await session.save(null, { useMasterKey: true }) | |
await session.destroy({ useMasterKey: true }) | |
}, | |
saveOrUpdate: async (allCloudSchema: any[], localSchema: any) => { | |
const cloudSchema = allCloudSchema.find((sc) => sc.className === localSchema.className) | |
if (cloudSchema) { | |
await lib.updateSchema(localSchema, cloudSchema) | |
} else { | |
await lib.saveSchema(localSchema) | |
} | |
}, | |
saveSchema: async (localSchema: any) => { | |
const newLocalSchema = new Parse.Schema(localSchema.className) | |
// Handle fields | |
Object.keys(localSchema.fields) | |
.filter((fieldName) => !lib.isDefaultFields(localSchema.className, fieldName)) | |
.forEach((fieldName) => { | |
const { type, ...others } = localSchema.fields[fieldName] | |
lib.handleFields(newLocalSchema, fieldName, type, others) | |
}) | |
// Handle indexes | |
if (localSchema.indexes) { | |
Object.keys(localSchema.indexes).forEach((indexName) => | |
newLocalSchema.addIndex(indexName, localSchema.indexes[indexName]), | |
) | |
} | |
// @ts-ignore | |
newLocalSchema.setCLP(localSchema.classLevelPermissions) | |
return newLocalSchema.save() | |
}, | |
updateSchema: async (localSchema: any, cloudSchema: any) => { | |
const newLocalSchema: any = new Parse.Schema(localSchema.className) | |
// Handle fields | |
// Check addition | |
Object.keys(localSchema.fields) | |
.filter((fieldName) => !lib.isDefaultFields(localSchema.className, fieldName)) | |
.forEach((fieldName) => { | |
const { type, ...others } = localSchema.fields[fieldName] | |
if (!cloudSchema.fields[fieldName]) | |
lib.handleFields(newLocalSchema, fieldName, type, others) | |
}) | |
// Check deletion | |
await Promise.all( | |
Object.keys(cloudSchema.fields) | |
.filter((fieldName) => !lib.isDefaultFields(localSchema.className, fieldName)) | |
.map(async (fieldName) => { | |
const field = cloudSchema.fields[fieldName] | |
if (!localSchema.fields[fieldName]) { | |
newLocalSchema.deleteField(fieldName) | |
await newLocalSchema.update() | |
return | |
} | |
const localField = localSchema.fields[fieldName] | |
if (!lib.paramsAreEquals(field, localField)) { | |
newLocalSchema.deleteField(fieldName) | |
await newLocalSchema.update() | |
// @ts-ignore | |
const { type, ...others } = localField | |
lib.handleFields(newLocalSchema, fieldName, type, others) | |
} | |
}), | |
) | |
// Handle Indexes | |
// Check addition | |
const cloudIndexes = lib.fixCloudIndexes(cloudSchema.indexes) | |
if (localSchema.indexes) { | |
Object.keys(localSchema.indexes).forEach((indexName) => { | |
if ( | |
!cloudIndexes[indexName] && | |
!lib.isNativeIndex(localSchema.className, indexName) | |
) | |
newLocalSchema.addIndex(indexName, localSchema.indexes[indexName]) | |
}) | |
} | |
const indexesToAdd: any[] = [] | |
// Check deletion | |
Object.keys(cloudIndexes).forEach(async (indexName) => { | |
if (!lib.isNativeIndex(localSchema.className, indexName)) { | |
if (!localSchema.indexes[indexName]) { | |
newLocalSchema.deleteIndex(indexName) | |
} else if ( | |
!lib.paramsAreEquals(localSchema.indexes[indexName], cloudIndexes[indexName]) | |
) { | |
newLocalSchema.deleteIndex(indexName) | |
indexesToAdd.push({ | |
indexName, | |
index: localSchema.indexes[indexName], | |
}) | |
} | |
} | |
}) | |
// @ts-ignore | |
newLocalSchema.setCLP(localSchema.classLevelPermissions) | |
await newLocalSchema.update() | |
indexesToAdd.forEach((o) => newLocalSchema.addIndex(o.indexName, o.index)) | |
return newLocalSchema.update() | |
}, | |
isDefaultSchema: (className: string) => | |
['_Session', '_PushStatus', '_Installation'].indexOf(className) !== -1, | |
isDefaultFields: (className: string, fieldName: string) => { | |
if (className === '_Role') return true | |
return ( | |
[ | |
'objectId', | |
'createdAt', | |
'updatedAt', | |
'ACL', | |
'emailVerified', | |
'authData', | |
'username', | |
'password', | |
'email', | |
] | |
.filter( | |
(value) => | |
(className !== '_User' && value !== 'email') || className === '_User', | |
) | |
.indexOf(fieldName) !== -1 | |
) | |
}, | |
fixCloudIndexes: (cloudSchemaIndexes: any) => { | |
if (!cloudSchemaIndexes) return {} | |
const { _id_, ...others } = cloudSchemaIndexes | |
return { | |
objectId: { objectId: 1 }, | |
...others, | |
} | |
}, | |
isNativeIndex: (className: string, indexName: string) => { | |
if (className === '_User') { | |
switch (indexName) { | |
case 'case_insensitive_username': | |
return true | |
case 'case_insensitive_email': | |
return true | |
case 'username_1': | |
return true | |
case 'objectId': | |
return true | |
case 'email_1': | |
return true | |
default: | |
break | |
} | |
} | |
if (className === '_Role') { | |
return true | |
} | |
return false | |
}, | |
paramsAreEquals: (indexA: any, indexB: any) => { | |
const keysIndexA = Object.keys(indexA) | |
const keysIndexB = Object.keys(indexB) | |
// Check key name | |
if (keysIndexA.length !== keysIndexB.length) return false | |
return keysIndexA.every((k) => indexA[k] === indexB[k]) | |
}, | |
handleFields: (newLocalSchema: Parse.Schema, fieldName: string, type: string, others: any) => { | |
if (type === 'Relation') { | |
newLocalSchema.addRelation(fieldName, others.targetClass) | |
} else if (type === 'Pointer') { | |
const { targetClass, ...others2 } = others | |
// @ts-ignore | |
newLocalSchema.addPointer(fieldName, targetClass, others2) | |
} else { | |
// @ts-ignore | |
newLocalSchema.addField(fieldName, type, others) | |
} | |
}, | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { User } from './user-example' | |
import { buildSchemas } from './buildSchema | |
const parseServer = ParseServer.start({ | |
databaseURI: 'mongodb://localhost:27017/parse', | |
cloud: 'some/cloud-code', | |
appId: 'test', | |
masterKey: 'test', | |
serverURL: 'http://localhost:1337/parse', | |
publicServerURL: 'http://localhost:1337/parse', | |
allowClientClassCreation: false, | |
port: 1337, | |
// Magic happen here, after the start | |
// buildSchemas will try to manage classes | |
serverStartComplete: async () => { | |
await buildSchemas([User]) | |
}, | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Follow the JSON structure from REST API https://docs.parseplatform.org/rest/guide/#schema | |
export const User = { | |
className: '_User', | |
fields: { | |
objectId: { type: 'String' }, | |
createdAt: { | |
type: 'Date', | |
}, | |
updatedAt: { | |
type: 'Date', | |
}, | |
ACL: { type: 'ACL' }, | |
email: { type: 'String' }, | |
authData: { type: 'Object' }, | |
password: { type: 'String' }, | |
username: { type: 'String' }, | |
firstname: { type: 'String' }, | |
lastname: { type: 'String' }, | |
picture: { type: 'File' }, | |
civility: { type: 'String' }, | |
type: { type: 'String' }, | |
birthDate: { type: 'Date' }, | |
address: { type: 'Object' }, | |
meta: { type: 'Array' }, | |
phone: { type: 'String' }, | |
}, | |
indexes: { | |
objectId: { objectId: 1 }, | |
type: { type: 1 }, | |
lastname: { lastname: 1 }, | |
}, | |
classLevelPermissions: { | |
find: { requiresAuthentication: true }, | |
count: { requiresAuthentication: true }, | |
get: { requiresAuthentication: true }, | |
update: { 'role:Admin': true }, | |
create: { '*': true }, | |
delete: { 'role:Admin': true }, | |
addField: {}, | |
protectedFields: { | |
'role:Admin': [], | |
}, | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment