Skip to main content

JunkBox libs/shared-core 가이드

1. 라이브러리 역할

  • shared-core는 모든 앱이 공통으로 사용하는 백엔드 기반 라이브러리임.
  • 설정, 비동기 DB, 공통 엔티티, 인증, 비밀번호, 예외, 공통 마스터 데이터 저장소, 공통 master-data 소비 클라이언트와 TTL 캐시를 제공함.
  • 공통 loguru 기반 애플리케이션 로깅 초기화도 제공함.

2. 패키지 구조

junkbox_shared_core/
├── config/
├── db/
├── exceptions/
├── logging.py
├── masterdata/
├── models/
└── security/

3. 설정 계층

3.1 Settings 구조

  • pydantic-settings 기반 Settings 클래스를 사용함.
  • 기본 ENV_FILE 값은 .env.local임.
  • ENV_FILE 기준 환경 파일과 같은 디렉터리의 .env.shared를 함께 로딩함.
  • ENV_FILE.env.local.jeevtrap, .env.prod.jeevtrap이면 같은 디렉터리의 .env.shared, .env.jeevtrap, 선택된 Jeevtrap 전용 env 파일을 로딩함.
  • ENV_FILE이 base profile env 파일명 뒤에 앱 suffix를 붙인 형태이고 Jeevtrap 전용 env가 아니면 같은 디렉터리의 .env.local 또는 .env.prod를 base profile env로 함께 로딩할 수 있음.
  • 설정 source 우선순위는 명시적 초기화 값, 선택된 ENV_FILE, 앱 공통 env, base profile env, .env.shared, 셸 환경 변수, file secret 순서임.
  • 실행 시점 환경 변수 오염으로 기본 설정이 흔들리지 않도록 설계함.
  • 운영 컨테이너는 /sorc001/junkbox/env/.env.shared/sorc001/junkbox/env/.env.prod를 함께 참조하고, ENV_FILE=/app/env/.env.prod 기준으로 로딩하는 전제를 가짐.
  • 운영 jeevtrap 컨테이너는 /sorc001/junkbox/env/.env.shared, /sorc001/junkbox/env/.env.jeevtrap, /sorc001/junkbox/env/.env.prod.jeevtrap를 함께 참조하고, ENV_FILE=/app/env/.env.prod.jeevtrap 기준으로 로딩하는 전제를 가짐.
  • 설정 객체는 app, database, auth, server, modules, masterdata, dashboard, rename, raid, lichess, jeevtrap, ai 중첩 모델 구조를 사용함.
  • 앱 코드는 settings.modules.hub.base_url, settings.modules.chess.base_url, settings.modules.workout.database_url, settings.modules.raid.base_url, settings.ai.providers.litellm.api_key, settings.ai.providers.litellm.base_url, settings.lichess.bot_token, settings.rename.asite.base_url, settings.raid.wcl.client_id, settings.jeevtrap.github.repo 같은 중첩 경로 접근을 기본으로 사용함.
  • 모듈별 로깅 설정도 settings.modules.hub.logging, settings.modules.chess.logging, settings.modules.workout.logging, settings.modules.raid.logging, settings.modules.jeevtrap.logging 같은 중첩 경로 접근을 사용함.
  • 기존 호환을 위해 일부 flat alias 프로퍼티도 제공하지만, 신규 구현은 중첩 모델 접근을 우선함.

