diff --git a/.config b/.config new file mode 100644 index 0000000..e69de29 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..7434fb5 --- /dev/null +++ b/.env.development @@ -0,0 +1 @@ +MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:27017/mongoose_test \ No newline at end of file diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..7434fb5 --- /dev/null +++ b/.env.local @@ -0,0 +1 @@ +MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:27017/mongoose_test \ No newline at end of file diff --git a/config.js b/config.js index 8cb3dd1..d8ca5c0 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,7 @@ 'use strict'; +console.log('Config file is loading...'); + if (!process.env.NODE_ENV) { process.env.NODE_ENV = 'local'; } @@ -11,7 +13,16 @@ const path = require('path'); console.log('NODE_ENV =', env); if (!['development', 'production'].includes(process.env.NODE_ENV)) { - dotenv.config({ + const result = dotenv.config({ path: path.resolve(__dirname, `.env.${env.toLocaleLowerCase()}`) }); + if (result.error) { + console.error('Error loading .env file:', result.error); + } else { + console.log('Loaded .env file:', path.resolve(__dirname, `.env.${env.toLocaleLowerCase()}`)); + console.log('Environment variables:', { + MONGODB_CONNECTION_STRING: process.env.MONGODB_CONNECTION_STRING ? '[SET]' : '[NOT SET]', + NODE_ENV: process.env.NODE_ENV + }); + } } diff --git a/index.js b/index.js index 11abb89..287419e 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,8 @@ require('dotenv').config(); +require('./config'); + const cors = require('cors'); const connect = require('./src/db'); const express = require('express'); diff --git a/netlify/functions/notifySlack.js b/netlify/functions/notifySlack.js new file mode 100644 index 0000000..34ed209 --- /dev/null +++ b/netlify/functions/notifySlack.js @@ -0,0 +1,5 @@ +'use strict'; + +const extrovert = require('extrovert'); + +module.exports = extrovert.toNetlifyFunction(require('../../src/actions/notifySlack')); diff --git a/package.json b/package.json index b18f2a2..07236da 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "scripts": { "seed": "env NODE_ENV=development node ./seed", "start": "node .", + "start:local": "env NODE_ENV=local node .", + "start:development": "env NODE_ENV=development node .", "test": "env NODE_ENV=test mocha -r ./config test/*.test.js", "lint": "eslint ." } diff --git a/src/actions/notifySlack.js b/src/actions/notifySlack.js new file mode 100644 index 0000000..f1b470e --- /dev/null +++ b/src/actions/notifySlack.js @@ -0,0 +1,78 @@ +'use strict'; + +const Archetype = require('archetype'); +const connect = require('../../src/db'); +const slack = require('../integrations/slack'); + +const NotifySlackParams = new Archetype({ + workspaceId: { + $type: 'string', + $required: true + }, + modelName: { + $type: 'string', + $required: true + }, + documentId: { + $type: 'string', + $required: true + }, + purpose: { + $type: 'string', + $required: true + } +}).compile('NotifySlackParams'); + +module.exports = async function notifySlack(params) { + const { workspaceId, modelName, documentId, purpose } = new NotifySlackParams(params); + + const db = await connect(); + const { Workspace } = db.models; + + const workspace = await Workspace.findById(workspaceId).orFail(); + + if (!workspace.slackWebhooks || workspace.slackWebhooks.length === 0) { + throw new Error('Workspace does not have any Slack webhook URLs configured'); + } + + const blocks = [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: `A new document was created in the *${modelName}* model.\n*Document ID:* \`${documentId}\`` + } + } + ]; + + if (workspace.baseUrl) { + const url = `${workspace.baseUrl}/model/${modelName}/document/${documentId}`; + blocks.push({ + type: 'section', + text: { + type: 'mrkdwn', + text: `<${url}|View Document>` + } + }); + } + + const messagePayload = { + blocks + }; + + const matchingWebhooks = workspace.slackWebhooks.filter(webhook => { + return webhook.enabled !== false && + webhook.purposes && + webhook.purposes.includes(purpose); + }); + + if (matchingWebhooks.length === 0) { + throw new Error(`No enabled webhooks found for purpose: ${purpose}`); + } + + await Promise.all( + matchingWebhooks.map(webhook => slack.sendWebhook(webhook.url, messagePayload)) + ); + + return { success: true }; +}; diff --git a/src/db/workspace.js b/src/db/workspace.js index 7718536..be0bbea 100644 --- a/src/db/workspace.js +++ b/src/db/workspace.js @@ -41,7 +41,45 @@ const workspaceSchema = new mongoose.Schema({ subscriptionTier: { type: String, enum: ['', 'free', 'pro'] - } + }, + slackWebhooks: [{ + _id: false, + url: { + type: String, + required: true + }, + name: { + type: String + }, + purposes: [{ + type: String, + required: true, + enum: ['documentCreated'] + }], + enabled: { + type: Boolean, + default: true + } + }], + discordWebhooks: [{ + _id: false, + url: { + type: String, + required: true + }, + name: { + type: String + }, + purposes: [{ + type: String, + required: true, + enum: ['documentCreated'] + }], + enabled: { + type: Boolean, + default: true + } + }] }, { timestamps: true, id: false }); workspaceSchema.index({ apiKey: 1 }, { unique: true }); diff --git a/src/integrations/slack.js b/src/integrations/slack.js index 1a240dc..cf3c881 100644 --- a/src/integrations/slack.js +++ b/src/integrations/slack.js @@ -8,5 +8,20 @@ module.exports = { const url = 'https://slack.com/api/chat.postMessage'; // Send to Slack await axios.post(url, jobs, { headers: { authorization: `Bearer ${config.slackToken}` } }); + }, + async sendWebhook(webhookUrl, payload) { + const response = await fetch(webhookUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + throw new Error(`Slack webhook request failed with status ${response.status}`); + } + + return response; } }; \ No newline at end of file