Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
130 changes: 38 additions & 92 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,121 +1,67 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview
# Project Overview

**Denamu (๋ฐ๋‚˜๋ฌด)** is an RSS-based tech blog curation platform. It aggregates developer blog content from multiple platforms (Tistory, Velog, Medium) into a single service with real-time trending, search, and developer chat features.

- **Production URL**: https://denamu.dev
- **Architecture**: Monorepo with microservices (Docker Compose orchestration)

## Repository Structure
# Repository Structure

```
server/ # NestJS backend API (port 8080)
client/ # React + Vite frontend (port 5173)
server/ # NestJS backend API (Dev port 8080)
client/ # React + Vite frontend (Dev port 5173)
feed-crawler/ # RSS feed crawler with AI tagging
email-worker/ # Email processing via RabbitMQ
docker-compose/ # Infrastructure configs (local, dev, prod)
nginx/ # Reverse proxy configuration
```

## Common Commands

### Root Level
```bash
npm run start:local # Start all services locally (no watch)
npm run start:dev # Start dev environment with hot reload
npm run start:was-dev # Start backend services only
npm run commit # Commitizen for structured commits
```

### Server (NestJS)
```bash
cd server
npm run start:dev # Development with watch mode
npm run build # Production build
npm run lint # ESLint
npm run test:unit # Unit tests
npm run test:e2e # E2E tests (uses Testcontainers)
npm run test:dto # DTO validation tests
npm run migration:create # Create DB migrations
```

### Client (React)
```bash
cd client
npm run dev # Development server
npm run build # Production build
npm run lint # ESLint
npm run test # Vitest unit tests
npm run test:coverage # Coverage report
```

### Feed Crawler
```bash
cd feed-crawler
npm run start:dev # Development mode
npm run test:unit # Unit tests
npm run test:e2e # Integration tests
```

## Tech Stack

- **Backend**: NestJS, TypeORM, MySQL 8.0, Redis 7.2, RabbitMQ 3.13
- **Frontend**: React 18, TypeScript, Vite, TanStack Query, Zustand, Tailwind CSS, Radix UI
- **Real-time**: Socket.IO (WebSocket chat)
- **AI**: Anthropic Claude SDK (automatic content tagging in feed-crawler)
- **Monitoring**: Prometheus, Grafana, Winston logging
- **Auth**: JWT + OAuth (Google, GitHub) via Passport.js

## Architecture Notes
# Commands

### Backend (Server)
- Module-based NestJS architecture: each feature has Module, Controller, Service
- TypeORM with MySQL for data persistence
- Redis for caching and session management
- Global exception filters and interceptors for consistent error handling
- Swagger API documentation available
npm run start:local # Start all services locally (no watch)
npm run start:dev # Start dev environment with hot reload
npm run start:dev:was # Start backend services only
npm run start:dev:feed # Start feed crawler services only
npm run start:dev:email # Start email worker services only
npm run commit # Commitizen for structured commits

### Frontend (Client)
- TanStack Query for server state, Zustand for client state
- Custom hooks pattern for logic reuse
- Socket.IO client for real-time chat
# Commit Message

### Feed Crawler
- tsyringe for dependency injection (not NestJS)
- Scheduled jobs via node-schedule for RSS polling
- Claude AI integration for automatic tag generation
- RabbitMQ for event publishing
Reference `.cz-config.js`

### Email Worker
- RabbitMQ consumer for async email processing
- Nodemailer for email delivery

## Code Review Convention
# Code Review Convention

Uses Pn-level system (Korean language reviews):

- **P1**: Critical - security issues, business logic errors (must fix before deploy)
- **P2**: Important - code quality/functionality issues (must fix)
- **P3**: Medium - potential bug risks, improvements (strongly consider)
- **P4**: Light - readability suggestions (optional)
- **P5**: Questions - optional suggestions

## Environment Variables
# CI/CD

