🚀 시놀로지 배포 준비 완료

 주요 변경사항:
- 단일 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:
Hyungi Ahn
2025-09-24 09:12:39 +09:00
parent 4c7d2d8290
commit 0b967a84fa
42 changed files with 5467 additions and 6691 deletions

View File

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