Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
14450b9
rename(pages): 폴더 구조 개선
wlrnjs May 8, 2026
086163d
refactor(type): gemini 코드 리뷰 반영
wlrnjs May 8, 2026
401d0b9
테스트 커밋
wlrnjs May 8, 2026
84e0f61
Merge pull request #42 from finditem/rename/page-forder-structure
wlrnjs May 9, 2026
3d5eeb0
refactor(supabase): 기본 구조 수정
junye0l May 11, 2026
2cfb8c7
feat(apis): apis 테이블 타입 구조 작성
junye0l May 11, 2026
d45ac71
feat(monitoring_results): monitoring_results 테이블 타입 구조 작성
junye0l May 11, 2026
e8cbb2c
feat(error_logs): error_logs 테이블 타입 구조 작성
junye0l May 11, 2026
8abe5c5
feat(manual_test_results): manual_test_results 테이블 타입 구조 작성
junye0l May 11, 2026
6925418
feat(affected_features): affected_features 테이블 타입 구조 작성
junye0l May 11, 2026
b8be901
feat(-): 배럴 패턴 수정
junye0l May 11, 2026
f8aebd1
feat(design): 디자인 토큰 자동화 파일 구성
wlrnjs May 11, 2026
60b0334
Merge pull request #45 from finditem/feat/figma-design-token
wlrnjs May 11, 2026
c2c653d
refactor(design): 폴더 및 워크플로우 수정
wlrnjs May 11, 2026
2d1e742
Merge pull request #46 from finditem/feat/figma-design-token
wlrnjs May 11, 2026
ea9bf96
Merge branch 'develop' into feat/github-actions
junye0l May 11, 2026
71ad100
test commit
wlrnjs May 11, 2026
b727dd5
Merge pull request #47 from finditem/design-tokens
wlrnjs May 11, 2026
f88364f
chore(-): workflow 에러 수정
wlrnjs May 11, 2026
de7a93f
Merge branch 'develop' into feat/figma-design-token
wlrnjs May 11, 2026
03f08cf
Merge pull request #48 from finditem/feat/figma-design-token
wlrnjs May 11, 2026
a945243
feat(api-monitoring): 공공 API 호출 후 응답값 데이터베이스 저장 코드 구현
junye0l May 11, 2026
4992584
Merge branch 'develop' into feat/github-actions
junye0l May 11, 2026
946d8ec
chore(workflow): 디자인 토큰 수동 버튼 추가
wlrnjs May 11, 2026
63faeed
Merge pull request #49 from finditem/chore/design-tokens-workflow
wlrnjs May 11, 2026
c1bbbe2
fix(design-tokens): 미해결 참조 필터링 및 js 플랫폼 제거
wlrnjs May 11, 2026
59d90bc
Merge pull request #50 from finditem/fix/design-tokens
wlrnjs May 11, 2026
eed5ea7
chore(dependa): 의존성 취약점 수정
wlrnjs May 11, 2026
2d69b00
Merge pull request #51 from finditem/chore/dependabot
wlrnjs May 11, 2026
4e5fd67
Merge branch 'develop' into feat/github-actions
junye0l May 12, 2026
cae3772
feat(design-tokens): Style Dictionary 빌드 파이프라인 구축
wlrnjs May 12, 2026
31bc8a4
feat(monitor-web): 디자인 토큰 연동 및 타이포그래피 플러그인 추가
wlrnjs May 12, 2026
2ab1a3f
Merge branch 'develop' of https://github.com/finditem/infra-support i…
wlrnjs May 12, 2026
e56f94c
fix(design-tokens): gemini 코멘트 수정
wlrnjs May 12, 2026
d1b0aa6
design(checkbox): 피그마 디자인 시안 스타일 적용
wlrnjs May 13, 2026
c54f786
design(textfield): 피그마 디자인 시안 스타일 적용
wlrnjs May 13, 2026
7212b88
feat(api-detail): UI 기초 퍼블리싱
wlrnjs May 13, 2026
67b72a8
feat(api-detail): 수정 아이콘 추가
wlrnjs May 13, 2026
fd54c63
refactor(api-detail): 각 책임 별 컴포넌트 분리
wlrnjs May 13, 2026
230b7f5
feat(server): 일부 중복 타입 선언 수정
junye0l May 13, 2026
e6db7a9
refactor(api-detail): DetailHeader 컴포넌트 리팩토링 및 접근성, 시맨틱 태그 개선
wlrnjs May 13, 2026
ce5feef
refactor(api-detail): DetailSummaryCards 컴포넌트 리팩토링 및 접근성, 시맨틱 태그 개선
wlrnjs May 13, 2026
7f78da1
refactor(api-detail): DetailCheckLogs 컴포넌트 리팩토링 및 접근성, 시맨틱 태그 개선
wlrnjs May 13, 2026
6e770b7
refactor(api-detail): DetailImpactedFeatures 컴포넌트 리팩토링 및 접근성, 시맨틱 태그 개선
wlrnjs May 13, 2026
a649f62
refactor(api-detail): DetailSettings 컴포넌트 리팩토링 및 접근성, 시맨틱 태그 개선
wlrnjs May 13, 2026
c5afa7b
refactor(api-detail): mock 폴더 생성 및 목업 관련 데이터 분리
wlrnjs May 13, 2026
3004fb1
rename(api-detail): mock 폴더 네이밍 수정
wlrnjs May 13, 2026
4a24bc2
refactor(monitoring): 병렬 호출 방식으로 수정
junye0l May 13, 2026
895b4b6
refactor(monitoring): TSDoc 작성 및 함수 분리
junye0l May 13, 2026
1f1b170
feat(github-actions): 자동화 관련 yml 파일 작성
junye0l May 13, 2026
085685d
refactor(monitoring): 데이터베이스 저장 오류에 대한 분기 추가
junye0l May 13, 2026
3b56705
refactor(monitoring): UST -> KST 포맷팅 함수 추가
junye0l May 13, 2026
82d63d6
revert(monitoring): 타임존 관련 수정파일 롤백 처리
junye0l May 13, 2026
bcb2f37
revert(monitoring): 타임존 수정 파일 리버트 처리
junye0l May 13, 2026
3d1ef57
Merge pull request #52 from finditem/feat/design-tokens
wlrnjs May 14, 2026
8c02f23
Merge branch 'develop' into design/check-box-text-field
wlrnjs May 14, 2026
4b15e7a
Merge branch 'develop' into feat/api-detail-publish
wlrnjs May 14, 2026
eed9033
refactor(monitoring): gemini 리뷰 반영
junye0l May 14, 2026
64afd11
Merge branch 'develop' into feat/github-actions
junye0l May 14, 2026
8bd11e4
Merge pull request #53 from finditem/design/check-box-text-field
wlrnjs May 15, 2026
a154db1
Merge branch 'develop' into feat/api-detail-publish
wlrnjs May 15, 2026
cd35e41
Merge branch 'develop' into feat/github-actions
junye0l May 15, 2026
e95e917
refactor(affected_features): 중복되는 타입 유틸리티 활용
junye0l May 15, 2026
2234c37
refactor(manual_test_results): 중복되는 타입 유틸리티 활용
junye0l May 15, 2026
a7e9249
refactor(monitoring_results): 중복되는 타입 유틸리티 활용
junye0l May 15, 2026
c05d5b7
refactor(error_logs): 중복되는 타입 유틸리티 활용
junye0l May 15, 2026
93b89b6
refactor(apis): 중복되는 타입 유틸리티 활용
junye0l May 15, 2026
338b028
refactor(apiStatus): ApiStatus 타입 분리 및 import 수정
wlrnjs May 15, 2026
862664b
Merge pull request #58 from finditem/feat/github-actions
junye0l May 15, 2026
3463bd5
Merge branch 'develop' into feat/api-detail-publish
wlrnjs May 16, 2026
1ffb833
Merge pull request #56 from finditem/feat/api-detail-publish
wlrnjs May 16, 2026
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
48 changes: 48 additions & 0 deletions .github/workflows/build-design-tokens.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Build Design Tokens