3.2 주요 설정 책임

  • PostgreSQL DB 연결 정보 보관
  • Hub 공통 SQLite 연결 정보 보관
  • Workout 도메인 SQLite 연결 정보 보관
  • JWT 서명과 알고리즘 보관
  • 쿠키 옵션 보관
  • 앱 프로필 보관
  • CORS 설정 보관
  • 공통 로깅 rotation, retention, compression 정책 보관
  • profile별 로깅 level, diagnose/backtrace 토글 보관
  • AI 라우팅 파일 경로와 공통 런타임 마스터데이터 디렉터리 경로를 보관함.
  • 체스 앱 전용 런타임 YAML 경로를 보관함.
  • 공통 로그인 복귀 기준이 되는 HUB_BASE_URL, WORKOUT_BASE_URL, RAID_BASE_URL 보관
  • 체스 샌드박스 진입 기준이 되는 CHESS_BASE_URL 보관
  • 모듈 간 내부 API 호출 기준이 되는 HUB_INTERNAL_BASE_URL 보관
  • Rename 수집과 파일 시스템 작업 기준이 되는 ASITE_*, RENAME_BASE_DIR 보관
  • Raid WCL 연동에 필요한 WCL_CLIENT_ID, WCL_CLIENT_SECRET, WCL_TOKEN_URL, WCL_GRAPHQL_URL, WCL_TIMEOUT_SECONDS 보관
  • Lichess Bot API 연동에 필요한 LICHESS_BOT_TOKEN, challenge 기본값, HTTP timeout, stream reconnect delay 보관
  • LiteLLM proxy 연동에 필요한 LITELLM_BASE_URL, LITELLM_API_KEY 보관
  • Jeevtrap Discord, GitHub, Linkding, 명령 target 설정, timeout 기준값, voice runtime 활성화 여부와 voice YAML 경로 보관
  • 내부 master-data API 토큰과 TTL/타임아웃 설정 보관
  • hub 대시보드 Glances API 접속 설정 보관
  • hub 대시보드 OpenRouter Management API 접속 설정 보관
  • COOKIE_NAME 기본값은 JUNKBOX_AUTH임.
  • JWT_SECRET_KEY는 브라우저 사용자 인증용 JWT 서명/검증 키임.
  • 사용자 세션 만료 시간과 슬라이딩 갱신 임계값은 shared-core 설정이 아니라 hub-api 정책 파일에서 관리함.
  • HUB_API_INTERNAL_TOKEN은 모듈 간 내부 인증 API와 내부 master-data API 호출용 서버 간 토큰임.
  • AI_ACCESS_TOKEN은 AI 에이전트가 표준 통합 master-data API를 호출할 때 사용하는 ROLE_AI JWT 값임.
  • FASTAPI_BASE_URL은 AI 에이전트 로컬 스크립트가 호출할 hub-api base URL임.
  • 사용자 인증 JWT 서명키, 서버 간 내부 토큰, AI 에이전트 토큰은 용도가 다르므로 서로 다른 랜덤 값을 사용하는 것을 기본 원칙으로 함.
  • server.cors_origins는 모든 FastAPI 앱이 공통으로 재사용하는 CORS 허용 origin 목록임.
  • modules.hub.logging.file_path, modules.chess.logging.file_path, modules.workout.logging.file_path, modules.raid.logging.file_path, modules.jeevtrap.logging.file_path는 모듈별 파일 로그 출력 위치를 나타냄.
  • modules.hub.database_url은 Hub 공통 SQLite DB 접속 문자열임.
  • HUB_DATABASE_URL이 비어 있으면 로컬 비컨테이너 실행은 /sorc001/junkbox/db/hub.sqlite3, 컨테이너 실행은 /app/db/hub.sqlite3 기준 SQLite DSN을 기본값으로 사용함.
  • modules.workout.database_url은 Workout 도메인 SQLite DB 접속 문자열임.
  • WORKOUT_DATABASE_URL이 비어 있으면 로컬 비컨테이너 실행은 /sorc001/junkbox/db/workout.sqlite3, 컨테이너 실행은 /app/db/workout.sqlite3 기준 SQLite DSN을 기본값으로 사용함.

