-
인증번호 안내
-
아래 인증번호를 입력해주세요.
-
-
${code}
+ const fromEmail = this.configService.get('FROM_EMAIL');
+
+ this.logger.log(`[EMAIL] ========== 이메일 발송 시작 ==========`);
+ this.logger.log(`[EMAIL] From: ${fromEmail}`);
+ this.logger.log(`[EMAIL] To: ${email}`);
+ this.logger.log(`[EMAIL] Code: ${code}`);
+ process.stdout.write(`[EMAIL] Sending to: ${email}, code: ${code}\n`);
+
+ try {
+ const result = await this.transporter.sendMail({
+ from: fromEmail,
+ to: email,
+ subject: '[한우 유전능력 시스템] 인증번호 안내',
+ html: `
+
+
인증번호 안내
+
아래 인증번호를 입력해주세요.
+
+
${code}
+
+
인증번호는 3분간 유효합니다.
+
+
본 메일은 발신 전용입니다.
-
인증번호는 3분간 유효합니다.
-
-
본 메일은 발신 전용입니다.
-
- `,
- });
+ `,
+ });
+
+ this.logger.log(`[EMAIL] 발송 성공 - MessageId: ${result.messageId}`);
+ this.logger.log(`[EMAIL] Response: ${JSON.stringify(result)}`);
+ process.stdout.write(`[EMAIL] SUCCESS - MessageId: ${result.messageId}\n`);
+
+ } catch (error) {
+ this.logger.error(`[EMAIL] ========== 발송 실패 ==========`);
+ this.logger.error(`[EMAIL] Error Name: ${error.name}`);
+ this.logger.error(`[EMAIL] Error Message: ${error.message}`);
+ this.logger.error(`[EMAIL] Error Code: ${error.code}`);
+ this.logger.error(`[EMAIL] Stack: ${error.stack}`);
+ process.stdout.write(`[EMAIL] ERROR: ${error.message}\n`);
+ process.stdout.write(`[EMAIL] STACK: ${error.stack}\n`);
+ throw error;
+ }
}
}
diff --git a/backend/src/shared/verification/verification.module.ts b/backend/src/shared/verification/verification.module.ts
index 487b73c..16dd47a 100644
--- a/backend/src/shared/verification/verification.module.ts
+++ b/backend/src/shared/verification/verification.module.ts
@@ -6,20 +6,15 @@ import { VerificationService } from './verification.service';
*
* @description
* 인증번호 생성 및 검증 기능을 제공하는 모듈입니다.
- * Redis를 사용하여 인증번호를 임시 저장하고 검증합니다.
+ * 메모리 Map을 사용하여 인증번호를 임시 저장하고 검증합니다.
*
* 사용 예:
* - 아이디 찾기 인증번호 발송
* - 비밀번호 재설정 인증번호 발송
* - 회원가입 이메일 인증
- *
- * RedisModule이 @Global로 설정되어 있어 자동으로 주입됩니다.
- *
- * @export
- * @class VerificationModule
*/
@Module({
- providers: [VerificationService],
- exports: [VerificationService],
+ providers: [VerificationService],
+ exports: [VerificationService],
})
export class VerificationModule {}
diff --git a/backend/src/shared/verification/verification.service.ts b/backend/src/shared/verification/verification.service.ts
index 7adf895..a52ed5f 100644
--- a/backend/src/shared/verification/verification.service.ts
+++ b/backend/src/shared/verification/verification.service.ts
@@ -1,97 +1,131 @@
-import { Injectable } from '@nestjs/common';
-import { InjectRedis } from '@nestjs-modules/ioredis';
-import Redis from 'ioredis';
+import { Injectable, Logger } from '@nestjs/common';
import * as crypto from 'crypto';
import { VERIFICATION_CONFIG } from 'src/common/config/VerificationConfig';
/**
- * 인증번호 생성 및 검증 서비스 (Redis 기반)
- *
- * @export
- * @class VerificationService
+ * 인증번호 생성 및 검증 서비스 (메모리 기반)
*/
@Injectable()
export class VerificationService {
- constructor(@InjectRedis() private readonly redis: Redis) {}
+ private readonly logger = new Logger(VerificationService.name);
+
+ // 메모리 저장소 (key -> { code, expiresAt })
+ private readonly store = new Map
();
- /**
- * 6자리 인증번호 생성
- *
- * @returns {string} 6자리 숫자
- */
- generateCode(): string {
- return Math.floor(100000 + Math.random() * 900000).toString();
- }
+ constructor() {
+ this.logger.log(`[VERIFY] VerificationService 초기화 (메모리 모드)`);
+
+ // 만료된 항목 정리 (1분마다)
+ setInterval(() => this.cleanup(), 60000);
+ }
- /**
- * 인증번호 저장 (Redis 3분 후 자동 삭제)
- *
- * @async
- * @param {string} key - Redis 키 (예: find-id:test@example.com)
- * @param {string} code - 인증번호
- * @returns {Promise}
- */
- async saveCode(key: string, code: string): Promise {
- await this.redis.set(key, code, 'EX', VERIFICATION_CONFIG.CODE_EXPIRY_SECONDS);
- }
+ /**
+ * 만료된 항목 정리
+ */
+ private cleanup(): void {
+ const now = Date.now();
+ let cleaned = 0;
+
+ for (const [key, data] of this.store.entries()) {
+ if (data.expiresAt < now) {
+ this.store.delete(key);
+ cleaned++;
+ }
+ }
+
+ if (cleaned > 0) {
+ this.logger.debug(`[VERIFY] 만료된 ${cleaned}개 항목 정리됨`);
+ }
+ }
- /**
- * 인증번호 검증
- *
- * @async
- * @param {string} key - Redis 키
- * @param {string} code - 사용자가 입력한 인증번호
- * @returns {Promise} 검증 성공 여부
- */
- async verifyCode(key: string, code: string): Promise {
- const savedCode = await this.redis.get(key);
- console.log(`[DEBUG VerificationService] Key: ${key}, Input code: ${code}, Saved code: ${savedCode}`);
+ /**
+ * 6자리 인증번호 생성
+ */
+ generateCode(): string {
+ const code = Math.floor(100000 + Math.random() * 900000).toString();
+ this.logger.log(`[VERIFY] 인증번호 생성: ${code}`);
+ return code;
+ }
- if (!savedCode) {
- console.log(`[DEBUG VerificationService] No saved code found for key: ${key}`);
- return false; // 인증번호 없음 (만료 또는 미발급)
- }
+ /**
+ * 인증번호 저장 (TTL 적용)
+ */
+ async saveCode(key: string, code: string): Promise {
+ const expiresAt = Date.now() + (VERIFICATION_CONFIG.CODE_EXPIRY_SECONDS * 1000);
+
+ this.store.set(key, { value: code, expiresAt });
+
+ this.logger.log(`[VERIFY] 코드 저장 - Key: ${key}, TTL: ${VERIFICATION_CONFIG.CODE_EXPIRY_SECONDS}초`);
+ }
- if (savedCode !== code) {
- console.log(`[DEBUG VerificationService] Code mismatch - Saved: ${savedCode}, Input: ${code}`);
- return false; // 인증번호 불일치
- }
+ /**
+ * 인증번호 검증
+ */
+ async verifyCode(key: string, code: string): Promise {
+ this.logger.log(`[VERIFY] 코드 검증 - Key: ${key}, Input: ${code}`);
- // 검증 성공 시 Redis에서 삭제 (1회용)
- await this.redis.del(key);
- console.log(`[DEBUG VerificationService] Code verified successfully!`);
- return true;
- }
+ const data = this.store.get(key);
- /**
- * 비밀번호 재설정 토큰 생성 및 저장
- *
- * @async
- * @param {string} userId - 사용자 ID
- * @returns {Promise} 재설정 토큰
- */
- async generateResetToken(userId: string): Promise {
- const token = crypto.randomBytes(VERIFICATION_CONFIG.TOKEN_BYTES_LENGTH).toString('hex');
- await this.redis.set(`reset:${token}`, userId, 'EX', VERIFICATION_CONFIG.RESET_TOKEN_EXPIRY_SECONDS);
- return token;
- }
+ if (!data) {
+ this.logger.warn(`[VERIFY] 저장된 코드 없음 (만료 또는 미발급)`);
+ return false;
+ }
- /**
- * 비밀번호 재설정 토큰 검증
- *
- * @async
- * @param {string} token - 재설정 토큰
- * @returns {Promise} 사용자 ID 또는 null
- */
- async verifyResetToken(token: string): Promise {
- const userId = await this.redis.get(`reset:${token}`);
+ // 만료 체크
+ if (data.expiresAt < Date.now()) {
+ this.logger.warn(`[VERIFY] 코드 만료됨`);
+ this.store.delete(key);
+ return false;
+ }
- if (!userId) {
- return null; // 토큰 없음 (만료 또는 미발급)
- }
+ if (data.value !== code) {
+ this.logger.warn(`[VERIFY] 코드 불일치 - Saved: ${data.value}, Input: ${code}`);
+ return false;
+ }
- // 검증 성공 시 토큰 삭제 (1회용)
- await this.redis.del(`reset:${token}`);
- return userId;
- }
+ // 검증 성공 시 삭제 (1회용)
+ this.store.delete(key);
+ this.logger.log(`[VERIFY] 검증 성공, 코드 삭제됨`);
+ return true;
+ }
+
+ /**
+ * 비밀번호 재설정 토큰 생성 및 저장
+ */
+ async generateResetToken(userId: string): Promise {
+ this.logger.log(`[VERIFY] 리셋 토큰 생성 - userId: ${userId}`);
+
+ const token = crypto.randomBytes(VERIFICATION_CONFIG.TOKEN_BYTES_LENGTH).toString('hex');
+ const expiresAt = Date.now() + (VERIFICATION_CONFIG.RESET_TOKEN_EXPIRY_SECONDS * 1000);
+
+ this.store.set(`reset:${token}`, { value: userId, expiresAt });
+
+ this.logger.log(`[VERIFY] 리셋 토큰 저장 완료`);
+ return token;
+ }
+
+ /**
+ * 비밀번호 재설정 토큰 검증
+ */
+ async verifyResetToken(token: string): Promise {
+ this.logger.log(`[VERIFY] 리셋 토큰 검증`);
+
+ const key = `reset:${token}`;
+ const data = this.store.get(key);
+
+ if (!data) {
+ this.logger.warn(`[VERIFY] 리셋 토큰 없음 또는 만료`);
+ return null;
+ }
+
+ if (data.expiresAt < Date.now()) {
+ this.logger.warn(`[VERIFY] 리셋 토큰 만료됨`);
+ this.store.delete(key);
+ return null;
+ }
+
+ this.store.delete(key);
+ this.logger.log(`[VERIFY] 리셋 토큰 검증 성공 - userId: ${data.value}`);
+ return data.value;
+ }
}
diff --git a/backend/src/system/system.controller.ts b/backend/src/system/system.controller.ts
new file mode 100644
index 0000000..acf360c
--- /dev/null
+++ b/backend/src/system/system.controller.ts
@@ -0,0 +1,14 @@
+import { Controller, Get } from '@nestjs/common';
+import { SystemService, SystemHealthResponse } from './system.service';
+import { Public } from '../common/decorators/public.decorator';
+
+@Controller('system')
+export class SystemController {
+ constructor(private readonly systemService: SystemService) {}
+
+ @Public()
+ @Get('health')
+ async getHealth(): Promise {
+ return this.systemService.getHealth();
+ }
+}
diff --git a/backend/src/system/system.module.ts b/backend/src/system/system.module.ts
new file mode 100644
index 0000000..e10e1ca
--- /dev/null
+++ b/backend/src/system/system.module.ts
@@ -0,0 +1,9 @@
+import { Module } from '@nestjs/common';
+import { SystemController } from './system.controller';
+import { SystemService } from './system.service';
+
+@Module({
+ controllers: [SystemController],
+ providers: [SystemService],
+})
+export class SystemModule {}
diff --git a/backend/src/system/system.service.ts b/backend/src/system/system.service.ts
new file mode 100644
index 0000000..b4c4e51
--- /dev/null
+++ b/backend/src/system/system.service.ts
@@ -0,0 +1,53 @@
+import { Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { InjectDataSource } from '@nestjs/typeorm';
+import { DataSource } from 'typeorm';
+
+export interface SystemHealthResponse {
+ status: 'ok' | 'error';
+ timestamp: string;
+ environment: string;
+ database: {
+ host: string;
+ port: number;
+ database: string;
+ user: string;
+ status: 'connected' | 'disconnected';
+ error?: string;
+ };
+}
+
+@Injectable()
+export class SystemService {
+ constructor(
+ private configService: ConfigService,
+ @InjectDataSource() private dataSource: DataSource,
+ ) {}
+
+ async getHealth(): Promise {
+ const dbHealth = await this.checkDatabase();
+
+ return {
+ status: dbHealth.status === 'connected' ? 'ok' : 'error',
+ timestamp: new Date().toISOString(),
+ environment: this.configService.get('NODE_ENV') || 'development',
+ database: dbHealth,
+ };
+ }
+
+ private async checkDatabase() {
+ const config = {
+ host: this.configService.get('POSTGRES_HOST') || 'unknown',
+ port: parseInt(this.configService.get('POSTGRES_PORT')) || 5432,
+ database: this.configService.get('POSTGRES_DB') || 'unknown',
+ user: this.configService.get('POSTGRES_USER') || 'unknown',
+ };
+
+ try {
+ await this.dataSource.query('SELECT 1');
+ return { ...config, status: 'connected' as const };
+ } catch (error) {
+ return { ...config, status: 'disconnected' as const, error: error.message };
+ }
+ }
+}
diff --git a/commands.txt b/commands.txt
deleted file mode 100644
index 3611d1e..0000000
--- a/commands.txt
+++ /dev/null
@@ -1,74 +0,0 @@
-[BACKEND]
-nest g resource
-npm run start:dev
-
-[FRONTEND]
-npm run build : 배포용, TypeScript/ESLint 오류가 있으면 빌드 실패
-npm run start
-npm run dev : 개발모드, TypeScript 오류가 있어도 실행, 개발할 때는 편하게 작업, 배포할 때는 버그 없는 코드를 보장
-
-[DOCKER]
-docker ps : 실행 중인 컨테이너 조회
-docker ps -a : 모든 컨테이너 조회
-docker compose up -d : 백그라운드 compose 실행
-docker compose down : 종료
-docker-compose up -d redis : redis 서버 실행
-docker-compose down -v : 볼륨 삭제 (데이터도 같이 삭제 / TypeORM이 테이블 구조는 자동 생성)
-docker-compose rm -f frontend
-
-docker-compose restart backend 서비스 재시작
-docker-compose up -d backend 컨테이너가 없으면 새로 생성하고, 이미 있으면 재시작
-
-dev 모드 docker 외부망 실행
-docker-compose -f docker-compose.dev.yml up -d
-docker-compose -f docker-compose.dev.yml down
-docker-compose -f docker-compose.dev.yml restart backend
-docker compose -f docker-compose.dev.yml build
-docker compose -f docker-compose.dev.yml up --build
-
-
-[캐시삭제]
-.next 폴더는 Next.js의 빌드 캐시 폴더
-삭제해도 안전한 이유:
- 1. 자동 재생성: 개발 서버(npm run dev)를 실행하면 자동으로 다시 생성됩니다
- 2. 캐시만 포함: 소스 코드가 아닌 컴파일된 결과물만 저장됩니다
- 3. 원본 보존: src/ 폴더의 실제 코드는 전혀 영향 없습니다
-
- .next 폴더에 들어있는 것:
-
- .next/
- ├── cache/ # Turbopack/Webpack 캐시
- ├── server/ # 서버 사이드 빌드 파일
- ├── static/ # 정적 에셋
- └── types/ # 자동 생성된 타입 정의
-
- 삭제하는 이유:
- - 캐시 손상: 패키지 설치 후 캐시가 오래된 버전 참조
- - 빌드 오류: 이전 빌드 오류가 캐시에 남아있을 때
- - 모듈 해결 문제: 새로 설치한 패키지(cmdk)를 인식 못할 때
-
-
- [방법]
- Windows에서 .next 폴더 삭제 방법:
-
- 방법 1: 파일 탐색기 (가장 쉬움)
-
- 1. C:\Users\COCOON\Desktop\repo14\repo14\next_nest_docker_template-main\frontend 폴더 열기
- 2. .next 폴더 찾기 (숨김 파일 보기 활성화 필요할 수 있음)
- 3. .next 폴더 우클릭 → 삭제
- 4. 휴지통 비우기 (선택사항)
-
- 방법 2: 명령 프롬프트 (CMD)
-
- cd C:\Users\COCOON\Desktop\repo14\repo14\next_nest_docker_template-main\frontend
- rmdir /s /q .next
-
- 방법 3: PowerShell
-
- cd C:\Users\COCOON\Desktop\repo14\repo14\next_nest_docker_template-main\frontend
- Remove-Item -Recurse -Force .next
-
- 방법 4: Git Bash (사용한 방법)
-
- cd /c/Users/COCOON/Desktop/repo14/repo14/next_nest_docker_template-main/frontend
- rm -rf .next
\ No newline at end of file
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
deleted file mode 100644
index e707b79..0000000
--- a/docker-compose.dev.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-version: '3.8'
-
-services:
- frontend:
- build:
- context: ./frontend
- dockerfile: Dockerfile
- container_name: nextjs-app
- ports:
- - "3000:3000"
- environment:
- - NODE_ENV=development
- - NEXT_PUBLIC_API_URL=/backend/api
- volumes:
- - ./frontend:/app
- - /app/node_modules
- depends_on:
- - backend
- networks:
- - app-network
-
- backend:
- build:
- context: ./backend
- dockerfile: Dockerfile
- container_name: nestjs-app
- ports:
- - "4000:4000"
- environment:
- - NODE_ENV=development
- - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
- - POSTGRES_HOST=postgres
- - REDIS_URL=redis://redis:6379
- - REDIS_HOST=redis
- volumes:
- - ./backend:/app
- - /app/node_modules
- depends_on:
- - postgres
- - redis
- networks:
- - app-network
-
- postgres:
- image: postgres:15-alpine
- container_name: postgres-db
- ports:
- - "5431:5432"
- environment:
- - POSTGRES_USER=${POSTGRES_USER}
- - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- - POSTGRES_DB=${POSTGRES_DB}
- volumes:
- - postgres_data:/var/lib/postgresql/data
- networks:
- - app-network
-
- redis:
- image: redis:7-alpine
- container_name: redis-cache
- ports:
- - "6379:6379"
- volumes:
- - redis_data:/data
- networks:
- - app-network
-
- # nginx:
- # image: nginx:alpine
- # container_name: nginx-proxy
- # ports:
- # - "80:80"
- # - "443:443"
- # volumes:
- # - ./nginx/nginx.conf:/etc/nginx/nginx.conf
- # - ./nginx/ssl:/etc/nginx/ssl
- # depends_on:
- # - frontend
- # - backend
- # networks:
- # - app-network
-
-volumes:
- postgres_data:
- redis_data:
-
-networks:
- app-network:
- driver: bridge
\ No newline at end of file
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
deleted file mode 100644
index c60e243..0000000
--- a/docker-compose.prod.yml
+++ /dev/null
@@ -1,80 +0,0 @@
-version: '3.8'
-
-services:
- frontend:
- build:
- context: ./frontend
- dockerfile: Dockerfile.prod
- container_name: nextjs-app-prod
- environment:
- - NODE_ENV=production
- - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-https://your-domain.com}
- depends_on:
- - backend
- networks:
- - app-network
-
- backend:
- build:
- context: ./backend
- dockerfile: Dockerfile.prod
- container_name: nestjs-app-prod
- environment:
- - NODE_ENV=production
- - DATABASE_URL=postgresql://${POSTGRES_USER:-prod_user}:${POSTGRES_PASSWORD:-CHANGE_THIS_STRONG_PASSWORD}@postgres:5432/${POSTGRES_DB:-prod_db}
- - REDIS_URL=redis://redis:${REDIS_PORT:-6379}
- - JWT_SECRET=${JWT_SECRET:-SUPER_SECURE_JWT_SECRET_AT_LEAST_32_CHARACTERS_LONG}
- - JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-1h}
- - CORS_ORIGIN=${CORS_ORIGIN:-https://your-domain.com}
- - RATE_LIMIT_WINDOW_MS=${RATE_LIMIT_WINDOW_MS:-900000}
- - RATE_LIMIT_MAX_REQUESTS=${RATE_LIMIT_MAX_REQUESTS:-50}
- depends_on:
- - postgres
- - redis
- networks:
- - app-network
-
- postgres:
- image: postgres:15-alpine
- container_name: postgres-db-prod
- environment:
- - POSTGRES_USER=${POSTGRES_USER:-prod_user}
- - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-CHANGE_THIS_STRONG_PASSWORD}
- - POSTGRES_DB=${POSTGRES_DB:-prod_db}
- volumes:
- - postgres_data:/var/lib/postgresql/data
- networks:
- - app-network
-
- redis:
- image: redis:7-alpine
- container_name: redis-cache-prod
- environment:
- - REDIS_PASSWORD=${REDIS_PASSWORD:-}
- volumes:
- - redis_data:/data
- networks:
- - app-network
-
- nginx:
- image: nginx:alpine
- container_name: nginx-proxy-prod
- ports:
- - "${NGINX_HTTP_PORT:-80}:80"
- - "${NGINX_HTTPS_PORT:-443}:443"
- volumes:
- - ./nginx/nginx.conf:/etc/nginx/nginx.conf
- - ./nginx/ssl:/etc/nginx/ssl
- depends_on:
- - frontend
- - backend
- networks:
- - app-network
-
-volumes:
- postgres_data:
- redis_data:
-
-networks:
- app-network:
- driver: bridge
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index d1dc72e..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-version: "3.8"
-
-services:
- postgres:
- image: postgres:15-alpine
- container_name: postgres-db
- ports:
- - "${POSTGRES_PORT:-5432}:5432"
- environment:
- - POSTGRES_USER=${POSTGRES_USER:-postgres}
- - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-turbo123}
- - POSTGRES_DB=${POSTGRES_DB:-genome_db}
- volumes:
- - postgres_data:/var/lib/postgresql/data
- healthcheck:
- test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER:-user}"]
- interval: 10s
- timeout: 5s
- retries: 5
- networks:
- - app-network
-
- redis:
- image: redis:7-alpine
- container_name: redis-cache
- ports:
- - "${REDIS_PORT:-6379}:6379"
- volumes:
- - redis_data:/data
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 10s
- timeout: 5s
- retries: 5
- networks:
- - app-network
-
-volumes:
- postgres_data:
- redis_data:
-
-networks:
- app-network:
- driver: bridge
diff --git a/frontend/.env b/frontend/.env
new file mode 100644
index 0000000..52fa998
--- /dev/null
+++ b/frontend/.env
@@ -0,0 +1,14 @@
+# ==============================================
+# 로컬 개발용 (npm run dev)
+# ==============================================
+NODE_ENV=development
+
+# 클라이언트 API URL
+NEXT_PUBLIC_API_URL=http://localhost:4000
+
+# 서버사이드 프록시용 (next.config.ts rewrites)
+BACKEND_INTERNAL_URL=http://localhost:4000
+
+# 앱 설정
+NEXT_PUBLIC_APP_NAME=한우 유전능력 시스템
+NEXT_TELEMETRY_DISABLED=1
diff --git a/frontend/.env.dev b/frontend/.env.dev
new file mode 100644
index 0000000..b38b0f7
--- /dev/null
+++ b/frontend/.env.dev
@@ -0,0 +1,14 @@
+# ==============================================
+# Docker 개발 환경용 (docker-compose)
+# ==============================================
+NODE_ENV=development
+
+# 클라이언트 API URL (브라우저에서 호출)
+NEXT_PUBLIC_API_URL=/backend/api
+
+# 서버사이드 프록시용 (next.config.ts rewrites)
+BACKEND_INTERNAL_URL=http://host.docker.internal:4000
+
+# 앱 설정
+NEXT_PUBLIC_APP_NAME=한우 유전능력 시스템
+NEXT_TELEMETRY_DISABLED=1
diff --git a/frontend/.env.prod b/frontend/.env.prod
new file mode 100644
index 0000000..1451306
--- /dev/null
+++ b/frontend/.env.prod
@@ -0,0 +1,14 @@
+# ==============================================
+# 프로덕션 환경용 (배포)
+# ==============================================
+NODE_ENV=production
+
+# 클라이언트 API URL (브라우저에서 호출)
+NEXT_PUBLIC_API_URL=/backend/api
+
+# 서버사이드 프록시용 (next.config.ts rewrites)
+BACKEND_INTERNAL_URL=http://host.docker.internal:4000
+
+# 앱 설정
+NEXT_PUBLIC_APP_NAME=한우 유전능력 시스템
+NEXT_TELEMETRY_DISABLED=1
diff --git a/frontend/.gitignore b/frontend/.gitignore
index 5ef6a52..211cf54 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -1,6 +1,6 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
+# ==============================================
+# Dependencies
+# ==============================================
/node_modules
/.pnp
.pnp.*
@@ -10,32 +10,68 @@
!.yarn/releases
!.yarn/versions
-# testing
-/coverage
-
-# next.js
+# ==============================================
+# Build Output
+# ==============================================
/.next/
/out/
-
-# production
/build
-# misc
+
+# ==============================================
+# Testing
+# ==============================================
+/coverage
+
+# ==============================================
+# IDE & Editors
+# ==============================================
+/.idea
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# ==============================================
+# OS Files
+# ==============================================
.DS_Store
+Thumbs.db
*.pem
-# debug
+# ==============================================
+# Debug Logs
+# ==============================================
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
-# env files (can opt-in for committing if needed)
-.env*
-
-# vercel
+# ==============================================
+# Vercel
+# ==============================================
.vercel
-# typescript
+# ==============================================
+# TypeScript
+# ==============================================
*.tsbuildinfo
next-env.d.ts
+
+# ==============================================
+# Misc
+# ==============================================
+.temp
+.tmp
+
+# ==============================================
+# Environment Variables
+# ==============================================
+.env
+.env.local
+.env.prod
+# .env.dev는 허용 (배포용)
+!.env.dev
+!.env.example
+
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 3185e3c..bc8f7c2 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -11,13 +11,11 @@ COPY package*.json ./
# 의존성 설치
RUN npm install
-# 소스 코드 복사
+# 소스 코드 복사 (node_modules 제외 - .dockerignore)
COPY . .
# 빌드 시 필요한 환경 변수 설정
ENV NEXT_TELEMETRY_DISABLED=1
-ENV NODE_ENV=production
-ENV NEXT_PUBLIC_API_URL=/backend/api
# Next.js 빌드
RUN npm run build
diff --git a/frontend/next.config.ts b/frontend/next.config.ts
index 9f2ccf9..6f4347f 100644
--- a/frontend/next.config.ts
+++ b/frontend/next.config.ts
@@ -1,19 +1,22 @@
import type { NextConfig } from "next";
-// Next.js 핵심 설정 파일, Next.js가 시작할 때 이 파일을 찾아서 읽음
-// 여기에 Next.js 설정 옵션을 정의할 수 있음
+// 백엔드 URL 설정
+// 로컬: http://localhost:4000
+// Docker: http://host.docker.internal:4000
+const BACKEND_URL = process.env.BACKEND_INTERNAL_URL || 'http://localhost:4000';
+
const nextConfig: NextConfig = {
eslint: {
- ignoreDuringBuilds: true, // 빌드 시 ESLint warning 무시
+ ignoreDuringBuilds: true,
},
typescript: {
- ignoreBuildErrors: true, // 빌드 시 TypeScript 에러 무시 (임시)
+ ignoreBuildErrors: true,
},
async rewrites() {
return [
{
- source: '/backend/api/:path*', // /api가 붙은 모든 요청
- destination: 'http://192.168.11.249:4000/:path*', // 백엔드 API로 요청
+ source: '/backend/api/:path*',
+ destination: `${BACKEND_URL}/:path*`,
},
];
},
diff --git a/frontend/next.config.ts.dev b/frontend/next.config.ts.dev
deleted file mode 100644
index 9f2ccf9..0000000
--- a/frontend/next.config.ts.dev
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { NextConfig } from "next";
-
-// Next.js 핵심 설정 파일, Next.js가 시작할 때 이 파일을 찾아서 읽음
-// 여기에 Next.js 설정 옵션을 정의할 수 있음
-const nextConfig: NextConfig = {
- eslint: {
- ignoreDuringBuilds: true, // 빌드 시 ESLint warning 무시
- },
- typescript: {
- ignoreBuildErrors: true, // 빌드 시 TypeScript 에러 무시 (임시)
- },
- async rewrites() {
- return [
- {
- source: '/backend/api/:path*', // /api가 붙은 모든 요청
- destination: 'http://192.168.11.249:4000/:path*', // 백엔드 API로 요청
- },
- ];
- },
-};
-
-export default nextConfig;
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
deleted file mode 100644
index 7971837..0000000
--- a/nginx/nginx.conf
+++ /dev/null
@@ -1,45 +0,0 @@
-events {
- worker_connections 1024;
-}
-
-http {
- upstream frontend {
- server frontend:3000;
- }
-
- upstream backend {
- server backend:4000;
- }
-
- server {
- listen 80;
- server_name _;
-
- # Frontend routes
- location / {
- proxy_pass http://frontend;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
-
- # Backend API routes
- location /api/ {
- proxy_pass http://backend/;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
-
- # WebSocket support for Next.js hot reload
- location /_next/webpack-hmr {
- proxy_pass http://frontend;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host $host;
- }
- }
-}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 2c4846f..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "next_nest_docker_template-main",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {}
-}
diff --git a/postgres/.env b/postgres/.env
deleted file mode 100644
index 1d60abc..0000000
--- a/postgres/.env
+++ /dev/null
@@ -1,102 +0,0 @@
-# ==============================================
-# DEVELOPMENT ENVIRONMENT VARIABLES
-# ==============================================
-# Copy this file to .env.local for local development
-# DO NOT commit sensitive values to version control
-
-# ==============================================
-# DATABASE CONFIGURATION
-# ==============================================
-DATABASE_URL=postgresql://user:password@localhost:5431/genome_db
-POSTGRES_HOST=localhost
-POSTGRES_USER=postgres
-POSTGRES_PASSWORD=turbo123
-POSTGRES_DB=postgres
-POSTGRES_PORT=5431
-POSTGRES_SYNCHRONIZE=true
-POSTGRES_LOGGING=true
-
-# ==============================================
-# REDIS CONFIGURATION
-# ==============================================
-REDIS_URL=redis://localhost:6379
-REDIS_HOST=localhost
-REDIS_PORT=6379
-
-# ==============================================
-# BACKEND CONFIGURATION
-# ==============================================
-BACKEND_PORT=4000
-NODE_ENV=development
-
-# ==============================================
-# JWT AUTHENTICATION
-# ==============================================
-JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
-JWT_EXPIRES_IN=24h
-JWT_REFRESH_SECRET=your-refresh-token-secret
-JWT_REFRESH_EXPIRES_IN=7d
-
-# ==============================================
-# CORS CONFIGURATION
-# ==============================================
-CORS_ORIGIN=http://localhost:3000,http://192.168.11.249:3000,http://123.143.174.11:5243
-CORS_CREDENTIALS=true
-
-# ==============================================
-# SECURITY SETTINGS
-# ==============================================
-RATE_LIMIT_WINDOW_MS=900000
-RATE_LIMIT_MAX_REQUESTS=100
-BCRYPT_SALT_ROUNDS=12
-
-# ==============================================
-# FILE UPLOAD
-# ==============================================
-MAX_FILE_SIZE=10485760
-UPLOAD_DESTINATION=./uploads
-ALLOWED_FILE_TYPES=jpg,jpeg,png,gif,pdf,doc,docx
-
-# ==============================================
-# EMAIL CONFIGURATION
-# ==============================================
-SMTP_HOST=smtp.gmail.com
-SMTP_PORT=587
-SMTP_SECURE=false
-SMTP_USER=your-email@gmail.com
-SMTP_PASS=your-app-password
-FROM_EMAIL=noreply@yourdomain.com
-
-# ==============================================
-# LOGGING
-# ==============================================
-LOG_LEVEL=debug
-LOG_FORMAT=dev
-LOG_FILE_ENABLED=true
-LOG_FILE_PATH=./logs
-
-# ==============================================
-# EXTERNAL SERVICES
-# ==============================================
-# AWS_ACCESS_KEY_ID=your-aws-access-key
-# AWS_SECRET_ACCESS_KEY=your-aws-secret
-# AWS_REGION=us-east-1
-# AWS_S3_BUCKET=your-bucket-name
-
-# ==============================================
-# MONITORING
-# ==============================================
-# SENTRY_DSN=your-sentry-dsn
-# HEALTH_CHECK_ENABLED=true
-
-# ==============================================
-# FRONTEND CONFIGURATION
-# ==============================================
-FRONTEND_PORT=3000
-NEXT_PUBLIC_API_URL=http://192.168.11.249:4000
-
-# ==============================================
-# NGINX CONFIGURATION
-# ==============================================
-NGINX_HTTP_PORT=80
-NGINX_HTTPS_PORT=443
\ No newline at end of file
diff --git a/postgres/docker-compose.yml b/postgres/docker-compose.yml
deleted file mode 100644
index ad19641..0000000
--- a/postgres/docker-compose.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-version: "1.0"
-# 개발 편의를 위한 옵션 폴더
-# 개발 편의성 (컨테이너 재빌드 안 함)
-# DB PostgreSQL, Redis만 Docker 컨테이너로 실행
-# Frontend/Backend는 로컬에서 직접 실행
-services:
- postgres:
- image: postgres:15-alpine
- container_name: postgres-db
- ports:
- - "${POSTGRES_PORT:-5431}:5432"
- environment:
- - POSTGRES_USER=${POSTGRES_USER:-postgres}
- - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-turbo123}
- - POSTGRES_DB=${POSTGRES_DB:-genomic}
- volumes:
- - postgres_data:/var/lib/postgresql/data
- healthcheck:
- test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER:-user}"]
- interval: 10s
- timeout: 5s
- retries: 5
- networks:
- - app-network
-
- redis:
- image: redis:7-alpine
- container_name: redis-cache
- ports:
- - "${REDIS_PORT:-6379}:6379"
- volumes:
- - redis_data:/data
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 10s
- timeout: 5s
- retries: 5
- networks:
- - app-network
-
-volumes:
- postgres_data:
- redis_data:
-
-networks:
- app-network:
- driver: bridge