본문으로 건너뛰기Jake
메뉴
WORK

포트폴리오

직접 설계·개발·배포한 서비스 세 개입니다. 세 곳 모두 지금 열어보실 수 있습니다. 의뢰받아 만든 사이트 두 개도 함께 싣습니다.

01 / 03 · RECUEVE

Recueve

콘텐츠 큐레이션 플랫폼

Recueve 레코 상세. AI가 채운 카테고리와 설명

흩어진 관심사를 한곳에 모으는 콘텐츠 큐레이션 플랫폼

2026.09 ~ 현재 · 개인 프로젝트 (풀스택)

  • NestJS
  • Next.js 16
  • TypeScript
  • Prisma
  • MySQL
  • Redis
  • BullMQ
  • Docker
  • AWS

보시는 방법

회원가입하시면 링크 주소만 넣어 AI가 설명과 카테고리를 채워 주는 기록(레코)을 직접 만들어 보실 수 있습니다(가입 시 AI 크레딧 30개 지급)

모노레포 구조 - NestJS + Next.js Turborepo 모노레포로 백엔드/프론트/워커 앱을 나누고, 공유 패키지(zod 스키마·타입)로 프론트·백 검증을 일원화했습니다.

인증·권한 시스템

  • JWT(RS256) 액세스·리프레시 토큰 인증 및 Role/Tier 기반 RBAC
  • HttpOnly 쿠키(웹) + Bearer(확장·모바일) 이원화, Google/Kakao OAuth, bcrypt 해싱
  • Redis 기반 단기 토큰으로 이메일 인증·비밀번호 재설정 (TTL 자동 만료)

백엔드 설계

  • 컨트롤러/서비스/리포지토리 계층 분리 및 Swagger 자동 문서화
  • BullMQ + Redis 작업 큐로 무거운 처리를 비동기 워커로 분리 (콘텐츠 정보 자동 생성은 아래 AI 활용 - 제품 참조)
  • throttler 기반 Rate Limiting, Winston 구조화 로깅

도메인 설계

  • 재귀 CTE 기반 계층 카테고리 및 캐노니컬 자동매핑, 사이드바 캐시 동기화로 트리 일관성 유지
  • 예약 표시이름 시스템 - username/displayName 도메인 분리 및 상태 머신. 브랜드명·일반명사를 예약해 두고 사용자 신청 → 관리자 심사로 배정

프론트엔드·배포 - Next.js 16(App Router) + React Query + Tailwind/shadcn, 다국어(i18n) 구조 설계(한·영 적용, 13개 언어 확장 가능), Vercel 배포

인프라 - 본인 AWS 계정에 직접 구축했습니다. EC2 1대에 Nginx·백엔드·워커·Redis·MySQL을 Docker 컨테이너로 올려 RDS 비용을 줄이고, GitHub Actions(OIDC)로 ECR 이미지를 빌드해 백엔드·워커를 자동 배포합니다. DB는 매일 S3로 백업합니다.

기획·디자인·개발·인프라·운영을 혼자 맡아 공개한 서비스입니다.

AI 활용 - 제품

Recueve의 저장 단위를 '레코'라고 부릅니다. 사용자가 URL 하나만 붙여넣으면 자막·본문을 읽어 카테고리·설명·노트를 생성해 레코를 채우는데, 이때 AI 사용 크레딧이 하나 소모됩니다. 즉 LLM 호출이 곧 돈이 나가는 트랜잭션입니다. 그래서 정산 시스템을 만들 때와 같은 방식으로 접근했습니다.

파이프라인

단계처리
① 크레딧 1개 차감Prisma 트랜잭션 + SELECT FOR UPDATE row lock
② BullMQ 큐 적재AUTO_FILL_QUEUE (백엔드 → 워커 분리)
③ 캐시 확인(uriHash, locale) 키로 조회
→ 있으면 자막 추출·설명 생성을 건너뛰고 카테고리·노트만 재생성
④ 자막 추출yt-dlp
⑤ 입력 크기 판단chars/4로 토큰 추정 → 8,000 초과 시 Haiku로 1차 요약
⑥ 제안 생성Sonnet이 카테고리·설명·노트 생성
⑦ 실패 시 환불워커 → 백엔드 internal webhook → 크레딧 환불