3.3 경로 및 내부 호출 설정

  • MASTER_DATA_DIR는 AI 라우팅 YAML 기본 디렉토리 계산에 사용함.
  • 로컬 기본 MASTER_DATA_DIR는 프로젝트 루트 기준 apps/hub-api/var/master-data임.
  • 컨테이너 기본 MASTER_DATA_DIR/app/var/master-data임.
  • AI_ROUTING_YAML_PATH는 AI 라우팅 파일 경로 오버라이드용임.
  • AI_ROUTING_YAML_PATH가 비어 있으면 MASTER_DATA_DIR/ai-routing.yml을 기본값으로 계산함.
  • CHESS_RUNTIME_CONFIG_YAML_PATH는 chess-api 전용 런타임 YAML 경로 오버라이드용임.
  • 로컬 기본 CHESS_RUNTIME_CONFIG_YAML_PATH는 프로젝트 루트 기준 apps/chess-api/config/chess-runtime.yml임.
  • 컨테이너 기본 CHESS_RUNTIME_CONFIG_YAML_PATH/app/apps/chess-api/config/chess-runtime.yml임.
  • JEEVTRAP_VOICE_ENABLED는 Jeevtrap voice runtime 시작 여부를 제어함.
  • JEEVTRAP_VOICE_CONFIG_YAML_PATH는 Jeevtrap voice runtime이 읽는 YAML 경로 오버라이드용임.
  • 로컬 기본 JEEVTRAP_VOICE_CONFIG_YAML_PATH는 프로젝트 루트 기준 apps/jeevtrap/var/voice.yml임.
  • 컨테이너 기본 JEEVTRAP_VOICE_CONFIG_YAML_PATH/app/apps/jeevtrap/var/voice.yml임.
  • HUB_API_INTERNAL_TOKEN, HUB_API_INTERNAL_TOKEN_HEADER는 내부 master-data API 호출 인증 설정임.
  • HUB_INTERNAL_BASE_URL은 소비 앱 실행 위치에서 접근 가능한 hub-api 내부 API base URL임.
  • 로컬 VS Code task 기준 hub-api18001 포트로 실행되며, 컨테이너 내부 통신 기준 운영 compose는 http://junkbox-hub:8001 계열 내부 주소를 사용함.
  • HUB_DATABASE_URLhub-api가 공통코드, 코드 타입, 메타, 포털 메뉴, 사용자, 사용자 세션, 대시보드 사용자 preference, AI usage log를 저장하는 SQLite 파일 DB URL임.
  • 로컬 비컨테이너 기준 기본 HUB_DATABASE_URLsqlite+aiosqlite:////sorc001/junkbox/db/hub.sqlite3임.
  • Docker 기준 HUB_DATABASE_URLsqlite+aiosqlite:////app/db/hub.sqlite3이며 compose가 /sorc001/junkbox/db/app/db로 마운트함.
  • Hub SQLite 파일은 전역 PostgreSQL DATABASE_URL과 별개이며 hub-api가 앱 import 시점에 settings.modules.hub.database_url로 전역 DB engine을 초기화함.
  • WORKOUT_DATABASE_URLworkout-api가 운동 세션, 운동 기록, 운동 계획을 저장하는 SQLite 파일 DB URL임.
  • 로컬 비컨테이너 기준 기본 WORKOUT_DATABASE_URLsqlite+aiosqlite:////sorc001/junkbox/db/workout.sqlite3임.
  • Docker 기준 WORKOUT_DATABASE_URLsqlite+aiosqlite:////app/db/workout.sqlite3이며 compose가 /sorc001/junkbox/db/app/db로 마운트함.
  • Workout SQLite 파일은 공통 PostgreSQL DATABASE_URL과 별개이며 workout-api가 앱 import 시점에 settings.modules.workout.database_url로 전역 DB engine을 초기화함.
  • MASTERDATA_CODES_TTL_SECONDS, MASTERDATA_METAS_TTL_SECONDS는 모듈별 공통 master-data TTL 캐시 설정임.
  • MASTERDATA_HTTP_TIMEOUT_SECONDS는 내부 master-data API 호출 타임아웃 설정임.
  • DASHBOARD_GLANCES_BASE_URL은 Glances REST API v4 base URL이며, 비어 있으면 홈서버 상태 카드는 미설정으로 표시됨.
  • DASHBOARD_GLANCES_USERNAME, DASHBOARD_GLANCES_PASSWORD는 Glances Basic Auth 설정임.
  • DASHBOARD_GLANCES_BEARER_TOKEN은 Glances 또는 프록시 앞단에서 Bearer 인증을 사용할 때의 토큰 설정임.
  • DASHBOARD_OPENROUTER_MANAGEMENT_API_KEY는 hub 대시보드 AI 카드가 OpenRouter credits와 API key별 사용량을 조회할 때 사용하는 Management API Key임.
  • DASHBOARD_OPENROUTER_MANAGEMENT_API_KEY는 completion 호출용 API key가 아니라 OpenRouter 관리 API 전용 secret임.
  • DASHBOARD_OPENROUTER_BASE_URL은 OpenRouter 관리 API base URL이며 기본값은 https://openrouter.ai임.
  • DASHBOARD_OPENROUTER_TIMEOUT_SECONDS는 OpenRouter credits/key 목록 조회 타임아웃 설정임.
  • OpenRouter Management API Key는 secret이므로 .env.shared에 값을 저장하지 않고 로컬은 .env.local, 운영은 /sorc001/junkbox/env/.env.prod 같은 profile별 secret env에서 관리함.
  • hub 대시보드 HTTP 수집 타임아웃, 전체 응답 TTL, 일반 외부 HTML source별 TTL, 핫딜 source별 TTL, Glances 호출 타임아웃은 apps/hub-api/var/dashboard-policy.yml에서 관리함.
  • ASITE_BASE_URL, ASITE_LOGIN_URL, ASITE_USERNAME, ASITE_PASSWORD는 Rename 수집기 로그인과 조회 기준값임.
  • RENAME_BASE_DIR는 Rename 파일 작업 루트 경로이며, ${RENAME_BASE_DIR}/010. tmp 하위 JSON 메타와 로그 파일 구조의 기준점임.
  • JEEVTRAP_LINKDING_DATABASE_URL은 SQLAlchemy async URL 형식 postgresql+asyncpg://...를 사용함.
  • JEEVTRAP_LINKDING_OWNER_ID는 Linkding DB 사용자 PK 기준 값임.
  • JEEVTRAP_GITHUB_*는 GitHub contents API 기반 마크다운 저장 경로 계산과 인증에 사용함.
  • JEEVTRAP_COMMAND_DEFAULT_TIMEOUT_SECONDSJEEVTRAP_TARGET_DESKTOP_MAIN_*은 명령 실행 서비스의 타깃 정보와 제한 시간을 제어함.
  • 선택된 ENV_FILE 값은 앱 공통 env, base profile env, .env.shared 공통값보다 우선하며, 앱 공통 env는 base profile env와 .env.shared보다 우선함. Jeevtrap 전용 env 파일은 base profile env를 로드하지 않고 .env.jeevtrap 공통값과 profile별 값을 조합함. APP_PROFILE은 profile별 AI 라우팅 파일 선택에도 사용함.
  • 운영에서는 APP_PROFILE=prod, HUB_BASE_URL, CHESS_BASE_URL, WORKOUT_BASE_URL, RAID_BASE_URL, HUB_INTERNAL_BASE_URL, COOKIE_DOMAIN, COOKIE_SECURE, HUB_API_INTERNAL_TOKEN, AI_ACCESS_TOKEN, FASTAPI_BASE_URL, RENAME_BASE_DIR, WCL_CLIENT_ID, WCL_CLIENT_SECRET, LICHESS_BOT_TOKEN, LITELLM_BASE_URL, LITELLM_API_KEY, DASHBOARD_OPENROUTER_MANAGEMENT_API_KEY의 정합성이 중요함.
  • 운영 컨테이너 내부 공통 로그 디렉터리는 /app/logs임.
  • 운영 컨테이너 내부 공통 AI 라우팅 런타임 경로는 /app/var/master-data/ai-routing.yml임.
  • 운영 호스트 공통 AI 라우팅 파일 경로는 /sorc001/junkbox/master-data/ai-routing.yml임.
  • 로컬 기본 로그 디렉터리는 프로젝트 루트 기준 var/log/{app-name} 구조임.
  • 로컬 var/log/ 하위 로그 파일과 rotation 압축 파일은 Git 추적 대상이 아니며 .gitignore에서 제외함.
  • 운영 기본 로그 파일 경로는 hub-api=/app/logs/app.log, chess-api=/app/logs/app.log, workout-api=/app/logs/app.log, raid-api=/app/logs/app.log, jeevtrap=/app/logs/app.log임.