on:
push:
branches:
- develop
paths:
- 'packages/design-tokens/tokens/**'
workflow_dispatch:

concurrency:
group: build-design-tokens
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.YML_GITHUB_TOKEN }}

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build design tokens
run: pnpm --filter @infra-support/design-tokens build

- name: Commit generated dist
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add packages/design-tokens/dist/
git diff --cached --quiet && echo "No changes to commit" || \
(git commit -m "chore(design-tokens): auto-build from Figma tokens" && git push)
39 changes: 39 additions & 0 deletions .github/workflows/monitor-server-schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Monitor Server Schedule

on:
schedule:
- cron: "0 0,4,9 * * *"
workflow_dispatch:

concurrency:
group: monitor-server-schedule
cancel-in-progress: false

jobs:
run-monitoring:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run monitor batch
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
run: pnpm --filter @infra-support/monitor-server exec tsx src/index.ts
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Dependencies
node_modules/
pnpm-lock.yaml
package-lock.json
yarn.lock

# Build outputs
dist/
!packages/design-tokens/dist/
build/
.next/
.turbo/
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"tailwindCSS.experimental.configFile": "apps/monitor-web/tailwind.config.ts",
"cSpell.words": ["jikwon", "supabase"],
"cSpell.words": ["jikwon", "junyeol", "supabase"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
Expand Down
2 changes: 1 addition & 1 deletion apps/monitor-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"check": "tsx src/index.ts",
"check": "tsx --env-file=.env src/index.ts",
"lint": "eslint src --ext ts",
"clean": "rm -rf dist"
},
Expand Down
12 changes: 10 additions & 2 deletions apps/monitor-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
// 나중에 구현
console.log("Monitor server is ready");
import { runMonitoring } from "./services/monitoring.service";

