feat: passport images for user
This commit is contained in:
		@@ -107,6 +107,13 @@ class User(BaseModel):
 | 
				
			|||||||
        uselist=True,
 | 
					        uselist=True,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    passport_images = relationship(
 | 
				
			||||||
 | 
					        'PassportImage',
 | 
				
			||||||
 | 
					        back_populates='user',
 | 
				
			||||||
 | 
					        lazy='selectin',
 | 
				
			||||||
 | 
					        cascade="all, delete-orphan"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Position(BaseModel):
 | 
					class Position(BaseModel):
 | 
				
			||||||
    __tablename__ = 'positions'
 | 
					    __tablename__ = 'positions'
 | 
				
			||||||
@@ -119,3 +126,12 @@ class Position(BaseModel):
 | 
				
			|||||||
        uselist=False,
 | 
					        uselist=False,
 | 
				
			||||||
        back_populates='position'
 | 
					        back_populates='position'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PassportImage(BaseModel):
 | 
				
			||||||
 | 
					    __tablename__ = 'passport_images'
 | 
				
			||||||
 | 
					    id: Mapped[int] = mapped_column(primary_key=True)
 | 
				
			||||||
 | 
					    user_id = mapped_column(ForeignKey('users.id'), nullable=False)
 | 
				
			||||||
 | 
					    user: Mapped["User"] = relationship(back_populates='passport_images')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    image_url: Mapped[str] = mapped_column(nullable=False)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from fastapi import APIRouter, Depends
 | 
					from fastapi import APIRouter, Depends, UploadFile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from backend.dependecies import SessionDependency
 | 
					from backend.dependecies import SessionDependency
 | 
				
			||||||
from schemas.user import *
 | 
					from schemas.user import *
 | 
				
			||||||
@@ -56,3 +56,17 @@ async def get_managers(
 | 
				
			|||||||
        session: SessionDependency,
 | 
					        session: SessionDependency,
 | 
				
			||||||
):
 | 
					):
 | 
				
			||||||
    return await UserService(session).get_managers()
 | 
					    return await UserService(session).get_managers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@user_router.post(
 | 
				
			||||||
 | 
					    '/passport-images/upload/{user_id}',
 | 
				
			||||||
 | 
					    response_model=UploadPassportImageResponse,
 | 
				
			||||||
 | 
					    operation_id='upload_passport_image'
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def upload_passport_image(
 | 
				
			||||||
 | 
					        user_id: int,
 | 
				
			||||||
 | 
					        upload_file: UploadFile,
 | 
				
			||||||
 | 
					        session: SessionDependency,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    file_bytes = upload_file.file.read()
 | 
				
			||||||
 | 
					    return await UserService(session).upload_passport_image(user_id, file_bytes)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
from typing import List, Optional
 | 
					from typing import List, Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pydantic import model_validator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from schemas.base import BaseSchema, OkMessageSchema
 | 
					from schemas.base import BaseSchema, OkMessageSchema
 | 
				
			||||||
from schemas.payrate import PayRateSchema
 | 
					from schemas.payrate import PayRateSchema
 | 
				
			||||||
from schemas.position import PositionSchema
 | 
					from schemas.position import PositionSchema
 | 
				
			||||||
@@ -8,6 +10,12 @@ from schemas.role import RoleSchema
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# region Entities
 | 
					# region Entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PassportImageSchema(BaseSchema):
 | 
				
			||||||
 | 
					    id: int
 | 
				
			||||||
 | 
					    user_id: int
 | 
				
			||||||
 | 
					    image_url: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BasicUser(BaseSchema):
 | 
					class BasicUser(BaseSchema):
 | 
				
			||||||
    telegram_id: int
 | 
					    telegram_id: int
 | 
				
			||||||
    phone_number: str | None = None
 | 
					    phone_number: str | None = None
 | 
				
			||||||
@@ -23,6 +31,18 @@ class BasicUser(BaseSchema):
 | 
				
			|||||||
    role_key: str
 | 
					    role_key: str
 | 
				
			||||||
    pay_rate: Optional[PayRateSchema] = None
 | 
					    pay_rate: Optional[PayRateSchema] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    passport_image_url: str | None = None
 | 
				
			||||||
 | 
					    passport_images: list[PassportImageSchema] | None = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @model_validator(mode="after")
 | 
				
			||||||
 | 
					    def image_url_to_list(cls, values):
 | 
				
			||||||
 | 
					        passport_images = values.passport_images
 | 
				
			||||||
 | 
					        if not passport_images:
 | 
				
			||||||
 | 
					            return values
 | 
				
			||||||
 | 
					        latest_image = passport_images[-1]
 | 
				
			||||||
 | 
					        values.passport_image_url = latest_image.image_url
 | 
				
			||||||
 | 
					        return values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseUser(BasicUser):
 | 
					class BaseUser(BasicUser):
 | 
				
			||||||
    id: int
 | 
					    id: int
 | 
				
			||||||
@@ -70,4 +90,8 @@ class CreateUserResponse(OkMessageSchema):
 | 
				
			|||||||
class GetManagersResponse(BaseSchema):
 | 
					class GetManagersResponse(BaseSchema):
 | 
				
			||||||
    managers: List[UserSchema]
 | 
					    managers: List[UserSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UploadPassportImageResponse(OkMessageSchema):
 | 
				
			||||||
 | 
					    image_url: str | None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# endregion
 | 
					# endregion
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
from sqlalchemy import select, update, delete, insert, and_
 | 
					from sqlalchemy import select, update, delete, insert, and_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from models import User, user_position, user_pay_rate
 | 
					from backend import config
 | 
				
			||||||
 | 
					from external.s3_uploader.uploader import S3Uploader
 | 
				
			||||||
 | 
					from models import User, user_position, user_pay_rate, PassportImage
 | 
				
			||||||
from services.base import BaseService
 | 
					from services.base import BaseService
 | 
				
			||||||
from schemas.user import *
 | 
					from schemas.user import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,6 +36,7 @@ class UserService(BaseService):
 | 
				
			|||||||
        try:
 | 
					        try:
 | 
				
			||||||
            base_fields = request.data.model_dump_parent()
 | 
					            base_fields = request.data.model_dump_parent()
 | 
				
			||||||
            del base_fields['pay_rate']
 | 
					            del base_fields['pay_rate']
 | 
				
			||||||
 | 
					            del base_fields['passport_image_url']
 | 
				
			||||||
            user = User(**base_fields)
 | 
					            user = User(**base_fields)
 | 
				
			||||||
            self.session.add(user)
 | 
					            self.session.add(user)
 | 
				
			||||||
            await self.session.flush()
 | 
					            await self.session.flush()
 | 
				
			||||||
@@ -62,6 +65,8 @@ class UserService(BaseService):
 | 
				
			|||||||
                return UpdateUserResponse(ok=False, message='Указанный пользователь не найден')
 | 
					                return UpdateUserResponse(ok=False, message='Указанный пользователь не найден')
 | 
				
			||||||
            base_fields = request.data.model_dump_parent()
 | 
					            base_fields = request.data.model_dump_parent()
 | 
				
			||||||
            del base_fields['pay_rate']
 | 
					            del base_fields['pay_rate']
 | 
				
			||||||
 | 
					            del base_fields['passport_image_url']
 | 
				
			||||||
 | 
					            del base_fields['passport_images']
 | 
				
			||||||
            stmt = update(User).values(**base_fields).where(User.id == request.data.id)
 | 
					            stmt = update(User).values(**base_fields).where(User.id == request.data.id)
 | 
				
			||||||
            await self.session.execute(stmt)
 | 
					            await self.session.execute(stmt)
 | 
				
			||||||
            await self.session.flush()
 | 
					            await self.session.flush()
 | 
				
			||||||
@@ -93,3 +98,30 @@ class UserService(BaseService):
 | 
				
			|||||||
            return UpdateUserResponse(ok=True, message='Пользователь успешно обновлен')
 | 
					            return UpdateUserResponse(ok=True, message='Пользователь успешно обновлен')
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            return UpdateUserResponse(ok=False, message=str(e))
 | 
					            return UpdateUserResponse(ok=False, message=str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def upload_passport_image(self, user_id: int, file_bytes: bytes) -> UploadPassportImageResponse:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            user: Optional[User] = await self.session.get(User, user_id)
 | 
				
			||||||
 | 
					            if not user:
 | 
				
			||||||
 | 
					                raise Exception("Не удалось пользователя с указанным ID")
 | 
				
			||||||
 | 
					            # removing previous images
 | 
				
			||||||
 | 
					            for image in user.passport_images:
 | 
				
			||||||
 | 
					                await self.session.delete(image)
 | 
				
			||||||
 | 
					            s3_uploader = S3Uploader(config.S3_API_KEY)
 | 
				
			||||||
 | 
					            response = await s3_uploader.upload(file_bytes)
 | 
				
			||||||
 | 
					            response_url = response.get('link')
 | 
				
			||||||
 | 
					            if not response_url:
 | 
				
			||||||
 | 
					                raise Exception("Не удалось загрузить изображение")
 | 
				
			||||||
 | 
					            passport_image = PassportImage(
 | 
				
			||||||
 | 
					                user_id=user_id,
 | 
				
			||||||
 | 
					                image_url=response_url,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            self.session.add(passport_image)
 | 
				
			||||||
 | 
					            await self.session.commit()
 | 
				
			||||||
 | 
					            return UploadPassportImageResponse(
 | 
				
			||||||
 | 
					                ok=True,
 | 
				
			||||||
 | 
					                message='Изображение успешно загружено',
 | 
				
			||||||
 | 
					                image_url=response_url
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            return UploadPassportImageResponse(ok=False, message=str(e))
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user