3.4 로깅 설정 구조

  • 공통 로깅 정책은 LOG_ROTATION_LIMIT, LOG_RETENTION_DAYS, LOG_COMPRESSION_TYPE 기준으로 제어함.
  • profile별 로깅 동작은 LOG_LEVEL, LOG_DIAGNOSE_TOGGLE 기준으로 제어함.
  • 위 공통 키는 modules.hub.logging, modules.chess.logging, modules.workout.logging, modules.raid.logging, modules.jeevtrap.logging에 공통 주입됨.
  • 필요 시 HUB_LOG_*, CHESS_LOG_*, WORKOUT_LOG_*, RAID_LOG_*, JEEVTRAP_LOG_* 키로 모듈별 override를 줄 수 있는 구조를 가짐.
  • diagnosebacktraceLOG_DIAGNOSE_TOGGLE 값과 연동되며, 로컬 디버깅 문맥에서만 활성화하는 구성을 전제로 함.

4. 공통 로깅 계층

4.1 초기화 책임

  • setup_app_logging()shared-core가 제공하는 공통 초기화 진입점임.
  • FastAPI 앱은 부팅 시점에 이 함수를 호출하여 표준 출력과 파일 출력을 동시에 구성함.
  • 표준 logging으로 들어오는 로그도 intercept handler를 통해 loguru로 수렴시킴.

4.2 파일 출력 정책

  • 콘솔 출력은 sys.stdout 기준 단일 sink를 사용함.
  • 파일 출력은 모듈별 logging.file_path 기준 sink를 사용함.
  • 파일 sink는 rotation, retention, compression, enqueue=True, encoding="utf-8" 정책을 공통으로 적용함.
  • 현재 기본 운영 파일명은 앱 디렉터리 아래 app.log임.
  • 운영 호스트 볼륨과 결합되면 실제 파일 경로는 /logs001/junkbox/{app-name}/app.log 구조가 됨.

4.3 적용 모듈

  • 현재 hub-api, chess-api, workout-api, raid-api, jeevtrap가 공통 로깅 초기화를 직접 사용함.
  • shared-ai 기반 기능의 로그도 최종적으로 같은 loguru sink 체계로 수렴함.

5. DB 계층

5.1 엔진

  • create_async_engine()을 사용함.
  • 기본 PostgreSQL DSN은 postgresql+asyncpg:// 계열임.
  • hub-apisqlite+aiosqlite:// 계열 HUB_DATABASE_URL로 engine을 초기화함.
  • workout-apisqlite+aiosqlite:// 계열 WORKOUT_DATABASE_URL로 engine을 초기화함.
  • 연결 풀과 echo 설정은 Settings에 의존함.
  • PostgreSQL engine은 pool_size, max_overflow 설정을 사용함.
  • SQLite engine은 PostgreSQL pool 옵션을 사용하지 않고 DB 파일 부모 디렉터리를 준비함.
  • SQLite engine은 연결 시 PRAGMA journal_mode=WAL, PRAGMA busy_timeout=5000, PRAGMA foreign_keys=ON을 적용함.
  • ensure_engine_initialized()는 필요 시 전역 엔진과 세션 메이커를 초기화함.
  • get_session_maker()는 저장소 계층이나 앱 계층이 공통 세션 메이커를 재사용할 수 있도록 제공됨.

