Skip to content

Repository files navigation

aolda-ahp-backend

이 프로젝트는 team, cloud, health 공개 API를 제공하는 TypeScript + Fastify 백엔드입니다. 현재는 실제 DB 연동을 넣기 전이므로, 기존 응답을 유지하면서 비즈니스 로직을 추가할 수 있는 구조로 설계되어 있습니다. 특히 team 모듈은 Notion을 원천 데이터소스로 사용하면서, 조회 로직과 데이터 해석 로직을 분리하는 방향으로 리팩터링 중입니다.

이 문서를 보는 초보자용 가이드

프로젝트를 처음 열었을 때, 어떤 파일이 무엇을 하는지 한 번에 이해할 수 있도록 작성했습니다. 아래 그림은 요청이 서버에서 처리되는 순서를 보여줍니다.

사용자 요청
  └─> Route(API 엔드포인트)
        └─> Service(유스케이스/비즈니스 규칙)
              └─> Repository(조회 시나리오 조합)
                    └─> Fetcher(외부 API / DB 호출)
                          └─> Extractor(순수 함수 기반 값 추출)
                                └─> 응답 반환

1) 프로젝트 구조를 먼저 이해하기

src/ 루트는 아래와 같이 역할별로 나뉩니다.

src/
  common/
    config/
      env.ts
  constants/
    team.ts
    cloud.ts
    schemas.ts
  routes/
    health.ts
    team.ts
    cloud.ts
  modules/
    team/
      services/
      repositories/
      datasources/
      notion/
        fetchers/
        extractors/
        parsers/
        assemblers/
    cloud/
      services/
      repositories/
      datasources/
    internal-example/
      routes/
      services/
      repositories/
      datasources/
  server.ts
prisma/
  schema.prisma
  • README에서 문서가 길어지는 대신, 실제 동작을 기준으로 파일을 읽으면 이해가 빠릅니다.
  • 공개 API는 routes에 선언되어 있고, 실제 동작은 modules에서 처리됩니다.

