Skip to content

Commit facfd9f

Browse files
committed
Merge branch 'main' into feat/onboarding
2 parents 7288b02 + 5cbdb7a commit facfd9f

File tree

11 files changed

+240
-14
lines changed

11 files changed

+240
-14
lines changed

.env.example

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
# Local Environment Variables (Secrets)
2-
# Copy this file to .env and fill in your actual values
3-
# .env is gitignored and should NEVER be committed
1+
# Discord Bot Credentials (REQUIRED - Secret values, do not commit!)
2+
DISCORD_TOKEN=your_discord_bot_token_here
3+
CLIENT_ID=your_discord_client_id_here
44

5-
# Discord Bot Token & Application ID (REQUIRED)
6-
# Get this from: https://discord.com/developers/applications
7-
DISCORD_TOKEN=your-bot-token-here
8-
CLIENT_ID=your-bot-application-id
5+
# Server Configuration (REQUIRED)
6+
SERVER_ID=your_server_id_here
97

10-
# Override any public config values for local testing
8+
# Channel IDs (REQUIRED)
9+
GUIDES_CHANNEL_ID=your_guides_channel_id_here
10+
ADVENT_OF_CODE_CHANNEL_ID=your_advent_of_code_forum_channel_id_here
11+
REPEL_LOG_CHANNEL_ID=your_repel_log_channel_id_here
1112

12-
# Discord Server ID (your dev server)
13-
SERVER_ID=your-server-id
13+
# Role IDs (REQUIRED)
14+
MODERATORS_ROLE_IDS=role_id_1,role_id_2,role_id_3
15+
REPEL_ROLE_ID=your_repel_role_id_here
1416

17+
# Optional Role IDs
18+
# ROLE_A_ID=optional_role_a_id
19+
# ROLE_B_ID=optional_role_b_id
20+
# ROLE_C_ID=optional_role_c_id
21+
22+
# Data Persistence (OPTIONAL)
23+
# Local development defaults to current directory
24+
# Docker deployments should use /app/data for persistence
1525
# Channel IDs (from your dev server)
1626
GUIDES_CHANNEL_ID=your-guide-channel-id
1727
REPEL_LOG_CHANNEL_ID=your-repel-log-channel-id
@@ -23,5 +33,7 @@ MODERATORS_ROLE_IDS=your-moderator-role-id
2333
ONBOARDING_ROLE_ID=onboarding-role-id
2434
# Other
2535
GUIDES_TRACKER_PATH=guides-tracker.json
36+
ADVENT_OF_CODE_TRACKER_PATH=advent-of-code-tracker.json
37+
2638

2739

.env.production

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ SERVER_ID=434487340535382016
88
# Channel IDs (from your dev server)
99
GUIDES_CHANNEL_ID=1429492053825290371
1010
REPEL_LOG_CHANNEL_ID=1403558160144531589
11+
ADVENT_OF_CODE_CHANNEL_ID=1047623689488830495
1112

1213
# Role IDs (from your dev server)
1314
REPEL_ROLE_ID=1002411741776461844
1415
MODERATORS_ROLE_IDS=849481536654803004
1516

1617
# Other
1718
GUIDES_TRACKER_PATH=/app/data/guides-tracker.json
19+
ADVENT_OF_CODE_TRACKER_PATH=/app/data/advent-of-code-tracker.json
1820

1921
# Note: DISCORD_TOKEN & CLIENT_ID should be in .env.local (not committed)

.env.test

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ SERVER_ID=your-server-id
1414

1515
# Channel IDs (from your dev server)
1616
GUIDES_CHANNEL_ID=your-guide-channel-id
17+
ADVENT_OF_CODE_CHANNEL_ID=your_advent_of_code_forum_channel_id_here
1718
REPEL_LOG_CHANNEL_ID=your-repel-log-channel-id
1819

1920
# Role IDs (from your dev server)
2021
REPEL_ROLE_ID=your-repel-role-id
2122
MODERATORS_ROLE_IDS=your-moderator-role-id
2223

2324
# Other
24-
GUIDES_TRACKER_PATH=guides-tracker.json
25+
GUIDES_TRACKER_PATH=guides-tracker.json
26+
ADVENT_OF_CODE_TRACKER_PATH=test-advent-tracker.json

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ yarn-error.log*
2020
!.env.example
2121
!.env.test
2222

23-
# guides tracker
23+
# tracker
2424
guides-tracker.json
25+
advent-of-code-tracker.json
2526