설계에서 신경 쓴 것

문제처리
긴 자막의 비용8,000 토큰이 넘으면 Haiku로 먼저 요약하고 Sonnet에 넘깁니다. 싼 모델로 줄이고 비싼 모델은 판단에만 씁니다
입력마다 다른 상황소스를 5종으로 나눠 폴백합니다 - 자막 → 페이지 본문 → 설명문만 → 캐시된 설명 → 자막 없음(이 경우 LLM을 아예 호출하지 않습니다)
같은 URL 중복 호출(uriHash, locale) 캐시에 사용 모델과 프롬프트 버전까지 저장합니다. 프롬프트를 고치면 버전으로 구분됩니다
실패했는데 돈만 나감LLM 호출 실패 시 자동 환불. 재전송·재시도로 중복 환불되지 않게 멱등 처리(이미 환불된 건이면 잔액만 반환)
호출했는데 못 만든 경우자막 없음은 LLM을 호출하지 않았으므로 환불 대상에서 제외합니다. 돈이 나간 것과 안 나간 것을 구분합니다
재시도가 오히려 위험YouTube 차단 위험 때문에 재시도를 1회로 고정했습니다. 무조건 재시도가 답이 아닌 경우입니다
분류할 카테고리가 없음사용자 카테고리 트리(Materialized Path)를 우선 쓰고, 적합한 게 없으면 시스템 canonical 트리로 폴백 분류
모델 교체모델 ID를 시스템 설정에서 주입할 수 있게 하고, 없으면 환경변수로 폴백

정산 엔진에서 하던 것(트랜잭션·원장·멱등성·실패 복원)을 LLM 호출에 그대로 적용했습니다. AI 기능을 붙일 때도 돈과 상태가 어긋나지 않게 만드는 것이 먼저라고 봤습니다.

결과는 서비스에서 볼 수 있습니다 - 모두의 레코스 (새 창)의 각 레코에 붙은 카테고리 태그와 설명 본문이 이 파이프라인의 산출물입니다.

02 / 03 · PROWITH

ProWith

MLM 정산 솔루션

ProWith 관리자 화면의 마감 내역

보상플랜과 쇼핑몰을 설정으로 켜고 끄는 MLM 정산 솔루션

2025.12 ~ 2026.01 · 프리랜서 프로젝트 (풀스택)

  • NestJS
  • Next.js 16
  • TypeScript
  • PostgreSQL
  • Prisma
  • Docker
  • AWS

보시는 방법

첫 화면이 쇼핑몰입니다. 오른쪽 위 로그인 → DEMO Login에서 일반회원으로는 주문을 해 보실 수 있고, 시스템 관리자로는 계정 없이 모든 설정 기능을 보실 수 있습니다. 회원·상품·매출은 시드 데이터이고, 마감 결과는 그 데이터로 정산을 실제로 돌려 나온 값입니다

정산 엔진 - 여기가 이 프로젝트의 심장입니다.

  • 이중 트리 조직 구조 - 추천라인(1:N) + 후원라인(바이너리 LEFT/RIGHT)을 동시에 관리
  • 마감 최대 4종(일/주/15일/월)과 수당 최대 13종 자동 계산을 트랜잭션으로 원자성 보장
  • 정산 원장 7종·회원 상태를 스냅샷으로 보존해 마감 취소 시 이전 상태로 복원
  • 마감 사전 검증·미리보기 지원

