Skip to content

Commit 9f4e011

Browse files
authored
Merge pull request #213 from kyumin1227/infra/pr-ci-workflow
infra(ci): PR마다 lint, build, test 실행하는 CI 워크플로우 추가
2 parents d13a456 + c99a2a6 commit 9f4e011

11 files changed

Lines changed: 130 additions & 35 deletions

.github/workflows/ci.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- dev
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
13+
jobs:
14+
ci:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: 22
25+
cache: npm
26+
27+
- name: Install dependencies
28+
run: npm ci
29+
30+
- name: Format check
31+
id: lint
32+
run: npx prettier --check "src/**/*.ts" "**/*.yml"
33+
continue-on-error: true
34+
35+
- name: Build
36+
id: build
37+
run: npm run build
38+
continue-on-error: true
39+
40+
- name: Test
41+
id: test
42+
run: npm run test:cov -- --forceExit
43+
continue-on-error: true
44+
45+
- name: Comment CI result on PR
46+
if: github.event_name == 'pull_request'
47+
uses: actions/github-script@v7
48+
with:
49+
script: |
50+
const emoji = (outcome) => outcome === 'success' ? '✅' : '❌';
51+
52+
const lintOutcome = '${{ steps.lint.outcome }}';
53+
const buildOutcome = '${{ steps.build.outcome }}';
54+
const testOutcome = '${{ steps.test.outcome }}';
55+
56+
const allPassed = [lintOutcome, buildOutcome, testOutcome].every(o => o === 'success');
57+
58+
const body = `#### CI 결과 — ${allPassed ? '✅ 통과' : '❌ 실패'}
59+
60+
| 단계 | 결과 |
61+
|------|------|
62+
| Format (Prettier) | ${emoji(lintOutcome)} ${lintOutcome} |
63+
| Build | ${emoji(buildOutcome)} ${buildOutcome} |
64+
| Test | ${emoji(testOutcome)} ${testOutcome} |`;
65+
66+
github.rest.issues.createComment({
67+
issue_number: context.issue.number,
68+
owner: context.repo.owner,
69+
repo: context.repo.repo,
70+
body,
71+
});
72+
73+
- name: Fail if any step failed
74+
if: steps.lint.outcome == 'failure' || steps.build.outcome == 'failure' || steps.test.outcome == 'failure'
75+
run: exit 1

.github/workflows/deploy-dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Deploy to Dev Server
22

33
on:
44
workflow_run:
5-
workflows: ["Build and Push Docker Image"]
5+
workflows: ['Build and Push Docker Image']
66
types: [completed]
77
branches: [dev]
88

.github/workflows/version-bump.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
if: steps.bump-type.outputs.type != 'none'
4242
uses: actions/setup-node@v4
4343
with:
44-
node-version: "20"
44+
node-version: '20'
4545

4646
- name: Bump version and commit
4747
if: steps.bump-type.outputs.type != 'none'

docker-compose.dev.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ services:
1111
image: ${DOCKER_IMAGE:-ghcr.io/kyumin1227/gsc-slack-app:dev}
1212
restart: always
1313
logging:
14-
driver: "json-file"
14+
driver: 'json-file'
1515
options:
16-
max-size: "10m"
17-
max-file: "3"
16+
max-size: '10m'
17+
max-file: '3'
1818

1919
cloudflared:
2020
image: cloudflare/cloudflared:latest

docker-compose.local.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ services:
22
app:
33
build: .
44
ports:
5-
- "3000:3000"
5+
- '3000:3000'

docker-compose.monitoring.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ services:
77
- '--config.file=/etc/prometheus/prometheus.yml'
88
- '--storage.tsdb.retention.time=30d'
99
extra_hosts:
10-
- "host.docker.internal:host-gateway"
10+
- 'host.docker.internal:host-gateway'
1111
volumes:
1212
- ${PROMETHEUS_CONFIG:-./monitoring/prometheus/prometheus.yml}:/etc/prometheus/prometheus.yml:ro
1313
- prometheus_data:/prometheus
@@ -17,9 +17,9 @@ services:
1717
container_name: gsc-loki
1818
restart: unless-stopped
1919
ports:
20-
- "3100:3100"
20+
- '3100:3100'
2121
extra_hosts:
22-
- "host.docker.internal:host-gateway"
22+
- 'host.docker.internal:host-gateway'
2323
volumes:
2424
- loki_data:/loki
2525

@@ -28,7 +28,7 @@ services:
2828
container_name: gsc-grafana
2929
restart: unless-stopped
3030
ports:
31-
- "3001:3000"
31+
- '3001:3000'
3232
environment:
3333
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
3434
volumes:

docker-compose.prod.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ services:
33
image: ${DOCKER_IMAGE:-gsc-slack-app:latest}
44
restart: always
55
logging:
6-
driver: "json-file"
6+
driver: 'json-file'
77
options:
8-
max-size: "10m"
9-
max-file: "3"
8+
max-size: '10m'
9+
max-file: '3'

docker-compose.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ services:
77
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
88
POSTGRES_DB: ${DB_DATABASE:-gsc_slack_app}
99
ports:
10-
- "5432:5432"
10+
- '5432:5432'
1111
volumes:
1212
- ./postgres_data:/var/lib/postgresql/data
1313
healthcheck:
14-
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-postgres}"]
14+
test: ['CMD-SHELL', 'pg_isready -U ${DB_USERNAME:-postgres}']
1515
interval: 10s
1616
timeout: 5s
1717
retries: 5
@@ -21,9 +21,9 @@ services:
2121
container_name: gsc-redis
2222
command: redis-server --requirepass ${REDIS_PASSWORD:-redis}
2323
ports:
24-
- "6379:6379"
24+
- '6379:6379'
2525
healthcheck:
26-
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redis}", "ping"]
26+
test: ['CMD', 'redis-cli', '-a', '${REDIS_PASSWORD:-redis}', 'ping']
2727
interval: 10s
2828
timeout: 5s
2929
retries: 5
@@ -36,7 +36,7 @@ services:
3636
redis:
3737
condition: service_healthy
3838
extra_hosts:
39-
- "host.docker.internal:host-gateway"
39+
- 'host.docker.internal:host-gateway'
4040
environment:
4141
NODE_ENV: ${NODE_ENV:-production}
4242
DB_HOST: db