2) 폴더 역할 (한 줄로 요약)

  • src/server.ts
    • 앱을 생성하고, 플러그인(CORS, Swagger), 라우트 등록, 환경변수 로깅을 담당합니다.
  • src/routes/*
    • HTTP 요청을 받아 문서화(Swagger)하고, Service를 호출해 결과를 반환합니다.
    • 이곳은 DB/API 직접 접근을 하지 않습니다.
  • src/modules/<도메인>/services/*
    • 팀 조회, 클라우드 조회 같은 업무 규칙이 배치될 장소입니다.
    • 현재는 Repository 호출만 래핑하고, 향후 정책/검증 로직을 넣을 예정입니다.
  • src/modules/<도메인>/repositories/*
    • 데이터 조회 계약(인터페이스)입니다.
    • Service는 이 계약만 알면 되고, 실제 구현체는 바뀌어도 서비스는 영향이 적습니다.
  • src/modules/<도메인>/datasources/*
    • mock은 현재 더미 데이터를 반환합니다.
    • prisma는 나중에 실제 DB 조회로 교체할 수 있는 준비부입니다.
  • src/modules/team/notion/fetchers/*
    • Notion SDK 호출만 담당합니다.
    • dataSources.query, blocks.children.list 같은 I/O를 이 계층에 둡니다.
    • CrewFetcherpage + profileImageUrl + description 같은 raw source만 반환합니다.
    • ActivityFetcher는 project/study datasource를 각각 조회한 뒤, repository가 메모리에서 병합할 수 있도록 page 기반 raw source를 반환합니다.
    • 임원 lookup처럼 별도 보조 데이터소스도 전용 fetcher로 분리해 조합합니다.
  • src/modules/team/notion/extractors/*
    • 이미 조회한 page/block에서 필요한 값을 읽는 순수 함수 계층입니다.
    • 네트워크 호출 없이 입력값만 받아 결과를 반환합니다.
  • src/modules/team/notion/parsers/*
    • Notion의 raw page를 팀 도메인에서 쓰기 쉬운 중간 형태로 해석합니다.
    • 예: activity page의 상태값, 시작학기, activity type 판별
  • src/modules/team/notion/assemblers/*
    • parser/fetcher 결과를 최종 REST 응답 형태로 조립합니다.
    • 이 계층은 응답 shape를 알지만, 직접 외부 API를 호출하지 않습니다.
    • crew의 경우 repository가 여러 source와 supplement를 aggregate로 묶고, assembler가 그 aggregate를 응답 DTO로 변환합니다.
    • activity/project도 parser가 page를 해석하고, repository가 mock supplement와 aggregate를 조합한 뒤 assembler가 응답을 만듭니다.
  • prisma/schema.prisma
    • Prisma 스키마 파일입니다.
    • 현재는 CrewProfileImageCache, TeamActivityMetadata 같은 운영 보조 테이블이 포함되어 있습니다.
    • TeamActivityMetadata는 team 활동의 안정적인 activityId와 관리자 보정 메타데이터를 저장하는 용도입니다.
  • src/constants/*
    • 현재 공개 API 응답 형식(오탈자 키 포함)을 보존하기 위한 고정 예시 데이터/스키마입니다.

3) 왜 team/cloud 구조가 분리되었는가?

현재 공개 API는 바뀌면 안 됩니다. 하지만 내부 구현은 바뀌어야 합니다. 이때 바로가기에 필요한 구조입니다.

  • 공개 API 호환성은 routesconstants 응답 샘플 기준으로 유지됩니다.
  • 실제 데이터 소스는 datasource만 바꾸면 되도록 계층을 분리했습니다.
  • USE_MOCK_DATA를 통해 공개 응답 유지 상태에서 점진적으로 전환 가능합니다.

3-1) Team Notion 리팩터링 방향

현재 team 모듈은 아래 방향으로 구조를 정리하고 있습니다.

  • fetcher는 Notion API를 호출합니다.
  • fetcher는 최종 응답이 아니라 raw source를 반환합니다.
  • extractor는 Notion page/block에서 필요한 값을 읽습니다.
  • parser는 raw Notion 객체를 도메인 친화적인 값으로 해석합니다.
  • assembler는 최종 REST 응답을 조립합니다.
  • repository는 여러 fetch 결과를 조합해 하나의 조회 흐름을 만듭니다.
  • repository는 cross-source 값이나 아직 미연동된 값의 mock supplement도 이 단계에서 관리합니다.
  • activity/projectactivityId, en, brief, descriptionTeamActivityMetadata 테이블을 source of truth로 사용하고, Notion은 활동 존재 여부와 live 상태 필드 확인용으로 사용합니다.
  • crewLogCrew Book 계정(프로필) people -> 임원 lookup datasource의 people 필드 매칭으로 role을 조합합니다.
  • crewLog.departmentCrew Book 작성기수 -> 활동학기/기수 매핑 datasource -> 해당 기수의 Crew Book 팀 필드 흐름으로 계산합니다.
  • lookup에 없는 기수는 일반 활동회원 이력으로 fallback 합니다.
  • 현재 role 코드는 회장 -> CREW_ROLE/P, 부회장 -> CREW_ROLE/VP, 총무 -> CREW_ROLE/EA, 일반 크루원 -> CREW_ROLE/CREW로 매핑합니다.
  • 현재 department 코드는 임원진 -> DEPARTMENT_TYPE/CLEVEL, 개발팀 -> DEPARTMENT_TYPE/DEV, 인프라개발팀 -> DEPARTMENT_TYPE/INFRA_DEV, 인프라팀 -> DEPARTMENT_TYPE/INFRA, 운영지원팀 -> DEPARTMENT_TYPE/GA, 디자인팀 -> DEPARTMENT_TYPE/DESIGN으로 매핑합니다.

예를 들어 GET /team/crew는 다음과 같은 흐름으로 읽으면 됩니다.

route/team.ts
  -> TeamQueryService
  -> TeamRealRepository
  -> CrewFetcher
  -> crew-page.extractor / notion-block.extractor
  -> repository aggregate composition
  -> crew-response.assembler

GET /team/activity, GET /team/project는 다음처럼 조금 더 세분화됩니다.

route/team.ts
  -> TeamQueryService
  -> TeamRealRepository
  -> ActivityFetcher(raw sources from project datasource)
  -> ActivityFetcher(raw sources from study datasource)
  -> repository merge in memory
  -> repository aggregate composition
  -> activity-page.parser
  -> activity-response.assembler or project-response.assembler

현재 실제 호출스택은 아래처럼 이해하면 가장 정확합니다.

  • GET /team/crew
    • route -> service -> TeamRealRepository -> CrewFetcher(raw sources) -> extractors -> repository aggregate composition -> crew-response.assembler
  • GET /team/activity
    • route -> service -> TeamRealRepository -> project/study ActivityFetcher(raw sources) -> repository merge in memory -> activity-page.parser -> repository aggregate composition -> activity-response.assembler
  • GET /team/project
    • route -> service -> TeamRealRepository -> project/study ActivityFetcher(raw sources) -> repository merge in memory -> activity-page.parser -> repository aggregate composition -> project-response.assembler

즉 현재 activity list는 project datasource와 study datasource를 각각 읽은 뒤 메모리에서 병합하고, project list는 그 병합 결과 중 ACTIVITY_TYPE/PROJECT로 판별된 항목만 추려 구성합니다.

4) 공개 API 목록

Team

  • GET /team/crew
    • 응답에는 total, keys, data가 함께 내려가며, keys에는 해당 응답에서 실제 사용된 crewLog.department / crewLog.type key-value 매핑이 포함됩니다.
  • GET /team/department
    • 전체 crewLog.department key-value 사전을 반환합니다.
  • GET /team/crewtype
    • 전체 crewLog.type key-value 사전을 반환합니다.
  • GET /team/activity
  • PATCH /team/activity/:activity_id/metadata
    • enName, briefName, description을 DB 메타데이터 기준으로 수정합니다.
    • 현재는 인증/인가가 붙지 않은 상태이므로 운영 적용 전 보호 장치가 필요합니다.
  • GET /team/crew/:crew_id
  • GET /team/project
  • GET /team/project/:project_id

Cloud

  • GET /cloud/brief
  • GET /cloud/use_project
  • GET /cloud/qna
  • GET /cloud/notice
  • GET /cloud/notice/:notice_id
  • GET /cloud/product
  • GET /cloud/product/:product_id

Health

  • GET /health

내부 학습용(개발 전용)

  • GET /internal/example/architecture-check
  • NODE_ENV=development일 때만 노출됩니다.

5) 처음 실행하기 (초심자용)

  • Node.js: 20.x
  • npm 설치
npm install

개발 실행

npm run dev

타입 체크

npm run typecheck

빌드

npm run build

실행

npm run start

기본 포트: 8001

사용 가능한 엔드포인트
http://localhost:8001/docs
http://localhost:8001/openapi.json

6) 환경변수 가이드

공통 환경

  • NODE_ENV
    • development면 내부 예시 API가 노출됩니다.
  • CORS_ALLOW_ORIGINS
    • 쉼표 구분 문자열
  • CORS_ALLOW_METHODS
  • CORS_ALLOW_HEADERS
  • CORS_ALLOW_CREDENTIALS
  • USE_MOCK_DATA
    • true: mock datasource 사용(기본)
    • false: prisma datasource 사용(현재는 동일 더미 반환)
  • DATABASE_URL
    • PostgreSQL 연결 문자열
    • 현재는 crew 프로필 이미지 URL 캐시와 team activity 메타데이터 저장소로 사용합니다.
  • NOTION_API_KEY
    • Notion API 호출용 integration secret
  • NOTION_TEAM_DB_IDS
    • crew:<id>,activity:<id>,study:<id>,project:<id>,crew_role_lookup:<id>,crew_profile:<id> 형식의 key-value 문자열
    • 현재 구현 기준 필수값은 crew, activity입니다.
    • study를 넣으면 /team/activity 병합 시 해당 datasource를 사용하고, 없으면 코드에 내장된 기본 study datasource ID를 사용합니다.
    • project는 향후 project detail 구현용 예약 키로 보고 있으며, 현 시점의 project list 호출에는 사용하지 않습니다.
    • crew_role_lookup는 crewLog 임원 정보 lookup용 보조 데이터소스입니다.
    • crew_profile은 회원목록의 univDepartment / univJoinedYear 보강용 보조 데이터소스입니다. 없으면 코드에 내장된 기본 datasource ID를 사용합니다.

예시:

NODE_ENV=development \
USE_MOCK_DATA=true \
CORS_ALLOW_ORIGINS=http://localhost:3000,http://localhost:5173 \
CORS_ALLOW_METHODS=GET,POST,OPTIONS \
CORS_ALLOW_HEADERS=Content-Type,Authorization \
CORS_ALLOW_CREDENTIALS=true \
npm run dev

모든 Origin 허용(개발 편의):

CORS_ALLOW_ORIGINS=* npm run dev

시작 시 로그로 다음이 출력됩니다.

  • 허용된 env 키 목록
  • 실제 적용된 env 값

7) Prisma(준비 상태)

현재는 준비 단계이므로 최소 예시 모델만 존재합니다.

npm run prisma:generate
npm run prisma:push
npm run prisma:migrate:dev
npm run prisma:studio

프로필 이미지 캐시를 수동으로 한 번 동기화하려면:

npm run team:profile-image:sync

USE_MOCK_DATA=false 이고 DATABASE_URL이 설정되어 있으면, 서버는 시작 후 즉시 한 번 동기화하고 이후 12시간마다 프로필 이미지 URL 캐시를 갱신합니다.

7-1) 관리자 콘솔 / 공개 API 제어

관리자 콘솔은 Notion 수집값과 관리자 설정값을 분리해서 관리합니다.

Notion -> admin content sync -> *Source 테이블
관리자 콘솔 -> *AdminProfile / Visibility / Override 테이블
공개 API -> DB read model -> response

접속

http://localhost:8001/admin

초기 관리자 계정은 DB에 관리자 계정이 하나도 없을 때 자동 생성됩니다.

email: admin
password: admin

운영에서는 첫 로그인 이후 반드시 비밀번호 변경 기능을 추가하거나, ADMIN_DEFAULT_EMAIL / ADMIN_DEFAULT_PASSWORD를 별도 값으로 설정하세요.

추가 환경변수

  • ADMIN_DEFAULT_EMAIL
    • 기본값: admin
  • ADMIN_DEFAULT_PASSWORD
    • 기본값: admin
  • ADMIN_SESSION_SECRET
    • 관리자 Bearer token HMAC 서명용 secret
  • NOTION_TEAM_DB_IDS
    • 기존 키에 더해 blog:<id>를 추가할 수 있습니다.
    • 예: crew:<id>,activity:<id>,project:<id>,blog:<id>

수동 Notion 콘텐츠 동기화

CLI:

npm run admin:content:sync

Admin API:

POST /admin/sync/notion
Authorization: Bearer <admin token>

동기화 대상:

  • CrewSource, CrewTermTeamSource
  • ProjectSource
  • BlogPostSource

새로 수집된 크루/프로젝트/블로그는 기본 비공개입니다.

관리자 API 요약

POST /admin/login
GET  /admin/me

GET   /admin/crews
GET   /admin/crews/:id
PATCH /admin/crews/:id
PUT   /admin/crews/:id/term-teams
PUT   /admin/crews/:id/projects
PUT   /admin/crews/:id/blogs

GET   /admin/projects
GET   /admin/projects/:id
PATCH /admin/projects/:id
PUT   /admin/projects/:id/periods
PUT   /admin/projects/:id/participants
PUT   /admin/projects/:id/featured-blogs

GET   /admin/blogs
POST  /admin/sync/notion

PUT /admin/crews/:id/term-teams는 관리자 설정값을 DB에 저장한 뒤, Notion crew datasource에서 작성기수계정(프로필) people id가 일치하는 페이지를 찾아 select를 수정합니다.

배포 순서

  1. npm run prisma:generate
  2. npm run prisma:migrate:deploy
  3. 서버 기동
  4. /admin 로그인
  5. POST /admin/sync/notion 또는 npm run admin:content:sync
  6. 관리자 콘솔에서 공개 여부와 override 값을 저장

운영 manifest에는 backend presync migration Job이 있으므로, 새 migration이 먼저 적용된 뒤 서버가 올라오는 흐름을 유지하세요.

8) 신규 API/로직 추가 방법

  1. repositories에서 인터페이스를 정의합니다.
  2. datasources에 mock/prisma 구현체를 추가합니다.
  3. services에서 비즈니스 규칙을 작성합니다.
  4. routes에서 엔드포인트와 Swagger 응답 형식을 등록합니다.
  5. server.ts에서 사용할 datasource와 서비스를 연결합니다.
  6. typecheckbuild로 타입/컴파일만 먼저 통과시킵니다.

9) 실무에서 꼭 지켜야 할 점

  • 공개 API 응답 키는 문서 기준으로 그대로 유지하세요.
    • 현재 코드에는 오탈자 키(예: attatchments, RECRIUTING, __________)가 실제 응답 규칙의 일부로 들어가 있습니다.
  • 내부 로직을 확장할 때는 바로 datasource만 교체하는 방식으로 접근 범위를 좁히세요.
  • 새 구조를 이해하려면 먼저 server.ts -> routes -> services -> repositories -> datasources 순으로 추적하면 전체 흐름이 보입니다.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages