🚀 시놀로지 배포 준비 완료
✨ 주요 변경사항: - 단일 docker-compose.yml로 통합 (로컬/시놀로지 환경 지원) - 시놀로지 볼륨 매핑 설정 (volume1: 이미지, volume3: 데이터) - 통합 배포 가이드 및 자동 배포 스크립트 추가 - 완전한 Memos 스타일 워크플로우 구현 🎯 새로운 기능: - 📝 메모 작성 (upload.html) - 이미지 업로드 지원 - 📥 수신함 (inbox.html) - 메모 편집 및 Todo/보드 변환 - ✅ Todo 목록 (todo-list.html) - 오늘 할 일 관리 - 📋 보드 (board.html) - 프로젝트 관리, 접기/펼치기, 이미지 지원 - 📚 아카이브 (archive.html) - 완료된 보드 보관 - 🔐 초기 설정 화면 - 관리자 계정 생성 🔧 기술적 개선: - 이미지 업로드/편집 완전 지원 - 반응형 디자인 및 모바일 최적화 - 보드 완료 후 자동 숨김 처리 - 메모 편집 시 제목 필드 제거 - 테스트 로그인 버튼 제거 (프로덕션 준비) - 과거 코드 정리 (TodoService, CalendarSyncService 등) 📦 배포 관련: - env.synology.example - 시놀로지 환경 설정 템플릿 - SYNOLOGY_DEPLOYMENT_GUIDE.md - 상세한 배포 가이드 - deploy-synology.sh - 원클릭 자동 배포 스크립트 - Nginx 정적 파일 서빙 및 이미지 프록시 설정 🗑️ 정리된 파일: - 사용하지 않는 HTML 페이지들 (dashboard, calendar, checklist 등) - 복잡한 통합 서비스들 (integrations 폴더) - 중복된 시놀로지 설정 파일들
This commit is contained in:
@@ -6,7 +6,9 @@ from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import logging
|
||||
import os
|
||||
|
||||
from .core.config import settings
|
||||
from .api.routes import auth, todos, calendar
|
||||
@@ -46,15 +48,35 @@ app.add_middleware(
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
logger.error(f"Validation 오류 - URL: {request.url}")
|
||||
logger.error(f"Validation 오류 상세: {exc.errors()}")
|
||||
|
||||
# JSON 직렬화 가능한 형태로 에러 변환
|
||||
serializable_errors = []
|
||||
for error in exc.errors():
|
||||
serializable_error = {}
|
||||
for key, value in error.items():
|
||||
if isinstance(value, bytes):
|
||||
serializable_error[key] = value.decode('utf-8')
|
||||
else:
|
||||
serializable_error[key] = str(value) if not isinstance(value, (str, int, float, bool, list, dict, type(None))) else value
|
||||
serializable_errors.append(serializable_error)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content={
|
||||
"detail": "요청 데이터 검증 실패",
|
||||
"errors": exc.errors()
|
||||
"errors": serializable_errors
|
||||
}
|
||||
)
|
||||
|
||||
# 정적 파일 서빙 (업로드된 이미지)
|
||||
UPLOAD_DIR = "/app/uploads"
|
||||
if not os.path.exists(UPLOAD_DIR):
|
||||
os.makedirs(UPLOAD_DIR)
|
||||
app.mount("/uploads", StaticFiles(directory=UPLOAD_DIR), name="uploads")
|
||||
|
||||
# 라우터 등록
|
||||
from .api.routes import setup
|
||||
app.include_router(setup.router, prefix="/api/setup", tags=["setup"])
|
||||
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
|
||||
app.include_router(todos.router, prefix="/api", tags=["todos"])
|
||||
app.include_router(calendar.router, prefix="/api", tags=["calendar"])
|
||||
@@ -87,6 +109,47 @@ async def create_sample_data():
|
||||
return
|
||||
|
||||
|
||||
async def wait_for_database():
|
||||
"""데이터베이스 연결 대기"""
|
||||
import asyncio
|
||||
import asyncpg
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# DATABASE_URL 파싱
|
||||
parsed_url = urlparse(settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://"))
|
||||
|
||||
max_retries = 30 # 최대 30번 시도 (30초)
|
||||
retry_count = 0
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
logger.info(f"🔄 데이터베이스 연결 시도 {retry_count + 1}/{max_retries}")
|
||||
|
||||
# asyncpg로 직접 연결 테스트
|
||||
conn = await asyncpg.connect(
|
||||
host=parsed_url.hostname,
|
||||
port=parsed_url.port or 5432,
|
||||
user=parsed_url.username,
|
||||
password=parsed_url.password,
|
||||
database=parsed_url.path.lstrip('/'),
|
||||
timeout=5
|
||||
)
|
||||
await conn.close()
|
||||
|
||||
logger.info("✅ 데이터베이스 연결 성공!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
retry_count += 1
|
||||
logger.warning(f"❌ 데이터베이스 연결 실패 ({retry_count}/{max_retries}): {e}")
|
||||
|
||||
if retry_count < max_retries:
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
logger.error("💥 데이터베이스 연결 최대 재시도 횟수 초과")
|
||||
raise Exception("데이터베이스에 연결할 수 없습니다.")
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""애플리케이션 시작 시 초기화"""
|
||||
@@ -94,6 +157,9 @@ async def startup_event():
|
||||
logger.info(f"📊 환경: {settings.ENVIRONMENT}")
|
||||
logger.info(f"🔗 데이터베이스: {settings.DATABASE_URL}")
|
||||
|
||||
# 데이터베이스 연결 대기
|
||||
await wait_for_database()
|
||||
|
||||
# 데이터베이스 초기화
|
||||
from .core.database import init_db
|
||||
await init_db()
|
||||
|
||||
Reference in New Issue
Block a user