PageRenderTime 52ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/src/ParseServer.js

https://gitlab.com/mlnkv/parse-server
JavaScript | 289 lines | 170 code | 21 blank | 98 comment | 16 complexity | bff344d3cd323d1e40a7abc1229d0966 MD5 | raw file
  1. // ParseServer - open-source compatible API Server for Parse apps
  2. var batch = require('./batch'),
  3. bodyParser = require('body-parser'),
  4. DatabaseAdapter = require('./DatabaseAdapter'),
  5. express = require('express'),
  6. middlewares = require('./middlewares'),
  7. multer = require('multer'),
  8. Parse = require('parse/node').Parse,
  9. path = require('path'),
  10. authDataManager = require('./authDataManager');
  11. if (!global._babelPolyfill) {
  12. require('babel-polyfill');
  13. }
  14. import { logger,
  15. configureLogger } from './logger';
  16. import cache from './cache';
  17. import Config from './Config';
  18. import parseServerPackage from '../package.json';
  19. import PromiseRouter from './PromiseRouter';
  20. import requiredParameter from './requiredParameter';
  21. import { AnalyticsRouter } from './Routers/AnalyticsRouter';
  22. import { ClassesRouter } from './Routers/ClassesRouter';
  23. import { FeaturesRouter } from './Routers/FeaturesRouter';
  24. import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
  25. import { FilesController } from './Controllers/FilesController';
  26. import { FilesRouter } from './Routers/FilesRouter';
  27. import { FunctionsRouter } from './Routers/FunctionsRouter';
  28. import { GlobalConfigRouter } from './Routers/GlobalConfigRouter';
  29. import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
  30. import { HooksController } from './Controllers/HooksController';
  31. import { HooksRouter } from './Routers/HooksRouter';
  32. import { IAPValidationRouter } from './Routers/IAPValidationRouter';
  33. import { InstallationsRouter } from './Routers/InstallationsRouter';
  34. import { loadAdapter } from './Adapters/AdapterLoader';
  35. import { LiveQueryController } from './Controllers/LiveQueryController';
  36. import { LoggerController } from './Controllers/LoggerController';
  37. import { LogsRouter } from './Routers/LogsRouter';
  38. import { ParseLiveQueryServer } from './LiveQuery/ParseLiveQueryServer';
  39. import { PublicAPIRouter } from './Routers/PublicAPIRouter';
  40. import { PushController } from './Controllers/PushController';
  41. import { PushRouter } from './Routers/PushRouter';
  42. import { randomString } from './cryptoUtils';
  43. import { RolesRouter } from './Routers/RolesRouter';
  44. import { SchemasRouter } from './Routers/SchemasRouter';
  45. import { SessionsRouter } from './Routers/SessionsRouter';
  46. import { UserController } from './Controllers/UserController';
  47. import { UsersRouter } from './Routers/UsersRouter';
  48. import ParsePushAdapter from 'parse-server-push-adapter';
  49. // Mutate the Parse object to add the Cloud Code handlers
  50. addParseCloud();
  51. // ParseServer works like a constructor of an express app.
  52. // The args that we understand are:
  53. // "filesAdapter": a class like GridStoreAdapter providing create, get,
  54. // and delete
  55. // "loggerAdapter": a class like FileLoggerAdapter providing info, error,
  56. // and query
  57. // "databaseURI": a uri like mongodb://localhost:27017/dbname to tell us
  58. // what database this Parse API connects to.
  59. // "cloud": relative location to cloud code to require, or a function
  60. // that is given an instance of Parse as a parameter. Use this instance of Parse
  61. // to register your cloud code hooks and functions.
  62. // "appId": the application id to host
  63. // "masterKey": the master key for requests to this app
  64. // "facebookAppIds": an array of valid Facebook Application IDs, required
  65. // if using Facebook login
  66. // "collectionPrefix": optional prefix for database collection names
  67. // "fileKey": optional key from Parse dashboard for supporting older files
  68. // hosted by Parse
  69. // "clientKey": optional key from Parse dashboard
  70. // "dotNetKey": optional key from Parse dashboard
  71. // "restAPIKey": optional key from Parse dashboard
  72. // "javascriptKey": optional key from Parse dashboard
  73. // "push": optional key from configure push
  74. // "sessionLength": optional length in seconds for how long Sessions should be valid for
  75. class ParseServer {
  76. constructor({
  77. appId = requiredParameter('You must provide an appId!'),
  78. masterKey = requiredParameter('You must provide a masterKey!'),
  79. appName,
  80. filesAdapter,
  81. push,
  82. loggerAdapter,
  83. logsFolder,
  84. databaseURI,
  85. databaseOptions,
  86. cloud,
  87. collectionPrefix = '',
  88. clientKey,
  89. javascriptKey,
  90. dotNetKey,
  91. restAPIKey,
  92. fileKey = 'invalid-file-key',
  93. facebookAppIds = [],
  94. enableAnonymousUsers = true,
  95. allowClientClassCreation = true,
  96. oauth = {},
  97. serverURL = requiredParameter('You must provide a serverURL!'),
  98. maxUploadSize = '20mb',
  99. verifyUserEmails = false,
  100. emailAdapter,
  101. publicServerURL,
  102. customPages = {
  103. invalidLink: undefined,
  104. verifyEmailSuccess: undefined,
  105. choosePassword: undefined,
  106. passwordResetSuccess: undefined
  107. },
  108. liveQuery = {},
  109. sessionLength = 31536000, // 1 Year in seconds
  110. expireInactiveSessions = true,
  111. verbose = false,
  112. revokeSessionOnPasswordReset = true,
  113. }) {
  114. // Initialize the node client SDK automatically
  115. Parse.initialize(appId, javascriptKey || 'unused', masterKey);
  116. Parse.serverURL = serverURL;
  117. if (logsFolder) {
  118. configureLogger({
  119. logsFolder
  120. })
  121. }
  122. if (databaseOptions) {
  123. DatabaseAdapter.setAppDatabaseOptions(appId, databaseOptions);
  124. }
  125. DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
  126. if (cloud) {
  127. addParseCloud();
  128. if (typeof cloud === 'function') {
  129. cloud(Parse)
  130. } else if (typeof cloud === 'string') {
  131. require(path.resolve(process.cwd(), cloud));
  132. } else {
  133. throw "argument 'cloud' must either be a string or a function";
  134. }
  135. }
  136. if (verbose || process.env.VERBOSE || process.env.VERBOSE_PARSE_SERVER) {
  137. configureLogger({level: 'silly'});
  138. }
  139. const filesControllerAdapter = loadAdapter(filesAdapter, () => {
  140. return new GridStoreAdapter(databaseURI);
  141. });
  142. // Pass the push options too as it works with the default
  143. const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push);
  144. const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter);
  145. const emailControllerAdapter = loadAdapter(emailAdapter);
  146. // We pass the options and the base class for the adatper,
  147. // Note that passing an instance would work too
  148. const filesController = new FilesController(filesControllerAdapter, appId);
  149. const pushController = new PushController(pushControllerAdapter, appId);
  150. const loggerController = new LoggerController(loggerControllerAdapter, appId);
  151. const hooksController = new HooksController(appId, collectionPrefix);
  152. const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails });
  153. const liveQueryController = new LiveQueryController(liveQuery);
  154. cache.apps.set(appId, {
  155. masterKey: masterKey,
  156. serverURL: serverURL,
  157. collectionPrefix: collectionPrefix,
  158. clientKey: clientKey,
  159. javascriptKey: javascriptKey,
  160. dotNetKey: dotNetKey,
  161. restAPIKey: restAPIKey,
  162. fileKey: fileKey,
  163. facebookAppIds: facebookAppIds,
  164. filesController: filesController,
  165. pushController: pushController,
  166. loggerController: loggerController,
  167. hooksController: hooksController,
  168. userController: userController,
  169. verifyUserEmails: verifyUserEmails,
  170. allowClientClassCreation: allowClientClassCreation,
  171. authDataManager: authDataManager(oauth, enableAnonymousUsers),
  172. appName: appName,
  173. publicServerURL: publicServerURL,
  174. customPages: customPages,
  175. maxUploadSize: maxUploadSize,
  176. liveQueryController: liveQueryController,
  177. sessionLength: Number(sessionLength),
  178. expireInactiveSessions: expireInactiveSessions,
  179. revokeSessionOnPasswordReset
  180. });
  181. // To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
  182. if (process.env.FACEBOOK_APP_ID) {
  183. cache.apps.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID);
  184. }
  185. Config.validate(cache.apps.get(appId));
  186. this.config = cache.apps.get(appId);
  187. hooksController.load();
  188. }
  189. get app() {
  190. return ParseServer.app(this.config);
  191. }
  192. static app({maxUploadSize = '20mb'}) {
  193. // This app serves the Parse API directly.
  194. // It's the equivalent of https://api.parse.com/1 in the hosted Parse API.
  195. var api = express();
  196. //api.use("/apps", express.static(__dirname + "/public"));
  197. // File handling needs to be before default middlewares are applied
  198. api.use('/', middlewares.allowCrossDomain, new FilesRouter().getExpressRouter({
  199. maxUploadSize: maxUploadSize
  200. }));
  201. api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressApp());
  202. // TODO: separate this from the regular ParseServer object
  203. if (process.env.TESTING == 1) {
  204. api.use('/', require('./testing-routes').router);
  205. }
  206. api.use(bodyParser.json({ 'type': '*/*' , limit: maxUploadSize }));
  207. api.use(middlewares.allowCrossDomain);
  208. api.use(middlewares.allowMethodOverride);
  209. api.use(middlewares.handleParseHeaders);
  210. let routers = [
  211. new ClassesRouter(),
  212. new UsersRouter(),
  213. new SessionsRouter(),
  214. new RolesRouter(),
  215. new AnalyticsRouter(),
  216. new InstallationsRouter(),
  217. new FunctionsRouter(),
  218. new SchemasRouter(),
  219. new PushRouter(),
  220. new LogsRouter(),
  221. new IAPValidationRouter(),
  222. new FeaturesRouter(),
  223. new GlobalConfigRouter(),
  224. ];
  225. if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) {
  226. routers.push(new HooksRouter());
  227. }
  228. let routes = routers.reduce((memo, router) => {
  229. return memo.concat(router.routes);
  230. }, []);
  231. let appRouter = new PromiseRouter(routes);
  232. batch.mountOnto(appRouter);
  233. api.use(appRouter.expressApp());
  234. api.use(middlewares.handleParseErrors);
  235. //This causes tests to spew some useless warnings, so disable in test
  236. if (!process.env.TESTING) {
  237. process.on('uncaughtException', (err) => {
  238. if ( err.code === "EADDRINUSE" ) { // user-friendly message for this common error
  239. console.error(`Unable to listen on port ${err.port}. The port is already in use.`);
  240. process.exit(0);
  241. } else {
  242. throw err;
  243. }
  244. });
  245. }
  246. return api;
  247. }
  248. static createLiveQueryServer(httpServer, config) {
  249. return new ParseLiveQueryServer(httpServer, config);
  250. }
  251. }
  252. function addParseCloud() {
  253. const ParseCloud = require("./cloud-code/Parse.Cloud");
  254. Object.assign(Parse.Cloud, ParseCloud);
  255. global.Parse = Parse;
  256. }
  257. export default ParseServer;