"최대"인 이유가 이 프로젝트의 핵심입니다. 마감 유형과 수당 종류를 코드가 아니라 설정으로 켜고 끕니다. 업체마다 보상플랜이 다르기 때문에, 만들어둔 것을 업체별로 필요한 것만 활성화합니다. 새 수당이 필요해지면 종류가 늘어나는 구조입니다. 데모에서 직접 확인하실 수 있습니다 - 마감에는 일/15일/월 3종만 떠 있는데, 주 마감을 시스템 설정에서 비활성화했기 때문입니다. 일 마감 화면 상단에도 "데일리수당, 추천수당, 후원수당, 롤업수당이 활성화되어 있습니다. 마감 시 해당 수당들이 계산됩니다"라고 현재 활성 수당이 표시되고, 비율 구간표 아래에는 "비율 변경은 시스템 설정 → 보상플랜 → 데일리수당에서 가능합니다"라고 설정 경로가 안내됩니다.

쇼핑몰 - 설정으로 켜는 옵션이고, 정산 로직은 건드리지 않고 붙였습니다.

  • 장바구니 → 주문 → 결제완료 때만 매출 생성 → 기존 마감으로 연결. 입금대기 주문은 정산에 닿지 않음
  • 무통장입금(관리자 입금확인, 금액 불일치 시 재확인)·쇼핑포인트 결제, 결제 기록을 주문과 분리해 PG 추가에 대비
  • 같은 회원의 주문·결제·취소를 DB 잠금(advisory lock)으로 직렬화해 포인트 이중 차감·중복 매출 차단
  • 관리자 주문 관리 - 입금기한 경과 자동취소, 송장 엑셀 일괄 등록(행별 미리 검증 후 통과 행만 등록), 반품. 마감 후 반품은 기록하고 정산 보정 경고
  • 상품 이미지 S3 저장(EC2 IAM 역할, 액세스 키 없음)·썸네일 자동 생성, 전자상거래법 고지 항목(사업자 정보·구매 안내·약관) 설정값 관리

동적 시스템 설정 엔진

  • 281개 설정값을 21개 카테고리로 DB 중앙 관리, 관리자 UI 변경이 즉시 반영
  • 설정 타입별 입력 폼 자동 생성 및 설정 간 의존성 관리(부모 → 자식 조건부 표시)

멀티테넌트 아키텍처

  • 단일 코드베이스로 복수 고객사 운영, 초기 설정값 덮어쓰기로 업체별 정책 분리
  • EC2 한 대에 업체별 백엔드를 Docker 컨테이너로 함께 띄우고 Nginx가 api.{도메인}으로 라우팅
  • RDS 한 대를 공용으로 쓰되 Database만 업체별로 분리, 프론트엔드는 업체별 Vercel 프로젝트
  • 업체별 GitHub Actions 파이프라인 독립 운영 (신규 구축·초기화 재배포 등 다중 배포 모드)

백엔드·회원 시스템

  • NestJS 23개 모듈 · 340여 개 REST API
  • JWT + Passport 인증, 4단계 RBAC
  • 다국어(i18n) - nestjs-i18n 한·영 응답 분기 + 메뉴 영문명을 DB 컬럼으로 관리해 운영자가 직접 수정, d3-org-chart 조직도 시각화, 포인트 이중 시스템(Cash/Shop)·CPV 관리
  • 조직도 미니맵 - 화면보다 큰 트리에서 전체를 축소해 띄우고 현재 보이는 영역을 사각형으로 표시, 클릭·드래그로 그 위치까지 바로 이동

AI 활용 - 개발

구현만 AI에게 맡기지 않고 검증까지 맡겼습니다. 대신 합격 기준은 코드로 고정했습니다.

ProWith를 운영하면서 정작 시간을 잡아먹는 건 코드가 아니라 운영이라는 걸 알게 됐습니다. 특히 세 가지가 그랬습니다.

정산 검증   수당 최대 13종 × 마감 최대 4종(일/주/15일/월)
신규 업체   DB·GitHub·AWS·Vercel 구성 + 도메인 연결 + 배포를 매번 손으로
보상플랜    업체마다 다른 규칙을 hwp·pptx 문서로 받아 코드로 옮기는 작업

