diff --git a/api/models.py b/api/models.py index 728d81f..aaf8c4f 100644 --- a/api/models.py +++ b/api/models.py @@ -136,4 +136,15 @@ class AuthorizationArchive(BaseModel): id: int | None = Field(None, description='Архив авторизаций') user_id: int = Field(..., max_length=250, description='ID пользователя, на чей аккаунт была произведена авторизация') auth_time: datetime = Field(default_factory=datetime.utcnow, description='Время авторизации') - ip: str = Field(..., max_length=15, description='IP-адрес') \ No newline at end of file + ip: str = Field(..., max_length=15, description='IP-адрес') + + +# TODO DepotCompanyCars + + +class Attachment(BaseModel): + id: int | None = Field(None, description='ID Вложения') + uuid: str = Field(..., max_length=100, description='UUID Вложения') + file_path: str = Field(..., max_length=100, description='Путь к файлу') + attachment_type: str = Field(..., max_length=100, description='Тип файла') + file_extension: str = Field(..., max_length=100, description='Расширение файла') \ No newline at end of file diff --git a/app.py b/app.py index 5826a39..7a6da23 100755 --- a/app.py +++ b/app.py @@ -8,6 +8,7 @@ from fastapi.security import OAuth2PasswordBearer from api.account.router import router as router_account +from api.attachment.router import router as router_attachment from api.group.router import router as router_group from src.db import create_tables, async_session_maker @@ -45,6 +46,7 @@ ) api.include_router(router_account) +api.include_router(router_attachment) api.include_router(router_group) api.openapi_schema = get_openapi( @@ -61,11 +63,10 @@ "bearerFormat": "JWT" } } - api.openapi_schema["security"] = [{"BearerAuth": []}] if __name__ == '__main__': - # asyncio.run(create_tables()) + asyncio.run(create_tables()) uvicorn.run( app='app:api', host='0.0.0.0', diff --git a/requirements.txt b/requirements.txt index 72847dd..a10083a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ aiohttp==3.11.11 packaging==24.2 aioboto3==13.4.0 asyncmy==0.2.10 +boto3==1.35.63 \ No newline at end of file diff --git a/src/db.py b/src/db.py index f0b8922..e68865f 100644 --- a/src/db.py +++ b/src/db.py @@ -176,16 +176,15 @@ class DepotCompanyCars(Base): vehicle_payload = Column(Integer, nullable=False) # Грузоподъемность авто (в кг) fuel_type = Column(String(100), nullable=True) # Тип топлива (бензин, дизель, электро) -# class AuthorizationArchive(Base): -# __tablename__ = 'authorization_archive' -# id = Column(Integer, primary_key=True, autoincrement=True) -# user_id = Column(String(250), ForeignKey('users.id'), nullable=False) -# auth_time = Column(DateTime, default=datetime.utcnow, nullable=False) -# ip = Column(String(15), nullable=False) - -# user = relationship('User', backref='items') # Связь с таблицей пользователей +class Attachment(Base): + __tablename__ = 'attachment' + id = Column(Integer, primary_key=True, autoincrement=True) + uuid = Column(String(100), nullable=False) # UUID вложения + file_path = Column(String(250), nullable=False) # Путь к файлу (это не URL) + attachment_type = Column(String(100), nullable=False) # Тип вложения: видео, фото, файл + file_extension = Column(String(100), nullable=False) # Расширение файла async def create_tables(): async with engine.begin() as conn: diff --git a/src/manager.py b/src/manager.py index 133a291..9fd09c7 100644 --- a/src/manager.py +++ b/src/manager.py @@ -11,13 +11,18 @@ import pyotp import qrcode import io +import os import aiohttp +import uuid as uuid_generate from datetime import datetime -from config import settings -from api.models import UserStructure from packaging.version import Version as _versionCompare +from api.models import UserStructure +from src.db import Attachment, async_session_maker +from src.s3 import _S3Connector, _S3Config +from config import settings + class TwoFactor: def __init__( @@ -90,6 +95,51 @@ def __init__( self.result = result self.message = message +S3Data = _S3Config( + bucket_name=settings.S3_BUCKET_NAME, + endpoint_url=settings.S3_ENDPOINT_URL, + region_name=settings.S3_REGION_NAME, + aws_access_key_id=settings.S3_ACCESS_KEY, + aws_secret_access_key=settings.S3_SECRET_ACCESS_KEY +) + +class AttachmentManager: + def __init__(self): + pass + + def file_url(self, file_name): + return f'{S3Data.endpoint_url}/{S3Data.bucket_name}/{file_name}' + + @staticmethod + async def upload( + file_name: str, + file_content: bytes + ): + uuid = uuid_generate.uuid4() + file_extension = os.path.splitext(file_name)[1].lstrip('.') + new_file_name = f'{uuid}.{file_extension}' + + file_stream = io.BytesIO(file_content) + + async with _S3Connector(S3Data) as s3: + await s3.upload_fileobj(fileobj=file_stream, key=new_file_name) + + attachment_data = Attachment( + uuid=uuid, + file_path=new_file_name, + attachment_type='file', + file_extension=file_extension + ) + + async with async_session_maker() as session: + session.add(attachment_data) + try: + await session.commit() + except Exception as e: + await session.rollback() + + return new_file_name + class UserManager: def __init__(self): diff --git a/src/s3.py b/src/s3.py index c4699d4..31b912b 100644 --- a/src/s3.py +++ b/src/s3.py @@ -3,46 +3,86 @@ from botocore.config import Config -class _S3Connector: - def __init__(self, bucket_name, endpoint_url, region_name, aws_access_key_id, aws_secret_access_key): +class _S3Config: + def __init__( + self, + bucket_name: str, + endpoint_url: str, + region_name: str, + aws_access_key_id: str, + aws_secret_access_key: str + ): self.bucket_name = bucket_name + self.endpoint_url = endpoint_url + self.region_name = region_name + self.aws_access_key_id = aws_access_key_id + self.aws_secret_access_key = aws_secret_access_key + + +class _S3Connector: + def __init__(self, s3_config: _S3Config): + self.bucket_name = s3_config.bucket_name + self.endpoint_url = s3_config.endpoint_url + self.region_name = s3_config.region_name + self.aws_access_key_id = s3_config.aws_access_key_id + self.aws_secret_access_key = s3_config.aws_secret_access_key + self.session = aioboto3.Session() self.client_args = { 'service_name': 's3', - 'endpoint_url': endpoint_url, - 'region_name': region_name, - 'aws_access_key_id': aws_access_key_id, - 'aws_secret_access_key': aws_secret_access_key, - 'config': Config(s3={'addressing_style': 'path'}) + 'endpoint_url': self.endpoint_url, + 'region_name': self.region_name, + 'aws_access_key_id': self.aws_access_key_id, + 'aws_secret_access_key': self.aws_secret_access_key, + 'config': Config(signature_version='s3v4') } + self.client = None async def __aenter__(self): self.client = await self.session.client(**self.client_args).__aenter__() return self async def __aexit__(self, exc_type, exc_val, exc_tb): - await self.client.__aexit__(exc_type, exc_val, exc_tb) + if self.client: + await self.client.__aexit__(exc_type, exc_val, exc_tb) async def create_bucket(self): + if not self.client: + raise AttributeError("S3 client is not initialized.") await self.client.create_bucket(Bucket=self.bucket_name) async def list_buckets(self): + if not self.client: + raise AttributeError("S3 client is not initialized.") return await self.client.list_buckets() async def upload_file(self, filename, key): + if not self.client: + raise AttributeError("S3 client is not initialized.") await self.client.upload_file(filename, self.bucket_name, key) - async def upload_fileobj(self, filename, key): - async with open(filename, 'rb') as data: - await self.client.upload_fileobj(data, self.bucket_name, key) + async def upload_fileobj(self, fileobj, key): + """Загружает файл в S3""" + if not self.client: + raise AttributeError("S3 client is not initialized.") + await self.client.upload_fileobj(fileobj, self.bucket_name, key) + + # Возвращаем URL, учитывая кастомный endpoint + return f"{self.client.meta.endpoint_url}/{self.bucket_name}/{key}" async def list_objects(self): + if not self.client: + raise AttributeError("S3 client is not initialized.") return await self.client.list_objects(Bucket=self.bucket_name) async def delete_object(self, key): + if not self.client: + raise AttributeError("S3 client is not initialized.") await self.client.delete_object(Bucket=self.bucket_name, Key=key) async def delete_bucket(self): + if not self.client: + raise AttributeError("S3 client is not initialized.") await self.client.delete_bucket(Bucket=self.bucket_name)