Исправление автомиграции и создания администратора

This commit is contained in:
2025-12-19 21:28:52 +03:00
parent f6ac03a869
commit 719631158d
4 changed files with 84 additions and 55 deletions
+12 -8
View File
@@ -11,7 +11,7 @@ from sqlmodel import Session, select
from library_service.models.db import Role, User from library_service.models.db import Role, User
from library_service.models.dto import TokenData from library_service.models.dto import TokenData
from library_service.settings import get_session from library_service.settings import get_session, get_logger
# Конфигурация из переменных окружения # Конфигурация из переменных окружения
@@ -20,12 +20,16 @@ SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-in-production")
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30")) ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30"))
REFRESH_TOKEN_EXPIRE_DAYS = int(os.getenv("REFRESH_TOKEN_EXPIRE_DAYS", "7")) REFRESH_TOKEN_EXPIRE_DAYS = int(os.getenv("REFRESH_TOKEN_EXPIRE_DAYS", "7"))
# Хэширование паролей
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto") # Получение логгера
logger = get_logger("uvicorn")
# OAuth2 схема # OAuth2 схема
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
# Хэширование паролей
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
def verify_password(plain_password: str, hashed_password: str) -> bool: def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Проверка пароль по его хешу.""" """Проверка пароль по его хешу."""
@@ -161,7 +165,7 @@ def seed_roles(session: Session) -> dict[str, Role]:
session.commit() session.commit()
session.refresh(role) session.refresh(role)
roles[role_data["name"]] = role roles[role_data["name"]] = role
print(f"[+] Created role: {role_data['name']}") logger.info(f"[+] Created role: {role_data['name']}")
return roles return roles
@@ -173,7 +177,7 @@ def seed_admin(session: Session, admin_role: Role) -> User | None:
).all() ).all()
if existing_admins: if existing_admins:
print(f"[*] Admin already exists: {existing_admins[0].username}") logger.info(f"[*] Admin already exists: {existing_admins[0].username}")
return None return None
admin_username = os.getenv("DEFAULT_ADMIN_USERNAME", "admin") admin_username = os.getenv("DEFAULT_ADMIN_USERNAME", "admin")
@@ -183,8 +187,8 @@ def seed_admin(session: Session, admin_role: Role) -> User | None:
if not admin_password: if not admin_password:
import secrets import secrets
admin_password = secrets.token_urlsafe(16) admin_password = secrets.token_urlsafe(16)
print(f"[!] Generated admin password: {admin_password}") logger.warning(f"[!] Generated admin password: {admin_password}")
print("[!] Please save this password and set DEFAULT_ADMIN_PASSWORD env var") logger.warning("[!] Please save this password and set DEFAULT_ADMIN_PASSWORD env var")
admin_user = User( admin_user = User(
username=admin_username, username=admin_username,
@@ -200,7 +204,7 @@ def seed_admin(session: Session, admin_role: Role) -> User | None:
session.commit() session.commit()
session.refresh(admin_user) session.refresh(admin_user)
print(f"[+] Created admin user: {admin_username}") logger.info(f"[+] Created admin user: {admin_username}")
return admin_user return admin_user
+28 -12
View File
@@ -6,27 +6,43 @@ from alembic import command
from alembic.config import Config from alembic.config import Config
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from sqlmodel import Session
from .auth import run_seeds
from .routers import api_router from .routers import api_router
from .settings import engine, get_app from .settings import engine, get_app, get_logger
app = get_app()
alembic_cfg = Config("alembic.ini")
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
"""Жизененый цикл сервиса""" """Жизненный цикл сервиса"""
print("[+] Initializing...") logger = get_logger("uvicorn")
logger.info("[+] Initializing database...")
# Настройка базы данных try:
with engine.begin() as connection: with engine.begin() as connection:
alembic_cfg.attributes["connection"] = connection alembic_cfg = Config("alembic.ini")
command.upgrade(alembic_cfg, "head") alembic_cfg.attributes["configure_logging"] = False
alembic_cfg.attributes["connection"] = connection
command.upgrade(alembic_cfg, "head")
except Exception as e:
logger.error(f"[-] Migration failed: {e}")
raise e
print("[+] Starting...") logger.info("[+] Running seeds...")
try:
with Session(engine) as session:
run_seeds(session)
logger.info("[+] Database setup completed.")
except Exception as e:
logger.error(f"[-] Seeding failed: {e}")
logger.info("[+] Starting application...")
yield # Обработка запросов yield # Обработка запросов
print("[+] Application shutdown") logger.info("[+] Application shutdown")
app = get_app(lifespan)
# Подключение маршрутов # Подключение маршрутов
+42 -34
View File
@@ -1,5 +1,5 @@
"""Модуль настроек проекта""" """Модуль настроек проекта"""
import os import os, logging
from dotenv import load_dotenv from dotenv import load_dotenv
from fastapi import FastAPI from fastapi import FastAPI
@@ -12,39 +12,42 @@ with open("pyproject.toml", 'r', encoding='utf-8') as f:
config = load(f) config = load(f)
def get_app() -> FastAPI: def get_app(lifespan=None) -> FastAPI:
"""Dependency, для получение экземплярра FastAPI application""" """Dependency для получения экземпляра FastAPI application"""
return FastAPI( if not hasattr(get_app, 'instance'):
title=config["tool"]["poetry"]["name"], get_app.instance = FastAPI(
description=config["tool"]["poetry"]["description"], title=config["tool"]["poetry"]["name"],
version=config["tool"]["poetry"]["version"], description=config["tool"]["poetry"]["description"],
openapi_tags=[ version=config["tool"]["poetry"]["version"],
{ lifespan=lifespan,
"name": "authentication", openapi_tags=[
"description": "Авторизация пользователя." {
}, "name": "authentication",
{ "description": "Авторизация пользователя."
"name": "authors", },
"description": "Действия с авторами.", {
}, "name": "authors",
{ "description": "Действия с авторами.",
"name": "books", },
"description": "Действия с книгами.", {
}, "name": "books",
{ "description": "Действия с книгами.",
"name": "genres", },
"description": "Действия с жанрами.", {
}, "name": "genres",
{ "description": "Действия с жанрами.",
"name": "relations", },
"description": "Действия с связями.", {
}, "name": "relations",
{ "description": "Действия с связями.",
"name": "misc", },
"description": "Прочие.", {
}, "name": "misc",
], "description": "Прочие.",
) },
],
)
return get_app.instance
HOST = os.getenv("POSTGRES_HOST") HOST = os.getenv("POSTGRES_HOST")
@@ -64,3 +67,8 @@ def get_session():
"""Dependency, для получение сессии БД""" """Dependency, для получение сессии БД"""
with Session(engine) as session: with Session(engine) as session:
yield session yield session
def get_logger(name: str = "uvicorn"):
"""Dependency, для получение логгера"""
return logging.getLogger(name)
+2 -1
View File
@@ -16,7 +16,8 @@ config.set_main_option("sqlalchemy.url", POSTGRES_DATABASE_URL)
# Interpret the config file for Python logging. # Interpret the config file for Python logging.
# This line sets up loggers basically. # This line sets up loggers basically.
if config.config_file_name is not None: if config.config_file_name is not None:
fileConfig(config.config_file_name) if config.attributes.get("configure_logging", True):
fileConfig(config.config_file_name)
# add your model's MetaData object here # add your model's MetaData object here
# for 'autogenerate' support # for 'autogenerate' support