Key environment variable groups (see `.env.example` files):
- Database: `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`
- Redis: `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD`
- RabbitMQ: `RABBITMQ_HOST`, `RABBITMQ_PORT`, `RABBITMQ_USER`, `RABBITMQ_PASSWORD`
- JWT: `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`
- OAuth: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`
GitHub Actions workflows deploy to self-hosted runner via GHCR:
`.github/workflows`

## CI/CD
- Deploy Application
- `deploy_server.yml` - NestJS backend
- `deploy_client.yml` - React frontend
- `deploy_feed-crawler.yml` - Crawler service
- `deploy_email-worker.yml` - Email worker
- `deploy_nginx.yml` - NGINX Conf Apply
- Test workflows run on PR
- `test_server_dto.yml`
- `test_server_e2e.yml`
- `test_feed-crawler.yml`
- `test_email-worker.yml`

GitHub Actions workflows deploy to self-hosted runner via GHCR:
- `deploy_server.yml` - NestJS backend
- `deploy_client.yml` - React frontend
- `deploy_feed-crawler.yml` - Crawler service
- `deploy_email-worker.yml` - Email worker
- Test workflows run on PR: `test_server_dto.yml`, `test_server_e2e.yml`, `test_feed-crawler.yml`
# Docker Compose

All Docker Compose files are located in the `docker-compose/` directory.

Use the appropriate compose file based on the target environment (local, dev, prod).

- `local`: docker-compose.local.yml + docker-compose.infra.yml
- `dev`: docker-compose.dev.yml + docker-compose.infra.yml
- `prod`: docker-compose.prod.yml + docker-compose.prod.infra.yml
60 changes: 60 additions & 0 deletions client/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# System Purpose

The client is the user-facing React SPA for Denamu. It consumes the server API and renders RSS-aggregated blog content with features including trending feeds, search, developer chat, and user authentication.

# Architecture

- **Framework**: React 18 + Vite (SWC)
- **Routing**: React Router v6 with code splitting (lazy + Suspense)
- **State**: Zustand v5 (global UI state), TanStack React Query v5 (server state + caching)
- **HTTP**: Axios with credentials
- **Real-time**: Socket.io-client (chat, trending)
- **UI**: Tailwind CSS + shadcn/ui (Radix UI) + Framer Motion
- **Testing**: Vitest + Testing Library

# Directory Structure

| Directory | Role |
| --- | --- |
| src/pages/ | Page-level components, one per route |
| src/routes/ | React Router route definitions |
| src/components/ | Feature components (auth, admin, chat, common, ui) |
| src/store/ | Zustand stores (auth, search, sidebar, chat, filter, etc.) |
| src/hooks/ | Custom hooks: auth/, queries/, common/ |
| src/api/services/ | Axios service layer per domain |
| src/api/mocks/ | MSW handlers for development |
| src/constants/ | API endpoint constants |
| src/types/ | TypeScript type definitions |
| src/providers/ | React context providers (TanStack Query) |
| src/components/ui/ | shadcn/ui base components |

# Routing

Background Location pattern is used: PostDetailPage renders as a modal overlay on the feed list, or as a full page on direct navigation.

Pages are lazy-loaded via React.lazy() for code splitting.

# State Management

| Store | Purpose |
| --- | --- |
| useAuthStore | User role (guest / user / admin), token state |
| useSearchStore | Search params, filter type, pagination |
| useSidebarStore | Sidebar visibility |
| useChatStore | WebSocket chat state |
| useRegisterModalStore | RSS registration modal |
| React Query | Server data fetching, caching, refetch |

# Build Configuration

Vite manual chunks split vendor libraries to optimize bundle size: radix-ui, charts (recharts), vendor (react core), animation (framer-motion), query, socket, utils.

Dev server uses `usePolling: true` for file system compatibility in Docker/WSL.

# Commands

npm run dev - Development server (port 5173)
npm run build - Production build (tsc + vite build)
npm run lint - ESLint
npm run test - Vitest
npm run test:coverage - Vitest with coverage report
52 changes: 52 additions & 0 deletions email-worker/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# System Purpose

The Email Worker exists to offload SMTP delivery from the API path and eliminate latency caused by synchronous email transmission.

Email sending MUST be processed asynchronously to:

- protect API response times
- isolate failures
- improve observability
- reduce operational coupling with the application server

Embedding SMTP logic inside the server is forbidden due to poor failure visibility and debugging complexity.

# Architectural Role

The Email Worker is a **message-driven infrastructure component** responsible for reliable email dispatch.

Design goals:

- failure containment
- retry-safe processing
- deterministic logging
- operational clarity

The API MUST never send emails directly.

# Processing Workflow

1. Listen to RabbitMQ queues continuously.
2. Upon message receipt, validate payload integrity.
3. Attempt email delivery via the configured SMTP relay.
4. If delivery fails:
- emit structured error logs (Winston)
- route the message to a RabbitMQ Dead Letter Queue (DLQ)

# Environment & Stack

Stack: Node.js 22, RabbitMQ, Winston.
Infra: Docker, AWS EC2.

# Reference Docs

| File | Authority |
| ---------------------------------- | ---------------- |
| agent_docs/service_architecture.md | Service patterns |
| agent_docs/test_design.md | Test contracts |

# Commands

npm run build - Production build
npm run start - Production start
npm run start:dev - Development start
108 changes: 108 additions & 0 deletions email-worker/agent_docs/service_architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
## Component Diagram

```mermaid
graph LR
subgraph RabbitMQ
SendQ["email.send.queue"]
Wait5["email.send.wait.5s"]
Wait10["email.send.wait.10s"]
Wait20["email.send.wait.20s"]
DLQ["email.deadLetter.queue"]
end

