🔧 Fix CORS and API endpoint issues

- Fix API endpoint paths (remove trailing slashes for 405 errors)
- Update API URLs to use api-todo.hyungi.net subdomain for HTTPS compatibility
- Improve CORS settings parsing in backend (handle brackets and quotes)
- Add frontend volume mount to docker-compose for real-time file updates
- Update Synology deployment config with wildcard CORS settings

Resolves:
- 405 Method Not Allowed errors
- Mixed Content security issues (HTTPS → HTTP)
- CORS preflight request failures
- Docker build requirements for every file change

Tested: API endpoints now correctly use HTTPS subdomain, eliminating security blocks
This commit is contained in:
hyungi
2025-09-24 18:54:07 +09:00
parent 2ccdf8c411
commit 03e6fe5b91
5 changed files with 92 additions and 13 deletions

View File

@@ -39,7 +39,9 @@ cors_origins = []
if settings.CORS_ORIGINS == "*": if settings.CORS_ORIGINS == "*":
cors_origins = ["*"] cors_origins = ["*"]
else: 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}") logger.info(f"🌐 CORS Origins: {cors_origins}")

64
docker-compose.dev.yml Normal file
View File

@@ -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

View File

@@ -5,6 +5,9 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
ports: ports:
- "${FRONTEND_PORT:-4000}:80" - "${FRONTEND_PORT:-4000}:80"
volumes:
# 프론트엔드 파일 실시간 반영 (개발용)
- ./frontend:/usr/share/nginx/html
depends_on: depends_on:
- backend - backend
restart: unless-stopped restart: unless-stopped
@@ -17,6 +20,8 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
ports: ports:
- "${BACKEND_PORT:-9000}:9000" - "${BACKEND_PORT:-9000}:9000"
# HTTPS용 포트 (SSL 인증서 있는 경우)
# - "9443:9443"
depends_on: depends_on:
database: database:
condition: service_healthy condition: service_healthy

View File

@@ -25,9 +25,11 @@ DEBUG=false
POSTGRES_USER=todo_user POSTGRES_USER=todo_user
POSTGRES_DB=todo_db POSTGRES_DB=todo_db
# --- CORS 설정 (시놀로지 IP/도메인에 맞게 수정) --- # --- CORS 설정 (시놀로지 배포용) ---
# 예시: 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_ORIGINS=*
# 또는 특정 IP만 허용하려면:
# CORS_ORIGINS=http://192.168.1.100:4000,https://your-domain.synology.me:4000
# --- Synology MailPlus 통합 설정 (선택사항) --- # --- Synology MailPlus 통합 설정 (선택사항) ---
SYNOLOGY_MAIL_SERVER= SYNOLOGY_MAIL_SERVER=

View File

@@ -3,9 +3,15 @@
*/ */
// 환경에 따른 API URL 설정 // 환경에 따른 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' // 로컬 개발 환경 ? '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 { class ApiClient {
constructor() { constructor() {
@@ -161,7 +167,7 @@ const AuthAPI = {
// Todo 관련 API // Todo 관련 API
const TodoAPI = { const TodoAPI = {
async getTodos(status = null, category = null) { async getTodos(status = null, category = null) {
let url = '/todos/'; let url = '/todos/'; // 슬래시 추가
const params = new URLSearchParams(); const params = new URLSearchParams();
if (status && status !== 'all') params.append('status', status); if (status && status !== 'all') params.append('status', status);
@@ -175,27 +181,27 @@ const TodoAPI = {
}, },
async createTodo(todoData) { async createTodo(todoData) {
return api.post('/todos/', todoData); return api.post('/todos/', todoData); // 슬래시 추가
}, },
async updateTodo(id, todoData) { async updateTodo(id, todoData) {
return api.put(`/todos/${id}/`, todoData); return api.put(`/todos/${id}`, todoData);
}, },
async deleteTodo(id) { async deleteTodo(id) {
return api.delete(`/todos/${id}/`); return api.delete(`/todos/${id}`);
}, },
async completeTodo(id) { async completeTodo(id) {
return api.put(`/todos/${id}/`, { status: 'completed' }); return api.put(`/todos/${id}`, { status: 'completed' });
}, },
async getTodayTodos() { async getTodayTodos() {
return api.get('/calendar/today/'); return api.get('/calendar/today');
}, },
async getTodoById(id) { async getTodoById(id) {
return api.get(`/todos/${id}/`); return api.get(`/todos/${id}`);
}, },
async uploadImage(imageFile) { async uploadImage(imageFile) {