Skip to content

Commit c06e24b

Browse files
authored
✨ implement member mention (#52)
1 parent c573d93 commit c06e24b

7 files changed

Lines changed: 122 additions & 52 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type DeployWebhookController = {
2+
handle: (body: unknown) => void;
3+
};

src/infrastructures/implementDeploymentService.ts

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,29 @@ import { type GithubDeploymentService } from '../services/GithubDeploymentServic
33

44
const identifierToSlackTs: Record<string, string> = {};
55

6+
const unknown = '@unknown';
7+
68
export const implementDeploymentService = ({
79
messengerPresenter,
810
}: {
911
messengerPresenter: MessengerPresenter;
1012
}): GithubDeploymentService => {
1113
return {
12-
handleCreateRelease: async (body) => {
13-
const author: string = body.release.author.login;
14-
const releaseBody: string = body.release.body;
15-
const tag: string = body.release.tag_name;
16-
const releaseUrl: string = body.release.html_url;
17-
const repository = body.repository.name;
18-
19-
const changes = releaseBody.split('\n').reduce<{ content: string; contributor: string }[]>((acc, cur) => {
20-
const match = cur.match(/\* (.*) by @(.*) in (.*)/);
21-
if (!match) return acc;
22-
return [...acc, { content: match[1], contributor: match[2] }];
23-
}, []);
24-
25-
const { ts } = await messengerPresenter.sendMessage(({ formatEmoji, formatLink }) => ({
14+
handleCreateRelease: async ({ changes, author, tag, releaseUrl, repository }) => {
15+
const { ts } = await messengerPresenter.sendMessage(({ formatMemberMention, formatEmoji, formatLink }) => ({
2616
text: [
27-
`${formatEmoji('rocket')} *${repository}* by @${author} (${formatLink(tag, { url: releaseUrl })})`,
28-
...changes.map((c) => ` - ${c.content} @${c.contributor}`),
17+
`${formatEmoji('rocket')} *${repository}* by ${author ? formatMemberMention(author) : unknown} (${formatLink(
18+
tag,
19+
{ url: releaseUrl },
20+
)})`,
21+
...changes.map((c) => ` - ${c.content} by ${c.author ? formatMemberMention(c.author) : unknown}`),
2922
].join('\n'),
3023
}));
3124

3225
identifierToSlackTs[toIdentifier({ tag, repository })] = ts;
3326
},
34-
handleActionStart: async (body) => {
35-
const workflowName: string = body.workflow_run.name;
36-
const tag: string = body.workflow_run.head_branch;
37-
const repository: string = body.repository.name;
38-
const workflowId: number = body.workflow_run.id;
39-
const workflowUrl: string = body.workflow_run.html_url;
40-
41-
const isDeploy = workflowName.toLowerCase().includes('deploy');
42-
if (!isDeploy) return;
27+
handleActionStart: async ({ workflowName, workflowId, workflowUrl, tag, repository }) => {
28+
if (!isDeployWorkflow(workflowName)) return;
4329

4430
const ts = identifierToSlackTs[toIdentifier({ tag, repository })];
4531
if (!ts) return;
@@ -53,15 +39,8 @@ export const implementDeploymentService = ({
5339
options: { ts },
5440
}));
5541
},
56-
handleActionComplete: async (body) => {
57-
const workflowName: string = body.workflow_run.name;
58-
const tag: string = body.workflow_run.head_branch;
59-
const repository: string = body.repository.name;
60-
const workflowId: number = body.workflow_run.id;
61-
const workflowUrl: string = body.workflow_run.html_url;
62-
63-
const isDeploy = workflowName.toLowerCase().includes('deploy');
64-
if (!isDeploy) return;
42+
handleActionComplete: async ({ workflowName, workflowId, workflowUrl, tag, repository }) => {
43+
if (!isDeployWorkflow(workflowName)) return;
6544

6645
const ts = identifierToSlackTs[toIdentifier({ tag, repository })];
6746
if (!ts) return;
@@ -79,3 +58,4 @@ export const implementDeploymentService = ({
7958
};
8059

8160
const toIdentifier = ({ tag, repository }: { repository: string; tag: string }) => `${repository}:${tag}`;
61+
const isDeployWorkflow = (workflowName: string) => workflowName.toLowerCase().includes('deploy');
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { type DeployWebhookController } from '../controllers/DeployWebhookConrtoller';
2+
import { Member } from '../entities/Member';
3+
import { type GithubDeploymentService } from '../services/GithubDeploymentService';
4+
5+
type ReleaseBody = {
6+
release: { author: { login: string }; body: string; tag_name: string; html_url: string };
7+
repository: { name: string };
8+
};
9+
10+
type WorkflowRunBody = {
11+
workflow_run: { name: string; head_branch: string; id: number; html_url: string };
12+
repository: { name: string };
13+
};
14+
15+
export const implementGitHubDeployWebhookController = ({
16+
deploymentService,
17+
}: {
18+
deploymentService: GithubDeploymentService;
19+
}): DeployWebhookController => {
20+
return {
21+
handle: (body) => {
22+
if (!body || typeof body !== 'object' || !('action' in body)) throw new Error('400');
23+
24+
if ('release' in body && body.action === 'released') {
25+
const releaseBody = body as unknown as ReleaseBody;
26+
27+
return deploymentService.handleCreateRelease({
28+
author: GITHUB_ID_MEMBER_MAP[releaseBody.release.author.login],
29+
changes: releaseBody.release.body
30+
.split('\n')
31+
.reduce<{ content: string; author: Member | undefined }[]>((acc, cur) => {
32+
const match = cur.match(/\* (.*) by @(.*) in (.*)/);
33+
if (!match) return acc;
34+
return [...acc, { content: match[1], author: GITHUB_ID_MEMBER_MAP[match[2]] }];
35+
}, []),
36+
tag: releaseBody.release.tag_name,
37+
releaseUrl: releaseBody.release.html_url,
38+
repository: releaseBody.repository.name,
39+
});
40+
}
41+
42+
if ('workflow_run' in body) {
43+
const workflowRunBody = body as unknown as WorkflowRunBody;
44+
45+
const params = {
46+
workflowName: workflowRunBody.workflow_run.name,
47+
tag: workflowRunBody.workflow_run.head_branch,
48+
repository: workflowRunBody.repository.name,
49+
workflowId: workflowRunBody.workflow_run.id,
50+
workflowUrl: workflowRunBody.workflow_run.html_url,
51+
};
52+
53+
return body.action === 'requested'
54+
? deploymentService.handleActionStart(params)
55+
: body.action === 'completed'
56+
? deploymentService.handleActionComplete(params)
57+
: null;
58+
}
59+
},
60+
};
61+
};
62+
63+
const GITHUB_ID_MEMBER_MAP: Record<string, Member | undefined> = {
64+
woohm402: Member.WOOHM402,
65+
JuTak97: Member.JUTAK97,
66+
};

src/infrastructures/implementSlackPresenter.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Member } from '../entities/Member';
12
import { type MessageHelper, type MessengerPresenter } from '../presenters/MessengerPresenter';
23

34
export const implementSlackPresenter = ({
@@ -22,6 +23,7 @@ const helpers: MessageHelper = {
2223
formatChannel: (channelId: string) => `<#${channelId}>`,
2324
formatEmoji: (emoji: string) => `:${emoji}:`,
2425
formatBold: (text: string) => `*${text}*`,
26+
formatMemberMention: (member) => `<@${MEMBER_SLACK_ID_MAP[member]}>`,
2527
};
2628

2729
const postMessage = async ({
@@ -44,3 +46,8 @@ const postMessage = async ({
4446
if (!data.ok) throw data;
4547
return data as { ts: string };
4648
};
49+
50+
const MEMBER_SLACK_ID_MAP = {
51+
[Member.WOOHM402]: 'U01JQM3GNBW',
52+
[Member.JUTAK97]: 'U030UCYA7U3',
53+
};

src/presenters/MessengerPresenter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { type Member } from '../entities/Member';
2+
13
export type MessageHelper = {
24
formatLink: (text: string, options: { url: string }) => string;
35
formatEmoji: (emoji: SupportedEmoji) => string;
46
formatChannel: (channelId: string) => string;
57
formatBold: (text: string) => string;
8+
formatMemberMention: (member: Member) => string;
69
};
710

811
type MessageGetter = (helper: MessageHelper) => { text: string; options?: { ts?: string } };

src/server.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
22
import express from 'express';
33

44
import { implementDeploymentService } from './infrastructures/implementDeploymentService';
5+
import { implementGitHubDeployWebhookController } from './infrastructures/implementGitHubDeployWebhookController';
56
import { implementSlackEventService } from './infrastructures/implementSlackEventService';
67
import { implementSlackPresenter } from './infrastructures/implementSlackPresenter';
78

@@ -34,10 +35,12 @@ const slackService = implementSlackEventService({
3435
channelId: slackWatcherChannelId,
3536
}),
3637
});
37-
const deploymentService = implementDeploymentService({
38-
messengerPresenter: implementSlackPresenter({
39-
slackAuthToken,
40-
channelId: deployWatcherChannelId,
38+
const deployWebhookController = implementGitHubDeployWebhookController({
39+
deploymentService: implementDeploymentService({
40+
messengerPresenter: implementSlackPresenter({
41+
slackAuthToken,
42+
channelId: deployWatcherChannelId,
43+
}),
4144
}),
4245
});
4346

@@ -67,11 +70,7 @@ app.post('/slack/action-endpoint', express.json(), (req, res) => {
6770
});
6871

6972
app.post('/github/webhook-endpoint', express.json(), async (req, res) => {
70-
if ('release' in req.body && req.body.action === 'released') await deploymentService.handleCreateRelease(req.body);
71-
if ('workflow_run' in req.body && req.body.action === 'requested')
72-
await deploymentService.handleActionStart(req.body);
73-
if ('workflow_run' in req.body && req.body.action === 'completed')
74-
await deploymentService.handleActionComplete(req.body);
73+
deployWebhookController.handle(req.body);
7574
res.sendStatus(200);
7675
});
7776

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1+
import { type Member } from '../entities/Member';
2+
13
export type GithubDeploymentService = {
24
handleCreateRelease: (body: {
3-
release: { author: { login: string }; body: string; tag_name: string; html_url: string };
4-
repository: { name: string };
5+
author: Member | undefined;
6+
changes: { author: Member | undefined; content: string }[];
7+
tag: string;
8+
releaseUrl: string;
9+
repository: string;
10+
}) => Promise<void>;
11+
handleActionStart: (body: {
12+
workflowName: string;
13+
tag: string;
14+
repository: string;
15+
workflowId: number;
16+
workflowUrl: string;
17+
}) => Promise<void>;
18+
handleActionComplete: (body: {
19+
workflowName: string;
20+
tag: string;
21+
repository: string;
22+
workflowId: number;
23+
workflowUrl: string;
524
}) => Promise<void>;
6-
handleActionStart: (body: WorkflowRunBody) => Promise<void>;
7-
handleActionComplete: (body: WorkflowRunBody) => Promise<void>;
8-
};
9-
10-
type WorkflowRunBody = {
11-
workflow_run: { name: string; head_branch: string; id: number; html_url: string };
12-
repository: { name: string };
1325
};

0 commit comments

Comments
 (0)