""" API маршруты для работы с пользователями, сообщениями и настройками """ from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session from typing import List, Optional import sys import os # Добавляем путь к корневой директории проекта sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from database.database import db from database.models import User, Message, BotSettings from api.schemas import ( UserCreate, UserUpdate, UserResponse, MessageCreate, MessageResponse, BotSettingCreate, BotSettingUpdate, BotSettingResponse, MessageResponseModel, ErrorResponse ) # Зависимость для получения сессии БД def get_db(): """Получение сессии базы данных""" session = db.get_session_sync() try: yield session finally: session.close() # Роутер для работы с пользователями users_router = APIRouter(prefix="/users", tags=["Users"]) @users_router.get("/{telegram_id}", response_model=UserResponse) async def get_user_by_telegram_id(telegram_id: int, db_session: Session = Depends(get_db)): """Получение пользователя по Telegram ID""" user = db_session.query(User).filter(User.telegram_id == telegram_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Пользователь не найден" ) return user @users_router.get("/", response_model=List[UserResponse]) async def get_all_users( limit: int = Query(default=100, ge=1, le=1000), offset: int = Query(default=0, ge=0), is_active: Optional[bool] = Query(default=None), db_session: Session = Depends(get_db) ): """Получение списка всех пользователей с фильтрацией""" query = db_session.query(User) if is_active is not None: query = query.filter(User.is_active == is_active) users = query.offset(offset).limit(limit).all() return users @users_router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def create_user(user_data: UserCreate, db_session: Session = Depends(get_db)): """Создание нового пользователя""" # Проверяем, существует ли уже пользователь existing_user = db_session.query(User).filter(User.telegram_id == user_data.telegram_id).first() if existing_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Пользователь уже существует" ) # Создаем нового пользователя new_user = User(**user_data.model_dump()) db_session.add(new_user) db_session.commit() db_session.refresh(new_user) return new_user @users_router.put("/{telegram_id}", response_model=UserResponse) async def update_user( telegram_id: int, user_data: UserUpdate, db_session: Session = Depends(get_db) ): """Обновление данных пользователя""" user = db_session.query(User).filter(User.telegram_id == telegram_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Пользователь не найден" ) # Обновляем только переданные поля update_data = user_data.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(user, field, value) db_session.commit() db_session.refresh(user) return user @users_router.delete("/{telegram_id}", response_model=MessageResponseModel) async def delete_user(telegram_id: int, db_session: Session = Depends(get_db)): """Деактивация пользователя (мягкое удаление)""" user = db_session.query(User).filter(User.telegram_id == telegram_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Пользователь не найден" ) user.is_active = False db_session.commit() return MessageResponseModel(message="Пользователь деактивирован") # Роутер для работы с сообщениями messages_router = APIRouter(prefix="/messages", tags=["Messages"]) @messages_router.get("/", response_model=List[MessageResponse]) async def get_messages( user_id: Optional[int] = Query(default=None), message_type: Optional[str] = Query(default=None), limit: int = Query(default=100, ge=1, le=1000), offset: int = Query(default=0, ge=0), db_session: Session = Depends(get_db) ): """Получение сообщений с фильтрацией""" query = db_session.query(Message) if user_id: query = query.filter(Message.user_id == user_id) if message_type: query = query.filter(Message.message_type == message_type) messages = query.order_by(Message.created_at.desc()).offset(offset).limit(limit).all() return messages @messages_router.get("/{message_id}", response_model=MessageResponse) async def get_message(message_id: int, db_session: Session = Depends(get_db)): """Получение конкретного сообщения по ID""" message = db_session.query(Message).filter(Message.id == message_id).first() if not message: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Сообщение не найдено" ) return message @messages_router.post("/", response_model=MessageResponse, status_code=status.HTTP_201_CREATED) async def create_message(message_data: MessageCreate, db_session: Session = Depends(get_db)): """Создание нового сообщения""" # Проверяем, существует ли пользователь user = db_session.query(User).filter(User.id == message_data.user_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Пользователь не найден" ) new_message = Message(**message_data.model_dump()) db_session.add(new_message) db_session.commit() db_session.refresh(new_message) return new_message # Роутер для работы с настройками бота settings_router = APIRouter(prefix="/settings", tags=["Bot Settings"]) @settings_router.get("/", response_model=dict) async def get_all_settings(db_session: Session = Depends(get_db)): """Получение всех настроек бота в виде словаря""" settings = db_session.query(BotSettings).all() return {setting.key: setting.value for setting in settings} @settings_router.get("/list", response_model=List[BotSettingResponse]) async def get_all_settings_list(db_session: Session = Depends(get_db)): """Получение всех настроек бота в виде списка объектов""" settings = db_session.query(BotSettings).all() return settings @settings_router.get("/{key}", response_model=BotSettingResponse) async def get_setting(key: str, db_session: Session = Depends(get_db)): """Получение конкретной настройки по ключу""" setting = db_session.query(BotSettings).filter(BotSettings.key == key).first() if not setting: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Настройка не найдена" ) return setting @settings_router.post("/", response_model=BotSettingResponse, status_code=status.HTTP_201_CREATED) async def create_or_update_setting( setting_data: BotSettingCreate, db_session: Session = Depends(get_db) ): """Создание или обновление настройки""" existing_setting = db_session.query(BotSettings).filter(BotSettings.key == setting_data.key).first() if existing_setting: # Обновляем существующую настройку update_data = setting_data.model_dump(exclude={"key"}) for field, value in update_data.items(): if value is not None: setattr(existing_setting, field, value) db_session.commit() db_session.refresh(existing_setting) return existing_setting else: # Создаем новую настройку new_setting = BotSettings(**setting_data.model_dump()) db_session.add(new_setting) db_session.commit() db_session.refresh(new_setting) return new_setting @settings_router.put("/{key}", response_model=BotSettingResponse) async def update_setting( key: str, setting_data: BotSettingUpdate, db_session: Session = Depends(get_db) ): """Обновление существующей настройки""" setting = db_session.query(BotSettings).filter(BotSettings.key == key).first() if not setting: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Настройка не найдена" ) update_data = setting_data.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(setting, field, value) db_session.commit() db_session.refresh(setting) return setting @settings_router.delete("/{key}", response_model=MessageResponseModel) async def delete_setting(key: str, db_session: Session = Depends(get_db)): """Удаление настройки""" setting = db_session.query(BotSettings).filter(BotSettings.key == key).first() if not setting: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Настройка не найдена" ) db_session.delete(setting) db_session.commit() return MessageResponseModel(message=f"Настройка '{key}' удалена") # Добавляем импорты для работы с тестами from database.models import Test, TestCategory, Question, AnswerOption, Image from database.services import ImageService, TestService, QuestionService from database.repositories import TestRepository, QuestionRepository, ImageRepository from api.schemas import ( TestCategoryCreate, TestCategoryResponse, TestCreate, TestResponse, QuestionCreate, QuestionResponse, AnswerOptionCreate, AnswerOptionResponse, ImageUploadResponse ) from fastapi import UploadFile, File # Роутер для работы с категориями тестов test_categories_router = APIRouter(prefix="/test-categories", tags=["Test Categories"]) @test_categories_router.post("/", response_model=TestCategoryResponse) async def create_test_category( category_data: TestCategoryCreate, db_session: Session = Depends(get_db) ): """Создание новой категории тестов""" category = TestCategory( name=category_data.name, description=category_data.description ) db_session.add(category) db_session.commit() db_session.refresh(category) return category @test_categories_router.get("/", response_model=List[TestCategoryResponse]) async def get_test_categories(db_session: Session = Depends(get_db)): """Получение всех категорий тестов""" categories = db_session.query(TestCategory).all() return categories # Роутер для работы с тестами tests_router = APIRouter(prefix="/tests", tags=["Tests"]) @tests_router.post("/", response_model=TestResponse) async def create_test( test_data: TestCreate, db_session: Session = Depends(get_db) ): """Создание нового теста""" test_repo = TestRepository(db_session) test = test_repo.create_test( title=test_data.title, description=test_data.description, category_id=test_data.category_id, is_active=test_data.is_active, time_limit_minutes=test_data.time_limit_minutes ) return test @tests_router.get("/", response_model=List[TestResponse]) async def get_tests( is_active: Optional[bool] = Query(default=None), category_id: Optional[int] = Query(default=None), db_session: Session = Depends(get_db) ): """Получение списка тестов с фильтрацией""" test_repo = TestRepository(db_session) tests = test_repo.get_tests(is_active=is_active, category_id=category_id) return tests @tests_router.get("/{test_id}", response_model=TestResponse) async def get_test_by_id(test_id: int, db_session: Session = Depends(get_db)): """Получение теста по ID""" test_repo = TestRepository(db_session) test = test_repo.get_test_by_id(test_id) if not test: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Тест не найден" ) return test # Роутер для работы с вопросами questions_router = APIRouter(prefix="/questions", tags=["Questions"]) @questions_router.post("/", response_model=QuestionResponse) async def create_question( question_data: QuestionCreate, db_session: Session = Depends(get_db) ): """Создание нового вопроса""" question_repo = QuestionRepository(db_session) question = question_repo.create_question( test_id=question_data.test_id, question_text=question_data.question_text, question_type=question_data.question_type, order_number=question_data.order_number, points=question_data.points, image_id=question_data.image_id ) return question @questions_router.get("/test/{test_id}", response_model=List[QuestionResponse]) async def get_questions_by_test(test_id: int, db_session: Session = Depends(get_db)): """Получение всех вопросов для теста""" question_repo = QuestionRepository(db_session) questions = question_repo.get_questions_by_test_id(test_id) return questions @questions_router.get("/{question_id}", response_model=QuestionResponse) async def get_question_by_id(question_id: int, db_session: Session = Depends(get_db)): """Получение вопроса по ID""" question_repo = QuestionRepository(db_session) question = question_repo.get_question_by_id(question_id) if not question: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Вопрос не найден" ) return question # Роутер для работы с вариантами ответов answer_options_router = APIRouter(prefix="/answer-options", tags=["Answer Options"]) @answer_options_router.post("/", response_model=AnswerOptionResponse) async def create_answer_option( option_data: AnswerOptionCreate, db_session: Session = Depends(get_db) ): """Создание нового варианта ответа""" option = AnswerOption( question_id=option_data.question_id, option_text=option_data.option_text, is_correct=option_data.is_correct, order_number=option_data.order_number ) db_session.add(option) db_session.commit() db_session.refresh(option) return option @answer_options_router.get("/question/{question_id}", response_model=List[AnswerOptionResponse]) async def get_answer_options_by_question(question_id: int, db_session: Session = Depends(get_db)): """Получение всех вариантов ответов для вопроса""" options = db_session.query(AnswerOption).filter( AnswerOption.question_id == question_id ).order_by(AnswerOption.order_number).all() return options # Роутер для работы с изображениями images_router = APIRouter(prefix="/images", tags=["Images"]) @images_router.post("/upload", response_model=ImageUploadResponse) async def upload_image( file: UploadFile = File(...), alt_text: Optional[str] = None, db_session: Session = Depends(get_db) ): """Загрузка изображения в базу данных""" # Проверяем тип файла if not file.content_type.startswith('image/'): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Загружаемый файл должен быть изображением" ) # Читаем содержимое файла image_data = await file.read() # Сохраняем в БД через сервис image = ImageService.save_image_to_db( db=db_session, filename=file.filename, content_type=file.content_type, image_data=image_data, alt_text=alt_text ) return ImageUploadResponse( id=image.id, filename=image.filename, content_type=image.content_type, file_size=image.file_size ) @images_router.get("/{image_id}") async def get_image(image_id: int, db_session: Session = Depends(get_db)): """Получение изображения по ID""" from fastapi.responses import Response image_repo = ImageRepository(db_session) image = image_repo.get_image_by_id(image_id) if not image: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Изображение не найдено" ) return Response( content=image.image_data, media_type=image.content_type, headers={"Content-Disposition": f"inline; filename={image.filename}"} )