src/announcement/announcement.controller.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,13 @@ export class AnnouncementController {
157157

158158
const limit = AnnouncementView.PAGE_SIZE;
159159
const offset = Number(body.view?.private_metadata ?? '0');
160-
const { items, total } = await this.announcementService.findPage(offset, limit);
160+
const { items, total } = await this.announcementService.findPage(
161+
offset,
162+
limit,
163+
);
161164
// 삭제 후 현재 페이지 항목이 없으면 이전 페이지로 이동
162-
const adjustedOffset = items.length === 0 && offset > 0 ? offset - limit : offset;
165+
const adjustedOffset =
166+
items.length === 0 && offset > 0 ? offset - limit : offset;
163167
const page =
164168
adjustedOffset !== offset
165169
? await this.announcementService.findPage(adjustedOffset, limit)
@@ -181,15 +185,17 @@ export class AnnouncementController {
181185
/** 이전 페이지 — action.value = 이동할 offset */
182186
@Action('announcement:list:prev')
183187
async handleListPrev(
184-
args: SlackActionMiddlewareArgs<BlockAction<ButtonAction>> & AllMiddlewareArgs,
188+
args: SlackActionMiddlewareArgs<BlockAction<ButtonAction>> &
189+
AllMiddlewareArgs,
185190
) {
186191
return this.handleListPage(args);
187192
}
188193

189194
/** 다음 페이지 — action.value = 이동할 offset */
190195
@Action('announcement:list:next')
191196
async handleListNext(
192-
args: SlackActionMiddlewareArgs<BlockAction<ButtonAction>> & AllMiddlewareArgs,
197+
args: SlackActionMiddlewareArgs<BlockAction<ButtonAction>> &
198+
AllMiddlewareArgs,
193199
) {
194200
return this.handleListPage(args);
195201
}
@@ -204,7 +210,10 @@ export class AnnouncementController {
204210

205211
const offset = Number(action.value);
206212
const limit = AnnouncementView.PAGE_SIZE;
207-
const { items, total } = await this.announcementService.findPage(offset, limit);
213+
const { items, total } = await this.announcementService.findPage(
214+
offset,
215+
limit,
216+
);
208217

209218
const viewId = body.view?.id;
210219
if (!viewId) {
@@ -292,8 +301,9 @@ export class AnnouncementController {
292301
values.channel_block.channel_input.selected_option?.value ?? '';
293302
const title = values.title_block.title_input.value ?? '';
294303
// rich_text_input은 rich_text_value로 읽음 (Slack Block Kit 타입 미지원 → any 캐스팅)
295-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
296-
const richTextBlock = (values.content_block?.content_input as any)?.rich_text_value ?? null;
304+
305+
const richTextBlock =
306+
(values.content_block?.content_input as any)?.rich_text_value ?? null;
297307

298308
if (!channelId) {
299309
await ack({
@@ -315,7 +325,10 @@ export class AnnouncementController {
315325
const contentJson = JSON.stringify(richTextBlock);
316326

317327
try {
318-
const message = AnnouncementView.announcementMessage(title, richTextBlock);
328+
const message = AnnouncementView.announcementMessage(
329+
title,
330+
richTextBlock,
331+
);
319332
const result = await client.chat.postMessage({
320333
channel: channelId,
321334
text: message.text,
@@ -378,8 +391,9 @@ export class AnnouncementController {
378391
const id = Number(view.private_metadata);
379392
const values = view.state.values;
380393
const title = values.title_block.title_input.value ?? '';
381-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
382-
const richTextBlock = (values.content_block?.content_input as any)?.rich_text_value ?? null;
394+
395+
const richTextBlock =
396+
(values.content_block?.content_input as any)?.rich_text_value ?? null;
383397

384398
if (!richTextBlock) {
385399
await ack({
@@ -404,15 +418,21 @@ export class AnnouncementController {
404418
}
405419

406420
try {
407-
const message = AnnouncementView.announcementMessage(title, richTextBlock);
421+
const message = AnnouncementView.announcementMessage(
422+
title,
423+
richTextBlock,
424+
);
408425
await client.chat.update({
409426
channel: announcement.channelId,
410427
ts: announcement.messageTs,
411428
text: message.text,
412429
blocks: message.blocks,
413430
});
414431

415-
await this.announcementService.update(id, { title, content: contentJson });
432+
await this.announcementService.update(id, {
433+
title,
434+
content: contentJson,
435+
});
416436

417437
this.logger.log(
418438
`공지 수정 완료 — id: ${id}, channel: ${announcement.channelId}`,

src/migrations/1776900000001-AddAnnouncementSoftDelete.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { MigrationInterface, QueryRunner } from 'typeorm';
22

3-
export class AddAnnouncementSoftDelete1776900000001
4-
implements MigrationInterface
5-
{
3+
export class AddAnnouncementSoftDelete1776900000001 implements MigrationInterface {
64
name = 'AddAnnouncementSoftDelete1776900000001';
75

86
public async up(queryRunner: QueryRunner): Promise<void> {

0 commit comments

Comments
 (0)