🔧 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:
@@ -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
64
docker-compose.dev.yml
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user