subgraph EmailWorker
RMQMgr["RabbitMQManager\nConnection"]
RMQSvc["RabbitMQService\nChannel Ops"]
Consumer["EmailConsumer\nDispatch & Error"]
EmailSvc["EmailService\nSMTP Send"]
Content["email.content\nTemplates"]
end

SMTP["SMTP Server"]

SendQ -->|consume| RMQSvc
RMQSvc -->|onMessage| Consumer
Consumer -->|dispatch by type| EmailSvc
EmailSvc --> Content
EmailSvc -->|send| SMTP

Consumer -->|transient, retry=0| Wait5
Consumer -->|transient, retry=1| Wait10
Consumer -->|transient, retry=2| Wait20
Consumer -->|permanent or retry>=3| DLQ

Wait5 -->|TTL expire| SendQ
Wait10 -->|TTL expire| SendQ
Wait20 -->|TTL expire| SendQ

RMQMgr --> RMQSvc
```

## Module Responsibilities

| Module | Responsibility |
| --------------- | ---------------------------------------------------------------------------------------------------------- |
| EmailConsumer | Consume queue messages, dispatch by type, classify errors, retry or route to DLQ, handle graceful shutdown |
| EmailService | Send emails via Nodemailer/SMTP, provide methods per email type |
| email.content | Generate HTML templates for each email type |
| RabbitMQService | Publish/consume messages via AMQP channels, handle ack/nack |
| RabbitMQManager | Manage AMQP connections and channel creation |

## Email Types

| Type | Trigger | Description |
| ------------------ | ---------------------- | ---------------------------------------- |
| USER_CERTIFICATION | User registration | Send email verification code |
| RSS_REGISTRATION | RSS approval/rejection | Send approval result or rejection reason |
| RSS_REMOVAL | RSS removal request | Send deletion confirmation code |
| PASSWORD_RESET | Password reset | Send reset code |
| ACCOUNT_DELETION | Account deletion | Send deletion confirmation code |

## Queue Topology

```mermaid
graph LR
EmailExchange["EmailExchange\n(Direct)"]
EmailExchange -->|"email.send"| SendQ["email.send.queue"]

SendQ -->|fail, retry < 3| WaitQueues
subgraph WaitQueues
W5["email.send.wait.5s\nTTL: 5000ms"]
W10["email.send.wait.10s\nTTL: 10000ms"]
W20["email.send.wait.20s\nTTL: 20000ms"]
end

WaitQueues -->|"TTL expires โ†’ x-dead-letter-exchange"| SendQ
SendQ -->|permanent or retry >= 3| DLQ["email.deadLetter.queue"]
```

## Error Classification

| Category | Errors | Action |
| -------------------- | ------------------------------------------------------ | ---------------------------- |
| Transient (Network) | ECONNREFUSED, ETIMEDOUT, ESOCKETNOTFOUND, Socket close | Retry via Wait Queue (max 3) |
| Transient (SMTP 4xx) | 421, 450, 451, 452 | Retry via Wait Queue (max 3) |
| Permanent (SMTP 5xx) | 550, 552, 553, 554 | Immediate DLQ |
| Unknown | Unclassified | Immediate DLQ |
| Max Retry | retryCount >= 3 | DLQ (MAX_RETRIES_EXCEEDED) |

## DLQ Header Schema

All DLQ messages include debugging headers:

| Header | Description |
| --------------- | ------------------------------------------------------------- |
| x-retry-count | Number of retries |
| x-error-code | SMTP / Node error code |
| x-error-message | Error message |
| x-failed-at | Failure timestamp (ISO 8601) |
| x-failure-type | SMTP_PERMANENT_FAILURE / MAX_RETRIES_EXCEEDED / UNKNOWN_ERROR |
| x-response-code | SMTP response code (optional) |
| x-error-stack | Stack trace (optional) |

## Graceful Shutdown

1. Receive SIGINT/SIGTERM
2. `stopConsuming()` โ€” Stop receiving new messages
3. `waitForPendingTasks()` โ€” Wait for in-progress tasks to complete
4. `close()` โ€” Clean up consumers and connections
Loading