5.2 세션

  • async_sessionmaker()를 사용함.
  • AsyncSession을 모든 앱의 공통 세션 타입으로 사용함.
  • get_session() 의존성은 async with 기반으로 세션을 제공함.
  • get_session()은 요청 종료 시 commit 또는 rollback을 공통 처리함.

5.3 AsyncSession 사용 규칙

  • session.exec()를 사용하지 않음.
  • await session.execute(select(...)) 패턴을 사용함.
  • 결과 해석은 scalar_one_or_none(), scalar_one(), scalars().all(), all()을 상황별로 명시함.
  • 저장소 계층은 asynccontextmanager 기반 세션 래퍼를 사용해 읽기와 쓰기 commit 경계를 분리할 수 있음.

6. 모델 계층

6.1 공통 모델

  • BaseEntitycreated_at, updated_at, created_by, updated_by, is_active 공통 필드를 제공함.
  • PostgreSQL 스키마별 엔티티와 SQLite 파일 DB 엔티티가 SQLModel로 정의됨.
  • BaseEntity 계열 엔티티는 감사 컬럼 NOT NULL 제약을 만족할 수 있도록 기본값 전략을 가짐.

6.2 현재 주요 엔티티

  • UserEntity
  • CodeTypeEntity
  • CommonCodeEntity
  • MetaEntity
  • PortalMenuEntity
  • DashboardUserPreferenceEntity
  • WorkoutSessionEntity
  • WorkoutRecordEntity
  • WorkoutPlanEntity
  • MemberEntity
  • RaidEntity
  • RaidMemberEntity
  • RaidBossScoreEntity

6.3 마스터데이터 엔티티 매핑

  • CodeTypeEntity는 Hub SQLite tb_code_type_c에 매핑되며 코드 타입 전용 컬럼명 code_type_code_id, code_type_code, code_type_code_name, code_type_code_desc를 사용함.
  • CommonCodeEntity는 Hub SQLite tb_common_c에 매핑됨.
  • MetaEntity는 Hub SQLite tb_meta_m에 매핑됨.
  • PortalMenuEntity는 Hub SQLite tb_portal_menu_m에 매핑됨.
  • UserEntity는 Hub SQLite tb_user_m에 매핑됨.
  • DashboardUserPreferenceEntity는 Hub SQLite tb_dashboard_user_preference_n에 매핑됨.
  • WorkoutSessionEntity는 Workout SQLite tb_workout_session_n에 매핑됨.
  • WorkoutRecordEntity는 Workout SQLite tb_workout_record_n에 매핑됨.
  • WorkoutPlanEntity는 Workout SQLite tb_workout_plan_n에 매핑됨.
  • MemberEntityraid.tb_member_m에 매핑됨.
  • RaidEntityraid.tb_raid_m에 매핑됨.
  • RaidMemberEntityraid.tb_raid_member_n에 매핑됨.
  • RaidBossScoreEntityraid.tb_raid_boss_score_n에 매핑됨.

6.4 엔티티 설계 규칙

  • PostgreSQL 특수 컬럼은 sa_column=Column(...)으로 직접 선언함.
  • JSONB, NUMERIC, TIMESTAMPTZ 같은 PostgreSQL 타입은 PostgreSQL 엔티티에서 SQLAlchemy 컬럼 타입으로 유지함.
  • SQLite 호환 도메인 엔티티는 PostgreSQL schema qualifier와 PostgreSQL 전용 타입을 사용하지 않음.
  • Hub/Workout SQLite JSON 컬럼은 SQLAlchemy 공통 JSON 타입을 사용하고, DB 내부 JSON 조건 검색보다 Python 객체 read/write를 기준으로 소비함.
  • Hub/Workout PK 컬럼은 SQLite에서 INTEGER PRIMARY KEY로 동작하도록 SQLite variant를 가진 정수 PK 타입을 사용함.
  • allowed_moduleslist[str] + SQLite JSON 배열 구조를 사용함.
  • DashboardUserPreferenceEntity.favorite_card_idslist[str] + SQLite JSON 배열 구조를 사용하고, 대시보드 큰 카드 즐겨찾기 id를 표시 순서대로 보관함.
  • DashboardUserPreferenceEntity.card_ordersdict[str, list[str]] + SQLite JSON object 구조를 사용하고, 대시보드 탭별 큰 카드 정렬 순서를 보관함.
  • DB 컬럼명이 레거시 이름이어도 앱 내부 필드명은 현재 계약에 맞게 매핑할 수 있음.
  • 사용자 엔티티는 id 물리 PK와 user_id 논리 식별자를 함께 사용함.
  • DB 기반 감사 컬럼이 NOT NULL 제약을 가지는 엔티티는 기본값 또는 API 계층 보강으로 None 삽입을 피해야 함.