그래서 반복 운영 업무를 재현 가능한 스킬 6종으로 고정했습니다. 매번 같은 결과가 나오는 작업 절차입니다.

무엇을 자동화했나

스킬하는 일
신규 업체 셋업
new-tenant
명령 한 번으로 디렉토리 복제 → 로컬 DB·Prisma → GitHub 레포 → AWS(ECR·RDS·SSL·Secrets) 배포 → Vercel 프론트 + 도메인 연결까지. 직접 하는 건 DNS 설정 정도입니다
보상플랜 문서화
compose-plan
원본 자료(PPTX·HWP·PDF·이미지)를 읽어 검증 스킬이 그대로 읽을 수 있는 규격 문서로 정리합니다. 자격 조건의 단위·마감 주기처럼 모호한 항목은 임의로 정하지 않고 되묻게 했습니다
보상플랜 반영
build-seed-plan
그 문서를 업체별 설정값으로 옮깁니다. 기본값과 같더라도 플랜이 정한 값은 명시해 둡니다 - 나중에 기본값이 바뀌어도 업체 설정이 따라 흔들리지 않게
마감 검증
verify-cutoffs
보상플랜 문서와 현재 설정을 읽어 합격 기준을 스스로 구성한 뒤, DB 재시드 → 테스트 매출 → 일/주/15일/월 순차 마감 → 집계 검증. 마감 취소 정합성·탈퇴자 실적 제외까지 (아래 ①)
재마감 대조
compare-recut
운영 덤프를 2벌(동결 기준본 + 작업본) 복원해 재마감 결과를 운영 원본과 전수 비교 (아래 ②)
업체 로고 생성
build-tenant-logo
로고 소스 이미지를 벡터화(potrace) 해 가로형 SVG·투명 PNG·favicon 자산으로 만듭니다

셋은 체인으로 물려 있습니다 - compose-plan(문서화) → build-seed-plan(설정 반영) → verify-cutoffs(검증). 앞 단계의 산출물 형식을 뒷 단계가 그대로 읽도록 규격을 맞춰 둬서, 새 업체 보상플랜이 들어오면 이 순서로 흘려보내면 됩니다.

검증도 AI가 수행합니다

작업 절차 안에 검사 두 가지를 넣어, AI가 구현을 끝내면 스스로 돌려보고 통과해야 끝나도록 했습니다. 사람이 만든 건 검사 자체가 아니라 무엇을 통과로 볼 것인가입니다.

① 보상플랜 불변식(invariant) 검사 보상플랜이 반드시 지켜야 할 조건을 코드로 박아두고, 계산 결과가 거기서 어긋나는지 봅니다. 이때는 조건을 만족하는 테스트 데이터를 만들어내는 것 자체가 일이었습니다.

② 운영 덤프 재마감 전수 대조 실제 운영 데이터를 그대로 다시 마감시켜, 기존 결과와 전수 비교합니다. 한 건이라도 어긋나면 통과하지 않습니다.

사람이 하는 일과 AI가 하는 일 - AI 코딩 도구가 등장하기 훨씬 전부터 다양한 로직을 직접 구현해 온 경험을 바탕으로 설계 방향과 합격 기준을 정합니다. 구현·테스트·검증 실행은 AI가 맡습니다. 기준이 코드로 박혀 있으니 결과를 눈으로 다시 확인할 필요가 없어졌고, 사람이 판단할 지점만 남았습니다.

03 / 03 · GENYS LOTTO AI

GENYS Lotto AI

통계 분석 + AI 예측

GENYS Lotto AI의 스마트 분석 화면

역대 로또 추첨 데이터 기반 통계적 패턴 분석과 AI 예측 모델을 결합한 서비스

2025.10 ~ 2025.11 · 개인 프로젝트 (풀스택)

  • Next.js 15
  • TypeScript
  • PostgreSQL
  • Prisma
  • Redis
  • MongoDB
  • AWS Lambda
  • SSE
  • NextAuth.js

보시는 방법

