Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Monorepo root context hints (when building from repo root)
.git
**/.git
**/node_modules
packages/web/.next
packages/mobile
**/.turbo
**/target
**/.venv
**/__pycache__
*.pyc
.env
.env.*
!.env.example
!.env.dev.example
logs
*.log
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,8 @@ bun run dev:local-deps # Docker deps only (see scripts/local-deps-up.sh)
bun run dev:local-fe # Next only (same as dev:web)
bun run dev:local-be # API + AI together; logs -> .logs/local/api.log & ai.log (tail -f in other terminals)
# Local host env templates: packages/api-server/.env.dev.example , packages/ai-server/.dev.env.example
# Port alignment (호스트 실행): MEILISEARCH_URL=http://localhost:7700 ; DECODED_AI_GRPC_URL=http://localhost:50052 (AI APP_ENV=dev)
# API GRPC_PORT must equal AI GRPC_BACKEND_PORT ; AI Redis localhost:6303 + SEARXNG localhost:4000 with local-deps
# Port alignment (호스트 실행): MEILISEARCH_URL=http://localhost:7700 ; AI_SERVER_GRPC_URL=http://localhost:50052 (AI APP_ENV=dev)
# API API_SERVER_GRPC_PORT must equal AI API_SERVER_GRPC_PORT ; 레거시 GRPC_PORT / GRPC_BACKEND_* 도 아직 지원. AI Redis localhost:6303 + SEARXNG localhost:4000 with local-deps
# just local-help # prints tail -f hints (root Justfile)

# Web package only
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ uv run python -m src.main

From repo root: `bun run dev:ai-server` (requires `uv` on PATH).

Docker Compose lives under `packages/ai-server/`; **build context** is that directory. See [`packages/ai-server/README.md`](packages/ai-server/README.md).
**Backend stack (Docker)** — `api` + `ai` + Meilisearch + Redis + SearXNG in one Compose: [`packages/api-server/docker/stack/README.md`](packages/api-server/docker/stack/README.md), **`scripts/deploy-backend.sh`**.
AI package docs: [`packages/ai-server/README.md`](packages/ai-server/README.md).

### Monorepo scripts