7. 공통 마스터 데이터 계층

7.1 저장소 책임

  • masterdata/ 패키지는 공통코드, 코드 타입, 메타, 포털 메뉴 저장소를 담당함.
  • 현재 구현 진입점은 JsonMasterDataStore임.
  • 클래스 이름은 호환성을 위해 유지되지만, 현재 구현은 JSON/YAML 파일 저장소가 아니라 DB 저장소임.
  • hub-api는 공통 마스터 데이터 CRUD와 AI snapshot 구성 시 이 저장소 계층을 사용함.
  • workout-api, raid-api, rename-api, jeevtrap 같은 소비 앱은 저장소를 직접 호출하지 않고 공통 소비 서비스로 hub-api 내부 API를 호출함.
  • MasterDataServiceHubMasterDataClientTtlAsyncCache를 조합하여 소비 앱의 코드 목록과 메타 조회를 담당함.
  • HUB_API_INTERNAL_TOKEN이 설정된 소비 앱은 local store fallback을 사용하지 않고 HUB_INTERNAL_BASE_URL의 내부 API 호출을 우선함.

7.2 관리 대상

  • Hub SQLite tb_code_type_c
  • Hub SQLite tb_common_c
  • Hub SQLite tb_meta_m
  • Hub SQLite tb_portal_menu_m

7.3 저장 원칙

  • 저장소는 공통 세션 메이커를 사용해 읽기/쓰기 세션을 분리함.
  • 쓰기 작업은 저장소 내부에서 commit 경계를 관리함.
  • 앱 계층은 저장소가 반환하는 현재 계약 필드명만 소비함.
  • 소비 앱은 저장소가 아니라 MasterDataService가 반환하는 현재 계약 필드명만 소비함.

7.4 공통코드 구조

  • 코드 타입 메타는 tb_code_type_c에 저장함.
  • 실제 코드는 tb_common_c에 저장함.
  • 코드 타입과 일반 코드를 한 테이블에 섞지 않음.
  • 공통코드 조회와 저장 API는 tb_common_c 기준의 common_code_id, code_type_code, common_code 계약을 유지하고, 코드 타입 메타는 tb_code_type_c 기준의 code_type_code_id, code_type_code, code_type_code_name, code_type_code_desc 계약을 사용함.

7.5 텍스트 처리 구조

  • 일반 UI 텍스트는 shared-core 마스터데이터 계약에 포함하지 않음.
  • Python 서버 렌더링 문구와 React UI 문구는 각 템플릿/컴포넌트 소스에 한국어로 직접 작성함.

7.6 메타 구조

  • 메타는 meta_key, meta_val, description, is_active 중심 구조를 사용함.
  • attribute01~attribute05, sort_order 같은 레거시 메타 보조 컬럼은 현재 계약에서 사용하지 않음.

7.7 포털 메뉴 구조

  • 포털 메뉴는 menu_id, menu_name, menu_type_code, menu_url, local_menu_url, icon_type_code, icon_val, description, is_desc_visible, sort_order_no, is_active 중심 구조를 사용함.
  • 앱 메뉴는 menu_type_code=APP을 사용함.
  • 하위 호환을 위해 menu_type_code=PROJECT 값은 포털 소비 화면과 정렬 저장에서 APP 그룹으로 해석될 수 있음.
  • 서비스 메뉴는 menu_type_code=SERVICE를 사용함.
  • icon_type_code=TEXTicon_val을 텍스트 아이콘으로 사용함.
  • icon_type_code=IMAGEicon_val을 이미지 URL로 사용함.
  • icon_type_code=APP_ICONicon_valshared-ui 앱 아이콘 slug로 사용함.
  • APP_ICONicon_val은 포털 카드 아이콘, 앱 favicon, PWA 설치 아이콘을 같은 앱 아이콘 세트로 연결하는 관리 값임.
  • 포털 소비 화면과 포털 메뉴 관리 화면은 같은 저장소를 공유함.
  • 포털 정렬 저장은 탭별 sort_order_no 갱신으로 처리함.
  • 포털 정렬 저장에서 APP 탭은 APP과 하위 호환 PROJECT active row 집합을 함께 대상으로 삼을 수 있음.