회원가입 후 이용하실 수 있습니다

구성 - Next.js 15 App Router 풀스택(78개 REST API) + AWS Lambda·EventBridge 수집기 + Python 예측 파이프라인이 하나의 PostgreSQL을 공유하는 구조입니다. 수집기가 매주 토요일 동행복권 회차를 가져와 저장하면, 예측 파이프라인이 다음 회차 예측을 같은 테이블에 써두고, 웹앱이 그걸 읽어 보여줍니다.

성능 최적화 - 세 번에 걸쳐 다른 병목을 잡았습니다

이 프로젝트에서 가장 많은 시간을 쓴 부분입니다. 한 번에 해결한 게 아니라, 병목이 옮겨갈 때마다 다른 방법을 썼습니다.

① 서버 응답이 느리다 → LRU 인메모리 캐시

9개 분석 API가 매 요청마다 전체 회차를 다시 계산하고 있었습니다. 로또는 주 1회 추첨이라 같은 범위의 분석 결과는 일주일 내내 변하지 않는다는 점에 착안해 캐시를 붙였습니다.

966ms → 31ms   (97% 개선)
백테스트 반복 분석 40% 향상 · DB 부하 90% 감소
  • TTL 24시간, API당 50 엔트리 LRU - 주 1회 갱신이라 24시간이 안전한 상한
  • 범위별 캐시 키 - _full(전체) · _dhl(836회~) · _{fromTurn}_{toTurn}(백테스트). 사용자가 임의 구간을 지정해도 캐시가 듣습니다
  • 자동 무효화 - 새 회차가 들어오면 CacheInvalidator.onNewDrawing()이 분석·통계·대시보드 캐시와 최신 회차 값을 한 번에 초기화합니다. 캐시는 넣는 것보다 언제 버릴지가 어렵습니다
  • ⚠️ 최신 회차 값은 5분만 캐싱 - 이 값이 toTurn === latestTurn으로 "전체 범위 요청인가"를 판정해 캐시 키를 결정하기 때문에, stale이면 옛 회차 결과가 _full 키에 들어앉습니다. 분석 결과는 24시간, 이 값은 5분으로 나눈 이유입니다
  • 개발 환경 HMR에서 캐시가 날아가지 않도록 globalThis 싱글톤으로 보관

② 예측 API가 10초 걸린다 → 불필요한 연산 제거 + 병렬화

캐시로 분석 API는 빨라졌는데 하이브리드 예측이 여전히 10초였습니다. 뜯어보니 원인이 둘이었습니다.

순차 API 호출 ~7s  +  백테스트 ~3s   =  약 10초
          ↓                   ↓
병렬 호출 ~2s        조건부 스킵        =  약 2초   (80% 단축)
  • 520회 백테스트를 조건부로 걷어냈습니다 - 동적 제약 조건을 쓸 때는 통계가 필요 없는데도 매번 돌고 있었습니다. includeStats=false로 스킵해 800ms~1s를 줄였습니다
  • 4개 패턴 분석을 Promise.allSettled()로 병렬화 - all이 아니라 allSettled를 쓴 건 하나가 실패해도 나머지 결과로 예측을 보여주기 위해서입니다. 실패한 것만 로깅합니다

③ 화면 전환이 굼뜨다 → 클라이언트 캐싱과 필터링

서버는 빨라졌는데 탭을 옮길 때마다 같은 데이터를 다시 받아오고 있었습니다. 서버 필터링을 클라이언트 필터링으로 옮기고, 조회한 회차는 Map에 담아 재사용했습니다.

API 호출     105회 → 1회    (99% 감소, 탭 5회 전환 기준)
네트워크     350KB → 50KB   (86% 절감)
탭 전환      1~2초 → 즉시

트레이드오프 - 캐시 TTL을 24시간으로 길게 잡은 대신 무효화 트리거를 정확히 걸어야 했고, 클라이언트로 데이터를 내리는 대신 첫 로딩이 무거워지는 것을 감수했습니다. 도메인 특성(주 1회 갱신, 데이터 크기 수백 KB)을 보고 내린 판단입니다.

