- Fix SyntaxError in viewer.js line 2868 (.bind(this) issue in setTimeout) - Resolve Alpine.js 'Can't find variable' errors (documentViewer, goBack, etc.) - Fix backlink rendering and persistence during temporary highlights - Add backlink protection and restoration mechanism in highlightAndScrollToText - Implement Note Management System with hierarchical notebooks - Add note highlights and memos functionality - Update cache version to force browser refresh (v=2025012641) - Add comprehensive logging for debugging backlink issues
123 lines
3.4 KiB
Python
123 lines
3.4 KiB
Python
"""
|
|
데이터베이스 설정 및 연결
|
|
"""
|
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
|
|
from sqlalchemy.orm import DeclarativeBase, sessionmaker, Session
|
|
from sqlalchemy import MetaData, create_engine
|
|
from typing import AsyncGenerator, Generator
|
|
|
|
from .config import settings
|
|
|
|
|
|
# SQLAlchemy 메타데이터 설정
|
|
metadata = MetaData(
|
|
naming_convention={
|
|
"ix": "ix_%(column_0_label)s",
|
|
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
|
"ck": "ck_%(table_name)s_%(constraint_name)s",
|
|
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
|
"pk": "pk_%(table_name)s"
|
|
}
|
|
)
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
"""SQLAlchemy Base 클래스"""
|
|
metadata = metadata
|
|
|
|
|
|
# 비동기 데이터베이스 엔진 생성
|
|
engine = create_async_engine(
|
|
settings.DATABASE_URL,
|
|
echo=settings.DEBUG,
|
|
future=True,
|
|
pool_pre_ping=True,
|
|
pool_recycle=300,
|
|
)
|
|
|
|
# 동기 데이터베이스 엔진 생성 (노트 API용)
|
|
sync_database_url = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
|
|
sync_engine = create_engine(
|
|
sync_database_url,
|
|
echo=settings.DEBUG,
|
|
pool_pre_ping=True,
|
|
pool_recycle=300,
|
|
)
|
|
|
|
# 비동기 세션 팩토리
|
|
AsyncSessionLocal = async_sessionmaker(
|
|
engine,
|
|
class_=AsyncSession,
|
|
expire_on_commit=False,
|
|
)
|
|
|
|
# 동기 세션 팩토리
|
|
SyncSessionLocal = sessionmaker(
|
|
sync_engine,
|
|
class_=Session,
|
|
expire_on_commit=False,
|
|
)
|
|
|
|
|
|
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
"""비동기 데이터베이스 세션 의존성"""
|
|
async with AsyncSessionLocal() as session:
|
|
try:
|
|
yield session
|
|
except Exception:
|
|
await session.rollback()
|
|
raise
|
|
finally:
|
|
await session.close()
|
|
|
|
|
|
def get_sync_db() -> Generator[Session, None, None]:
|
|
"""동기 데이터베이스 세션 의존성 (노트 API용)"""
|
|
session = SyncSessionLocal()
|
|
try:
|
|
yield session
|
|
except Exception:
|
|
session.rollback()
|
|
raise
|
|
finally:
|
|
session.close()
|
|
|
|
|
|
async def init_db() -> None:
|
|
"""데이터베이스 초기화"""
|
|
from ..models import user, document, highlight, note, bookmark
|
|
|
|
async with engine.begin() as conn:
|
|
# 모든 테이블 생성
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
|
|
# 관리자 계정 생성
|
|
await create_admin_user()
|
|
|
|
|
|
async def create_admin_user() -> None:
|
|
"""관리자 계정 생성 (존재하지 않을 경우)"""
|
|
from ..models.user import User
|
|
from .security import get_password_hash
|
|
from sqlalchemy import select
|
|
|
|
async with AsyncSessionLocal() as session:
|
|
# 관리자 계정 존재 확인
|
|
result = await session.execute(
|
|
select(User).where(User.email == settings.ADMIN_EMAIL)
|
|
)
|
|
admin_user = result.scalar_one_or_none()
|
|
|
|
if not admin_user:
|
|
# 관리자 계정 생성
|
|
admin_user = User(
|
|
email=settings.ADMIN_EMAIL,
|
|
hashed_password=get_password_hash(settings.ADMIN_PASSWORD),
|
|
is_active=True,
|
|
is_admin=True,
|
|
full_name="Administrator"
|
|
)
|
|
session.add(admin_user)
|
|
await session.commit()
|
|
print(f"관리자 계정이 생성되었습니다: {settings.ADMIN_EMAIL}")
|