This repository has been archived by the owner on Aug 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 83d18a1
Showing
25 changed files
with
2,481 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# EditorConfig is awesome: https://EditorConfig.org | ||
|
||
# top-most EditorConfig file | ||
root = true | ||
|
||
[*] | ||
indent_style = space | ||
indent_size = 2 | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = false | ||
insert_final_newline = false |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CLIENT_ID= | ||
CLIENT_SECRET= | ||
SLUG= | ||
PORT= | ||
ENVIRONMENT=thinkific.com | ||
MONGODB_URI= | ||
APP_URL= | ||
APP_FRAMES_REMOTE_URL=https://cdn.thinkific.com/assets/app-frames/remote/latest/index.js | ||
PROTOCOL=https |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
node_modules | ||
.env | ||
|
||
# yalc | ||
.yalc | ||
yalc.* |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"singleQuote": true | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Thinkific Embedded App NodeJS Example | ||
|
||
This is an example of a NodeJS application to set up an embedded app for Thinkific. | ||
|
||
## Prerequisites | ||
|
||
You will need the credentials for your app created in Thinkific Platform. | ||
|
||
- client_id: App's client id; | ||
- redirect_uri: App's registered callback uri. For example, this app uses `{app_url}/install/callback`. | ||
|
||
Additionally, you will need a MongoDB database. | ||
|
||
## Description | ||
|
||
This project takes you through Thinkific's OAuth app install flow then renders a SSR app inside Thinkific's app details page i.e. _/manage/apps/{slug}_. The app is built to be compatible for the embedded app experience i.e. to be iframed into Thinkific app details page. | ||
|
||
It consumes a couple of Thinkific's packages, namely Toga (UI library) and App Frames (client-side library). The use of former is optional, but the latter must be used in order to interact with the host environment for certain behaviours e.g. dispatch a toast message. | ||
|
||
|
||
## Project setup | ||
|
||
Create `.env` file | ||
|
||
``` | ||
cp .env-example .env | ||
``` | ||
|
||
and populate the `CLIENT_ID` with your app's client_id | ||
|
||
``` | ||
CLIENT_ID= | ||
``` | ||
|
||
You must also populate the rest of the environment variables according to your app's needs: | ||
|
||
``` | ||
PORT= | ||
MONGODB_URI= | ||
APP_URL= | ||
``` | ||
|
||
Install dependencies: | ||
``` | ||
npm install | ||
``` | ||
|
||
Run the app: | ||
``` | ||
npm run dev | ||
``` |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
const axios = require('axios'); | ||
|
||
const User = require('../models/user'); | ||
|
||
require('dotenv').config(); | ||
|
||
const { codeChallenge } = require('../utils/utils'); | ||
|
||
exports.index = (req, res, next) => { | ||
const { subdomain } = req.query; | ||
const callbackUrl = `${process.env.APP_URL}/install/callback`; | ||
const authorizeUrl = `${process.env.PROTOCOL}://${subdomain}.${ | ||
process.env.ENVIRONMENT | ||
}/oauth2/authorize?client_id=${ | ||
process.env.CLIENT_ID | ||
}&response_type=code&redirect_uri=${callbackUrl}&code_challenge=${codeChallenge( | ||
codeVerifier | ||
)}&code_challenge_method=S256`; | ||
res.redirect(authorizeUrl); | ||
}; | ||
|
||
exports.callback = async (req, res, next) => { | ||
const { code, subdomain } = req.query; | ||
const tokenUrl = `${process.env.PROTOCOL}://${subdomain}.${process.env.ENVIRONMENT}/oauth2/token`; | ||
const options = { | ||
grant_type: 'authorization_code', | ||
code_verifier: codeVerifier, | ||
code, | ||
}; | ||
const authParams = { | ||
auth: { | ||
username: process.env.CLIENT_ID, | ||
}, | ||
}; | ||
|
||
try { | ||
const token = await axios.post(tokenUrl, options, authParams); | ||
const { access_token: accessToken, gid } = token.data; | ||
let user = await User.findOne({ subdomain }); | ||
|
||
if (!user) { | ||
user = await User.create({ subdomain, gid, accessToken }); | ||
} | ||
|
||
const appSubviewUrl = `${process.env.PROTOCOL}://${subdomain}.${process.env.ENVIRONMENT}/manage/apps/${process.env.SLUG}#embedded-app`; | ||
|
||
if (user) { | ||
res.redirect(appSubviewUrl); | ||
} else { | ||
res.send(`<h1>User not found.</h1>`); | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
const User = require('../models/user'); | ||
const { verifyHmacSignature } = require('../utils/utils'); | ||
|
||
exports.index = async (req, res, next) => { | ||
const { hmac, subdomain, tgid, timestamp } = req.query; | ||
|
||
if (!verifyHmacSignature(hmac, subdomain, tgid, timestamp)) { | ||
res.send(`<h1>Invalid HMAC</h1>`); | ||
} | ||
|
||
try { | ||
const user = await User.findOne({ subdomain }).orFail( | ||
new Error('User not found') | ||
); | ||
|
||
if (user) { | ||
res.render('root/index', { subdomain, userId: user._id }); | ||
} else { | ||
res.send(`<h1>User not found.</h1>`); | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const { addTask, deleteTask, task, tasks } = require('./resolvers/task'); | ||
|
||
module.exports = { | ||
addTask, | ||
deleteTask, | ||
task, | ||
tasks, | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
const Task = require('../../models/task'); | ||
|
||
async function task(args, _) { | ||
const { id } = args; | ||
|
||
try { | ||
const task = await Task.findById(id).orFail(new Error('Task not found')); | ||
|
||
return task; | ||
} catch (err) { | ||
console.error(err); | ||
return err; | ||
} | ||
} | ||
|
||
async function tasks(args, _) { | ||
const { userId } = args; | ||
|
||
try { | ||
const tasks = await Task.find({ userId }); | ||
return tasks; | ||
} catch (err) { | ||
console.error(err); | ||
return err; | ||
} | ||
} | ||
|
||
async function addTask(args, _) { | ||
const { description, userId } = args.input; | ||
|
||
try { | ||
const task = await Task.create({ description, userId }); | ||
return task; | ||
} catch (err) { | ||
console.error(err); | ||
return err; | ||
} | ||
} | ||
|
||
async function deleteTask(args, _) { | ||
const { id } = args; | ||
|
||
try { | ||
const task = await Task.findByIdAndDelete(id).orFail( | ||
new Error('Task not found') | ||
); | ||
return task; | ||
} catch (err) { | ||
console.error(err); | ||
return err; | ||
} | ||
} | ||
|
||
module.exports = { | ||
addTask, | ||
deleteTask, | ||
task, | ||
tasks, | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
const { buildSchema } = require('graphql'); | ||
|
||
module.exports = buildSchema(` | ||
type Task { | ||
_id: ID! | ||
description: String! | ||
userId: ID! | ||
} | ||
input TaskInput { | ||
description: String! | ||
userId: ID! | ||
} | ||
type RootQuery { | ||
task(id: ID!): Task! | ||
tasks(userId: ID!): [Task]! | ||
} | ||
type RootMutation { | ||
addTask(input: TaskInput!): Task! | ||
deleteTask(id: ID!): Task! | ||
} | ||
schema { | ||
query: RootQuery | ||
mutation: RootMutation | ||
} | ||
`); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
const path = require('path'); | ||
const express = require('express'); | ||
const crypto = require('crypto'); | ||
const bodyParser = require('body-parser'); | ||
const mongoose = require('mongoose'); | ||
const { graphqlHTTP } = require('express-graphql'); | ||
const cors = require('cors'); | ||
|
||
const graphqlSchema = require('./graphql/schema'); | ||
const graphqlResolver = require('./graphql/resolvers'); | ||
|
||
const rootRoutes = require('./routes/root'); | ||
const installRoutes = require('./routes/install'); | ||
|
||
const { base64EncodeUrlSafe } = require('./utils/utils'); | ||
|
||
require('dotenv').config(); | ||
|
||
const app = express(); | ||
|
||
global.codeVerifier = base64EncodeUrlSafe(crypto.randomBytes(32)); | ||
|
||
app.set('view engine', 'ejs'); | ||
|
||
app.use(bodyParser.urlencoded({ extended: false })); | ||
app.use(bodyParser.json()); | ||
app.use(express.static(path.join(__dirname, 'public'))); | ||
|
||
app.use(cors()); | ||
app.use('/', rootRoutes); | ||
app.use( | ||
'/graphql', | ||
graphqlHTTP({ | ||
schema: graphqlSchema, | ||
rootValue: graphqlResolver, | ||
graphiql: true, | ||
}) | ||
); | ||
app.use('/install', installRoutes); | ||
|
||
app.use((req, res, next) => { | ||
res.status(404).render('404', { pageTitle: 'Page not found', path: '/404' }); | ||
}); | ||
|
||
mongoose | ||
.connect(process.env.MONGODB_URI, { | ||
useNewUrlParser: true, | ||
useUnifiedTopology: true, | ||
}) | ||
.then((_) => { | ||
app.listen(process.env.PORT || 3001); | ||
}) | ||
.catch((err) => console.error(err)); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const mongoose = require('mongoose'); | ||
|
||
const taskSchema = mongoose.Schema( | ||
{ | ||
description: { | ||
type: String, | ||
required: true, | ||
}, | ||
userId: { | ||
type: mongoose.Types.ObjectId, | ||
ref: 'user', | ||
}, | ||
}, | ||
{ timestamps: true } | ||
); | ||
|
||
module.exports = mongoose.model('task', taskSchema); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
const mongoose = require('mongoose'); | ||
|
||
const userSchema = mongoose.Schema( | ||
{ | ||
accessToken: { | ||
type: String, | ||
required: true, | ||
}, | ||
gid: { | ||
type: String, | ||
required: true, | ||
}, | ||
subdomain: { | ||
type: String, | ||
required: true, | ||
}, | ||
}, | ||
{ timestamps: true } | ||
); | ||
|
||
module.exports = mongoose.model('user', userSchema); |
Oops, something went wrong.