로그는 관계형 DB에 넣지 않았습니다

제약 조건 때문에 버렸는데 실제로는 당첨된 조합을 기록해 추적하는 기능이 있습니다. 스키마가 자주 바뀌고 조회보다 적재가 훨씬 많은 데이터라, PostgreSQL 대신 MongoDB Atlas에 Mongoose ODM으로 따로 담았습니다.

  • 사용자별·회차별 집계 파이프라인 구현
  • 듀얼 로깅 - LOG_TARGET 환경변수로 파일 / MongoDB / 둘 다를 골라 씁니다. 로컬에서는 파일, 운영에서는 MongoDB
  • Winston 구조화 로깅과 연동

등급이 3개라 계정 전환기를 만들었습니다

무료·프리미엄·관리자 3등급에 비로그인까지, 화면과 API 분기가 4가지입니다. 등급 하나 고칠 때마다 로그아웃하고 다른 계정으로 다시 로그인하는 게 반복돼서, 개발 모드에서만 뜨는 플로팅 계정 전환기를 붙였습니다.

  • 한 번 클릭으로 전환 - 등급별 대표 계정을 자동으로 뽑아 보여주고(role별 1명), 필요하면 전체 계정 목록을 펼칩니다. 로그아웃(비로그인 테스트) 버튼으로 비로그인 화면도 같은 자리에서 확인합니다
  • 비밀번호 없이 세션을 바꿉니다 - NextAuth credentials provider에 개발 전용 플래그를 태워 전환하고, 그게 실패하면 /api/dev/force-login이 NextAuth JWT를 직접 발급해 세션 쿠키로 심습니다
  • 🔒 프로덕션 차단을 두 겹으로 걸었습니다 - 컴포넌트가 NODE_ENV !== 'development'면 렌더 자체를 하지 않고, /api/dev/* 라우트도 같은 조건으로 403을 돌려줍니다. 화면만 숨기면 엔드포인트는 그대로 열려 있기 때문입니다

인증 및 보안 - NextAuth.js OAuth(Google/Kakao)·이메일 OTP 및 RBAC 등급별 접근 제어, Redis 인증 코드(10분 TTL), AES-256 개인정보 암호화

인프라 - Vercel + Supabase(PostgreSQL) · Upstash Redis · MongoDB Atlas · AWS Lambda·EventBridge · Vercel CI/CD

SITES

의뢰받아 만든 사이트

회사 소개 사이트 두 곳을 의뢰받아 만들었습니다. 두 사이트 모두 소스를 공개 저장소에 두었습니다.

Brain House 사이트 첫 화면
brainhouse.kr

Brain House

AI·ICT 컨설팅 회사의 소개 사이트입니다. 한국어, 영어, 일본어로 볼 수 있고, Three.js로 만든 3D 요소와 문의 메일 발송을 넣었습니다.

외부 의뢰 · 2025.12 ~ 2026.02 · 커밋 74개

  • Next.js 14
  • TypeScript
  • Tailwind CSS
  • Three.js
  • Resend
AI TOOLS 사이트 첫 화면
aitoolz.kr

AI TOOLS

AI 광고 성과 분석 솔루션을 소개하고 데모 신청과 문의를 받는 B2B 사이트입니다. 9개 페이지를 기획서에서 배포까지 혼자 만들었습니다.

Claude Code를 활용해 기획서를 명세(SPEC.md), 계획, 태스크 문서로 옮기고, 태스크마다 타입 검사와 린트, 테스트를 통과시키며 구현했습니다.

기획서 → SPEC.md → plan.md → todo.md → 구현·검증

외부 의뢰 · 2026.06 · 구현 커밋 46개 · 첫 커밋부터 23시간

  • Next.js 16
  • React 19
  • Tailwind CSS 4
  • Motion
  • Resend
  • Vitest
  • Playwright