7.8 소비 클라이언트와 TTL 캐시

  • HubMasterDataClienthub-api 내부 master-data API를 호출하는 공통 HTTP 클라이언트임.
  • HubMasterDataClientHUB_INTERNAL_BASE_URL, HUB_API_INTERNAL_TOKEN, HUB_API_INTERNAL_TOKEN_HEADER, MASTERDATA_HTTP_TIMEOUT_SECONDS 설정을 사용함.
  • TtlAsyncCache는 key별 TTL 캐시와 key별 lock을 제공하여 동일 master-data 요청의 중복 로딩을 줄임.
  • MasterDataService는 코드 타입별 코드 목록, 코드명 맵, 메타 값, active 포털 메뉴 목록을 조회하는 공통 서비스임.
  • MasterDataService.get_portal_app_icon_key(app_key, match_values=...)는 active 포털 메뉴 중 APP 또는 하위 호환 PROJECT 메뉴의 APP_ICON 값을 찾아 앱 아이콘 slug를 반환함.
  • 앱별 favicon 또는 PWA icon slug는 앱 내부 상수보다 포털 메뉴의 APP_ICON + icon_val을 우선 사용하고, 조회 실패 시 앱 기본 slug를 fallback으로 사용할 수 있음.
  • create_masterdata_service()는 앱별 FastAPI 의존성 또는 daemon 초기화 코드가 공통 소비 서비스를 만들 때 사용하는 진입점임.
  • MASTERDATA_CODES_TTL_SECONDS, MASTERDATA_METAS_TTL_SECONDS가 각 데이터 범주의 캐시 TTL을 제어함.

7.9 텍스트 포맷팅 기준

  • 메시지 DB 조회나 공통 메시지 포맷팅 헬퍼는 사용하지 않음.
  • 파라미터가 필요한 사용자 문구는 각 호출 지점에서 템플릿 리터럴, f-string, Jinja 변수 치환 등 언어별 기본 문자열 기능으로 작성함.
  • 반복되는 문구가 실제 중복 비용을 만들 때만 앱 내부 상수나 작은 helper를 검토함.

7.10 local fallback 정책

  • APP_PROFILE=local에서 HUB_API_INTERNAL_TOKEN이 비어 있으면 HubMasterDataClient는 개발 편의용 local store fallback을 사용할 수 있음.
  • local fallback은 JsonMasterDataStore를 통해 같은 DB 저장소 계약을 조회함.
  • 운영 profile에서는 HUB_API_INTERNAL_TOKEN 누락 시 내부 master-data API 소비가 실패함.
  • 운영 profile에서는 공통 master-data 조회가 조용히 로컬 DB 직접 조회로 전환되지 않음.
  • fallback이 동작하면 경고 로그를 남김.

8. 인증 계층

8.1 JWT 유틸리티

  • 토큰 생성, 검증, 쿠키 생성 책임을 가짐.
  • 앱은 JWT 구현 세부사항을 직접 다루지 않음.
  • 사용자 로그인 토큰 생성 시 만료 시간은 호출자가 명시적으로 전달함.
  • 사용자 로그인 토큰은 sid claim을 포함할 수 있으며, sidhub-api가 소유하는 사용자 세션 row의 session_id와 매칭됨.
  • 사용자 세션 만료 시간과 슬라이딩 갱신 임계값의 소스 오브 트루스는 hub-api 정책 파일임.
  • shared-core는 사용자 세션 row를 직접 저장하거나 조회하지 않으며, 일반 사용자 JWT의 세션 유효성 판정은 hub 내부 인증 API에 위임함.
  • 쿠키 생성 시 max_age_seconds는 호출자가 명시적으로 전달하며, shared-core는 세션 정책값을 직접 소유하지 않음.
  • AI 에이전트 전용 토큰은 sub=JUNKBOX_AI_AGENT, role=ROLE_AI claim을 포함하고 만료 클레임을 포함하지 않는 장기 토큰 구조임.

8.2 의존성 함수

  • get_current_user는 필수 인증임.
  • get_optional_user는 선택 인증임.
  • require_ai_role은 AI 에이전트 전용 read-only API에서 ROLE_AI claim을 강제하는 의존성임.
  • require_module(module_code)는 권한 강제 의존성임.
  • 공통 인증 의존성은 Authorization: Bearer 헤더를 우선 해석하고, 없으면 COOKIE_NAME 설정값 기준 쿠키를 해석함.
  • 일반 사용자 JWT는 로컬 서명/만료 검증 후 HUB_INTERNAL_BASE_URLPOST /api/internal/auth/validate를 호출해 세션 유효성, 최신 사용자 활성 상태, 최신 권한을 확인함.
  • POST /api/internal/auth/validate 호출에는 HUB_API_INTERNAL_TOKEN_HEADER 헤더와 HUB_API_INTERNAL_TOKEN을 사용함.
  • hub 인증 validate 응답의 allowed_modules가 현재 권한 판단의 기준임.
  • ROLE_AI JWT는 사용자 세션 테이블 검증 대상이 아니며 require_ai_role이 필요한 read-only API에서만 허용됨.
  • require_module(module_code)ROLE_AI 토큰이 일반 비즈니스 API에 접근하면 403으로 차단함.

