-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Okay, this is the doozy. We are now adding a createInfrastructure fun…
…ction that does a lot of things for us. 1. Creates all of the default routers, integration, auth, user, entity 2. Creates the serverless.yml definition programmatically based on configured integrations 3. Adds a new router, queue, and queue worker for every integration by default 4. Adds a websocket handler just because we can? Largely for demos but useful in a lot of other contexts too. 5. Sets the stage for iterating TODO: [] Need to figure out if this works with a locally defined serverless.yml / whether we should support that [] Webpack was having a lot of issues due to it being referenced from a different scope/directory... really unclear how to resolve this, but probably critical due to build size [] Add some more override options to the serverless.yml definition based on the overall Frigg App definition provided [] Get the Frigg App definition handled at root instead of in the backend, so we can also run the frontend at the same time (if it exists) [] Tests, tests, and more tests needed [] This needs to be tested for build and deploy. Unclear if it does yet, though running sls offline usually indicates we're alright [] Check defaults and have different defaults for when someone wants KMS Key vs. env, when they want to use Secrets Manager, and probably a few others that are common/critical (like domain management and VPC stuff). [] More TODOs but those will become apparent when working through the tests and using the `frigg start` command [] add options to frigg-start? [] support for TS?
- Loading branch information
1 parent
7d0a8ee
commit 9bc9068
Showing
14 changed files
with
707 additions
and
4 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 |
---|---|---|
@@ -1,5 +1,7 @@ | ||
const test = require('./test'); | ||
const { createFriggInfrastructure } = require('./infrastructure'); | ||
|
||
module.exports = { | ||
...test | ||
} | ||
createFriggInfrastructure, | ||
...test, | ||
}; |
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,57 @@ | ||
const { createHandler, flushDebugLog } = require('@friggframework/core'); | ||
const express = require('express'); | ||
const bodyParser = require('body-parser'); | ||
const cors = require('cors'); | ||
const Boom = require('@hapi/boom'); | ||
const loadUserManager = require('./routers/middleware/loadUser'); | ||
const serverlessHttp = require('serverless-http'); | ||
|
||
const createApp = (applyMiddleware) => { | ||
const app = express(); | ||
|
||
app.use(bodyParser.json({ limit: '10mb' })); | ||
app.use(bodyParser.urlencoded({ extended: true })); | ||
app.use( | ||
cors({ | ||
origin: '*', | ||
credentials: true, | ||
}) | ||
); | ||
|
||
app.use(loadUserManager); | ||
|
||
if (applyMiddleware) applyMiddleware(app); | ||
|
||
// Handle sending error response and logging server errors to console | ||
app.use((err, req, res, next) => { | ||
const boomError = err.isBoom ? err : Boom.boomify(err); | ||
const { | ||
output: { statusCode = 500 }, | ||
} = boomError; | ||
|
||
if (statusCode >= 500) { | ||
flushDebugLog(boomError); | ||
res.status(statusCode).json({ error: 'Internal Server Error' }); | ||
} else { | ||
res.status(statusCode).json({ error: err.message }); | ||
} | ||
}); | ||
|
||
return app; | ||
}; | ||
|
||
function createAppHandler(eventName, router, shouldUseDatabase = true) { | ||
const app = createApp((app) => { | ||
app.use(router); | ||
}); | ||
return createHandler({ | ||
eventName, | ||
method: serverlessHttp(app), | ||
shouldUseDatabase, | ||
}); | ||
} | ||
|
||
module.exports = { | ||
createApp, | ||
createAppHandler, | ||
}; |
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,90 @@ | ||
const { createFriggBackend, Worker } = require('@friggframework/core'); | ||
const { | ||
findNearestBackendPackageJson, | ||
} = require('../frigg-cli/utils/backend-path'); | ||
const path = require('path'); | ||
const fs = require('fs-extra'); | ||
|
||
const backendPath = findNearestBackendPackageJson(); | ||
if (!backendPath) { | ||
throw new Error('Could not find backend package.json'); | ||
} | ||
|
||
const backendDir = path.dirname(backendPath); | ||
const backendFilePath = path.join(backendDir, 'index.js'); | ||
if (!fs.existsSync(backendFilePath)) { | ||
throw new Error('Could not find index.js'); | ||
} | ||
|
||
const backendJsFile = require(backendFilePath); | ||
const { Router } = require('express'); | ||
const appDefinition = backendJsFile.Definition; | ||
|
||
const backend = createFriggBackend(appDefinition); | ||
const loadRouterFromObject = (IntegrationClass, routerObject) => { | ||
const router = Router(); | ||
const { path, method, event } = routerObject; | ||
console.log( | ||
`Registering ${method} ${path} for ${IntegrationClass.Definition.name}` | ||
); | ||
router[method.toLowerCase()](path, async (req, res, next) => { | ||
try { | ||
const integration = new IntegrationClass({}); | ||
await integration.loadModules(); | ||
await integration.registerEventHandlers(); | ||
const result = await integration.send(event, req.body); | ||
res.json(result); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}); | ||
|
||
return router; | ||
}; | ||
const createQueueWorker = (integrationClass) => { | ||
class QueueWorker extends Worker { | ||
constructor(params) { | ||
super(params); | ||
} | ||
async _run(params, context) { | ||
try { | ||
let instance; | ||
if (!params.integrationId) { | ||
instance = new integrationClass({}); | ||
await instance.loadModules(); | ||
// await instance.loadUserActions(); | ||
await instance.registerEventHandlers(); | ||
console.log( | ||
`${params.event} for ${integrationClass.Definition.name} integration with no integrationId` | ||
); | ||
} else { | ||
instance = | ||
await integrationClass.getInstanceFromIntegrationId({ | ||
integrationId: params.integrationId, | ||
}); | ||
console.log( | ||
`${params.event} for ${instance.integration.config.type} of integrationId: ${params.integrationId}` | ||
); | ||
} | ||
const res = await instance.send(params.event, { | ||
data: params.data, | ||
context, | ||
}); | ||
return res; | ||
} catch (error) { | ||
console.error( | ||
`Error in ${params.event} for ${integrationClass.Definition.name}:`, | ||
error | ||
); | ||
throw error; | ||
} | ||
} | ||
} | ||
return QueueWorker; | ||
}; | ||
|
||
module.exports = { | ||
...backend, | ||
loadRouterFromObject, | ||
createQueueWorker, | ||
}; |
38 changes: 38 additions & 0 deletions
38
packages/devtools/infrastructure/create-frigg-infrastructure.js
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,38 @@ | ||
const path = require('path'); | ||
const fs = require('fs-extra'); | ||
const { composeServerlessDefinition } = require('./serverless-template'); | ||
|
||
const { | ||
findNearestBackendPackageJson, | ||
} = require('../frigg-cli/utils/backend-path'); | ||
|
||
function createFriggInfrastructure() { | ||
const backendPath = findNearestBackendPackageJson(); | ||
if (!backendPath) { | ||
throw new Error('Could not find backend package.json'); | ||
} | ||
|
||
const backendDir = path.dirname(backendPath); | ||
const backendFilePath = path.join(backendDir, 'index.js'); | ||
if (!fs.existsSync(backendFilePath)) { | ||
throw new Error('Could not find index.js'); | ||
} | ||
|
||
const backend = require(backendFilePath); | ||
const appDefinition = backend.Definition; | ||
|
||
// const serverlessTemplate = require(path.resolve( | ||
// __dirname, | ||
// './serverless-template.js' | ||
// )); | ||
const definition = composeServerlessDefinition( | ||
appDefinition, | ||
backend.IntegrationFactory | ||
); | ||
|
||
return { | ||
...definition, | ||
}; | ||
} | ||
|
||
module.exports = { createFriggInfrastructure }; |
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,4 @@ | ||
const { createFriggInfrastructure } = require('./create-frigg-infrastructure'); | ||
module.exports = { | ||
createFriggInfrastructure, | ||
}; |
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,26 @@ | ||
const { createIntegrationRouter } = require('@friggframework/core'); | ||
const { createAppHandler } = require('./../app-handler-helpers'); | ||
const { requireLoggedInUser } = require('./middleware/requireLoggedInUser'); | ||
const { | ||
moduleFactory, | ||
integrationFactory, | ||
IntegrationHelper, | ||
} = require('./../backend-utils'); | ||
|
||
const router = createIntegrationRouter({ | ||
factory: { moduleFactory, integrationFactory, IntegrationHelper }, | ||
requireLoggedInUser, | ||
getUserId: (req) => req.user.getUserId(), | ||
}); | ||
|
||
router.route('/redirect/:appId').get((req, res) => { | ||
res.redirect( | ||
`${process.env.FRONTEND_URI}/redirect/${ | ||
req.params.appId | ||
}?${new URLSearchParams(req.query)}` | ||
); | ||
}); | ||
|
||
const handler = createAppHandler('HTTP Event: Auth', router); | ||
|
||
module.exports = { handler, router }; |
37 changes: 37 additions & 0 deletions
37
packages/devtools/infrastructure/routers/integration-defined-routers.js
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,37 @@ | ||
const { createIntegrationRouter } = require('@friggframework/core'); | ||
const { createAppHandler } = require('./../app-handler-helpers'); | ||
const { requireLoggedInUser } = require('./middleware/requireLoggedInUser'); | ||
const { | ||
moduleFactory, | ||
integrationFactory, | ||
IntegrationHelper, | ||
loadRouterFromObject, | ||
} = require('./../backend-utils'); | ||
const { Router } = require('express'); | ||
|
||
const handlers = {}; | ||
integrationFactory.integrationClasses.forEach((IntegrationClass) => { | ||
const router = Router(); | ||
const basePath = `/api/${IntegrationClass.Definition.name}-integration`; | ||
IntegrationClass.Definition.routes.forEach((routeDef) => { | ||
if (typeof routeDef === 'function') { | ||
router.use(basePath, routeDef(IntegrationClass)); | ||
} else if (typeof routeDef === 'object') { | ||
router.use( | ||
basePath, | ||
loadRouterFromObject(IntegrationClass, routeDef) | ||
); | ||
} else if (routeDef instanceof express.Router) { | ||
router.use(basePath, routeDef); | ||
} | ||
}); | ||
|
||
handlers[`${IntegrationClass.Definition.name}`] = { | ||
handler: createAppHandler( | ||
`HTTP Event: ${IntegrationClass.Definition.name}`, | ||
router | ||
), | ||
}; | ||
}); | ||
|
||
module.exports = { handlers }; |
15 changes: 15 additions & 0 deletions
15
packages/devtools/infrastructure/routers/middleware/loadUser.js
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,15 @@ | ||
const catchAsyncError = require('express-async-handler'); | ||
const { User } = require('../../backend-utils'); | ||
|
||
module.exports = catchAsyncError(async (req, res, next) => { | ||
const authorizationHeader = req.headers.authorization; | ||
|
||
if (authorizationHeader) { | ||
// Removes "Bearer " and trims | ||
const token = authorizationHeader.split(' ')[1].trim(); | ||
// Load user for later middleware/routes to use | ||
req.user = await User.newUser({ token }); | ||
} | ||
|
||
return next(); | ||
}); |
12 changes: 12 additions & 0 deletions
12
packages/devtools/infrastructure/routers/middleware/requireLoggedInUser.js
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 @@ | ||
const Boom = require('@hapi/boom'); | ||
|
||
// CheckLoggedIn Middleware | ||
const requireLoggedInUser = (req, res, next) => { | ||
if (!req.user || !req.user.isLoggedIn()) { | ||
throw Boom.unauthorized('Invalid Token'); | ||
} | ||
|
||
next(); | ||
}; | ||
|
||
module.exports = { requireLoggedInUser }; |
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,41 @@ | ||
const express = require('express'); | ||
const { createAppHandler } = require('../app-handler-helpers'); | ||
const { checkRequiredParams } = require('@friggframework/core'); | ||
const { User } = require('../backend-utils'); | ||
const catchAsyncError = require('express-async-handler'); | ||
|
||
const router = express(); | ||
|
||
// define the login endpoint | ||
router.route('/user/login').post( | ||
catchAsyncError(async (req, res) => { | ||
const { username, password } = checkRequiredParams(req.body, [ | ||
'username', | ||
'password', | ||
]); | ||
const user = await User.loginUser({ username, password }); | ||
const token = await user.createUserToken(120); | ||
res.status(201); | ||
res.json({ token }); | ||
}) | ||
); | ||
|
||
router.route('/user/create').post( | ||
catchAsyncError(async (req, res) => { | ||
const { username, password } = checkRequiredParams(req.body, [ | ||
'username', | ||
'password', | ||
]); | ||
const user = await User.createIndividualUser({ | ||
username, | ||
password, | ||
}); | ||
const token = await user.createUserToken(120); | ||
res.status(201); | ||
res.json({ token }); | ||
}) | ||
); | ||
|
||
const handler = createAppHandler('HTTP Event: User', router); | ||
|
||
module.exports = { handler, router }; |
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 { createHandler } = require('@friggframework/core'); | ||
const { WebsocketConnection } = require('@friggframework/core'); | ||
|
||
const handleWebSocketConnection = async (event, context) => { | ||
// Handle different WebSocket events | ||
switch (event.requestContext.eventType) { | ||
case 'CONNECT': | ||
// Handle new connection | ||
try { | ||
const connectionId = event.requestContext.connectionId; | ||
await WebsocketConnection.create({ connectionId }); | ||
console.log(`Stored new connection: ${connectionId}`); | ||
return { statusCode: 200, body: 'Connected.' }; | ||
} catch (error) { | ||
console.error('Error storing connection:', error); | ||
return { statusCode: 500, body: 'Error connecting.' }; | ||
} | ||
|
||
case 'DISCONNECT': | ||
// Handle disconnection | ||
try { | ||
const connectionId = event.requestContext.connectionId; | ||
await WebsocketConnection.deleteOne({ connectionId }); | ||
console.log(`Removed connection: ${connectionId}`); | ||
return { statusCode: 200, body: 'Disconnected.' }; | ||
} catch (error) { | ||
console.error('Error removing connection:', error); | ||
return { statusCode: 500, body: 'Error disconnecting.' }; | ||
} | ||
|
||
case 'MESSAGE': | ||
// Handle incoming message | ||
const message = JSON.parse(event.body); | ||
console.log('Received message:', message); | ||
|
||
// Process the message and send a response | ||
const responseMessage = { message: 'Message received' }; | ||
return { | ||
statusCode: 200, | ||
body: JSON.stringify(responseMessage), | ||
}; | ||
|
||
default: | ||
return { statusCode: 400, body: 'Unhandled event type.' }; | ||
} | ||
}; | ||
|
||
const handler = createHandler({ | ||
eventName: 'WebSocket Event', | ||
method: handleWebSocketConnection, | ||
shouldUseDatabase: true, // Set to true as we're using the database | ||
isUserFacingResponse: true, // This is a server-to-server response | ||
}); | ||
|
||
module.exports = { handler }; |
Oops, something went wrong.