/
aastray
/
StudentPart
Обзор
Документация
Войти
/
aastray
/
StudentPart
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
service/src/main.py
236 строк
11 KB
aastray
Реализован функционал загрузки файлов и просмотра документов, удалены базовые эндпоинты, обновлена документация
10 янв 2026, 15:38
10 янв 2026, 15:38
4d47c55
Код
Авторство
О чём код?
""" Главный файл приложения FastAPI для цифрового двойника документооборота практиками. Этот файл инициализирует приложение и подключает все роутеры. Архитектура проекта: - src/routers/ - роутеры для различных типов эндпоинтов - base.py - обязательные эндпоинты для всех цифровых двойников - practice.py - эндпоинты для работы с практиками студентов - src/services/ - бизнес-логика и сервисы - src/schemas/ - схемы данных (Pydantic модели) - src/database/ - работа с базой данных """ from fastapi import FastAPI, Depends, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from contextlib import asynccontextmanager from pathlib import Path from src.database import engine from src.database.db import AsyncSession, get_session from src.routers import practice @asynccontextmanager async def lifespan(app: FastAPI): """ Управление жизненным циклом приложения. Инициализация и очистка ресурсов при старте/остановке. """ from pathlib import Path # Создание временного хранилища для документов # service/src/main.py -> service/src -> service # Для Docker: используем /app/temp_storage (том смонтирован в docker-compose.yml) # Для локального запуска: корень проекта /temp_storage current_file = Path(__file__).resolve() service_dir = current_file.parent.parent # service/src -> service # Проверяем Docker окружение docker_storage = Path("/app/temp_storage") if docker_storage.exists() or Path("/app").exists(): temp_storage = docker_storage else: # Для локального запуска: корень проекта (на уровень выше service/) project_root = service_dir.parent # service -> корень проекта temp_storage = project_root / "temp_storage_local" temp_storage.mkdir(exist_ok=True, parents=True) print(f"Временное хранилище создано: {temp_storage}") # Инициализация подключения к БД (опционально, не блокирует запуск) try: async with engine.begin() as conn: print("✓ Подключение к базе данных установлено") except Exception as e: print(f"⚠ Предупреждение: не удалось подключиться к базе данных при старте: {e}") print(" Приложение продолжит работу, но некоторые функции могут быть недоступны") print(" Убедитесь, что PostgreSQL запущен и настройки в .env файле корректны") # Загрузка шаблонов документов в БД при старте try: from src.services.template_loader import load_all_templates from src.database.db import AsyncSessionLocal async with AsyncSessionLocal() as session: templates = await load_all_templates(session, force_reload=False) print(f"✓ Загружено шаблонов документов: {len(templates)}") except Exception as e: print(f"⚠ Предупреждение: не удалось загрузить шаблоны при старте: {e}") print(" Приложение продолжит работу, но генерация документов может быть недоступна") yield # Закрытие подключений при остановке await engine.dispose() # Создание экземпляра FastAPI приложения app = FastAPI( title="Digital Twin Template", description="Шаблон для создания цифровых двойников", version="1.0.0", lifespan=lifespan ) # Настройка CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], # В продакшене указать конкретные домены allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Подключение роутеров # Роутеры для работы с практиками app.include_router(practice.router) @app.get('/', tags=["Root"], summary="Корневой эндпоинт") def root(): """ Корневой эндпоинт для проверки работоспособности API. """ return { "message": "Digital Twin API for Student Practice Management", "version": "1.0.0", "description": "Сервис для автоматизации процесса оформления документов для прохождения практики студентами", "endpoints": { "practice_application": "/practice/application", "practice_applications": "/practice/applications", "practice_template": "/practice/template", "practice_upload_document": "/practice/upload-document", "practice_upload_report": "/practice/upload-report", "practice_grade": "/practice/grade", "practice_files_all": "/practice/files/all", "practice_files_student": "/practice/files/student/{student_id}", "practice_files_application": "/practice/files/application/{application_id}", "download_file": "/download/files/{file_path}", "download_document": "/download/{filename}" }, "docs": { "swagger": "/docs", "redoc": "/redoc" } } @app.get('/health', tags=["Health"], summary="Health check эндпоинт") def health(): """ Эндпоинт для проверки работоспособности приложения (healthcheck). """ return {"status": "healthy"} # Эндпоинт для скачивания файлов из хранилища (загруженные документы и отчеты) @app.get("/download/files/{file_path:path}", tags=["Files"], summary="Скачать файл из хранилища") async def download_file_from_storage(file_path: str): """ Скачивает файл из файлового хранилища (загруженные документы и отчеты). Args: file_path: Относительный путь к файлу из БД (например: documents/{application_id}/{filename}) Returns: FileResponse: Файл для скачивания """ from src.services.file_storage import file_storage import logging logger = logging.getLogger(__name__) # Получаем файл из хранилища full_path = file_storage.get_file(file_path) if not full_path: logger.error(f"Файл не найден в хранилище: {file_path}") raise HTTPException(status_code=404, detail=f"Файл не найден: {file_path}") # Определяем MIME тип mime_type = 'application/octet-stream' if full_path.suffix == '.pdf': mime_type = 'application/pdf' elif full_path.suffix in ['.doc', '.docx']: mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' logger.info(f"Отправка файла из хранилища: {full_path.name}") return FileResponse( path=str(full_path), filename=full_path.name, media_type=mime_type ) # Эндпоинт для скачивания сгенерированных документов @app.get("/download/{filename:path}", tags=["Files"], summary="Скачать сгенерированный документ") async def download_document(filename: str): """ Скачивает сгенерированный документ из временного хранилища. Args: filename: Имя файла (может быть с путём, например: temp_storage/Договор_Иванов.docx) Returns: FileResponse: Файл для скачивания """ import logging logger = logging.getLogger(__name__) # Убираем возможное дублирование /download/ в начале пути if filename.startswith("/download/"): filename = filename[len("/download/"):] if filename.startswith("download/"): filename = filename[len("download/"):] # Определяем путь к temp_storage (та же логика, что и в lifespan) current_file = Path(__file__).resolve() service_dir = current_file.parent.parent # service/src -> service # Проверяем Docker окружение docker_storage = Path("/app/temp_storage") if docker_storage.exists() or Path("/app").exists(): temp_storage_path = docker_storage else: # Для локального запуска: корень проекта project_root = service_dir.parent # service -> корень проекта temp_storage_path = project_root / "temp_storage_local" file_path = temp_storage_path / filename # Логирование для отладки logger.info(f"Запрос на скачивание: filename={filename}, file_path={file_path}, exists={file_path.exists()}") # Проверяем, что файл находится в temp_storage (безопасность) if not str(file_path.resolve()).startswith(str(temp_storage_path.resolve())): logger.warning(f"Попытка доступа вне temp_storage: {file_path}") raise HTTPException(status_code=403, detail="Доступ запрещён") if not file_path.exists(): logger.error(f"Файл не найден: {file_path} (filename={filename})") raise HTTPException(status_code=404, detail=f"Файл не найден: {filename}") logger.info(f"Отправка файла: {file_path.name}") return FileResponse( path=str(file_path), filename=file_path.name, media_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document' ) # Пример эндпоинта для работы с БД (шаблон) @app.post("/post_user_test", tags=["Database"], summary="Тестовый эндпоинт для работы с БД") async def test_db(db: AsyncSession = Depends(get_session)): """ Пример эндпоинта для работы с базой данных. """ # Здесь добавить логику работы с БД return {"message": "Database connection test endpoint"}