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_AIJWT 값임.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-api는18001포트로 실행되며, 컨테이너 내부 통신 기준 운영 compose는http://junkbox-hub:8001계열 내부 주소를 사용함. HUB_DATABASE_URL은hub-api가 공통코드, 코드 타입, 메타, 포털 메뉴, 사용자, 사용자 세션, 대시보드 사용자 preference, AI usage log를 저장하는 SQLite 파일 DB URL임.- 로컬 비컨테이너 기준 기본
HUB_DATABASE_URL은sqlite+aiosqlite:////sorc001/junkbox/db/hub.sqlite3임. - Docker 기준
HUB_DATABASE_URL은sqlite+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_URL은workout-api가 운동 세션, 운동 기록, 운동 계획을 저장하는 SQLite 파일 DB URL임.- 로컬 비컨테이너 기준 기본
WORKOUT_DATABASE_URL은sqlite+aiosqlite:////sorc001/junkbox/db/workout.sqlite3임. - Docker 기준
WORKOUT_DATABASE_URL은sqlite+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_SECONDS와JEEVTRAP_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를 줄 수 있는 구조를 가짐. diagnose와backtrace는LOG_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기반 기능의 로그도 최종적으로 같은logurusink 체계로 수렴함.
5. DB 계층
5.1 엔진
create_async_engine()을 사용함.- 기본 PostgreSQL DSN은
postgresql+asyncpg://계열임. hub-api는sqlite+aiosqlite://계열HUB_DATABASE_URL로 engine을 초기화함.workout-api는sqlite+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 공통 모델
BaseEntity는created_at,updated_at,created_by,updated_by,is_active공통 필드를 제공함.- PostgreSQL 스키마별 엔티티와 SQLite 파일 DB 엔티티가 SQLModel로 정의됨.
BaseEntity계열 엔티티는 감사 컬럼NOT NULL제약을 만족할 수 있도록 기본값 전략을 가짐.
6.2 현재 주요 엔티티
UserEntityCodeTypeEntityCommonCodeEntityMetaEntityPortalMenuEntityDashboardUserPreferenceEntityWorkoutSessionEntityWorkoutRecordEntityWorkoutPlanEntityMemberEntityRaidEntityRaidMemberEntityRaidBossScoreEntity
6.3 마스터데이터 엔티티 매핑
CodeTypeEntity는 Hub SQLitetb_code_type_c에 매핑되며 코드 타입 전용 컬럼명code_type_code_id,code_type_code,code_type_code_name,code_type_code_desc를 사용함.CommonCodeEntity는 Hub SQLitetb_common_c에 매핑됨.MetaEntity는 Hub SQLitetb_meta_m에 매핑됨.PortalMenuEntity는 Hub SQLitetb_portal_menu_m에 매핑됨.UserEntity는 Hub SQLitetb_user_m에 매핑됨.DashboardUserPreferenceEntity는 Hub SQLitetb_dashboard_user_preference_n에 매핑됨.WorkoutSessionEntity는 Workout SQLitetb_workout_session_n에 매핑됨.WorkoutRecordEntity는 Workout SQLitetb_workout_record_n에 매핑됨.WorkoutPlanEntity는 Workout SQLitetb_workout_plan_n에 매핑됨.MemberEntity는raid.tb_member_m에 매핑됨.RaidEntity는raid.tb_raid_m에 매핑됨.RaidMemberEntity는raid.tb_raid_member_n에 매핑됨.RaidBossScoreEntity는raid.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_modules는list[str]+ SQLite JSON 배열 구조를 사용함.DashboardUserPreferenceEntity.favorite_card_ids는list[str]+ SQLite JSON 배열 구조를 사용하고, 대시보드 큰 카드 즐겨찾기 id를 표시 순서대로 보관함.DashboardUserPreferenceEntity.card_orders는dict[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를 호출함.MasterDataService는HubMasterDataClient와TtlAsyncCache를 조합하여 소비 앱의 코드 목록과 메타 조회를 담당함.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=TEXT는icon_val을 텍스트 아이콘으로 사용함.icon_type_code=IMAGE는icon_val을 이미지 URL로 사용함.icon_type_code=APP_ICON은icon_val을shared-ui앱 아이콘 slug로 사용함.APP_ICON의icon_val은 포털 카드 아이콘, 앱 favicon, PWA 설치 아이콘을 같은 앱 아이콘 세트로 연결하는 관리 값임.- 포털 소비 화면과 포털 메뉴 관리 화면은 같은 저장소를 공유함.
- 포털 정렬 저장은 탭별
sort_order_no갱신으로 처리함. - 포털 정렬 저장에서
APP탭은APP과 하위 호환PROJECTactive row 집합을 함께 대상으로 삼을 수 있음.
7.8 소비 클라이언트와 TTL 캐시
HubMasterDataClient는hub-api내부 master-data API를 호출하는 공통 HTTP 클라이언트임.HubMasterDataClient는HUB_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 구현 세부사항을 직접 다루지 않음.
- 사용자 로그인 토큰 생성 시 만료 시간은 호출자가 명시적으로 전달함.
- 사용자 로그인 토큰은
sidclaim을 포함할 수 있으며,sid는hub-api가 소유하는 사용자 세션 row의session_id와 매칭됨. - 사용자 세션 만료 시간과 슬라이딩 갱신 임계값의 소스 오브 트루스는
hub-api정책 파일임. shared-core는 사용자 세션 row를 직접 저장하거나 조회하지 않으며, 일반 사용자 JWT의 세션 유효성 판정은 hub 내부 인증 API에 위임함.- 쿠키 생성 시
max_age_seconds는 호출자가 명시적으로 전달하며,shared-core는 세션 정책값을 직접 소유하지 않음. - AI 에이전트 전용 토큰은
sub=JUNKBOX_AI_AGENT,role=ROLE_AIclaim을 포함하고 만료 클레임을 포함하지 않는 장기 토큰 구조임.
8.2 의존성 함수
get_current_user는 필수 인증임.get_optional_user는 선택 인증임.require_ai_role은 AI 에이전트 전용 read-only API에서ROLE_AIclaim을 강제하는 의존성임.require_module(module_code)는 권한 강제 의존성임.- 공통 인증 의존성은
Authorization: Bearer헤더를 우선 해석하고, 없으면COOKIE_NAME설정값 기준 쿠키를 해석함. - 일반 사용자 JWT는 로컬 서명/만료 검증 후
HUB_INTERNAL_BASE_URL의POST /api/internal/auth/validate를 호출해 세션 유효성, 최신 사용자 활성 상태, 최신 권한을 확인함. POST /api/internal/auth/validate호출에는HUB_API_INTERNAL_TOKEN_HEADER헤더와HUB_API_INTERNAL_TOKEN을 사용함.- hub 인증 validate 응답의
allowed_modules가 현재 권한 판단의 기준임. ROLE_AIJWT는 사용자 세션 테이블 검증 대상이 아니며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_URL의POST /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-apiAPI 계약에 맞는지 검토함. - AI 전용 master-data 접근은 앱별 임의 구현보다 hub-api의
ROLE_AI기반 통합 snapshot API와 공통 인증 의존성을 우선 사용함. - AI 라우팅처럼 정책성 파일이 필요한 경우에만 YAML 경로 관리 방식을 사용함.