Skip to content

Commit

Permalink
Remove config document to simplify things (#12)
Browse files Browse the repository at this point in the history
* Remove config document to simplify things

* Update README

* Update README again
  • Loading branch information
justinyaodu authored Oct 5, 2023
1 parent 488660a commit 2a85e49
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 237 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ SLACK_SIGNING_SECRET=
# MongoDB connection URI, including credentials:
MONGODB_URI=mongodb://127.0.0.1:27017/muffin

# How often to run periodic jobs (e.g. checking for and sending scheduled messages):
PERIODIC_JOB_INTERVAL_SEC=60

# Whether to mock scheduled messages. Should be set to false in production.
MOCK_SCHEDULED_MESSAGES=false
19 changes: 3 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,11 @@ If you send muffin the word "help" as a direct message, it should react to your
To schedule pairings, use the following command:

```
round_schedule #my-channel 2023-06-05T10:00:00-0700
round_schedule #my-channel 2023-06-05T10:00:00-0700 2w
```

Everyone in that channel will be paired up at the specified date and time. This is an ISO 8601 date string; be sure to replace `-0700` with the offset of your time zone.

You can repeat this command with different dates (and even channels) to schedule as many rounds as you like. If you omit the date, muffin will schedule a round that starts after the previous round ends.
`2w` specifies a duration of two weeks. Also try days (`4d`) or even hours and minutes (`1h10m`).

## Configuration

muffin allows you to customize how long each round lasts, and when the various
messages are sent. However, there is currently no user interface for these config options.

Instead, you can update the config document directly in MongoDB. For example, using mongosh to make each round last one week instead of two:

```js
db.configs.updateOne({}, { $set: { roundDurationDays: 7 } });
```

After updating the config document, use the `reload_config` command to apply your changes.

See [ConfigModel.ts](src/models/ConfigModel.ts) for the available config options.
You can repeat this command with different dates (and even channels) to schedule as many rounds as you like. If you're scheduling multiple consecutive rounds in the same channel, you can schedule the first one as usual, then use `round_repeat #my-channel`.
27 changes: 13 additions & 14 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import mongoose from "mongoose";
import env from "./env";
import { onReactionAddedToMessage } from "./handlers/reaction";
import { JobRunner } from "./jobs/runner";
import { cacheProvider } from "./services/config-cache";
import { addReaction, getUserInfo } from "./services/slack";
import { addReaction, getBotUserId, getUserInfo } from "./services/slack";
import { CommandContext, runCommand } from "./shell/commands";
import { shell } from "./shell/shell";
import { formatUser } from "./util/formatting";
Expand Down Expand Up @@ -75,8 +74,7 @@ app.event("reaction_added", async (context) => {

app.event("app_mention", async (context) => {
// Only respond to messages that start with a mention.
const botUserId = (await cacheProvider.get(app)).botUserId;
const botMention = formatUser(botUserId);
const botMention = formatUser(env.BOT_USER_ID);
let text = context.event.text.trimStart();
if (!text.startsWith(botMention)) {
// TODO: use postEphemeral to send a hint to the user?
Expand All @@ -102,25 +100,26 @@ app.message(async (context) => {
});

async function main() {
console.log(env);

console.log("connecting to MongoDB...");
await mongoose.connect(env.MONGODB_URI);
console.log("connected to MongoDB!");

console.log(`starting Bolt app on port: ${env.PORT}`);
console.log(`starting Bolt app...`);
await app.start();
console.log("started Bolt app!");

try {
await cacheProvider.get(app);
} catch (e) {
const message = "failed to initialize config cache";
console.error(`${message}: stopping Bolt app...`);
await app.stop();

throw new Error(message, { cause: e });
const botUserIdResult = await getBotUserId(app);
if (botUserIdResult.ok) {
const botUserId = botUserIdResult.value;
(env as { BOT_USER_ID: string }).BOT_USER_ID = botUserId;
console.log(`bot user ID: ${botUserId}`);
} else {
throw new Error(botUserIdResult.error);
}

const runner = await JobRunner.create(app);
const runner = new JobRunner(app, env.PERIODIC_JOB_INTERVAL_SEC);
await runner.run();

shell(app).catch(console.error);
Expand Down
25 changes: 25 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,40 @@ function assertBooleanEnvVar(
);
}

function assertIntegerEnvVar(
env: Record<string, string | undefined>,
key: string,
): number {
const value = assertEnvVar(env, key);

if (/^[0-9]+$/.test(value)) {
const parsed = parseInt(value, 10);
if (!Number.isNaN(parsed)) {
return parsed;
}
}

throw new Error(
`Environment variable '${key}' cannot be parsed as an integer.`,
);
}

const env = {
PORT: getPort(),
MONGODB_URI: assertEnvVar(process.env, "MONGODB_URI"),
SLACK_BOT_TOKEN: assertEnvVar(process.env, "SLACK_BOT_TOKEN"),
SLACK_SIGNING_SECRET: assertEnvVar(process.env, "SLACK_SIGNING_SECRET"),
PERIODIC_JOB_INTERVAL_SEC: assertIntegerEnvVar(
process.env,
"PERIODIC_JOB_INTERVAL_SEC",
),
MOCK_SCHEDULED_MESSAGES: assertBooleanEnvVar(
process.env,
"MOCK_SCHEDULED_MESSAGES",
),

// Initialized at application startup.
BOT_USER_ID: "",
} as const;

export default env;
34 changes: 7 additions & 27 deletions src/jobs/runner.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
import { App } from "@slack/bolt";

import { cacheProvider } from "../services/config-cache";
import { IntervalTimer } from "../util/timer";

import { allJobs } from "./jobs";

class JobRunner {
private reloadPending = false;
timer: IntervalTimer;

private constructor(
constructor(
private app: App,
public timer: IntervalTimer,
) {}

static async create(app: App): Promise<JobRunner> {
const timer = await JobRunner.getTimer(app);
const runner = new JobRunner(app, timer);
cacheProvider.addObserver(runner);
return runner;
}

private async reload() {
this.timer = await JobRunner.getTimer(this.app);
periodicJobIntervalSec: number,
) {
this.timer = JobRunner.getTimer(periodicJobIntervalSec);
}

private static async getTimer(app: App): Promise<IntervalTimer> {
const config = (await cacheProvider.get(app)).config;
const intervalMs = config.periodicJobIntervalSec * 1000;
private static getTimer(periodicJobIntervalSec: number): IntervalTimer {
const intervalMs = periodicJobIntervalSec * 1000;

// Round down to the preceding multiple of intervalMs. This ensures that,
// if the interval is one hour (for example), the intervals are aligned to
Expand Down Expand Up @@ -60,15 +49,6 @@ class JobRunner {
console.log("(err)");
}
}

if (this.reloadPending) {
await this.reload();
this.reloadPending = false;
}
}

onConfigCacheReload() {
this.reloadPending = true;
}
}

Expand Down
73 changes: 0 additions & 73 deletions src/models/ConfigModel.ts

This file was deleted.

88 changes: 0 additions & 88 deletions src/services/config-cache.ts

This file was deleted.

7 changes: 4 additions & 3 deletions src/services/group.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { App } from "@slack/bolt";
import mongoose from "mongoose";

import env from "../env";
import { GroupModel } from "../models/GroupModel";
import { RoundDocument } from "../models/RoundModel";
import { Result } from "../util/result";

import { cacheProvider } from "./config-cache";
import { getConversationMembers } from "./slack";

/**
Expand All @@ -22,8 +22,9 @@ async function getUsersToMatch(
}

// Ensure that we don't pair anyone with this bot.
const botUserId = (await cacheProvider.get(app)).botUserId;
const filtered = getMembersResult.value.filter((user) => user !== botUserId);
const filtered = getMembersResult.value.filter(
(user) => user !== env.BOT_USER_ID,
);

return Result.ok(filtered);
}
Expand Down
Loading

0 comments on commit 2a85e49

Please sign in to comment.