Expand All @@ -98,6 +99,7 @@ bun run dev:api-server # Rust API (cargo watch)
bun run dev:ai-server # Python AI (uv)
bun run build # Production build (Turborepo)
bun run lint # Lint tasks where defined
bun run deploy:backend -- dev up --build # multi-container stack (see docker/stack README)
```

## Packages (summary)
Expand Down Expand Up @@ -126,6 +128,7 @@ Expo 54 React Native.

- **[CLAUDE.md](CLAUDE.md)** — conventions, routes, commands, design system
- **[docs/BACKEND-ONBOARDING.md](docs/BACKEND-ONBOARDING.md)** — API server in the monorepo
- **[packages/api-server/docker/stack/README.md](packages/api-server/docker/stack/README.md)** — Docker Compose stack (api, ai, meili, redis, searxng), deploy script
- **`packages/api-server/`** — Rust API docs, ADRs, `AGENTS.md`
- **`packages/ai-server/README.md`** — AI service architecture and Docker

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"build:backend": "bun run build:api-server",
"lint": "turbo run lint",
"test": "turbo run test",
"ci:local": "bash scripts/git-pre-push.sh"
"ci:local": "bash scripts/git-pre-push.sh",
"deploy:backend": "bash scripts/deploy-backend.sh"
},
"devDependencies": {
"@types/react": "^19.2.0",
Expand Down
15 changes: 9 additions & 6 deletions packages/ai-server/.dev.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# 로컬 개발 (호스트에서 uv run) + Docker 는 redis/searxng 만
# 로컬 개발 (호스트에서 uv run) + Docker 는 redis/searxng 만 (scripts/local-deps-up.sh)
# Docker 전체 스택: packages/api-server/docker/stack — Compose 가 아래 URL 들을 덮어씀
# REDIS_HOST=redis, SEARXNG_API_URL=http://searxng:8080, API_SERVER_HTTP_URL=http://api:8080, API_SERVER_GRPC_HOST=api
# AI gRPC 리슨 포트는 AI_GRPC_LISTEN_PORT 로 지정 가능(기본: dev=50052, 그 외=50051). api 의 AI_SERVER_GRPC_URL 과 포트를 맞출 것.
# 사용: cp .dev.env.example .dev.env 후 토큰·키를 채우세요.
# api-server .env.dev 의 GRPC_PORTGRPC_BACKEND_PORT 를 맞추세요.
# api-server .env.dev 의 API_SERVER_GRPC_PORT아래 API_SERVER_GRPC_PORT 를 맞추세요.

APP_ENV=dev

Expand All @@ -17,13 +20,13 @@ REDIS_DB=0

QUEUE_BATCH_SIZE=10

DECODED_BACKEND_URL=http://localhost:8000
DECODED_BACKEND_ACCESS_TOKEN=your_backend_token
API_SERVER_HTTP_URL=http://localhost:8000
API_SERVER_ACCESS_TOKEN=your_backend_token

SELENIUM_URL=http://localhost:4444

GRPC_BACKEND_HOST=localhost
GRPC_BACKEND_PORT=50053
API_SERVER_GRPC_HOST=localhost
API_SERVER_GRPC_PORT=50053

PERPLEXITY_API_KEY=
PERPLEXITY_API_URL=https://api.perplexity.ai
Expand Down
12 changes: 6 additions & 6 deletions packages/ai-server/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# AI Server — 예시 (복사: cp .env.example .env)
# 필수 필드는 Environment (src/config/_environment.py) 와 이름이 일치해야 합니다.

# APP_ENV=dev 일 때 Queue gRPC 는 main.py 에서 50052 로 바인딩됩니다 (DECODED_AI_GRPC_URL 과 맞출 것).
# APP_ENV=dev 일 때 Queue gRPC 는 main.py 에서 50052 로 바인딩됩니다 (api-server 의 AI_SERVER_GRPC_URL 과 맞출 것).
APP_ENV=dev

HOST=0.0.0.0
Expand All @@ -22,14 +22,14 @@ REDIS_DB=0
QUEUE_BATCH_SIZE=10

# Rust API HTTP
DECODED_BACKEND_URL=http://localhost:8000
DECODED_BACKEND_ACCESS_TOKEN=your_backend_token
API_SERVER_HTTP_URL=http://localhost:8000
API_SERVER_ACCESS_TOKEN=your_backend_token

SELENIUM_URL=http://localhost:4444

# Rust API 백엔드 gRPC — api-server .env.dev 의 GRPC_PORT 와 동일
GRPC_BACKEND_HOST=localhost
GRPC_BACKEND_PORT=50053
# Rust API 콜백 gRPC — api-server 의 API_SERVER_GRPC_PORT 와 동일
API_SERVER_GRPC_HOST=localhost
API_SERVER_GRPC_PORT=50053

PERPLEXITY_API_KEY=
PERPLEXITY_API_URL=https://api.perplexity.ai
Expand Down
1 change: 1 addition & 0 deletions packages/ai-server/Dockerfile.ai.prod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ FROM python:3.11-slim
# 필요한 시스템 패키지 설치
RUN apt-get update && apt-get install -y \
gcc \
curl \
&& rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir uv
Expand Down
2 changes: 1 addition & 1 deletion packages/ai-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ ARQ Worker → 백그라운드 처리 → Backend 콜백
### 1. 환경 변수 설정

- `.prod.env`, `.dev.env` 등 환경 파일을 준비하세요.
- 예시 변수: `APP_ENV`, `REDIS_PASSWORD`, `DECODED_BACKEND_ACCESS_TOKEN` 등
- 예시 변수: `APP_ENV`, `REDIS_PASSWORD`, `API_SERVER_ACCESS_TOKEN` 등

### 2. Docker Compose로 실행

Expand Down
44 changes: 33 additions & 11 deletions packages/ai-server/src/config/_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
logger = logging.getLogger(__name__)


def _apply_legacy_env_aliases() -> None:
"""If new env keys are unset, copy from deprecated names (api-server / docs migration)."""
pairs = [
("API_SERVER_HTTP_URL", "DECODED_BACKEND_URL"),
("API_SERVER_ACCESS_TOKEN", "DECODED_BACKEND_ACCESS_TOKEN"),
("API_SERVER_GRPC_HOST", "GRPC_BACKEND_HOST"),
("API_SERVER_GRPC_PORT", "GRPC_BACKEND_PORT"),
]
for new_key, old_key in pairs:
if new_key not in os.environ and old_key in os.environ:
os.environ[new_key] = os.environ[old_key]


# Environment Settings
class Environment(BaseModel):
model_config = ConfigDict(extra="allow")
Expand All @@ -29,13 +42,13 @@ class Environment(BaseModel):

QUEUE_BATCH_SIZE: int = 10

DECODED_BACKEND_URL: str
DECODED_BACKEND_ACCESS_TOKEN: str
API_SERVER_HTTP_URL: str
API_SERVER_ACCESS_TOKEN: str

SELENIUM_URL: str

GRPC_BACKEND_HOST: str
GRPC_BACKEND_PORT: int
API_SERVER_GRPC_HOST: str
API_SERVER_GRPC_PORT: int

# External API Configuration
PERPLEXITY_API_KEY: str = ""
Expand Down Expand Up @@ -103,6 +116,7 @@ def from_environ(*, env_file: Optional[str] = None):
load_dotenv(dev)
elif exists(dotenv_path):
load_dotenv(dotenv_path)
_apply_legacy_env_aliases()
return Environment(**os.environ)

@property
Expand Down Expand Up @@ -131,11 +145,11 @@ def queue_batch_size(self) -> int:

@property
def backend_url(self) -> str:
return self.DECODED_BACKEND_URL
return self.API_SERVER_HTTP_URL

@property
def decoded_backend_access_token(self) -> str:
return self.DECODED_BACKEND_ACCESS_TOKEN
return self.API_SERVER_ACCESS_TOKEN

@property
def llm_host(self) -> str:
Expand All @@ -153,13 +167,21 @@ def llm_model_name(self) -> str:
def selenium_url(self) -> str:
return self.SELENIUM_URL

@property
def api_server_grpc_host(self) -> str:
return self.API_SERVER_GRPC_HOST

@property
def api_server_grpc_port(self) -> int:
return self.API_SERVER_GRPC_PORT

@property
def grpc_backend_host(self) -> str:
return self.GRPC_BACKEND_HOST
return self.API_SERVER_GRPC_HOST

@property
def grpc_backend_port(self) -> int:
return self.GRPC_BACKEND_PORT
return self.API_SERVER_GRPC_PORT

# External API Properties
@property
Expand Down Expand Up @@ -216,12 +238,12 @@ def refresh_backend_token(self, token: str):
try:
set_key(
join(getcwd(), ".env"),
"DECODED_BACKEND_ACCESS_TOKEN",
"API_SERVER_ACCESS_TOKEN",
token,
)
except Exception as e:
logger.warning(f"Error updating .env file: {e}")

# 중요: 인스턴스 변수 직접 업데이트
self.DECODED_BACKEND_ACCESS_TOKEN = token
os.environ["DECODED_BACKEND_ACCESS_TOKEN"] = token
self.API_SERVER_ACCESS_TOKEN = token
os.environ["API_SERVER_ACCESS_TOKEN"] = token
4 changes: 2 additions & 2 deletions packages/ai-server/src/config/helpers/_override.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def use_dev_mock_overrides(application: "Application") -> "Application":
# 필요한 필드만 오버라이드
overridden_env = original_env.model_copy(
update={
"DECODED_BACKEND_URL": "http://localhost:8000",
"API_SERVER_HTTP_URL": "http://localhost:8000",
"REDIS_HOST": "localhost",
"REDIS_PORT": 6300,
"REDIS_PASSWORD": "password",
Expand All @@ -32,7 +32,7 @@ def use_prod_mock_overrides(application: "Application") -> "Application":
# 필요한 필드만 오버라이드
overridden_env = original_env.model_copy(
update={
"DECODED_BACKEND_URL": "https://api.decoded.style",
"API_SERVER_HTTP_URL": "https://api.decoded.style",
"REDIS_HOST": "localhost",
"REDIS_PORT": 6379,
"REDIS_PASSWORD": "password",
Expand Down
10 changes: 7 additions & 3 deletions packages/ai-server/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,14 @@ async def main():
except Exception as e:
logger.warning(f"Failed to connect backend client: {str(e)}. Will retry on first use.")

# GRPC server configuration
# GRPC server configuration (Docker: set AI_GRPC_LISTEN_PORT to match api's AI_SERVER_GRPC_URL)
grpc_host = "0.0.0.0"
grpc_port = 50052 if env == "dev" else 50051

grpc_port_env = os.environ.get("AI_GRPC_LISTEN_PORT", "").strip()
if grpc_port_env:
grpc_port = int(grpc_port_env)
else:
grpc_port = 50052 if env == "dev" else 50051

logger.debug(f"API Server: http://0.0.0.0:10000")
logger.info(f"GRPC Server: {grpc_host}:{grpc_port}")

Expand Down
6 changes: 3 additions & 3 deletions packages/api-server/.env.dev.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 로컬 개발용 템플릿 (호스트에서 API 실행 + Docker 는 deps 만)
# 사용: cp .env.dev.example .env.dev 후 비밀 값·Supabase URL 을 채우세요.
# ai-server `.dev.env` 의 GRPC_BACKEND_PORT 는 이 파일의 GRPC_PORT 와 같아야 합니다.
# ai-server `.dev.env` 의 API_SERVER_GRPC_PORT 는 이 파일의 API_SERVER_GRPC_PORT 와 같아야 합니다.

ENV=development
HOST=0.0.0.0
Expand All @@ -15,7 +15,7 @@ DB_MIN_CONNECTIONS=5
DB_CONNECT_TIMEOUT=30
DB_IDLE_TIMEOUT=600

GRPC_PORT=50053
API_SERVER_GRPC_PORT=50053

SUPABASE_URL=https://[PROJECT-REF].supabase.co
SUPABASE_ANON_KEY=
Expand All @@ -34,7 +34,7 @@ R2_PUBLIC_URL=
RAKUTEN_API_KEY=
RAKUTEN_PUBLISHER_ID=

DECODED_AI_GRPC_URL=http://localhost:50052
AI_SERVER_GRPC_URL=http://localhost:50052

OPENAI_API_KEY=
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
Expand Down
10 changes: 6 additions & 4 deletions packages/api-server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ DB_MIN_CONNECTIONS=5
DB_CONNECT_TIMEOUT=30
DB_IDLE_TIMEOUT=600

# gRPC — 이 API가 리스닝하는 백엔드 gRPC 포트. ai-server `.dev.env` 의 GRPC_BACKEND_PORT 와 반드시 동일해야 함.
GRPC_PORT=50053
# gRPC — API가 AI 콜백용으로 리스닝하는 포트. ai-server 의 API_SERVER_GRPC_PORT 와 반드시 동일해야 함.
# (레거시: GRPC_PORT)
API_SERVER_GRPC_PORT=50053

# Supabase Auth
SUPABASE_URL=https://[PROJECT-REF].supabase.co
Expand All @@ -39,8 +40,9 @@ R2_PUBLIC_URL=https://pub-xxxxx.r2.dev
RAKUTEN_API_KEY=your-rakuten-key
RAKUTEN_PUBLISHER_ID=your-publisher-id

# AI Queue gRPC — 로컬: ai-server 가 APP_ENV=dev 일 때 Queue 서버는 보통 50052 (packages/ai-server/src/main.py)
DECODED_AI_GRPC_URL=http://localhost:50052
# AI Queue gRPC (API → ai-server) — 로컬: ai-server 가 APP_ENV=dev 일 때 Queue 는 보통 50052 (packages/ai-server/src/main.py)
# (레거시: DECODED_AI_GRPC_URL)
AI_SERVER_GRPC_URL=http://localhost:50052

# Vector Search (OpenAI Embeddings)
OPENAI_API_KEY=sk-...
Expand Down
6 changes: 3 additions & 3 deletions packages/api-server/REQUIREMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ MEILISEARCH_MASTER_KEY=your-master-key
GROQ_API_KEY=your-groq-api-key

# decoded-ai gRPC 서버 (이미지 분석용)
DECODED_AI_GRPC_URL=http://localhost:50051
AI_SERVER_GRPC_URL=http://localhost:50051

# Affiliate Links (Rakuten)
RAKUTEN_API_KEY=your-rakuten-api-key
Expand Down Expand Up @@ -1249,7 +1249,7 @@ Content-Type: multipart/form-data
- **클라이언트 (decoded-api -> decoded-ai)**: `DecodedAIGrpcClient` (`AnalyzeLink`, `ExtractOGData`)
- **서버 (decoded-api <- decoded-ai)**: `BackendGrpcServer` (`ProcessedBatchUpdate` 콜백 수신)
- **프로토콜**: `ai.proto` (AI 서비스), `backend.proto` (백엔드 콜백 서비스)
- **설정**: `DECODED_AI_GRPC_URL`, `GRPC_BACKEND_PORT` (기본값: 50052)
- **설정**: `AI_SERVER_GRPC_URL`, `API_SERVER_GRPC_PORT` (ai-server와 동일 값; 레거시 `DECODED_AI_GRPC_URL` / `GRPC_BACKEND_PORT` 지원)
- **데이터베이스**: `solutions` 테이블에 `link_type`, `metadata`, `qna`, `keywords` 컬럼 (JSONB)
- **LinkMetadata 구조**: `link_type` 필드 추가, `og_*` 필드 제거, `metadata` map으로 타입별 동적 메타데이터 저장

Expand Down Expand Up @@ -2721,7 +2721,7 @@ PERPLEXITY_API_KEY=your-perplexity-api-key
PERPLEXITY_MODEL=sonar # optional

# decoded-ai gRPC 서버 (이미지 분석용)
DECODED_AI_GRPC_URL=http://localhost:50051
AI_SERVER_GRPC_URL=http://localhost:50051
```

**주요 특징:**
Expand Down
Loading