8.3 hub 인증 클라이언트

  • HubAuthClient는 개별 앱과 공통 인증 계층이 hub 내부 인증 API를 호출할 때 사용하는 공통 HTTP 클라이언트임.
  • HubAuthClient.validate()POST /api/internal/auth/validate를 호출해 일반 사용자 JWT의 세션 유효성을 확인함.
  • HubAuthClient.refresh()POST /api/internal/auth/refresh를 호출해 슬라이딩 세션 갱신 필요 여부와 새 JWT를 조회함.
  • 내부 호출 설정은 HUB_INTERNAL_BASE_URL, HUB_API_INTERNAL_TOKEN, HUB_API_INTERNAL_TOKEN_HEADER, MASTERDATA_HTTP_TIMEOUT_SECONDS를 사용함.
  • 클라이언트는 요청의 user-agent와 client IP 또는 x-forwarded-for 값을 hub에 전달할 수 있으며, hub는 이를 세션 last_seen_at, user_agent, ip_address 갱신에 사용할 수 있음.
  • hub 내부 인증 API가 401을 반환하면 클라이언트는 인증 실패로 해석하고 사용자 인증 흐름에 처리를 위임함.

8.4 권한 정규화

  • allowed_modules는 배열, JSON 문자열, 콤마 문자열 등 다양한 입력을 수용할 수 있어야 함.
  • 내부 판단은 대문자 모듈 코드 배열 기준으로 수행함.
  • 과거 SYSTEM 코드는 HUB와 같은 운영 허브 권한으로 정규화함.
  • 사용자 관리 API는 허용 모듈 입력을 대문자 코드 집합으로 정규화하고, 중복과 미허용 코드를 제거함.

8.5 슬라이딩 세션 미들웨어

  • SlidingSessionMiddleware는 브라우저 쿠키 기반 요청의 세션 갱신을 공통 처리함.
  • 미들웨어는 요청 쿠키에 COOKIE_NAME 토큰이 없으면 아무 동작도 하지 않음.
  • 미들웨어는 HUB_INTERNAL_BASE_URLPOST /api/internal/auth/refresh를 호출하고 HUB_API_INTERNAL_TOKEN_HEADER 헤더에 HUB_API_INTERNAL_TOKEN을 전달함.
  • hub 내부 인증 API가 refreshed=true와 새 토큰을 반환하면 미들웨어는 응답에 같은 쿠키 이름으로 새 HttpOnly 쿠키를 설정함.
  • hub 내부 인증 API가 refreshed=false를 반환하면 미들웨어는 쿠키를 변경하지 않음.
  • hub 내부 인증 API가 인증 실패 토큰에 대해 401을 반환하면 미들웨어는 세션 갱신을 수행하지 않고 이후 인증 흐름에 처리를 위임함.
  • 미들웨어의 갱신 판단은 hub 세션 테이블과 정책 YAML을 기준으로 하며, shared-core는 직접 세션 저장소를 소유하지 않음.
  • hub 내부 인증 API 호출이 네트워크 오류, timeout, 401을 제외한 오류 상태로 실패하면 미들웨어는 공통 예외 응답으로 처리함.
  • 미들웨어는 로그인, 로그아웃, /api/internal/*, 정적 자산, 문서, 헬스체크 경로를 갱신 대상에서 제외함.
  • add_sliding_session_middleware(app)는 hub, workout, raid, rename, chess 같은 공통 로그인 소비 앱의 FastAPI 앱 초기화 시 등록함.
  • jeevtrap은 Discord interaction context를 인증 기준으로 사용하므로 공통 로그인 슬라이딩 세션 미들웨어 등록 대상이 아님.

9. 비밀번호 계층

9.1 해싱

  • passlib 컨텍스트를 사용함.
  • bcrypt 조합을 기준으로 동작함.

9.2 호환성 처리

  • passlib가 기대하는 bcrypt 인터페이스 차이를 보정하는 shim을 포함함.
  • 목적은 경고 제거와 안정적 해시/검증 유지임.

10. 예외 계층

  • 커스텀 예외 타입을 제공함.
  • FastAPI 예외 핸들러 등록 함수를 제공함.
  • 앱은 HTTP 상태 코드와 메시지 포맷을 공통 규칙으로 유지함.

11. 사용 원칙

  • 앱 코드에서 중복 인증, 중복 설정, 중복 세션 생성을 만들지 않음.
  • 공통 규칙은 shared-core에 수렴시킴.
  • 새 앱 추가 시 우선 shared-core 의존으로 시작함.
  • 공통 마스터 데이터 소비가 필요하면 앱별 HTTP 클라이언트나 TTL 캐시를 새로 만들지 않고 MasterDataService를 사용함.
  • 공통 마스터 데이터 추가가 필요하면 우선 DB 저장소와 hub-api API 계약에 맞는지 검토함.
  • AI 전용 master-data 접근은 앱별 임의 구현보다 hub-api의 ROLE_AI 기반 통합 snapshot API와 공통 인증 의존성을 우선 사용함.
  • AI 라우팅처럼 정책성 파일이 필요한 경우에만 YAML 경로 관리 방식을 사용함.