diff --git a/backend/src/main.py b/backend/src/main.py index 4d1293d..37cb8fd 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -39,7 +39,9 @@ cors_origins = [] if settings.CORS_ORIGINS == "*": cors_origins = ["*"] else: - cors_origins = [origin.strip() for origin in settings.CORS_ORIGINS.split(",")] + # 공백과 대괄호 제거 후 쉼표로 분리 + cors_str = settings.CORS_ORIGINS.strip('[]"\'') + cors_origins = [origin.strip().strip('"\'') for origin in cors_str.split(",") if origin.strip()] logger.info(f"🌐 CORS Origins: {cors_origins}") diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..23070df --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,64 @@ +services: + frontend: + image: nginx:alpine + ports: + - "${FRONTEND_PORT:-4000}:80" + volumes: + # 프론트엔드 파일 실시간 반영 + - ./frontend:/usr/share/nginx/html + - ./frontend/nginx.conf:/etc/nginx/nginx.conf + depends_on: + - backend + restart: unless-stopped + networks: + - todo-network + + backend: + build: + context: ./backend + dockerfile: Dockerfile + ports: + - "${BACKEND_PORT:-9000}:9000" + depends_on: + database: + condition: service_healthy + environment: + - DATABASE_URL=postgresql+asyncpg://todo_user:${POSTGRES_PASSWORD:-todo_password}@database:5432/todo_db + - SECRET_KEY=${SECRET_KEY:-your-secret-key-change-this-in-production} + - DEBUG=${DEBUG:-false} + - CORS_ORIGINS=${CORS_ORIGINS:-*} + volumes: + # 시놀로지 볼륨 매핑 + - ${SYNOLOGY_UPLOADS_PATH:-/volume1/todo-project/uploads}:/data/uploads + - ${SYNOLOGY_CONFIG_PATH:-/volume3/docker/todo-project/config}:/app/config + # 백엔드 소스 실시간 반영 + - ./backend/src:/app/src + restart: unless-stopped + networks: + - todo-network + + database: + image: postgres:15-alpine + ports: + - "${DATABASE_PORT:-5432}:5432" + environment: + - POSTGRES_USER=${POSTGRES_USER:-todo_user} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-todo_password} + - POSTGRES_DB=${POSTGRES_DB:-todo_db} + volumes: + # 시놀로지 볼륨 매핑 + - ${SYNOLOGY_DB_PATH:-/volume3/docker/todo-project/postgres}:/var/lib/postgresql/data + - ${SYNOLOGY_CONFIG_PATH:-/volume3/docker/todo-project/config}/migrations:/docker-entrypoint-initdb.d + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-todo_user} -d ${POSTGRES_DB:-todo_db}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - todo-network + +networks: + todo-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index 05b21dd..c66797c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,9 @@ services: dockerfile: Dockerfile ports: - "${FRONTEND_PORT:-4000}:80" + volumes: + # 프론트엔드 파일 실시간 반영 (개발용) + - ./frontend:/usr/share/nginx/html depends_on: - backend restart: unless-stopped @@ -17,6 +20,8 @@ services: dockerfile: Dockerfile ports: - "${BACKEND_PORT:-9000}:9000" + # HTTPS용 포트 (SSL 인증서 있는 경우) + # - "9443:9443" depends_on: database: condition: service_healthy diff --git a/env.synology.example b/env.synology.example index 55e2309..767c741 100644 --- a/env.synology.example +++ b/env.synology.example @@ -25,9 +25,11 @@ DEBUG=false POSTGRES_USER=todo_user POSTGRES_DB=todo_db -# --- CORS 설정 (시놀로지 IP/도메인에 맞게 수정) --- -# 예시: CORS_ORIGINS=["http://192.168.1.100:4000", "https://your-domain.synology.me:4000"] -CORS_ORIGINS=["http://localhost:4000", "http://127.0.0.1:4000"] +# --- CORS 설정 (시놀로지 배포용) --- +# 프로덕션 환경에서 모든 출처 허용 (보안상 주의 필요) +CORS_ORIGINS=* +# 또는 특정 IP만 허용하려면: +# CORS_ORIGINS=http://192.168.1.100:4000,https://your-domain.synology.me:4000 # --- Synology MailPlus 통합 설정 (선택사항) --- SYNOLOGY_MAIL_SERVER= diff --git a/frontend/static/js/api.js b/frontend/static/js/api.js index b8ba601..fbb6d7c 100644 --- a/frontend/static/js/api.js +++ b/frontend/static/js/api.js @@ -3,9 +3,15 @@ */ // 환경에 따른 API URL 설정 -const API_BASE_URL = window.location.hostname === 'localhost' +const API_BASE_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' ? 'http://localhost:9000/api' // 로컬 개발 환경 - : `${window.location.protocol}//${window.location.hostname}:9000/api`; // 시놀로지 배포 환경 + : window.location.hostname === 'todo.hyungi.net' + ? 'https://api-todo.hyungi.net/api' // API 서브도메인 (HTTPS 통일) + : window.location.hostname === '192.168.219.116' + ? 'http://192.168.219.116:9000/api' // IP 직접 접근 + : window.location.protocol === 'https:' + ? `https://${window.location.hostname}:9000/api` // HTTPS 환경 + : `http://${window.location.hostname}:9000/api`; // HTTP 환경 class ApiClient { constructor() { @@ -161,7 +167,7 @@ const AuthAPI = { // Todo 관련 API const TodoAPI = { async getTodos(status = null, category = null) { - let url = '/todos/'; + let url = '/todos/'; // 슬래시 추가 const params = new URLSearchParams(); if (status && status !== 'all') params.append('status', status); @@ -175,27 +181,27 @@ const TodoAPI = { }, async createTodo(todoData) { - return api.post('/todos/', todoData); + return api.post('/todos/', todoData); // 슬래시 추가 }, async updateTodo(id, todoData) { - return api.put(`/todos/${id}/`, todoData); + return api.put(`/todos/${id}`, todoData); }, async deleteTodo(id) { - return api.delete(`/todos/${id}/`); + return api.delete(`/todos/${id}`); }, async completeTodo(id) { - return api.put(`/todos/${id}/`, { status: 'completed' }); + return api.put(`/todos/${id}`, { status: 'completed' }); }, async getTodayTodos() { - return api.get('/calendar/today/'); + return api.get('/calendar/today'); }, async getTodoById(id) { - return api.get(`/todos/${id}/`); + return api.get(`/todos/${id}`); }, async uploadImage(imageFile) {