diff --git a/.eslintrc.json b/.eslintrc.json index b47bc599df..f1eade9305 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,6 +24,7 @@ "no-await-in-loop" : 1 }, "globals" : { - "Parse" : true + "Parse" : true, + "document": true } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a3d4ae74b..fbe3152d94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x] + node-version: [15.x] name: ${{ matrix.name }} steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index b627d4e64a..96c1a4e992 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,15 @@ [![License][license-svg]][license-link] [](https://twitter.com/intent/follow?screen_name=ParsePlatform) -Example project using the [parse-server](https://github.com/ParsePlatform/parse-server) module on Express. Read the full [Parse Server Guide](https://docs.parseplatform.org/parse-server/guide/) for more information. +Example project using the [parse-server](https://github.com/ParsePlatform/parse-server) module on Express, utilising AWS Secret Manager Read the full [Parse Server Guide](https://docs.parseplatform.org/parse-server/guide/) for more information. + +Please note: this example uses top level await which is only available in Node >= v14.8.0. # Table of Contents - [Local Development](#local-development) + - [Creating AWS Secrets](#creating-aws-secrets) + - [File Setup](#file-setup) - [Helpful Scripts](#helpful-scripts) - [Remote Deployment](#remote-deployment) - [Heroku](#heroku) @@ -29,6 +33,20 @@ Example project using the [parse-server](https://github.com/ParsePlatform/parse- # Local Development +## Creating AWS Secrets +* Log into the AWS Console and navigate to AWS Secrets Manager +* Click "store a new secret" +* Select "other type of secret" +* Enter the initial secret value +* Name the secret (`/src/config` will reference this secret name). If you have selected key pairs, make sure you properly destructure the returned secret. +* If you would like to automatically rotate the key, follow [this](https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-secrets_turn-on-for-other.html) guide. + + +## Local Development + +* Install AWS SDK with `npm install aws-sdk -g` +* Create an AWS profile with `aws configure --profile profileName` +* Update `npm start`'s `AWS_Profile` and `AWS_REGION` * Make sure you have at least Node 4.3. `node --version` * Clone this repo and change directory to it. * `npm install` @@ -39,6 +57,16 @@ Example project using the [parse-server](https://github.com/ParsePlatform/parse- * You now have a database named "dev" that contains your Parse data * Install ngrok and you can test with devices +## File Setup +Feel free to change this at your discretion. Example projects are just that - an example. + +* `/spec` contains unit tests you can write to validate your Parse Server. +* `/src/cloud` contains Parse.Cloud files to run custom cloud code. +* `/src/public` contains public assets. +* `/src/views` contains views that express can render. +* `/src/config.js` contains all Parse Server settings. +* `index.js` is the main entry point for `npm start`, and includes express routing. + ## Helpful Scripts These scripts can help you to develop your app for Parse Server: @@ -83,7 +111,7 @@ Detailed information is available here: ## Google App Engine -1. Clone the repo and change directory to it +1. Clone the repo and change directory to it 1. Create a project in the [Google Cloud Platform Console](https://console.cloud.google.com/). 1. [Enable billing](https://console.cloud.google.com/project/_/settings) for your project. 1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/). @@ -164,9 +192,11 @@ curl -X POST \ ### JavaScript +We have built an example page to show JS SDK usage, available at [http://localhost:1337/](http://localhost:1337/). + ```js // Initialize SDK -Parse.initialize("YOUR_APP_ID", "unused"); +Parse.initialize("YOUR_APP_ID"); Parse.serverURL = 'http://localhost:1337/parse'; // Save object diff --git a/index.js b/index.js index e720980bf0..b4c57bda09 100644 --- a/index.js +++ b/index.js @@ -1,65 +1,33 @@ // Example express application adding the parse-server module to expose Parse // compatible API routes. -const express = require('express'); -const ParseServer = require('parse-server').ParseServer; -const path = require('path'); -const args = process.argv || []; -const test = args.some(arg => arg.includes('jasmine')); +import express from 'express'; +import { ParseServer } from 'parse-server'; +import { createServer } from 'http'; +import { config } from './src/config.js'; +import { renderFile } from 'ejs'; -const databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI; - -if (!databaseUri) { - console.log('DATABASE_URI not specified, falling back to localhost.'); -} -const config = { - databaseURI: databaseUri || 'mongodb://localhost:27017/dev', - cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js', - appId: process.env.APP_ID || 'myAppId', - masterKey: process.env.MASTER_KEY || '', //Add your master key here. Keep it secret! - serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse', // Don't forget to change to https if needed - liveQuery: { - classNames: ['Posts', 'Comments'], // List of classes to support for query subscriptions - }, -}; -// Client-keys like the javascript key or the .NET key are not necessary with parse-server -// If you wish you require them, you can set them as options in the initialization above: -// javascriptKey, restAPIKey, dotNetKey, clientKey - -const app = express(); +export const app = express(); +app.set('view engine', 'ejs'); +app.engine('html', renderFile); +app.set('views', `./src/views`); // Serve static assets from the /public folder -app.use('/public', express.static(path.join(__dirname, '/public'))); - -// Serve the Parse API on the /parse URL prefix -const mountPath = process.env.PARSE_MOUNT || '/parse'; -if (!test) { - const api = new ParseServer(config); - app.use(mountPath, api); -} +app.use('/public', express.static('./src/public')); // Parse Server plays nicely with the rest of your web routes -app.get('/', function (req, res) { - res.status(200).send('I dream of being a website. Please star the parse-server repo on GitHub!'); +app.get('/', (req, res) => { + res.render('test.html', { appId: config.appId, serverUrl: config.serverURL }); }); -// There will be a test page available on the /test path of your server url -// Remove this before launching your app -app.get('/test', function (req, res) { - res.sendFile(path.join(__dirname, '/public/test.html')); -}); +if (!process.env.TESTING) { + const api = new ParseServer(config); + app.use('/parse', api); -const port = process.env.PORT || 1337; -if (!test) { - const httpServer = require('http').createServer(app); - httpServer.listen(port, function () { - console.log('parse-server-example running on port ' + port + '.'); + const httpServer = createServer(app); + const port = 1337; + httpServer.listen(port, () => { + console.log(`parse-server-example running on port ${port}.`); }); - // This will enable the Live Query real-time server ParseServer.createLiveQueryServer(httpServer); } - -module.exports = { - app, - config, -}; diff --git a/package.json b/package.json index 32c43e7a7f..cb3cc7196d 100644 --- a/package.json +++ b/package.json @@ -9,34 +9,38 @@ }, "license": "MIT", "dependencies": { + "aws-sdk": "2.994.0", + "ejs": "3.1.6", "express": "4.17.1", - "kerberos": "1.1.4", - "parse": "2.19.0", - "parse-server": "4.5.0" + "kerberos": "1.1.6", + "parse": "3.3.0", + "parse-server": "4.10.3" }, "scripts": { - "start": "node index.js", - "lint": "eslint --cache ./cloud && eslint --cache index.js && eslint --cache ./spec", - "lint-fix": "eslint --cache --fix ./cloud && eslint --cache --fix index.js && eslint --cache --fix ./spec", - "test": "mongodb-runner start && jasmine", + "start": "AWS_PROFILE=aws_profile AWS_REGION=aws_region node index.js", + "lint": "eslint --cache ./src && eslint --cache index.js && eslint --cache ./spec", + "lint-fix": "eslint --cache --fix ./src && eslint --cache --fix index.js && eslint --cache --fix ./spec", + "test": "mongodb-runner start && TESTING=true jasmine", + "test:kill": "kill $(lsof -ti:27017) && npm test", "coverage": "nyc jasmine", - "prettier": "prettier --write '{cloud,spec}/{**/*,*}.js' 'index.js'", + "prettier": "prettier --write '{src,spec}/{**/*,*}{.js,.html,.css}' 'index.js'", "watch": "babel-watch index.js" }, "engines": { "node": ">=4.3" }, + "type": "module", "devDependencies": { "babel-eslint": "10.1.0", - "babel-watch": "7.4.0", - "eslint": "7.19.0", - "eslint-config-standard": "16.0.2", - "eslint-plugin-import": "2.22.1", + "babel-watch": "7.5.0", + "eslint": "7.32.0", + "eslint-config-standard": "16.0.3", + "eslint-plugin-import": "2.24.2", "eslint-plugin-node": "11.1.0", - "eslint-plugin-promise": "4.2.1", - "jasmine": "3.6.4", - "mongodb-runner": "4.8.1", + "eslint-plugin-promise": "5.1.0", + "jasmine": "3.9.0", + "mongodb-runner": "4.8.3", "nyc": "15.1.0", - "prettier": "2.2.1" + "prettier": "2.3.2" } } diff --git a/public/assets/css/style.css b/public/assets/css/style.css deleted file mode 100644 index 699311429d..0000000000 --- a/public/assets/css/style.css +++ /dev/null @@ -1,243 +0,0 @@ -body { - margin: 0; - padding: 0; - font-family: Helvetica, Arial, sans-serif; - font-size: 14px; - letter-spacing: 0.2px; - line-height: 24px; - color: #585858; -} - -a { - color: #169CEE; - text-decoration: underline; -} - -a:hover { - color: #2C3D50; -} - -a:visited { - color: #2a6496; -} - - -/* - helpers - */ - -.align-center { - text-align: center; -} - -.hidden { - display: none; -} - -/* - app css - */ - -.container { - margin: 0 auto; - margin-top: 45px; - max-width: 860px; -} - -#parse-logo { - width: 109px; - height: 110px; - margin: 0 0 20px; - text-align: center; -} - -.up-and-running, .time-to-deploy { - font-weight: bold; -} - -.advice { - margin-bottom: 40px; -} - -.advice { - background: #f4f4f4; - border-radius: 4px; - -webkit-border-radius: 4px 4px; - -moz-border-radius: 4px 4px; - -ms-border-radius: 4px 4px; - -o-border-radius: 4px 4px; - padding: 10px 20px; -} - -#parse-url { - color: #169CEE; - font-weight: bold; -} - -.step--container { - margin: 30px 0 20px; - border-top: 1px solid #E2E2E2; - padding-top: 30px; -} - -/* Disabled step */ -.step--disabled .step--number { - background: #fff; - border-color: #B5B5B5; - color: #B5B5B5; -} - -.step--disabled .step--info { - border-color: #B5B5B5; - color: #B5B5B5; -} - -.step--disabled .step--action-btn, - .step--disabled .step--action-btn:hover { - border-color: #B5B5B5; - background: #fff; - color: #B5B5B5; - cursor: default; -} - -/* Disabled step eof */ - -.step--action-btn.success, -.step--action-btn.success:hover { - background: #57C689; - border-color: #57C689; - color: #fff; - cursor: default; - font-weight: bold; -} - -.step--number { - background: #169CEE; - border: 1px solid #169CEE; - border-radius: 28px; - -webkit-border-radius: 28px 28px; - -moz-border-radius: 28px 28px; - -ms-border-radius: 28px 28px; - -o-border-radius: 28px 28px; - display: block; - margin: auto; - width: 47px; - height: 47px; - font-weight: bolder; - font-size: 20px; - color: #FFFFFF; - line-height: 47px; /* follows width and height */ -} - -.step--info { } - -.step--action-btn { - color: #169CEE; - font-size: 14px; - font-weight: 100; - border: 1px solid #169CEE; - padding: 12px 18px; - border-radius: 28px; - -webkit-border-radius: 28px 28px; - -moz-border-radius: 28px 28px; - -ms-border-radius: 28px 28px; - -o-border-radius: 28px 28px; - cursor: pointer; - text-decoration: none; - display:inline-block; - text-align: center; - text-transform: uppercase; -} - -.step--action-btn:hover { - background: #169CEE; - color: white; -} - -.step--pre { - margin-top: 4px; - margin-bottom: 0; - background: #f4f4f4; - border-radius: 4px; - -webkit-border-radius: 4px 4px; - -moz-border-radius: 4px 4px; - -ms-border-radius: 4px 4px; - -o-border-radius: 4px 4px; - padding: 10px 20px; - word-wrap: break-word; - white-space: inherit; - font-size: 13px; -} - -#local-parse-working { - font-size: 18px; - line-height: 24px; - color: #57C689; - font-weight: bold; -} - -#step-4 .step--number { - background: #57C689; - border-color: #57C689; - color: #fff; - display: inline-block; -} - -.step--deploy-btn { - display: block; - margin-top: 20px; - width: 170px; - color: #57C689 !important; - font-weight: bold; - border-color: #57C689; -} - -.step--deploy-btn:hover { - background: #57C689; - color: #fff !important; -} - -.step--error { - color: red; - font-weight: bold; -} - -#prod-test { - margin-bottom: 60px; -} - -#prod-test input { - background-color: #fff; - border: 1px solid #B5B5B5; - color: #000000; - font-family: "Inconsolata"; - font-size: 16px; - line-height: 17px; - padding: 12px; - width: 260px; - border-radius: 4px; - -webkit-border-radius: 4px 4px; - -moz-border-radius: 4px 4px; - -ms-border-radius: 4px 4px; - -o-border-radius: 4px 4px; - display:block; - margin-bottom: 10px; -} - -#footer { - border-top: 1px solid #E2E2E2; - padding: 20px; -} - -#footer ul li { - list-style-type: none; - display:inline-block; -} -#footer ul li:after { - content: "-"; - padding: 10px; -} -#footer ul li:last-child:after { - content: ""; -} - diff --git a/public/assets/js/script.js b/public/assets/js/script.js deleted file mode 100644 index 96cc688e53..0000000000 --- a/public/assets/js/script.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Steps handler - */ - -var Steps = {}; - -Steps.init = function() { - this.buildParseUrl(); - this.bindBtn('#step-1-btn', function(e){ - ParseRequest.postData(); - e.preventDefault(); - }) -} - -Steps.buildParseUrl = function() { - var url = Config.getUrl(); - $('#parse-url').html(url + '/parse'); -} - -Steps.bindBtn = function(id, callback) { - $(id).click(callback); -} - -Steps.closeStep = function(id) { - $(id).addClass('step--disabled'); -} - -Steps.openStep = function(id) { - $(id).removeClass('step--disabled'); -} - -Steps.fillStepOutput = function(id, data) { - $(id).html('Output: ' + data).slideDown(); -} - -Steps.fillStepError = function(id, errorMsg) { - $(id).html(errorMsg).slideDown(); -} - - -Steps.fillBtn = function(id, message) { - $(id).addClass('success').html('✓ ' + message); -} - -Steps.showWorkingMessage = function() { - $('#step-4').delay(500).slideDown(); -} - - -/** - * Parse requests handler - */ - -var ParseRequest = {}; - -ParseRequest.postData = function() { - XHR.setCallback(function(data){ - // store objectID - Store.objectId = JSON.parse(data).objectId; - // close first step - Steps.closeStep('#step-1'); - Steps.fillStepOutput('#step-1-output', data); - Steps.fillBtn('#step-1-btn', 'Posted'); - // open second step - Steps.openStep('#step-2'); - Steps.bindBtn('#step-2-btn', function(e){ - ParseRequest.getData(); - e.preventDefault(); - }); - }, - function(error) { - Steps.fillStepError('#step-1-error', 'There was a failure: ' + error); - }); - XHR.POST('/parse/classes/GameScore'); -}; - -ParseRequest.getData = function() { - XHR.setCallback(function(data){ - // close second step - Steps.closeStep('#step-2'); - Steps.fillStepOutput('#step-2-output', data); - Steps.fillBtn('#step-2-btn', 'Fetched'); - // open third step - Steps.openStep('#step-3'); - Steps.bindBtn('#step-3-btn', function(e){ - ParseRequest.postCloudCodeData(); - e.preventDefault(); - }); - }, - function(error) { - Steps.fillStepError('#step-2-error', 'There was a failure: ' + error); - }); - XHR.GET('/parse/classes/GameScore'); -}; - -ParseRequest.postCloudCodeData = function() { - XHR.setCallback(function(data){ - // close second step - Steps.closeStep('#step-3'); - Steps.fillStepOutput('#step-3-output', data); - Steps.fillBtn('#step-3-btn', 'Tested'); - // open third step - Steps.showWorkingMessage(); - }, - function(error) { - Steps.fillStepError('#step-3-error', 'There was a failure: ' + error); - }); - XHR.POST('/parse/functions/hello'); -} - - -/** - * Store objectId and other references - */ - -var Store = { - objectId: "" -}; - -var Config = {}; - -Config.getUrl = function() { - if (url) return url; - var port = window.location.port; - var url = window.location.protocol + '//' + window.location.hostname; - if (port) url = url + ':' + port; - return url; -} - - -/** - * XHR object - */ - -var XHR = {}; - -XHR.setCallback = function(callback, failureCallback) { - this.xhttp = new XMLHttpRequest(); - var _self = this; - this.xhttp.onreadystatechange = function() { - if (_self.xhttp.readyState == 4) { - if (_self.xhttp.status >= 200 && _self.xhttp.status <= 299) { - callback(_self.xhttp.responseText); - } else { - failureCallback(_self.xhttp.responseText); - } - } - }; -} - -XHR.POST = function(path, callback) { - var seed = {"score":1337,"playerName":"Sean Plott","cheatMode":false} - this.xhttp.open("POST", Config.getUrl() + path, true); - this.xhttp.setRequestHeader("X-Parse-Application-Id", $('#appId').val()); - this.xhttp.setRequestHeader("Content-type", "application/json"); - this.xhttp.send(JSON.stringify(seed)); -} - -XHR.GET = function(path, callback) { - this.xhttp.open("GET", Config.getUrl() + path + '/' + Store.objectId, true); - this.xhttp.setRequestHeader("X-Parse-Application-Id", $('#appId').val()); - this.xhttp.setRequestHeader("Content-type", "application/json"); - this.xhttp.send(null); -} - - -/** - * Boot - */ - -Steps.init(); diff --git a/public/test.html b/public/test.html deleted file mode 100644 index 429470bd0c..0000000000 --- a/public/test.html +++ /dev/null @@ -1,108 +0,0 @@ - - -
-Hi! We've prepared a small 3-steps page to assist you testing your local Parse server.
-These first steps will help you run and test the Parse server locally and were referrenced by the migration guide provided by Parse Platform.
-Looks like our local Parse Serve is running under .... Let’s test it?
- -We'll use an app id of "myAppId" to connect to Parse Server. Or, you can - change it. - -
We have an express server with Parse server running on top of it connected to a MongoDB.
- -The following steps will try to save some data on parse server and then fetch it back. Hey ho?
- -Post data to local parse server:
-Fetch data from local parse server:
-Test Cloud Code function from ./cloud/main.js:
-+ Hi, and welcome to Parse Server! We've prepared a small 3-steps page to + assist you testing your local Parse server. +
+These first steps will help you run and test the Parse server locally.
++ Looks like our local Parse Server is running under + <%= serverUrl %>. Let’s test it? +
+ ++ We have an express server with Parse server running on top of it connected to a MongoDB. +
+ ++ The following steps will try to save some data on parse server and then fetch it back. Hey + ho? +
+ +
+
+ await Parse.User.logOut();
+
+ + +
+ + + +
+ await Parse.User.logIn(username, password);
+ // or
+ const user = new Parse.User();
+ user.setUsername(username);
+ user.setPassword(password);
+ await user.signUp();
+
+
+ const obj = new Parse.Object('TestObject');
+ obj.set('name', name);
+ await obj.save();
+
+
+ const query = new Parse.Query('TestObject');
+ const objects = await query.find();
+
+ const result = await Parse.Cloud.run('hello');
+