runMonitoring()
.then(() => {
console.log("monitoring job completed");
})
.catch((error) => {
console.error("monitoring job failed:", error);
process.exit(1);
});
25 changes: 25 additions & 0 deletions apps/monitor-server/src/repositories/api.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { supabase } from "../utils/supabase";
import { ApisRow } from "@infra-support/shared";

export type ActiveApiRow = Pick<ApisRow, "id" | "name" | "source_url" | "is_active">;

/**
* 모니터링 대상 API 목록을 조회하는 함수입니다.
*
* @returns 활성화되어 있고 source_url이 존재하는 API 목록
*
* @author junyeol
*/

const getActiveApis = async (): Promise<ActiveApiRow[]> => {
const { data, error } = await supabase
.from("apis")
.select("id, name, source_url, is_active")
.eq("is_active", true)
.not("source_url", "is", null);

if (error) throw new Error(`apis 조회 실패: ${error.message}`);
return (data ?? []) as ActiveApiRow[];
};

export default getActiveApis;
28 changes: 28 additions & 0 deletions apps/monitor-server/src/repositories/monitoring.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { supabase } from "../utils/supabase";
import type { ErrorLogsInsert, MonitoringResultsInsert } from "@infra-support/shared";

/**
* 모니터링 결과를 저장하는 함수입니다.
*
* @param payload - monitoring_results 테이블 insert payload
*
* @author junyeol
*/

export const insertMonitoringResult = async (payload: MonitoringResultsInsert): Promise<void> => {
const { error } = await supabase.from("monitoring_results").insert(payload);
if (error) throw new Error(`monitoring_results 저장 실패: ${error.message}`);
};

/**
* 에러 로그를 저장하는 함수입니다.
*
* @param payload - error_logs 테이블 insert payload
*
* @author junyeol
*/

export const insertErrorLog = async (payload: ErrorLogsInsert): Promise<void> => {
const { error } = await supabase.from("error_logs").insert(payload);
if (error) throw new Error(`error_logs 저장 실패: ${error.message}`);
};
139 changes: 139 additions & 0 deletions apps/monitor-server/src/services/monitoring.processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import type { ActiveApiRow } from "../repositories/api.repository";
import {
insertErrorLog,
insertMonitoringResult,
} from "../repositories/monitoring.repository";
import { resolveApiStatus, shouldWriteErrorLog } from "../utils/status";

type CallResult = {
ok: boolean;
httpStatus: number | null;
responseTime: number | null;
errorType: string | null;
errorMessage: string | null;
};

/**
* 단일 API 엔드포인트를 호출해 상태 판별용 결과를 반환하는 함수입니다.
*
* @remarks
* - HTTP 비정상 응답은 `ok: false`와 `http_error`로 반환합니다.
* - 네트워크/타임아웃 예외는 throw하지 않고 결과 객체로 변환합니다.
*
* @returns API 호출 결과(성공 여부, HTTP 상태, 응답 시간, 에러 정보)
*
* @author junyeol
*/

