← 이력서로

DataGSM

학교 데이터 OpenAPI·OAuth 플랫폼에서 관리 화면과 인증 흐름, JavaScript·React SDK를 개발했습니다.

기간
2025.10 – 운영 중
역할
프론트엔드 파트 리더
팀
프론트엔드 3 · 백엔드 2 · 클라우드 2
기술
  • Next.js 15
  • React 19
  • TypeScript
  • Tailwind CSS
  • shadcn/ui
  • TanStack Query
  • React Hook Form
  • Zod
  • Turborepo
  • tsup
  • Vercel
모니터에 띄운 DataGSM API 키 발급 화면 목업

배경

DataGSM은 광주소프트웨어마이스터고등학교의 학년·학급·기숙사 호실·동아리처럼 분산된 학교 데이터를 표준화해 제공하는 OpenAPI·OAuth 플랫폼입니다. 교내 서비스를 만들 때마다 학교 데이터를 각자 모으고 로그인을 따로 구현하는 일이 반복됐습니다. 이 수고를 한 곳에서 해결하려고 시작했습니다. 교내 서비스를 만드는 개발자가 학교 데이터를 같은 형식으로 조회하고, DataGSM 계정으로 인증을 연동할 수 있습니다. 일 평균 5,000건 이상, 누적 120만 건 이상의 요청을 처리하고 있으며, 2026 교내 DEV FEST에서 최우수상(1위)을 수상했습니다.

맡은 일

프론트엔드 3명·백엔드 2명·클라우드 2명으로 구성된 팀에서 프론트엔드 파트 리더를 맡았습니다. API 키 발급, 학생·동아리 관리, OAuth 클라이언트 등록과 로그인·비밀번호 재설정, 상태 대시보드를 개발했습니다. 별도 패키지로 배포하는 OpenAPI JavaScript SDK의 초기 설계와 서버 명세 반영, OAuth React SDK의 API·빌드 구조 정리도 담당했습니다. 세 레포에서 PR 52개를 머지했습니다. 클라이언트 47개 · OpenAPI SDK 4개 · OAuth React SDK 1개

한 일

OAuth 클라이언트 관리와 서버 경유 토큰 교환

  • 문제: 자체 이메일 인증과 외부 서비스의 OAuth 로그인을 지원하면서, 연동 서비스의 클라이언트·리다이렉트 URL·권한 범위를 관리해야 했습니다. 토큰 교환에 쓰는 client_secret도 브라우저에 노출하지 않아야 했습니다.
  • 해결: 이메일 회원가입·로그인과 OAuth 코드 요청을 연결하고, 클라이언트 조회·생성·수정·삭제 화면을 개발했습니다. 리다이렉트 URL 입력을 검증하고 권한 선택 로직을 별도 훅으로 추출했습니다. OAuth 서버 마이그레이션에서는 코드 교환·토큰 갱신 훅을 만들고, /api/oauth/token Route Handler가 서버 환경변수의 client_secret을 붙여 OAuth 서버에 요청하도록 바꿨습니다. 브라우저에서 사용하는 요청 타입에서는 clientSecret을 제거했습니다. 클라이언트 #21, #22, #25, #39
  • 결과: 서비스 등록부터 로그인 연동까지 필요한 화면을 제공하고, 토큰 교환의 비밀값을 서버에서 처리하는 경계를 만들었습니다. 이후 clientName과 serviceName을 분리해 로그인 화면에 이용 중인 서비스 명칭을 표시했고, 이메일 코드 발송·검증·비밀번호 변경 훅과 재설정 폼도 추가했습니다. #73, #72

OpenAPI JavaScript SDK의 도메인·HTTP·에러 계층 설계

  • 문제: API를 사용하는 프로젝트마다 인증 헤더, 요청 파라미터, 응답 타입과 에러 처리를 반복해서 작성해야 했습니다. 서버 명세가 바뀌면 클라이언트 타입과 실제 응답이 어긋나는 문제도 있었습니다.
  • 해결: @themoment-team/datagsm-openapi의 초기 구현을 맡았습니다. src/client.ts를 진입점으로 두고 domains/, http/, types/, errors/를 분리했습니다. new DataGsmClient({ apiKey })로 설정을 받고, students()·clubs()·projects()·neis()·health()가 도메인 객체를 필요할 때 생성해 재사용하도록 설계했습니다. 공통 HTTP 클라이언트는 네이티브 fetch로 요청하며 X-API-KEY, 쿼리 직렬화, AbortController 기반 타임아웃을 담당합니다. OpenAPI SDK #1
  • 결과: 사용자는 client.students().getStudents({ grade: 2 })처럼 도메인 메서드를 호출하고 공개 타입을 가져다 쓸 수 있게 됐습니다. HTTP 오류는 DataGsmError 하위 클래스로 구분하고, 429는 RateLimitError.retryAfter, 통신 실패·타임아웃은 NetworkError로 전달했습니다. 에러를 숨기는 대신 호출자가 같은 인터페이스로 분기하도록 정리했습니다. tsup은 단일 src/index.ts에서 ESM·CommonJS와 타입 선언을 생성하고, package.json의 조건부 exports로 각 형식과 타입을 연결하도록 구성했습니다. #1

