Skip to content

Instantly share code, notes, and snippets.

@gaupoit
Forked from Moumouls/buildSchema.ts
Created May 11, 2021 03:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gaupoit/6b4512527bb127cb360e5f35b0065494 to your computer and use it in GitHub Desktop.
Save gaupoit/6b4512527bb127cb360e5f35b0065494 to your computer and use it in GitHub Desktop.
Static Schema for Parse Server (TS/JS) (tested, and production ready)
// 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)
}
},
}
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])
},
})
// 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