Skip to content

Commit 74a1f55

Browse files
Add api route
1 parent 7b5e691 commit 74a1f55

7 files changed

Lines changed: 94 additions & 21 deletions

File tree

.env.template

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ FORM_TRACKER_ID=
1313
TRACKER_SHEET_NAME=
1414
FORM_INFO_SHEET_NAME=
1515

16-
PORT=3000
16+
# Express server configuration
17+
PORT=
18+
API_SECRET=

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,26 @@ Configurable coffee chat pairings to help team members get to know each other be
119119
- **⏸️ Pause Future Pairings** — Opt out of all future pairings
120120
- After opting out, a **▶️ Resume Pairings** button lets users opt back in
121121
- Pairings are tracked in MongoDB to prevent frequent repeats
122+
123+
### 3. API Endpoints
124+
125+
The bot exposes an HTTP server (default port `3000`) designed to receive notifications from external services.
126+
127+
#### `POST /api/send-message`
128+
129+
Sends a message or a rich Block Kit payload to a specified Slack channel as the bot.
130+
131+
**Headers:**
132+
133+
- `Authorization: Bearer <API_SECRET>`
134+
- `Content-Type: application/json`
135+
136+
**Body Payload:**
137+
138+
```json
139+
{
140+
"channelId": "C01234567",
141+
"text": "Fallback notification text",
142+
"blocks": []
143+
}
144+
```

package-lock.json

Lines changed: 5 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"@typegoose/typegoose": "^13.1.0",
1010
"@types/node": "^24.10.13",
1111
"dotenv": "^17.3.1",
12+
"express": "^5.2.1",
1213
"google-auth-library": "^10.5.0",
1314
"google-spreadsheet": "^5.2.0",
1415
"moment-timezone": "^0.6.0",
@@ -28,6 +29,7 @@
2829
},
2930
"devDependencies": {
3031
"@eslint/js": "^10.0.1",
32+
"@types/express": "^5.0.6",
3133
"@types/jest": "^29.5.14",
3234
"@types/node-cron": "^3.0.11",
3335
"eslint": "^10.0.2",

src/api/routes.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import express, { Express } from "express";
2+
import slackbot from "../slackbot";
3+
import { logWithTime } from "../utils/timeUtils";
4+
5+
export const registerApiRoutes = (app: Express) => {
6+
app.use(express.json());
7+
8+
app.post("/api/send-message", async (req, res) => {
9+
const authHeader = req.headers.authorization;
10+
const apiSecret = process.env.API_SECRET;
11+
12+
if (!apiSecret || authHeader !== `Bearer ${apiSecret}`) {
13+
res.status(401).send({ success: false, error: "Unauthorized" });
14+
return;
15+
}
16+
17+
const { channelId, text, blocks } = req.body;
18+
19+
if (!channelId || !text) {
20+
res
21+
.status(400)
22+
.send({ success: false, error: "Missing channelId or text" });
23+
return;
24+
}
25+
26+
try {
27+
await slackbot.client.chat.postMessage({
28+
channel: channelId,
29+
text: text,
30+
...(blocks ? { blocks } : {}),
31+
});
32+
res.status(200).send({ success: true });
33+
} catch (error) {
34+
const err = error as Error;
35+
logWithTime(`Error sending slack message: ${err.message}`);
36+
res.status(500).send({ success: false, error: err.message });
37+
}
38+
});
39+
};

src/app.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import cron from "node-cron";
2+
import express from "express";
23
import { dbConnect } from "./database";
3-
import { sendFormReminders } from "./forms/formService";
4+
// import { sendFormReminders } from "./forms/formService";
45
import slackbot from "./slackbot";
56
import { logWithTime } from "./utils/timeUtils";
7+
import { registerApiRoutes } from "./api/routes";
68
import {
79
createNewCoffeeChatRounds,
810
reportStats,
@@ -15,10 +17,10 @@ import { registerWelcomeHandler } from "./coffeeChats/coffeeChatWelcome";
1517
export const SEMESTER = "sp24";
1618
export const DEFAULT_PAIRING_FREQUENCY_DAYS = 14; // Default to every 2 weeks
1719

18-
const initializeFormServices = async () => {
19-
await sendFormReminders();
20-
setInterval(sendFormReminders, 1000 * 60 * 60 * 24); // Run every 24 hours
21-
};
20+
// const initializeFormServices = async () => {
21+
// await sendFormReminders();
22+
// setInterval(sendFormReminders, 1000 * 60 * 60 * 24); // Run every 24 hours
23+
// };
2224

2325
const initializeCoffeeChatServices = async () => {
2426
// Register coffee chat actions and commands
@@ -57,10 +59,18 @@ const initializeCoffeeChatServices = async () => {
5759
logWithTime("✅ Midway reminders scheduled to run daily at 4pm");
5860
};
5961

62+
// Set up custom API endpoints
63+
const apiServer = express();
64+
registerApiRoutes(apiServer);
65+
6066
export const startServer = async () => {
6167
await dbConnect();
6268
logWithTime("✅ Connected to MongoDB");
63-
await slackbot.start(process.env.PORT || 3000);
69+
await slackbot.start();
70+
const port = Number(process.env.PORT) || 3000;
71+
apiServer.listen(port, () => {
72+
logWithTime(`✅ API Server listening on port ${port}`);
73+
});
6474
logWithTime("✅ Slackbot up and running!");
6575

6676
// Currently unused, so commenting out to avoid unnecessary API calls and logs. Can re-enable when form services are needed.

src/coffeeChats/coffeeChatCommands.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export function registerCoffeeChatCommands(slackbot: App) {
164164
type: "section",
165165
text: {
166166
type: "mrkdwn",
167-
text: `📅 Next automatic pairing will be on ${nextPairingDate.format("dddd (MMM Do)")} at ${nextPairingDate.format("h:mm A z")}`,
167+
text: `📅 Next automatic pairing will be on ${nextPairingDate.format("dddd (MMM Do)")}`,
168168
},
169169
},
170170
],
@@ -385,7 +385,11 @@ export function registerCoffeeChatCommands(slackbot: App) {
385385
let status = "";
386386
if (pairing.meetupConfirmed) {
387387
status = "✅ Met";
388-
} else if (moment(pairing.dueDate).tz("America/New_York").isAfter(moment().tz("America/New_York"))) {
388+
} else if (
389+
moment(pairing.dueDate)
390+
.tz("America/New_York")
391+
.isAfter(moment().tz("America/New_York"))
392+
) {
389393
status = "⏳ Pending";
390394
} else {
391395
status = "❌ Did not meet";

0 commit comments

Comments
 (0)