초기 배포 이후에도 서버 변경을 SDK와 문서에 함께 반영했습니다. 학생·동아리 조회 필터와 공통 ApiResponse 타입을 정리하고, NEIS 시간표 조회와 프로젝트 status 필터를 추가했습니다. 급식·학사일정 응답도 실제 객체 구조에 맞췄고, 클라이언트 레포의 SDK 가이드와 예제 접근 경로를 갱신했습니다. 메서드와 TypeScript 타입은 서버 명세에 맞춰 직접 관리했습니다. OpenAPI SDK #2, #3, #4, 클라이언트 #36, #157

OAuth React SDK의 헤드리스 API와 tsup 빌드

  • 문제: 로그인 리다이렉트를 돕는 작은 SDK에 중첩된 디렉토리, 기본 스타일과 아이콘, Vite 빌드 설정이 함께 들어 있었습니다. 소비하는 앱의 디자인과 라이브러리 자체 역할을 분리할 필요가 있었습니다.
  • 해결: src/lib/ 중심 구조를 OAuthProvider.tsx, OAuthLoginButton.tsx, context.ts, hooks.ts, types.ts 등 역할별 파일로 평탄화했습니다. 공개 API를 OAuthProvider·OAuthLoginButton·useOAuth로 정리하고, 버튼 스타일과 아이콘을 제거해 앱이 모양을 결정하는 헤드리스 컴포넌트로 바꿨습니다. Provider는 clientId·redirectUri로 인가 URL을 만들고 login()을 Context로 제공합니다. useOAuth는 Provider 바깥에서 사용하면 명확한 오류를 던지며, 버튼은 사용자 onClick이 preventDefault()를 호출하면 로그인을 시작하지 않도록 했습니다. OAuth React SDK #1
  • 결과: Vite·vite-plugin-dts 대신 tsup에서 ESM·CommonJS·타입 선언을 한 번에 생성하도록 구성했습니다. React는 peerDependencies와 external로 분리하고, minify·tree shaking을 적용했습니다. 빌드 성공 후 출력된 .js·.cjs에 'use client'를 삽입해 Next.js에서 클라이언트 컴포넌트 경계를 유지하도록 했습니다. 번들 크기는 소스맵 제외 약 19KB → 4KB로 약 79% 줄었습니다. #1, 빌드 전환 커밋, 클라이언트 지시문 처리 커밋

트러블슈팅

외부 OAuth를 위해 비밀번호를 ref에 저장하던 흐름 분리

  • 상황: OAuth 서버 마이그레이션 PR의 코드 리뷰에서, 이메일·비밀번호를 formDataRef에 저장한 뒤 외부 OAuth 코드 발급 요청에 다시 사용하는 문제가 지적됐습니다. 클라이언트 #39
  • 원인: 외부 서비스에서 시작한 로그인도 내부 코드 발급 → 토큰 교환 → 외부 코드 발급을 순서대로 수행했습니다. 서로 목적이 다른 두 흐름이 이어져 있어 첫 요청의 비밀번호를 다음 인증 요청까지 보관하고 있었습니다.
  • 해결: client_id·redirect_uri 유무로 요청을 분기했습니다. 내부 로그인은 내부 코드 발급과 토큰 교환만, 외부 OAuth는 외부 코드 발급과 리다이렉트만 수행하도록 변경했습니다. formDataRef와 후속 비밀번호 재사용 코드를 제거해, 인증 흐름을 잇기 위해 자격 증명을 따로 보관하던 경로를 없앴습니다. 수정 커밋 bbe804d

이메일 인증 완료 후 타이머가 인증 상태를 되돌리는 문제

  • 상황: 비밀번호 재설정에서 이메일 인증을 마쳤더라도 타이머가 만료되면 인증 상태가 초기화돼, 새 비밀번호를 입력하던 필드가 다시 비활성화될 수 있었습니다. PR 리뷰에서 지적받은 문제입니다. 클라이언트 #72
  • 원인: 타이머를 생성하는 useEffect의 의존성 배열에 isCodeVerified가 빠져 있었고, 만료 분기는 인증 완료 여부와 무관하게 상태를 초기화했습니다. interval 콜백이 이전 렌더의 인증 상태를 참조하는 stale closure 문제와 초기화 조건을 함께 수정해야 했습니다.
  • 해결: 의존성 배열에 isCodeVerified를 추가하고, 만료 시 !isCodeVerified인 경우에만 인증 코드·검증 상태와 알림을 초기화하도록 바꿨습니다. 같은 타이머 처리를 사용하던 회원가입 폼에도 함께 반영했습니다. 수정 커밋 215d24a