Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/_reusable_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
- name: Biome Check
run: npm run check

- name: Test
run: npm test

- name: TypeCheck
run: npm run typecheck

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/_reusable_lambda_rie_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- run: npm ci

- name: Bundle handler
run: npx esbuild index.ts --bundle --platform=node --target=node24 --external:@slack/bolt --outfile=dist/index.js
run: npx esbuild src/handler.ts --bundle --platform=node --target=node24 --external:@slack/bolt --outfile=dist/index.js

- name: Prepare Lambda artifact
run: cp -R node_modules dist/
Expand Down
91 changes: 0 additions & 91 deletions index.ts

This file was deleted.

21 changes: 7 additions & 14 deletions infra/lib/times-all-bot-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,26 @@ import * as iam from 'aws-cdk-lib/aws-iam';
import * as runtime from 'aws-cdk-lib/aws-lambda';
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
import type { Construct } from 'constructs';
import { loadEnv } from '../../src/env.ts';

export class TimesAllBotStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const slackBotToken = process.env['SLACK_BOT_TOKEN'];
if (!slackBotToken) throw new Error('SLACK_BOT_TOKEN is required');
const slackSigningSecret = process.env['SLACK_SIGNING_SECRET'];
if (!slackSigningSecret)
throw new Error('SLACK_SIGNING_SECRET is required');
const timesAllChannelId = process.env['TIMES_ALL_CHANNEL_ID'];
if (!timesAllChannelId) throw new Error('TIMES_ALL_CHANNEL_ID is required');
const workspaceUrl = process.env['WORKSPACE_URL'];
if (!workspaceUrl) throw new Error('WORKSPACE_URL is required');
const env = loadEnv();

const projectRoot = path.join(import.meta.dirname, '../..');

const fn = new lambda.NodejsFunction(this, 'SlackHandler', {
entry: path.join(projectRoot, 'index.ts'),
entry: path.join(projectRoot, 'src/handler.ts'),
handler: 'handler',
runtime: runtime.Runtime.NODEJS_24_X,
timeout: cdk.Duration.seconds(30),
environment: {
SLACK_BOT_TOKEN: slackBotToken,
SLACK_SIGNING_SECRET: slackSigningSecret,
TIMES_ALL_CHANNEL_ID: timesAllChannelId,
WORKSPACE_URL: workspaceUrl,
SLACK_BOT_TOKEN: env.slackBotToken,
SLACK_SIGNING_SECRET: env.slackSigningSecret,
TIMES_ALL_CHANNEL_ID: env.timesAllChannelId,
WORKSPACE_URL: env.workspaceUrl,
},
bundling: {
nodeModules: ['@slack/bolt'],
Expand Down
19 changes: 17 additions & 2 deletions infra/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion infra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},
"dependencies": {
"aws-cdk-lib": "^2.245.0",
"constructs": "^10.6.0"
"constructs": "^10.6.0",
"valibot": "^1.3.1"
},
"devDependencies": {
"@tsconfig/strictest": "^2.0.8",
Expand Down
2 changes: 1 addition & 1 deletion infra/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"types": ["node"],
"resolveJsonModule": true
},
"include": ["bin/**/*.ts", "lib/**/*.ts"]
"include": ["bin/**/*.ts", "lib/**/*.ts", "../src/env.ts"]
}
30 changes: 0 additions & 30 deletions joinBotToTimesChannels.ts

This file was deleted.

19 changes: 17 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
"description": "repost times-* channel to \"time-all\"",
"type": "module",
"scripts": {
"dev": "node --env-file=.env index.ts",
"dev": "node --env-file=.env src/handler.ts",
"lint": "biome check --write .",
"format": "biome format .",
"check": "biome check .",
"typecheck": "tsc --noEmit"
"typecheck": "tsc",
"test": "node --test 'src/**/*.test.ts'"
Comment thread
eai04191 marked this conversation as resolved.
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@slack/bolt": "^4.6.0"
"@slack/bolt": "^4.6.0",
"valibot": "^1.3.1"
},
"devDependencies": {
"@biomejs/biome": "2.4.9",
Expand Down
61 changes: 61 additions & 0 deletions src/__tests__/env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import assert from 'node:assert/strict';
import { afterEach, beforeEach, describe, it } from 'node:test';
import * as v from 'valibot';
import { loadEnv } from '../env.ts';

describe('loadEnv', () => {
const validEnv = {
SLACK_SIGNING_SECRET: 'secret',
SLACK_BOT_TOKEN: 'xoxb-token',
TIMES_ALL_CHANNEL_ID: 'C12345',
WORKSPACE_URL: 'https://workspace.slack.com',
};

let originalEnv: NodeJS.ProcessEnv;

beforeEach(() => {
originalEnv = { ...process.env };
for (const [key, value] of Object.entries(validEnv)) {
process.env[key] = value;
}
});

afterEach(() => {
process.env = originalEnv;
Comment thread
eai04191 marked this conversation as resolved.
Outdated
});

it('すべての環境変数が設定されている場合はEnvオブジェクトを返す', () => {
const env = loadEnv();
assert.deepEqual(env, {
slackSigningSecret: 'secret',
slackBotToken: 'xoxb-token',
timesAllChannelId: 'C12345',
workspaceUrl: 'https://workspace.slack.com',
});
});

it('SLACK_SIGNING_SECRETが未設定の場合はValiErrorを投げる', () => {
delete process.env['SLACK_SIGNING_SECRET'];
assert.throws(() => loadEnv(), v.ValiError);
});

it('SLACK_BOT_TOKENが未設定の場合はValiErrorを投げる', () => {
delete process.env['SLACK_BOT_TOKEN'];
assert.throws(() => loadEnv(), v.ValiError);
});

it('TIMES_ALL_CHANNEL_IDが未設定の場合はValiErrorを投げる', () => {
delete process.env['TIMES_ALL_CHANNEL_ID'];
assert.throws(() => loadEnv(), v.ValiError);
});

it('WORKSPACE_URLが未設定の場合はValiErrorを投げる', () => {
delete process.env['WORKSPACE_URL'];
assert.throws(() => loadEnv(), v.ValiError);
});

it('WORKSPACE_URLが不正なURLの場合はValiErrorを投げる', () => {
process.env['WORKSPACE_URL'] = 'not-a-url';
assert.throws(() => loadEnv(), v.ValiError);
});
});
25 changes: 25 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { App, AwsLambdaReceiver } from '@slack/bolt';
import type { Env } from './env.ts';
import { handleChannelCreated } from './handlers/channelCreated.ts';
import { createMessageHandler } from './handlers/message.ts';

export interface SlackApp {
app: App;
receiver: AwsLambdaReceiver;
}

export function createSlackApp(env: Env): SlackApp {
const receiver = new AwsLambdaReceiver({
signingSecret: env.slackSigningSecret,
});

const app = new App({
token: env.slackBotToken,
receiver,
});

app.message(createMessageHandler(env.timesAllChannelId, env.workspaceUrl));
app.event('channel_created', handleChannelCreated);

return { app, receiver };
}
Loading