feat: 풀스택 할일관리 앱 구현 (통합 모달 + 간트차트)
- Backend: FastAPI + MongoDB + Redis (카테고리, 할일 CRUD, 파일 첨부, 검색, 대시보드) - Frontend: Next.js 15 + Tailwind + React Query + Zustand - 통합 TodoModal: 생성/수정 모달 통합, 탭 구조 (기본/태그와 첨부) - 간트차트: 카테고리별 할일 타임라인 시각화 - TodoCard: 제목/카테고리/우선순위/태그/첨부 한줄 표시 - Docker Compose 배포 (Frontend:3010, Backend:8010, MongoDB:27021, Redis:6391) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
98
backend/app/database.py
Normal file
98
backend/app/database.py
Normal file
@ -0,0 +1,98 @@
|
||||
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
||||
import redis.asyncio as aioredis
|
||||
from app.config import get_settings
|
||||
|
||||
|
||||
class Database:
|
||||
client: AsyncIOMotorClient | None = None
|
||||
db: AsyncIOMotorDatabase | None = None
|
||||
redis: aioredis.Redis | None = None
|
||||
|
||||
|
||||
db = Database()
|
||||
|
||||
|
||||
def get_database() -> AsyncIOMotorDatabase:
|
||||
"""MongoDB 데이터베이스 인스턴스를 반환한다.
|
||||
주의: `if not db.db:` 사용 금지 (pymongo 4.9.x NotImplementedError)
|
||||
"""
|
||||
if db.db is None:
|
||||
raise RuntimeError("Database not initialized")
|
||||
return db.db
|
||||
|
||||
|
||||
def get_redis() -> aioredis.Redis:
|
||||
"""Redis 클라이언트 인스턴스를 반환한다."""
|
||||
if db.redis is None:
|
||||
raise RuntimeError("Redis not initialized")
|
||||
return db.redis
|
||||
|
||||
|
||||
async def connect_db():
|
||||
"""MongoDB + Redis 연결 초기화"""
|
||||
settings = get_settings()
|
||||
|
||||
# MongoDB
|
||||
db.client = AsyncIOMotorClient(settings.mongodb_url)
|
||||
db.db = db.client[settings.mongodb_database]
|
||||
|
||||
# 인덱스 생성
|
||||
await create_indexes(db.db)
|
||||
|
||||
# Redis
|
||||
db.redis = aioredis.from_url(
|
||||
settings.redis_url,
|
||||
encoding="utf-8",
|
||||
decode_responses=True,
|
||||
)
|
||||
|
||||
|
||||
async def disconnect_db():
|
||||
"""연결 종료"""
|
||||
if db.client is not None:
|
||||
db.client.close()
|
||||
if db.redis is not None:
|
||||
await db.redis.close()
|
||||
|
||||
|
||||
async def create_indexes(database: AsyncIOMotorDatabase):
|
||||
"""컬렉션 인덱스 생성"""
|
||||
todos = database["todos"]
|
||||
categories = database["categories"]
|
||||
|
||||
# todos 텍스트 검색 인덱스
|
||||
await todos.create_index(
|
||||
[("title", "text"), ("content", "text"), ("tags", "text")],
|
||||
name="text_search_index",
|
||||
weights={"title": 10, "content": 5, "tags": 3},
|
||||
)
|
||||
|
||||
# todos 복합 인덱스
|
||||
await todos.create_index(
|
||||
[("category_id", 1), ("created_at", -1)],
|
||||
name="category_created",
|
||||
)
|
||||
await todos.create_index(
|
||||
[("completed", 1), ("created_at", -1)],
|
||||
name="completed_created",
|
||||
)
|
||||
await todos.create_index(
|
||||
[("priority", 1), ("created_at", -1)],
|
||||
name="priority_created",
|
||||
)
|
||||
await todos.create_index(
|
||||
[("tags", 1)],
|
||||
name="tags",
|
||||
)
|
||||
await todos.create_index(
|
||||
[("due_date", 1)],
|
||||
name="due_date",
|
||||
)
|
||||
await todos.create_index(
|
||||
[("completed", 1), ("due_date", 1)],
|
||||
name="completed_due_date",
|
||||
)
|
||||
|
||||
# categories 인덱스
|
||||
await categories.create_index("name", unique=True, name="category_name_unique")
|
||||
await categories.create_index("order", name="category_order")
|
||||
Reference in New Issue
Block a user