const callApi = async (url: string, timeoutMs = 5000): Promise<CallResult> => {
const startedAt = Date.now();
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
const getElapsed = () => Date.now() - startedAt;

try {
const res = await fetch(url, {
method: "GET",
signal: controller.signal,
headers: { Accept: "application/json" },
});

if (!res.ok) {
return {
ok: false,
httpStatus: res.status,
responseTime: getElapsed(),
errorType: "http_error",
errorMessage: `HTTP ${res.status} ${res.statusText}`,
};
}

return {
ok: true,
httpStatus: res.status,
responseTime: getElapsed(),
errorType: null,
errorMessage: null,
};
} catch (error) {
const isAbort = error instanceof Error && error.name === "AbortError";
const elapsed = Date.now() - startedAt;

return {
ok: false,
httpStatus: null,
responseTime: elapsed,
errorType: isAbort ? "timeout" : "network_error",
errorMessage: error instanceof Error ? error.message : "unknown error",
};
} finally {
clearTimeout(timeout);
}
};

/**
* 단일 API 모니터링 결과를 저장하는 함수입니다.
*
* @remarks
* - 모니터링 결과는 항상 `monitoring_results`에 저장합니다.
* - 상태가 `degraded`/`outage`이면 `error_logs`도 추가 저장합니다.
* - 처리 중 예외는 내부에서 로깅하고 `false`를 반환합니다.
*
* @returns 저장 성공 시 `true`, 실패 시 `false`
*
* @author junyeol
*/

export const processApi = async (api: ActiveApiRow): Promise<boolean> => {
try {
const checkedAt = new Date().toISOString();

if (!api.source_url) return true;

const result = await callApi(api.source_url);
const status = resolveApiStatus({
ok: result.ok,
httpStatus: result.httpStatus,
responseTime: result.responseTime,
});

const normalizedErrorType =
status === "degraded" && !result.errorType ? "slow_response" : result.errorType;

const normalizedErrorMessage =
status === "degraded" && !result.errorMessage
? `Response delayed: ${result.responseTime ?? "unknown"}ms`
: result.errorMessage;

const monitoringPayload = {
api_id: api.id,
status,
response_time: result.responseTime,
http_status: result.httpStatus,
error_type: normalizedErrorType,
error_message: normalizedErrorMessage,
checked_at: checkedAt,
};

await insertMonitoringResult(monitoringPayload);

if (shouldWriteErrorLog(status)) {
const errorLogPayload = {
api_id: api.id,
status,
error_type: normalizedErrorType,
error_message: normalizedErrorMessage,
response_time: result.responseTime,
http_status: result.httpStatus,
is_checked: false,
occurred_at: checkedAt,
};

await insertErrorLog(errorLogPayload);
}
return true;
} catch (error) {
console.error("[monitoring] api_id=" + api.id + " (" + api.name + ") 처리 실패", error);
return false;
}
};
39 changes: 39 additions & 0 deletions apps/monitor-server/src/services/monitoring.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import getActiveApis from "../repositories/api.repository";
import { processApi } from "./monitoring.processor";

const CONCURRENCY = 5;

/**
* 모니터링 배치를 실행하는 함수입니다.
*
* @remarks
* - 활성 API 목록을 조회한 뒤 병렬 점검합니다.
* - API 단위 실패는 집계 후 배치 종료 시점에 에러로 전파합니다.
*
* @throws 하나 이상의 API 처리에 실패하면 에러를 던집니다.
*
* @author junyeol
*/

export const runMonitoring = async (): Promise<void> => {
const apis = await getActiveApis();
let nextIndex = 0;
let failedCount = 0;
const workerCount = Math.min(CONCURRENCY, apis.length);

const workers = Array.from({ length: workerCount }, async () => {
while (true) {
const currentIndex = nextIndex++;
if (currentIndex >= apis.length) break;

const ok = await processApi(apis[currentIndex]);
if (!ok) failedCount += 1;
}
});

await Promise.all(workers);

if (failedCount > 0) {
throw new Error(`모니터링 저장 실패 ${failedCount}건`);
}
};
Loading
Loading