2627
# Docker
2728
docker-compose.yml

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ services:
1515
volumes:
1616
# Mount environment config file
1717
- ./.env.production:/app/.env.production:ro
18-
# Persist guides tracker data
18+
# Persist tracker data
1919
- guides-data:/app/data
2020
profiles:
2121
- prod

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"check": "biome check .",
2222
"check:fix": "biome check --write .",
2323
"typecheck": "tsc --noEmit",
24-
"test": "pnpm run build:dev && node --test dist/**/*.test.js",
24+
"test": "tsx --test '**/*.test.ts'",
2525
"test:ci": "NODE_ENV=test node --test dist/**/*.test.js",
2626
"prepare": "husky",
2727
"pre-commit": "lint-staged",
@@ -34,12 +34,14 @@
3434
"packageManager": "pnpm@10.17.1",
3535
"dependencies": {
3636
"discord.js": "^14.22.1",
37+
"node-cron": "^4.2.1",
3738
"typescript": "^5.9.3",
3839
"web-features": "^3.7.0"
3940
},
4041
"devDependencies": {
4142
"@biomejs/biome": "2.2.4",
4243
"@types/node": "^24.5.2",
44+
"@types/node-cron": "^3.0.11",
4345
"husky": "^9.1.7",
4446
"lint-staged": "^16.2.1",
4547
"tsup": "^8.5.0",

pnpm-lock.yaml

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

src/env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const config = {
2323
serverId: requireEnv('SERVER_ID'),
2424
fetchAndSyncMessages: true,
2525
guidesTrackerPath: optionalEnv('GUIDES_TRACKER_PATH'),
26+
adventOfCodeTrackerPath: requireEnv('ADVENT_OF_CODE_TRACKER_PATH'),
2627
roleIds: {
2728
moderators: requireEnv('MODERATORS_ROLE_IDS')
2829
? requireEnv('MODERATORS_ROLE_IDS').split(',')
@@ -35,6 +36,7 @@ export const config = {
3536
channelIds: {
3637
repelLogs: requireEnv('REPEL_LOG_CHANNEL_ID'),
3738
guides: requireEnv('GUIDES_CHANNEL_ID'),
39+
adventOfCode: requireEnv('ADVENT_OF_CODE_CHANNEL_ID'),
3840
},
3941
onboarding: {
4042
channelId: optionalEnv('ONBOARDING_CHANNEL_ID'),

src/events/ready.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Events } from 'discord.js';
22
import { config } from '../env.js';
3+
import { initializeAdventScheduler } from '../util/advent-scheduler.js';
34
import { fetchAndCachePublicChannelsMessages } from '../util/cache.js';
45
import { createEvent } from '../util/events.js';
56
import { syncGuidesToChannel } from '../util/post-guides.js';
@@ -44,5 +45,12 @@ export const readyEvent = createEvent(
4445
}
4546
}
4647
}
48+
49+
// Initialize Advent of Code scheduler
50+
try {
51+
initializeAdventScheduler(client, config.channelIds.adventOfCode);
52+
} catch (error) {
53+
console.error('❌ Failed to initialize Advent of Code scheduler:', error);
54+
}
4755
}
4856
);

src/util/advent-scheduler.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import assert from 'node:assert/strict';
2+
import { promises as fs } from 'node:fs';
3+
import test from 'node:test';
4+
import { config } from '../env.js';
5+
6+
// Import after setting env var
7+
const { loadTracker, saveTracker } = await import('./advent-scheduler.js');
8+
9+
async function cleanupTestTracker() {
10+
try {
11+
await fs.unlink(config.adventOfCodeTrackerPath);
12+
} catch (_error) {
13+
// File might not exist, that's fine
14+
}
15+
}
16+
17+
test('advent scheduler: tracker file operations', async (t) => {
18+
await t.test('should create empty tracker if file does not exist', async () => {
19+
await cleanupTestTracker();
20+
const tracker = await loadTracker();
21+
assert.deepEqual(tracker, {});
22+
});
23+
24+
await t.test('should save and load tracker data correctly', async () => {
25+
const testData = {
26+
'2025': [1, 2, 3],
27+
'2026': [1],
28+
};
29+
await saveTracker(testData);
30+
const loaded = await loadTracker();
31+
assert.deepEqual(loaded, testData);
32+
});
33+
34+
await t.test('should track multiple days per year', async () => {
35+
const tracker = {
36+
'2025': [1, 5, 10, 15, 20, 25],
37+
};
38+
await saveTracker(tracker);
39+
const loaded = await loadTracker();
40+
assert.equal(loaded['2025'].length, 6);
41+
assert.ok(loaded['2025'].includes(1));
42+
assert.ok(loaded['2025'].includes(25));
43+
});
44+
45+
// Cleanup
46+
await cleanupTestTracker();
47+
});

0 commit comments

Comments
 (0)