├── .gitignore
├── node
├── .gitignore
├── LICENSE
├── README.md
├── jest.config.js
├── package-lock.json
├── package.json
├── src
│ ├── app.ts
│ ├── controllers
│ │ └── base.ts
│ ├── server.ts
│ ├── types
│ │ ├── express-flash.d.ts
│ │ ├── fbgraph.d.ts
│ │ └── nylas.d.ts
│ └── util
│ │ ├── logger.ts
│ │ └── secrets.ts
├── test
│ └── app.test.ts
├── tsconfig.json
└── views
│ ├── base.mustache
│ └── partials
│ ├── after_authorized.mustache
│ └── before_authorized.mustache
├── python
├── .vscode
│ └── settings.json
├── Pipfile
├── Pipfile.lock
├── Procfile
├── README.md
├── app
│ ├── __init__.py
│ ├── config.py
│ └── templates
│ │ ├── after_authorized.html
│ │ ├── base.html
│ │ └── before_authorized.html
├── config.json
└── requirements.txt
├── readme.md
└── simple-example.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # next.js build output
79 | .next
80 |
81 | # nuxt.js build output
82 | .nuxt
83 |
84 | # gatsby files
85 | .cache/
86 | public
87 |
88 | # vuepress build output
89 | .vuepress/dist
90 |
91 | # Serverless directories
92 | .serverless/
93 |
94 | # FuseBox cache
95 | .fusebox/
96 |
97 | # DynamoDB Local files
98 | .dynamodb/
99 |
100 |
101 | # Byte-compiled / optimized / DLL files
102 | __pycache__/
103 | *.py[cod]
104 | *$py.class
105 |
106 | # C extensions
107 | *.so
108 |
109 | # Distribution / packaging
110 | .Python
111 | build/
112 | develop-eggs/
113 | dist/
114 | downloads/
115 | eggs/
116 | .eggs/
117 | lib/
118 | lib64/
119 | parts/
120 | sdist/
121 | var/
122 | wheels/
123 | pip-wheel-metadata/
124 | share/python-wheels/
125 | *.egg-info/
126 | .installed.cfg
127 | *.egg
128 | MANIFEST
129 |
130 | # PyInstaller
131 | # Usually these files are written by a python script from a template
132 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
133 | *.manifest
134 | *.spec
135 |
136 | # Installer logs
137 | pip-log.txt
138 | pip-delete-this-directory.txt
139 |
140 | # Unit test / coverage reports
141 | htmlcov/
142 | .tox/
143 | .nox/
144 | .coverage
145 | .coverage.*
146 | .cache
147 | nosetests.xml
148 | coverage.xml
149 | *.cover
150 | *.py,cover
151 | .hypothesis/
152 | .pytest_cache/
153 |
154 | # Translations
155 | *.mo
156 | *.pot
157 |
158 | # Django stuff:
159 | *.log
160 | local_settings.py
161 | db.sqlite3
162 | db.sqlite3-journal
163 |
164 | # Flask stuff:
165 | instance/
166 | .webassets-cache
167 |
168 | # Scrapy stuff:
169 | .scrapy
170 |
171 | # Sphinx documentation
172 | docs/_build/
173 |
174 | # PyBuilder
175 | target/
176 |
177 | # Jupyter Notebook
178 | .ipynb_checkpoints
179 |
180 | # IPython
181 | profile_default/
182 | ipython_config.py
183 |
184 | # pyenv
185 | .python-version
186 |
187 | # pipenv
188 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
189 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
190 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
191 | # install all needed dependencies.
192 | #Pipfile.lock
193 |
194 | # celery beat schedule file
195 | celerybeat-schedule
196 |
197 | # SageMath parsed files
198 | *.sage.py
199 |
200 | # Environments
201 | .env
202 | .venv
203 | env/
204 | venv/
205 | ENV/
206 | env.bak/
207 | venv.bak/
208 |
209 | # Spyder project settings
210 | .spyderproject
211 | .spyproject
212 |
213 | # Rope project settings
214 | .ropeproject
215 |
216 | # mkdocs documentation
217 | /site
218 |
219 | # mypy
220 | .mypy_cache/
221 | .dmypy.json
222 | dmypy.json
223 |
224 | # Pyre type checker
225 | .pyre/
226 | .DS_Store
227 |
--------------------------------------------------------------------------------
/node/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/node/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/node/README.md:
--------------------------------------------------------------------------------
1 | # Example: Hosted OAuth + Scheduling
2 |
3 | This is an example project that implements login via [Nylas Hosted OAuth flow](https://docs.nylas.com/reference#oauth) and demonstrates both instant and smart integration types with the Nylas Scheduler.
4 |
5 | This example uses the [Express](https://expressjs.com/) web framework for NodeJS and is written in TypeScript. In order to successfully run this example and go through the Nylas OAuth flow, you need to follow the steps below.
6 |
7 |
8 | ## Get an Application Client ID & Client Secret from Nylas
9 |
10 | To do this, login (or create) your Nylas developer account using the [Nylas Dashboard](https://dashboard.nylas.com/) account. You should see your `client_id` and `client_secret` on the dashboard.
11 |
12 | ## Set Up HTTPS
13 |
14 | The OAuth protocol requires that all communication occur via the secure HTTPS connections, rather than insecure HTTP connections. You can deploy the application to a serivce like Heroku to test it, or configure HTTPS on your computer. Perhaps the simplest is to use [ngrok](https://ngrok.com), a tool that lets you create a secure tunnel from the ngrok website to your computer. Install it from the website, and then run the following command:
15 |
16 | ```
17 | ngrok http 5000
18 | ```
19 |
20 | Notice that ngrok will show you two "forwarding" URLs, which may look something like `http://ed90abe7.ngrok.io` and `https://ed90abe7.ngrok.io`. (The hash subdomain will be different for you.) You'll be using the second URL, which starts with `https`.
21 |
22 | ## Set the Nylas Callback URL
23 |
24 | Once you have a HTTPS URL that points to your computer, you'll need to tell Nylas about it. On the [Nylas Dashboard](https://dashboard.nylas.com) click on the Application Dropdown Menu on the left, then "View all Applications". From there, select "Edit" for the app you'd like to use and select the "Application Callbacks" tab. Paste your HTTPS URL into the text field, and add `/login_callback` after it. For example, if your HTTPS URL is `https://ad172180.ngrok.io`, then you would put `https://ad172180.ngrok.io/login_callback` into the text field in the "Application Callbacks" tab.
25 |
26 | Then click the "Add Callback" button to save.
27 |
28 | ## Install the Dependencies
29 |
30 | This project depends on a few third-party Node modules, like `express` and `requests`. These dependencies are listed in the `package.json` file in this directory.
31 |
32 | ```
33 | npm install
34 | ```
35 |
36 | ## Run the Example
37 |
38 | Finally, run the example project like this, specifying the redirect URL you configured along with your Nylas client ID and secret as environment variables:
39 |
40 | ```
41 | REDIRECT_URI=https://ad172180.ngrok.io/login_callback NYLAS_OAUTH_CLIENT_ID=XXX NYLAS_OAUTH_CLIENT_SECRET=XXX npm start
42 | ```
43 |
44 | Once the server is running, visit the ngrok URL in your browser to test it out!
45 |
46 |
47 | ## Troubleshooting
48 |
49 | * For OAuth to succeed, you need to visit the Ngrok URL in your browser, not localhost.
50 | * For OAuth to succeed, you need to pass the Ngrok redirect URL with the "/login_callback" path to the app as an environment variable.
51 |
--------------------------------------------------------------------------------
/node/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | globals: {
3 | 'ts-jest': {
4 | tsConfig: 'tsconfig.json'
5 | }
6 | },
7 | moduleFileExtensions: [
8 | 'ts',
9 | 'js'
10 | ],
11 | transform: {
12 | '^.+\\.(ts|tsx)$': 'ts-jest'
13 | },
14 | testMatch: [
15 | '**/test/**/*.test.(ts|js)'
16 | ],
17 | testEnvironment: 'node'
18 | };
19 |
--------------------------------------------------------------------------------
/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nylas-scheduling-example-nodejs",
3 | "version": "0.1.0",
4 | "description": "A starting point for Node.js express apps with TypeScript",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/Nylas/Nylas-Scheduling-Beta"
8 | },
9 | "author": "",
10 | "license": "MIT",
11 | "scripts": {
12 | "start": "npm run build && npm run serve",
13 | "build": "tsc",
14 | "serve": "node dist/server.js",
15 | "test": "jest --forceExit --coverage --verbose"
16 | },
17 | "dependencies": {
18 | "async": "^3.0.1",
19 | "bcrypt-nodejs": "^0.0.3",
20 | "bluebird": "^3.5.5",
21 | "body-parser": "^1.19.0",
22 | "compression": "^1.7.4",
23 | "consolidate": "^0.15.1",
24 | "dotenv": "^8.0.0",
25 | "errorhandler": "^1.5.1",
26 | "express": "^4.17.1",
27 | "express-flash": "0.0.2",
28 | "express-session": "^1.16.2",
29 | "express-validator": "^6.0.1",
30 | "lodash": "^4.17.19",
31 | "lusca": "^1.6.1",
32 | "mustache": "^3.0.1",
33 | "nodemailer": "^6.2.1",
34 | "nylas": "^4.6.1",
35 | "request": "^2.88.0",
36 | "request-promise": "^4.2.4",
37 | "winston": "^2.4.2"
38 | },
39 | "devDependencies": {
40 | "@types/async": "^3.0.0",
41 | "@types/bcrypt-nodejs": "^0.0.30",
42 | "@types/bluebird": "^3.5.27",
43 | "@types/body-parser": "^1.17.0",
44 | "@types/compression": "^0.0.36",
45 | "@types/consolidate": "^0.14.0",
46 | "@types/dotenv": "^6.1.1",
47 | "@types/errorhandler": "^0.0.32",
48 | "@types/express": "^4.17.0",
49 | "@types/express-session": "^1.15.13",
50 | "@types/jest": "^24.0.15",
51 | "@types/jquery": "^3.3.29",
52 | "@types/lodash": "^4.14.134",
53 | "@types/lusca": "^1.6.0",
54 | "@types/morgan": "^1.7.35",
55 | "@types/node": "^12.0.10",
56 | "@types/nodemailer": "^6.2.0",
57 | "@types/request": "^2.48.1",
58 | "@types/request-promise": "^4.1.44",
59 | "@types/shelljs": "^0.8.5",
60 | "@types/supertest": "^2.0.7",
61 | "@types/winston": "^2.3.9",
62 | "chai": "^4.2.0",
63 | "concurrently": "^4.1.0",
64 | "jest": "^24.8.0",
65 | "shelljs": "^0.8.3",
66 | "supertest": "^4.0.2",
67 | "ts-jest": "^24.0.2",
68 | "ts-node": "^8.3.0",
69 | "typescript": "^3.5.3"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/node/src/app.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import flash from "express-flash";
3 | import session from "express-session";
4 | import bodyParser from "body-parser";
5 | import compression from "compression"; // compresses requests
6 | import consolidate from "consolidate";
7 | import lusca from "lusca";
8 | import path from "path";
9 | import Nylas from "nylas";
10 |
11 | // Controllers (route handlers)
12 | import router from "./controllers/base";
13 |
14 | const NylasClientID = process.env["NYLAS_OAUTH_CLIENT_ID"];
15 | const NylasClientSecret = process.env["NYLAS_OAUTH_CLIENT_SECRET"];
16 |
17 | if (!NylasClientID || !NylasClientSecret) {
18 | console.warn(`
19 | To run this example, pass the NYLAS_OAUTH_CLIENT_ID and NYLAS_OAUTH_CLIENT_SECRET
20 | environment variables when launching the service. eg:\n
21 | REDIRECT_URI=https://ad172180.ngrok.io/login_callback NYLAS_OAUTH_CLIENT_ID=XXX NYLAS_OAUTH_CLIENT_SECRET=XXX npm start
22 | `);
23 | process.exit(1);
24 | }
25 |
26 | // Configure Nylas
27 | Nylas.config({
28 | appId: NylasClientID,
29 | appSecret: NylasClientSecret
30 | });
31 |
32 | // Create Express server
33 | const app = express();
34 |
35 | // assign the mustache enging to .mustache files
36 | app.engine("mustache", consolidate.mustache);
37 |
38 | // Express configuration
39 | app.set("port", process.env["PORT"] || 5000);
40 | app.set("views", path.join(__dirname, "../views"));
41 | app.set("view engine", "mustache");
42 | app.use(compression());
43 | app.use(bodyParser.json());
44 | app.use(bodyParser.urlencoded({ extended: true }));
45 | app.use(
46 | session({
47 | resave: true,
48 | saveUninitialized: true,
49 | secret: process.env["SECRET_KEY"] || "test-secret",
50 | cookie: { maxAge: 60000 }
51 | })
52 | );
53 | app.use(flash());
54 | app.use(lusca.xframe("SAMEORIGIN"));
55 | app.use(lusca.xssProtection(true));
56 |
57 | app.use(
58 | express.static(path.join(__dirname, "public"), { maxAge: 31557600000 })
59 | );
60 |
61 | app.use(router);
62 |
63 | export default app;
64 |
--------------------------------------------------------------------------------
/node/src/controllers/base.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response } from "express";
2 | import request from "request-promise";
3 | import Nylas from "nylas";
4 |
5 | const redirectURI = process.env.REDIRECT_URI ||
6 | "https://nylas-customer-example-nodejs.herokuapp.com/login_callback";
7 |
8 | const router = express.Router();
9 |
10 | router.get("/", async (req: Request, res: Response) => {
11 | const accessToken = req.session["access_token"];
12 | if (!accessToken) {
13 | res.render("base", {
14 | login_href: Nylas.urlForAuthentication({
15 | redirectURI,
16 | scopes: ["calendar"]
17 | }),
18 | insecure_override: req.protocol !== "https",
19 | partials: { authorization_partial: "partials/before_authorized" }
20 | });
21 | return;
22 | }
23 |
24 | const nylas = Nylas.with(accessToken);
25 | const account = await nylas.account.get();
26 |
27 | // If the user has already connected to Nylas via OAuth,
28 | // `nylas.authorized` will be true. Otherwise, it will be false.
29 | const pages = await request.get({
30 | uri: "https://schedule.api.nylas.com/manage/pages",
31 | headers: { Authorization: `Bearer ${accessToken}` },
32 | json: true
33 | });
34 |
35 | res.render("base", {
36 | pages,
37 | account,
38 | accessToken,
39 | partials: { authorization_partial: "partials/after_authorized" }
40 | });
41 | });
42 |
43 | router.post("/", async (req: Request, res: Response) => {
44 | const accessToken = req.session["access_token"];
45 | if (!accessToken) {
46 | res.redirect("/");
47 | return;
48 | }
49 |
50 | const newPage = await request.post({
51 | uri: "https://schedule.api.nylas.com/manage/pages",
52 | json: true,
53 | body: {
54 | name: req.body["name"],
55 | slug: req.body["slug"],
56 | access_tokens: [accessToken],
57 | config: {
58 | // You can provide as few or as many page configuration options as you like.
59 | // Check out the Scheduling Page documentation for a full list of settings.
60 | event: {
61 | title: req.body["event_title"]
62 | }
63 | }
64 | }
65 | });
66 |
67 | res.redirect("/");
68 | });
69 |
70 | router.get("/login_callback", async (req: Request, res: Response) => {
71 | if (req.query.error) {
72 | res.status(400).send(`Login error: ${req.query["error"]}`);
73 | return;
74 | }
75 |
76 | const token = await Nylas.exchangeCodeForToken(req.query.code);
77 |
78 | // save the token to the current session, save it to the user model, etc.
79 | req.session["access_token"] = token;
80 |
81 | res.redirect("/");
82 | });
83 |
84 | export default router;
85 |
--------------------------------------------------------------------------------
/node/src/server.ts:
--------------------------------------------------------------------------------
1 | import errorHandler from "errorhandler";
2 |
3 | import app from "./app";
4 |
5 | /**
6 | * Error Handler. Provides full stack - remove for production
7 | */
8 | app.use(errorHandler());
9 |
10 | /**
11 | * Start Express server.
12 | */
13 | const server = app.listen(app.get("port"), () => {
14 | console.log(
15 | " App is running at http://localhost:%d in %s mode",
16 | app.get("port"),
17 | app.get("env")
18 | );
19 | console.log(" Press CTRL-C to stop\n");
20 | });
21 |
22 | export default server;
23 |
--------------------------------------------------------------------------------
/node/src/types/express-flash.d.ts:
--------------------------------------------------------------------------------
1 |
2 | ///
2 | Signed in as {{ account.name }} ({{account.emailAddress}}). 3 |
4 | 5 | 6 |33 | The easiest way to integrate scheduling pages into your app is to use the 34 | instant schedule integration type and pass the user's Nylas access_token. 35 | When provided a Nylas access_token, the scheduling widget shows all pages that 36 | use that token. 37 |
38 | 39 | 61 | 62 |66 | This approach allows you to restrict the ability of users to create arbitrary scheduling pages 67 | and also allows for scheduling pages that use more than one Nylas API token. It does not require 68 | putting the Nylas API token in the client-side JavaScript. 69 |
70 | This flow is split into two parts: 71 |
87 |
Your Scheduling Pages | 91 |92 | |
---|---|
98 | {{name}} 99 | https://schedule.nylas.com/{{ slug }} 100 | |
101 | 102 | 103 | | 104 |
Thanks for giving Nylas a try! This demo app implements Nylas authentication and allows you to create and 18 | manage scheduling pages. To get started, click this button to connect to Nylas and login to your account.
19 | Log In 20 | 21 | -------------------------------------------------------------------------------- /python/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/Users/bengotow/.local/share/virtualenvs/customer-example-18suMFtB/bin/python" 3 | } -------------------------------------------------------------------------------- /python/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | nylas = ">=4.7.0" 10 | gunicorn = ">=19.9.0" 11 | requests = "*" 12 | Flask = ">=0.11" 13 | Flask-Dance = ">=0.11.1" 14 | flask-talisman = "*" 15 | werkzeug = "==0.16.0" 16 | 17 | [requires] 18 | python_version = "3.7" 19 | -------------------------------------------------------------------------------- /python/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "06e1712522e02452f4a172ece404abbaf938881def158ca24dbb7ac81f939ca8" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "bump2version": { 20 | "hashes": [ 21 | "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", 22 | "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" 23 | ], 24 | "markers": "python_version >= '3.5'", 25 | "version": "==1.0.1" 26 | }, 27 | "bumpversion": { 28 | "hashes": [ 29 | "sha256:4ba55e4080d373f80177b4dabef146c07ce73c7d1377aabf9d3c3ae1f94584a6", 30 | "sha256:4eb3267a38194d09f048a2179980bb4803701969bff2c85fa8f6d1ce050be15e" 31 | ], 32 | "version": "==0.6.0" 33 | }, 34 | "certifi": { 35 | "hashes": [ 36 | "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", 37 | "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" 38 | ], 39 | "version": "==2020.6.20" 40 | }, 41 | "cffi": { 42 | "hashes": [ 43 | "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d", 44 | "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b", 45 | "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4", 46 | "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f", 47 | "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3", 48 | "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579", 49 | "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537", 50 | "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e", 51 | "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05", 52 | "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171", 53 | "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca", 54 | "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522", 55 | "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c", 56 | "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc", 57 | "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d", 58 | "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808", 59 | "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828", 60 | "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869", 61 | "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d", 62 | "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9", 63 | "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0", 64 | "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc", 65 | "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15", 66 | "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c", 67 | "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a", 68 | "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3", 69 | "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1", 70 | "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768", 71 | "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d", 72 | "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b", 73 | "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e", 74 | "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d", 75 | "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730", 76 | "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394", 77 | "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1", 78 | "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591" 79 | ], 80 | "version": "==1.14.3" 81 | }, 82 | "chardet": { 83 | "hashes": [ 84 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 85 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 86 | ], 87 | "version": "==3.0.4" 88 | }, 89 | "click": { 90 | "hashes": [ 91 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 92 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 93 | ], 94 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 95 | "version": "==7.1.2" 96 | }, 97 | "cryptography": { 98 | "hashes": [ 99 | "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538", 100 | "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f", 101 | "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77", 102 | "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b", 103 | "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33", 104 | "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e", 105 | "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb", 106 | "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e", 107 | "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7", 108 | "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297", 109 | "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d", 110 | "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7", 111 | "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b", 112 | "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7", 113 | "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4", 114 | "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8", 115 | "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b", 116 | "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851", 117 | "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13", 118 | "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b", 119 | "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3", 120 | "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df" 121 | ], 122 | "version": "==3.2.1" 123 | }, 124 | "flask": { 125 | "hashes": [ 126 | "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", 127 | "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" 128 | ], 129 | "index": "pypi", 130 | "version": "==1.1.2" 131 | }, 132 | "flask-dance": { 133 | "hashes": [ 134 | "sha256:6fc8b53994de627981b7c1073144604279f1f71003a20aa26b2defdc7d3d8772", 135 | "sha256:b4603d19f49ea820d05b133ecfc16e664cd5fe8dba6dbf4ae40bfe91503ae33a", 136 | "sha256:d240f2509db7a9e95482dc4ba79ad1ffc631ec993da99bdfe9988cfe5b816734" 137 | ], 138 | "index": "pypi", 139 | "version": "==3.1.0" 140 | }, 141 | "flask-talisman": { 142 | "hashes": [ 143 | "sha256:468131464a249274ed226efc21b372518f442487e58918ccab8357eaa638fd1f", 144 | "sha256:eaa754f4b771dfbe473843391d69643b79e3a38c865790011ac5e4179c68e3ec" 145 | ], 146 | "index": "pypi", 147 | "version": "==0.7.0" 148 | }, 149 | "gunicorn": { 150 | "hashes": [ 151 | "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", 152 | "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" 153 | ], 154 | "index": "pypi", 155 | "version": "==20.0.4" 156 | }, 157 | "idna": { 158 | "hashes": [ 159 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 160 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 161 | ], 162 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 163 | "version": "==2.10" 164 | }, 165 | "itsdangerous": { 166 | "hashes": [ 167 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 168 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 169 | ], 170 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 171 | "version": "==1.1.0" 172 | }, 173 | "jinja2": { 174 | "hashes": [ 175 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", 176 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" 177 | ], 178 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 179 | "version": "==2.11.2" 180 | }, 181 | "markupsafe": { 182 | "hashes": [ 183 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 184 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 185 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 186 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 187 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 188 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 189 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 190 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 191 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 192 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 193 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 194 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 195 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 196 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 197 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 198 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 199 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 200 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 201 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 202 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 203 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 204 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 205 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 206 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 207 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 208 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 209 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 210 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 211 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 212 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 213 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 214 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 215 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 216 | ], 217 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 218 | "version": "==1.1.1" 219 | }, 220 | "nylas": { 221 | "hashes": [ 222 | "sha256:64b480881b7cf78571330463560374f6f1b368f3b7596cec89a2fe076e0a3640", 223 | "sha256:76e4991d9f80c497a9af9167782171e2981deb189eb38caedfd1187d0cdddb8c", 224 | "sha256:95b3a958cce85595f0ebb96e424fb6e1f30de046ddffa940ca00163de6696eba" 225 | ], 226 | "index": "pypi", 227 | "version": "==4.12.1" 228 | }, 229 | "oauthlib": { 230 | "hashes": [ 231 | "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", 232 | "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" 233 | ], 234 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 235 | "version": "==3.1.0" 236 | }, 237 | "pycparser": { 238 | "hashes": [ 239 | "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", 240 | "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" 241 | ], 242 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 243 | "version": "==2.20" 244 | }, 245 | "pyopenssl": { 246 | "hashes": [ 247 | "sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504", 248 | "sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507" 249 | ], 250 | "version": "==19.1.0" 251 | }, 252 | "requests": { 253 | "extras": [ 254 | "security" 255 | ], 256 | "hashes": [ 257 | "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", 258 | "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" 259 | ], 260 | "index": "pypi", 261 | "version": "==2.24.0" 262 | }, 263 | "requests-oauthlib": { 264 | "hashes": [ 265 | "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", 266 | "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", 267 | "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" 268 | ], 269 | "version": "==1.3.0" 270 | }, 271 | "six": { 272 | "hashes": [ 273 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 274 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 275 | ], 276 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 277 | "version": "==1.15.0" 278 | }, 279 | "urllib3": { 280 | "hashes": [ 281 | "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", 282 | "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" 283 | ], 284 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 285 | "version": "==1.25.11" 286 | }, 287 | "urlobject": { 288 | "hashes": [ 289 | "sha256:47b2e20e6ab9c8366b2f4a3566b6ff4053025dad311c4bb71279bbcfa2430caa" 290 | ], 291 | "version": "==2.4.3" 292 | }, 293 | "werkzeug": { 294 | "hashes": [ 295 | "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", 296 | "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4" 297 | ], 298 | "index": "pypi", 299 | "version": "==0.16.0" 300 | } 301 | }, 302 | "develop": {} 303 | } 304 | -------------------------------------------------------------------------------- /python/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # Example: Hosted OAuth + Scheduling 2 | 3 | This is an example project that implements login via [Nylas Hosted OAuth flow](https://docs.nylas.com/reference#oauth) and demonstrates both instant and smart integration types with the Nylas Scheduler. 4 | 5 | Try it out now! Visit [http://nylas-customer-example.herokuapp.com/](http://nylas-customer-example.herokuapp.com/). 6 | 7 | This example uses the [Flask](http://flask.pocoo.org/) web framework for Python. In order to successfully run this example and go through the Nylas OAuth flow, you need to follow the steps below. 8 | 9 | 10 | ## Get an Application Client ID & Client Secret from Nylas 11 | 12 | To do this, login (or create) your Nylas developer account using the [Nylas Dashboard](https://dashboard.nylas.com/) account. You should see your `client_id` and `client_secret` on the dashboard. 13 | 14 | ## Update the `config.json` File 15 | 16 | Open the `config.json` file in this directory, and replace the example `client_id` and `client_secret` with the real values that you got from the Nylas Developer dashboard. You'll also need to replace the example secret key with any random string of letters and numbers: a keyboard mash will do. 17 | 18 | ## Set Up HTTPS 19 | 20 | The OAuth protocol requires that all communication occur via the secure HTTPS connections, rather than insecure HTTP connections. There are several ways to set up HTTPS on your computer, but perhaps the simplest is to use 21 | [ngrok](https://ngrok.com), a tool that lets you create a secure tunnel from the ngrok website to your computer. Install it from the website, and then run the following command: 22 | 23 | ``` 24 | ngrok http 5000 25 | ``` 26 | 27 | Notice that ngrok will show you two "forwarding" URLs, which may look something like `http://ed90abe7.ngrok.io` and `https://ed90abe7.ngrok.io`. (The hash subdomain will be different for you.) You'll be using the second URL, which starts with `https`. 28 | 29 | Alternatively, you can set the `OAUTHLIB_INSECURE_TRANSPORT` environment variable in your shell, to disable the HTTPS check. That way, you'll be able to use `localhost` to refer to your app, instead of an ngrok URL. However, be aware that you won't be able to do this when you deploy your app to production, so it's usually a better idea to set up HTTPS properly. 30 | 31 | ## Set the Nylas Callback URL 32 | 33 | Once you have a HTTPS URL that points to your computer, you'll need to tell Nylas about it. On the [Nylas Dashboard](https://dashboard.nylas.com) click on the Application Dropdown Menu on the left, then "View all Applications". From there, select "Edit" for the app you'd like to use and select the "Application Callbacks" tab. Paste your HTTPS URL into the text field, and add `/login/nylas/authorized` after it. For example, if your HTTPS URL is `https://ad172180.ngrok.io`, then you would put `https://ad172180.ngrok.io/login/nylas/authorized` into the text field in the "Application Callbacks" tab. 34 | 35 | Then click the "Add Callback" button to save. 36 | 37 | ## Install the Dependencies 38 | 39 | This project depends on a few third-party Python modules, like Flask. These dependencies are listed in the `requirements.txt` file in this directory. To get started, install [pipenv](https://pypi.org/project/pipenv/) and run the command below to setup your python virtualenv for the example app: 40 | 41 | ``` 42 | pipenv install 43 | ``` 44 | 45 | ## Run the Example 46 | 47 | Finally, run the example project like this: 48 | 49 | ``` 50 | pipenv run flask run 51 | ``` 52 | 53 | Once the server is running, visit the ngrok URL in your browser to test it out! 54 | -------------------------------------------------------------------------------- /python/app/__init__.py: -------------------------------------------------------------------------------- 1 | # Imports from the Python standard library 2 | import os 3 | import sys 4 | import textwrap 5 | import requests 6 | from json import dumps 7 | from flask import Flask, session, request, render_template, redirect, url_for, flash 8 | from flask_talisman import Talisman 9 | from werkzeug.contrib.fixers import ProxyFix 10 | from nylas import APIClient 11 | 12 | # This example uses Flask, a micro web framework written in Python. 13 | # For more information, check out the documentation: http://flask.pocoo.org 14 | # Create a Flask app, and load the configuration file. 15 | app = Flask(__name__) 16 | app_settings = os.getenv("APP_SETTINGS", "app.config.DevelopmentConfig") 17 | app.config.from_object(app_settings) 18 | 19 | # force SSL, enable HTTP Strict Transport Security 20 | csp = {"default-src": "'self' 'unsafe-inline' *.nylas.com maxcdn.bootstrapcdn.com" } 21 | Talisman(app, content_security_policy=csp) 22 | 23 | # Check for dummy configuration values. 24 | # If you are building your own application based on this example, 25 | # you can remove this check from your code. 26 | cfg_needs_replacing = [ 27 | key 28 | for key, value in app.config.items() 29 | if isinstance(value, str) and value.startswith("replace me") 30 | ] 31 | if cfg_needs_replacing: 32 | message = textwrap.dedent( 33 | """ 34 | This example will only work if you replace the fake configuration 35 | values in `config.json` with real configuration values. 36 | The following config values need to be replaced: 37 | {keys} 38 | Consult the README.md file in this directory for more information. 39 | """ 40 | ).format(keys=", ".join(cfg_needs_replacing)) 41 | print(message, file=sys.stderr) 42 | sys.exit(1) 43 | 44 | # Teach Flask how to find out that it's behind an ngrok proxy 45 | app.wsgi_app = ProxyFix(app.wsgi_app) 46 | 47 | redirect_url = "https://nylas-customer-example.herokuapp.com/login_callback" 48 | client = APIClient(app.config["NYLAS_OAUTH_CLIENT_ID"], 49 | app.config["NYLAS_OAUTH_CLIENT_SECRET"]) 50 | 51 | 52 | # Define what Flask should do when someone visits the root URL of this website. 53 | @app.route("/", methods=['GET']) 54 | def index(): 55 | # If the user has already connected to Nylas via OAuth, 56 | # `nylas.authorized` will be True. Otherwise, it will be False. 57 | if session.get('access_token') is not None: 58 | # If we've gotten to this point, then the user has already connected 59 | # to Nylas via OAuth. Let's set up the SDK client with the OAuth token: 60 | client.access_token = session['access_token'] 61 | 62 | # We'll use the Nylas client to fetch information from Nylas 63 | # about the current user, and pass that to the template. 64 | try: 65 | account = client.account 66 | except requests.HTTPError: 67 | # The user's token may be invalid - log them out. 68 | return redirect("/logout") 69 | 70 | # Server-side scheduling page integration: fetch the list of pages that 71 | # include the current user's API token 72 | response = requests.get('https://schedule.api.nylas.com/manage/pages', 73 | headers=dict(Authorization="Bearer " + session['access_token']), 74 | ) 75 | pages = response.json() 76 | return render_template("after_authorized.html", account=account, pages=pages) 77 | 78 | # OAuth requires HTTPS. The template will display a handy warning, 79 | # unless we've overridden the check. 80 | login_callback_var = client.authentication_url(redirect_url) 81 | 82 | return render_template( 83 | "before_authorized.html", login_callback_var=login_callback_var 84 | ) 85 | 86 | @app.route("/", methods=['POST']) 87 | def index_create_page(): 88 | if session.get('access_token') is None: 89 | return redirect(url_for('index')) 90 | 91 | json = { 92 | "name": request.form["name"], 93 | "slug": request.form["slug"], 94 | "config": { 95 | # You can provide as few or as many page configuration options as you like. 96 | # Check out the Scheduling Page documentation for a full list of settings. 97 | "event": { 98 | "title": request.form["event_title"], 99 | }, 100 | }, 101 | "access_tokens": [session['access_token']] 102 | } 103 | 104 | response = requests.post('https://schedule.api.nylas.com/manage/pages', json=json) 105 | if response.status_code != 201: 106 | data = response.json() 107 | print(dumps(data)) 108 | flash(data['message']) 109 | return redirect(url_for('index')) 110 | 111 | @app.route("/login_callback") 112 | def login_callback(): 113 | if 'error' in request.args: 114 | return "Login error: {0}".format(request.args['error']) 115 | 116 | # Exchange the authorization code for an access token 117 | code = request.args.get('code') 118 | session['access_token'] = client.token_for_code(code) 119 | print(session['access_token']) 120 | return redirect("/") 121 | 122 | 123 | @app.route("/logout", methods=['GET', 'POST']) 124 | def logout(): 125 | del session['access_token'] 126 | return redirect("/") 127 | 128 | def ngrok_url(): 129 | """ 130 | If ngrok is running, it exposes an API on port 4040. We can use that 131 | to figure out what URL it has assigned, and suggest that to the user. 132 | https://ngrok.com/docs#list-tunnels 133 | """ 134 | try: 135 | ngrok_resp = requests.get("http://localhost:4040/api/tunnels") 136 | except requests.ConnectionError: 137 | # I guess ngrok isn't running. 138 | return None 139 | ngrok_data = ngrok_resp.json() 140 | secure_urls = [ 141 | tunnel["public_url"] 142 | for tunnel in ngrok_data["tunnels"] 143 | if tunnel["proto"] == "https" 144 | ] 145 | return secure_urls[0] 146 | 147 | 148 | # When this file is executed, run the Flask web server. 149 | if __name__ == "__main__": 150 | url = ngrok_url() 151 | if url: 152 | print(" * Visit {url} to view this Nylas example".format(url=url)) 153 | 154 | app.run() 155 | -------------------------------------------------------------------------------- /python/app/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | base_dir = os.path.abspath(os.path.dirname(__file__)) 4 | 5 | 6 | class BaseConfig: 7 | """ 8 | Base application configuration 9 | """ 10 | 11 | DEBUG = False 12 | SECRET_KEY = os.getenv("SECRET_KEY", "houdsfhoisd") 13 | NYLAS_OAUTH_CLIENT_ID = os.getenv("NYLAS_OAUTH_CLIENT_ID", "") 14 | NYLAS_OAUTH_CLIENT_SECRET = os.getenv("NYLAS_OAUTH_CLIENT_SECRET", "") 15 | 16 | 17 | class DevelopmentConfig(BaseConfig): 18 | """ 19 | Development application configuration 20 | """ 21 | 22 | DEBUG = True 23 | 24 | 25 | class TestingConfig(BaseConfig): 26 | """ 27 | Testing application configuration 28 | """ 29 | 30 | DEBUG = True 31 | TESTING = True 32 | 33 | 34 | class ProductionConfig(BaseConfig): 35 | """ 36 | Production application configuration 37 | """ 38 | 39 | DEBUG = False 40 | -------------------------------------------------------------------------------- /python/app/templates/after_authorized.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block body %} 3 |4 | Signed in as {{ account.name }} ({{account.email_address}}). 5 |
6 | 7 | 8 |35 | The easiest way to integrate scheduling pages into your app is to use the built-in management 36 | UI and pass the user's Nylas API token. When provided a Nylas API token, the scheduling widget 37 | shows all pages that use the token. 38 |
39 | 40 | 62 | 63 |67 | This approach allows you to restrict the ability of users to create arbitrary scheduling pages 68 | and also allows for scheduling pages that use more than one Nylas API token. It does not require 69 | putting the Nylas API token in the client-side JavaScript. 70 |
71 | This flow is split into two parts: 72 |
88 |
Your Scheduling Pages | 92 |93 | |
---|---|
98 | {{page.name}} 99 | https://schedule.nylas.com/{{ page.slug }} 100 | |
101 | 102 | 103 | | 104 |
Thanks for giving Nylas a try! This demo app implements Nylas authentication and allows you to create and 17 | manage scheduling pages. To get started, click this button to connect to Nylas and login to your account.
18 | Log In 19 | 20 | 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /python/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "SECRET_KEY": "any-random-string", 3 | "NYLAS_OAUTH_CLIENT_ID": "your-client-id", 4 | "NYLAS_OAUTH_CLIENT_SECRET": "your-client-secret" 5 | } 6 | -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask>=1.0.2 2 | nylas>=4.7.0 3 | gunicorn>=19.9.0 4 | werkzeug==0.16.0 5 | requests 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Nylas Scheduler Sample Code 🎯 4 | 5 | - **[Quick Start Simple Example](https://github.com/nylas/scheduler-examples/tree/master/simple-example.html)**: A simple html file that is the quickest way to 6 | test out the schedule editor and create your own scheduling page. Showcases the instant integration type. You'll need to get your own access token through the [Nylas Dashboard](https://dashboard.nylas.com) to run this example. 7 | 8 | - **[NodeJS Hosted Auth + Scheduling](https://github.com/nylas/scheduler-examples/tree/master/node)**: A NodeJS / TypeScript example that showcases both instant and smart integration types. Also demonstrates how to theme the schedule editor to match your application. 9 | 10 | - **[Python Hosted Auth + Scheduling](https://github.com/nylas/scheduler-examples/tree/master/python)**: A Python example that showcases both instant and smart integration types. Also demonstrates how to theme the schedule editor to match your application. 11 | -------------------------------------------------------------------------------- /simple-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |