자연어 일정 요청을 받아 실제 장소 후보, 이동 동선, 시간표를 생성하는 풀스택 AI 일정 플래너입니다. 서비스 도메인은 https://date-planner.us, 테스트 도메인은 https://test.date-planner.us입니다.
이 저장소는 두 애플리케이션과 배포 인프라 설정으로 구성됩니다.
backend/: NestJS 11 API 서버frontend/: Next.js 16 App Router 웹 앱infra/: 테스트 환경 Compose/Nginx/서버 초기화 스크립트.github/workflows/: CI, canary/test/production 배포, 릴리즈 자동화
핵심 사용자 흐름은 다음과 같습니다.
- 사용자가 프론트엔드에서 자연어 일정 요청을 입력합니다.
- 프론트엔드가 인증 상태로
POST /api/plan/generate를 호출합니다. - 백엔드 AI 파이프라인이 입력을 구조화하고 장소 후보를 검색합니다.
- 후보 장소를 선별하고 이동 동선을 정렬한 뒤 시간표를 생성합니다.
- 생성 결과를 DB에 저장하고 프론트엔드가 지도/카드 UI로 렌더링합니다.
현재 앱은 일정 생성 외에도 카테고리, 플랜 편집/메모, 워크스페이스 초대, 알림, 구독 결제, 관리자 운영 화면을 포함합니다.
| 영역 | 기술 |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS 4, TanStack Query/Table, Zustand |
| Backend | NestJS 11, TypeScript, Passport, JWT, Schedule, Sentry |
| Database | PostgreSQL, Prisma 5 |
| Cache/Rate limit | Redis optional |
| AI | OpenRouter 기반 LLM 호출 |
| 지도/검색 | NAVER Maps JS SDK, NAVER Local Search API, Kakao 장소 검색 |
| 인증 | 이메일/비밀번호, Google OAuth, Kakao OAuth, Naver OAuth |
| 결제 | Toss Payments 구독 결제 |
| 분석/보안 | GA4, Cloudflare Turnstile, Sentry |
| Infra | Docker, Docker Compose, GHCR, Nginx, AWS EC2/CloudWatch |
| CI/CD | GitHub Actions |
ai-planner/
├── .github/workflows/
│ ├── auto-tag.yml
│ ├── canary.yml
│ ├── ci.yml
│ ├── deploy-test.yml
│ ├── deploy.yml
│ └── release.yml
├── backend/
│ ├── prisma/
│ ├── scripts/
│ └── src/
│ ├── modules/
│ ├── shared/
│ └── services/
├── docs/
├── frontend/
│ └── src/
│ ├── app/
│ ├── components/
│ ├── hooks/
│ ├── lib/
│ └── stores/
├── infra/
├── .env.production.template
├── .env.test.template
└── README.md
| 경로 | 설명 |
|---|---|
backend/src/modules/ai/ |
자연어 입력 해석, 장소 검색, 후보 선택, 경로 최적화, 일정 생성 |
backend/src/modules/auth/ |
로컬 로그인, refresh token, OAuth, 계정 설정, 계정 삭제 |
backend/src/modules/plan/ |
플랜 생성/조회/수정/삭제, 일정 항목, 플랜 메모 |
backend/src/modules/category/ |
사용자별 플랜 카테고리 |
backend/src/modules/workspace/ |
워크스페이스 생성, 초대, 참여, 삭제 |
backend/src/modules/payment/ |
Toss 결제 준비/승인/웹훅, 구독 상태/해지/재구독 |
backend/src/modules/admin/ |
관리자 대시보드, 사용자/플랜/청구/운영 로그/비용/Sentry 조회 |
backend/src/shared/region/ |
지역 정규화와 별칭 학습 |
frontend/src/app/ |
App Router 페이지, API route, 레이아웃 |
frontend/src/components/plan/ |
플랜 입력, 지도, 일정 목록, 히스토리, 메모 UI |
frontend/src/components/payment/ |
Toss 결제 위젯 |
frontend/src/components/notification/ |
인앱 알림 UI |
frontend/src/lib/ |
API client, 인증/세션, 타입, 서버 유틸 |
infra/ |
테스트 서버용 Compose, Nginx server block, 초기화 스크립트 |
docs/release-versioning.md |
브랜치 운영/릴리즈/버저닝 정책 |
backend/src/app.module.ts 기준 주요 모듈은 다음과 같습니다.
AuthModule: 이메일 로그인, 이메일 인증, OAuth, refresh/logout, 비밀번호 재설정, 계정 설정PlacesModule: Naver/Kakao 장소 검색 통합AiModule: 자연어 입력 해석, 후보 선택, 경로 최적화, 일정 생성PlanModule: 생성 결과 저장, 플랜 편집, 일정 항목, 메모CategoryModule: 사용자별 카테고리 관리WorkspaceModule: 공유 워크스페이스와 초대 링크NotificationModule: 인앱 알림PaymentModule: Toss 결제와 구독 상태 관리ApiBudgetModule: 일정 생성 API 사용량/비용 제한AdminModule: 운영 대시보드 APIUserModule: 사용자 정리 스케줄러
ApiBudgetMiddleware는 POST /api/plan/generate 요청에 적용됩니다. main.ts에서 전역 prefix는 /api이고, /health만 prefix에서 제외됩니다.
| 영역 | 엔드포인트 |
|---|---|
| Health/version | GET /health, GET /api/version |
| Auth | POST /api/auth/email/request-code, POST /api/auth/email/verify-code, POST /api/auth/email/check, POST /api/auth/register, POST /api/auth/login, POST /api/auth/admin/login, POST /api/auth/refresh, POST /api/auth/logout, POST /api/auth/logout-all, POST /api/auth/forgot-password, POST /api/auth/reset-password, GET /api/auth/me, PATCH /api/auth/password, PATCH /api/auth/settings, DELETE /api/auth/me |
| OAuth | GET /api/auth/google, GET /api/auth/google/callback, GET /api/auth/kakao, GET /api/auth/kakao/callback, GET /api/auth/naver, GET /api/auth/naver/callback, POST /api/auth/oauth/:provider/link-token, DELETE /api/auth/oauth/:provider |
| Plan | GET /api/plan/list, GET /api/plan/:id, POST /api/plan/generate, PATCH /api/plan/:id, DELETE /api/plan/:id, item CRUD, memo CRUD |
| Category | GET /api/category/list, POST /api/category, PATCH /api/category/:id, DELETE /api/category/:id |
| Workspace | POST /api/workspace, GET /api/workspace/mine, POST /api/workspace/:id/invite, POST /api/workspace/join/:token, DELETE /api/workspace/:id |
| Notification | GET /api/notification/unread, PATCH /api/notification/read-all, PATCH /api/notification/:id/read |
| Payment/subscription | POST /api/payment/prepare, POST /api/payment/confirm, POST /api/payment/webhook, GET /api/subscription/status, DELETE /api/subscription/cancel, POST /api/subscription/resubscribe |
| Budget | GET /api/budget/usage, GET /api/budget/limits |
| Admin | GET /api/admin/summary, users, billing, plans, workspace plans, CloudWatch logs, cost, Sentry, API usage |
구현은 backend/src/modules/ai/steps/에 있습니다.
사용자 자연어 입력
-> ParseInputStep
-> ExtractIntentStep
-> SearchPlacesStep
-> SelectCandidatesStep
-> OptimizeRouteStep
-> GenerateScheduleStep
ParseInputStep: 지역, 분위기, 제약 조건, 활동 의도를 구조화합니다.ExtractIntentStep: 지역/활동 정보를 내부 검색 가능한 intent로 정규화합니다.SearchPlacesStep: Naver/Kakao 등 외부 장소 검색 API를 호출해 후보를 수집합니다.SelectCandidatesStep: 거리, 활동 적합도, 체인 패널티 등을 고려해 후보를 압축합니다.OptimizeRouteStep: 이동 거리를 줄이는 순서로 동선을 정렬합니다.GenerateScheduleStep: 시간표, 요약, 지도 표시용 데이터를 생성합니다.
backend/prisma/schema.prisma의 주요 모델은 다음과 같습니다.
User: 로컬/OAuth 계정, 관리자 권한, 알림 설정, 소프트 삭제RefreshToken,PasswordResetToken: 세션 재발급과 비밀번호 재설정Plan,PlanItem,PlanMemo: 일정, 일정 항목, 협업 메모Category: 사용자별 일정 분류Workspace,WorkspaceMember,WorkspaceInvite: 공유 워크스페이스Subscription,Payment: Toss 구독 결제Notification: 인앱 알림ApiUsage: API 비용/호출량 추적
frontend/src/app/는 Next.js App Router를 사용합니다.
/: 서비스 소개 및 시작 화면/plan: 일정 생성 화면/plans/[id],/library,/library/plans/[id]: 저장된 일정 조회/dashboard,/mypage,/settings: 사용자 홈/계정 설정/workspace,/workspace/join/[token]: 워크스페이스와 초대 수락/subscribe,/subscribe/success,/subscribe/fail: 구독 결제 플로우/admin,/admin/*: 관리자 대시보드/login,/register,/forgot-password,/reset-password,/auth/callback: 인증 플로우/privacy,/terms: 정책 페이지
frontend/src/lib/api.ts는NEXT_PUBLIC_API_URL을 기준으로 API base URL을 정규화하고, access token 첨부와401refresh 재시도를 처리합니다.frontend/src/stores/authStore.ts와frontend/src/hooks/useAuth.ts가 클라이언트 인증 상태를 관리합니다.frontend/src/components/providers/QueryProvider.tsx가 TanStack Query client를 주입합니다.frontend/src/app/api/admin/*는 관리자 화면에서 사용하는 Next.js API route 프록시/세션 처리를 담당합니다.
로컬 개발에서는 보통 backend/.env와 frontend/.env.local을 사용합니다. 배포 기준 예시는 .env.production.template, 테스트 기준 예시는 .env.test.template에 있습니다.
| 변수 | 설명 |
|---|---|
DATABASE_URL |
PostgreSQL 연결 문자열 |
PORT |
API 서버 포트. 기본값 4000 |
APP_URL |
백엔드 공개 URL |
FRONTEND_URL |
프론트엔드 공개 URL과 OAuth 리다이렉트 기준 |
CORS_ORIGIN |
허용할 프론트엔드 origin. 쉼표로 여러 개 지정 가능 |
JWT_SECRET |
JWT 서명 키 |
JWT_EXPIRES_IN |
access token 만료 시간 |
REFRESH_TOKEN_EXPIRES_DAYS |
refresh token 보관 기간 |
LINK_TOKEN_SECRET |
OAuth 계정 연결 토큰 서명 키. 없으면 JWT_SECRET 사용 |
OPENROUTER_API_KEY |
LLM 호출용 API 키 |
DAILY_API_LIMIT, MONTHLY_API_BUDGET |
일정 생성 API 예산 제한 |
NAVER_SEARCH_CLIENT_ID, NAVER_SEARCH_CLIENT_SECRET |
Naver 지역 검색 API |
KAKAO_REST_API_KEY |
Kakao 장소 검색 API |
REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_TLS |
Redis 캐시/제한 설정 |
TURNSTILE_SECRET_KEY |
Cloudflare Turnstile secret |
SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN, SENTRY_ENVIRONMENT |
Sentry 수집/관리자 조회 설정 |
AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY |
관리자 운영 조회용 AWS 인증 |
CLOUDWATCH_LOG_GROUP_BACKEND, CLOUDWATCH_LOG_GROUP_FRONTEND |
CloudWatch 로그 그룹 |
EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, EMAIL_FROM, SUPPORT_EMAIL |
이메일 발송 설정 |
TOSS_SECRET_KEY, TOSS_WEBHOOK_SECRET, SUBSCRIPTION_MONTHLY_AMOUNT |
Toss 결제/구독 설정 |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL |
Google OAuth |
KAKAO_CLIENT_ID, KAKAO_CLIENT_SECRET, KAKAO_CALLBACK_URL |
Kakao OAuth |
NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, NAVER_CALLBACK_URL |
Naver OAuth |
SCHEDULER_ENABLED |
false면 정리 스케줄러 비활성화. 테스트 환경 중복 cron 방지용 |
| 변수 | 설명 |
|---|---|
NEXT_PUBLIC_API_URL |
백엔드 API 루트. /api가 있어도 되고 없어도 됩니다 |
BACKEND_URL |
서버 사이드 API route가 직접 호출할 백엔드 URL |
NEXT_PUBLIC_SITE_URL |
canonical/sitemap/robots 기준 사이트 URL |
NEXT_PUBLIC_NAVER_MAP_CLIENT_ID |
Naver Maps JS SDK 키 |
NEXT_PUBLIC_GA_MEASUREMENT_ID |
GA4 Measurement ID |
NEXT_PUBLIC_TURNSTILE_SITE_KEY |
Turnstile 사이트 키 |
NEXT_PUBLIC_SENTRY_DSN |
프론트엔드 Sentry DSN |
NEXT_PUBLIC_TOSS_CLIENT_KEY |
Toss Payments client key |
NEXT_PUBLIC_ADMIN_PUBLIC_LOGIN_ENABLED |
관리자 공개 로그인 UI 노출 여부 |
ADMIN_PUBLIC_LOGIN_ENABLED, SEED_PUBLIC_ADMIN_EMAIL, SEED_PUBLIC_ADMIN_PASSWORD |
Next.js admin public-login API route 설정 |
JWT_SECRET, FRONTEND_URL, APP_URL |
관리자 세션/서버 API route에서 사용 |
주의사항:
NAVER_SEARCH_*와NAVER_CLIENT_*는 서로 다른 용도의 키입니다.- OAuth callback URL은 백엔드 도메인의
/api/auth/{provider}/callback이어야 합니다. NEXT_PUBLIC_*값은 프론트엔드 이미지 빌드 시점에 반영됩니다.
- Node.js 22.x
- npm 10+
- Docker / Docker Compose
- PostgreSQL 또는 Docker 기반 DB
cd backend
npm ci
npx prisma generate
cd ../frontend
npm ci# terminal 1
cd backend
npm run start:dev
# terminal 2
cd frontend
npm run dev기본 포트는 다음과 같습니다.
- backend:
http://localhost:4000 - frontend:
http://localhost:3000
cd backend
# schema 변경 후 개발 마이그레이션 생성/적용
npx prisma migrate dev --name <migration_name>
# 배포 환경 마이그레이션 적용
npx prisma migrate deploy
# Prisma client 재생성
npx prisma generate
# seed 실행
npm run seedcd backend
npm run lint:check
npx tsc -p tsconfig.build.json --noEmit
npm run test
npm run test:e2e
npm run test:cov참고: npm run lint는 --fix를 실행하므로 CI와 같은 검사만 필요하면 npm run lint:check를 사용합니다.
cd frontend
npm run lint:check
npx tsc --noEmit
npm run buildGHCR 이미지를 사용합니다.
- production:
ghcr.io/<owner>/ai-planner-backend:latest,ghcr.io/<owner>/ai-planner-frontend:latest - canary/test:
ghcr.io/<owner>/ai-planner-backend:canary,ghcr.io/<owner>/ai-planner-frontend:canary - 각 배포는 commit SHA 태그도 함께 생성합니다.
- production 도메인:
https://date-planner.us - test 도메인:
https://test.date-planner.us - production 앱 스택 기준 경로:
/srv/apps/ai-planner - test 앱 스택 기준 경로:
/srv/apps/ai-planner-test - reverse proxy: Nginx
- 컨테이너 구성: backend, frontend, postgres. 테스트 Compose 예시는
infra/docker-compose.ai-planner.test.yml에 있습니다.
| 워크플로우 | 트리거 | 역할 |
|---|---|---|
ci.yml |
PR | backend/frontend 변경 감지 후 typecheck, lint, Docker dry-run build |
canary.yml |
canary push |
변경된 서비스의 canary 이미지 build/push |
deploy-test.yml |
Deploy Canary 성공 |
canary 이미지를 test.date-planner.us EC2 스택에 배포 |
deploy.yml |
main push |
production 이미지 build/push 및 EC2 배포 |
auto-tag.yml |
main 대상 PR merge |
release:major/minor/patch 라벨에 따라 SemVer 태그 생성 |
release.yml |
v* tag push |
GitHub Release 생성 |
CI의 고정 job 이름은 Backend CI, Frontend CI입니다. 변경이 없는 영역은 성공 종료로 처리되므로 required checks와 연결하기 쉽습니다.
feature branch
-> PR to canary
-> CI 통과
-> merge to canary
-> canary 이미지 빌드
-> test.date-planner.us 배포/검증
-> PR from canary to main
-> merge to main
-> production deploy
-> release label 기반 tag 생성
-> GitHub Release 생성
canary: 통합 개발 및 테스트 배포 기준 브랜치main: production release 브랜치release:major,release:minor,release:patch라벨이mainmerge PR에 붙으면auto-tag.yml이 다음 SemVer 태그를 생성합니다.
| 증상 | 점검 포인트 |
|---|---|
| 장소 검색 결과가 부정확하거나 비어 있음 | NAVER_SEARCH_CLIENT_ID/SECRET, KAKAO_REST_API_KEY, 지역 정규화 로직 확인 |
| OAuth 로그인 후 프론트로 돌아오지 않음 | FRONTEND_URL, provider callback URL, OAuth 앱에 등록된 callback URL 일치 여부 확인 |
| 프론트에서 계속 401 후 로그인 화면으로 이동 | access/refresh token 저장 상태와 /api/auth/refresh 응답 확인 |
| 지도 대신 텍스트 fallback이 보임 | NEXT_PUBLIC_NAVER_MAP_CLIENT_ID 누락 여부 확인 |
| 결제 위젯이 로드되지 않음 | NEXT_PUBLIC_TOSS_CLIENT_KEY, TOSS_SECRET_KEY, 구독 금액 설정 확인 |
| 관리자 운영 로그/비용 화면이 비어 있음 | AWS credential, CloudWatch log group, Cost Explorer 권한 확인 |
| Sentry 관리자 화면이 비어 있음 | SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT 확인 |
| 테스트 환경에서 스케줄러가 중복 실행됨 | .env.test의 SCHEDULER_ENABLED=false 확인 |
| 배포 후 env가 반영되지 않음 | 서버 .env, Compose environment, 프론트 빌드 시점 NEXT_PUBLIC_* 값 확인 |
별도 오픈소스 라이선스를 부여하지 않은 개인 프로젝트입니다.