Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 45 additions & 23 deletions app/api/api_v1/project.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from dependencies.annotations import (
AuthUserByAccessTokenDep,
MinioClientDep,
PaginationPageDep,
PaginationSizeDep,
ProjectServiceDep,
)
from fastapi import APIRouter, status
from dependencies.auth import get_auth_user_by_access_token
from fastapi import APIRouter, Depends, status
from schemas.project import (
ProjectPartialUpdate,
ProjectResponseList,
Expand All @@ -19,24 +22,6 @@
)


@router.get(
"/{project_id}",
response_model=ProjectWithRenderFileFullResponse,
status_code=status.HTTP_200_OK,
)
async def get_project(
project_id: int,
user_id: AuthUserByAccessTokenDep,
project_service: ProjectServiceDep,
s3_client: MinioClientDep,
) -> ProjectWithRenderFileFullResponse:
return await project_service.get_by_id(
project_id=project_id,
user_id=user_id,
s3_client=s3_client,
)


@router.get(
"/",
response_model=ProjectResponseList,
Expand All @@ -45,8 +30,8 @@ async def get_project(
async def get_current_user_projects(
project_service: ProjectServiceDep,
user_id: AuthUserByAccessTokenDep,
size: int = 10,
page: int = 1,
size: PaginationSizeDep = 10,
page: PaginationPageDep = 1,
) -> ProjectResponseList:
return await project_service.get_user_projects(
user_id=user_id,
Expand All @@ -55,16 +40,35 @@ async def get_current_user_projects(
)


@router.get(
"/public",
response_model=ProjectResponseList,
status_code=status.HTTP_200_OK,
dependencies=[
Depends(get_auth_user_by_access_token),
],
)
async def get_public_projects(
project_service: ProjectServiceDep,
size: PaginationSizeDep = 10,
page: PaginationPageDep = 1,
) -> ProjectResponseList:
return await project_service.get_public_projects(size=size, page=page)


@router.get(
"/public/user/{user_id}",
response_model=ProjectResponseList,
status_code=status.HTTP_200_OK,
dependencies=[
Depends(get_auth_user_by_access_token),
],
)
async def get_user_public_projects(
project_service: ProjectServiceDep,
user_id: int,
size: int = 10,
page: int = 1,
size: PaginationSizeDep = 10,
page: PaginationPageDep = 1,
) -> ProjectResponseList:
return await project_service.get_user_public_projects(
user_id=user_id,
Expand Down Expand Up @@ -120,3 +124,21 @@ async def delete_project(
project_id=project_id,
user_id=user_id,
)


@router.get(
"/{project_id}",
response_model=ProjectWithRenderFileFullResponse,
status_code=status.HTTP_200_OK,
)
async def get_project(
project_id: int,
user_id: AuthUserByAccessTokenDep,
project_service: ProjectServiceDep,
s3_client: MinioClientDep,
) -> ProjectWithRenderFileFullResponse:
return await project_service.get_by_id(
project_id=project_id,
user_id=user_id,
s3_client=s3_client,
)
4 changes: 3 additions & 1 deletion app/core/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@

from core.config.database import DataBaseConfig
from core.config.jwt import JWTConfig
from core.config.kafka import KafkaConfig
from core.config.logging import LoggingConfig
from core.config.minio import MinioConfig


class Settings(BaseSettings):
base_dir: Path = Path(__file__).parent.parent.parent
database: DataBaseConfig = DataBaseConfig()
jwt: JWTConfig = JWTConfig()
minio: MinioConfig = MinioConfig()
kafka: KafkaConfig = KafkaConfig()
jwt: JWTConfig = JWTConfig()
logging: LoggingConfig = LoggingConfig()
oauth2_scheme: OAuth2PasswordBearer = OAuth2PasswordBearer(
tokenUrl="/api/v1/auth/login",
Expand Down
13 changes: 13 additions & 0 deletions app/core/config/kafka.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pydantic import BaseModel


class KafkaTopicConfig(BaseModel):
create_project: str = "create_project"
generate_render: str = "generate_model"


class KafkaConfig(BaseModel):
host: str = "kafka"
port: int = 9092

topic: KafkaTopicConfig = KafkaTopicConfig()
7 changes: 7 additions & 0 deletions app/core/config/minio.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
from pydantic import BaseModel


class MinioBucketConfig(BaseModel):
input: str = "input"
output: str = "output"


class MinioConfig(BaseModel):
host: str = "minio"
port: int = 9000
username: str = "adminadmin"
password: str = "adminadmin"

bucket: MinioBucketConfig = MinioBucketConfig()

@property
def url(self) -> str:
return f"http://{self.host}:{self.port}"
Expand Down
21 changes: 4 additions & 17 deletions app/core/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from dataclasses import dataclass
from enum import StrEnum

USER_SURNAME_MIN_LENGTH = 3
Expand All @@ -24,6 +23,9 @@
PROJECT_DESCRIPTION_MIN_LENGTH = 0
PROJECT_DESCRIPTION_MAX_LENGTH = 512

PROJECT_PAGINATION_SIZE_MIN_VALUE = 1
PROJECT_PAGINATION_PAGE_MIN_VALUE = 1

RENDER_WIDTH_MIN_VALUE = 20
RENDER_WIDTH_MAX_VALUE = 2160

Expand All @@ -44,6 +46,7 @@

OUTBOX_TOPIC_MAX_LENGTH = 512


FIELD_SUB = "sub"
TOKEN_TYPE = "type"

Expand All @@ -65,19 +68,3 @@ class RenderStatus(StrEnum):
class ProjectVisibility(StrEnum):
public = "public"
private = "private"


@dataclass(frozen=True)
class S3Bucket:
input_bucket = "input"
output_bucket = "output"


@dataclass(frozen=True)
class KafkaTopic:
create_project: str = "create_project"
generate_model: str = "generate_model"


s3_bucket = S3Bucket()
kafka_topic = KafkaTopic()
4 changes: 2 additions & 2 deletions app/core/exceptions/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ class PermissionDeniedError(ForbiddenError):
Ошибка, связанная с отсутствием прав доступ.
"""

def __init__(self, access_level: str) -> None:
detail = f"Permission denied: access level = {access_level} is not sufficient."
def __init__(self) -> None:
detail = "Permission denied: access level is not sufficient."
super().__init__(detail)
13 changes: 12 additions & 1 deletion app/core/interfaces/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,22 @@ async def get_project_owner(self, project_id: int) -> User | None:
"""

@abstractmethod
async def get_user_projects(self, user_id: int) -> list[Project]:
async def get_user_projects(
self,
user_id: int,
size: int,
page: int,
) -> list[Project]:
"""
Посмотреть список проектов пользователя.
"""

@abstractmethod
async def get_public_projects(self, size: int, page: int) -> list[Project]:
"""
Посмотреть список публичных проектов.
"""

@abstractmethod
async def get_user_public_projects(
self,
Expand Down
16 changes: 15 additions & 1 deletion app/dependencies/annotations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Annotated

from fastapi import Depends
from fastapi import Depends, Query
from infrastructure.database.repositories.render import RenderRepository
from infrastructure.database.unit_of_work import UnitOfWork
from infrastructure.minio.client import MinioClient
Expand Down Expand Up @@ -74,3 +74,17 @@
MinioClient,
Depends(get_minio_client),
]

PaginationSizeDep = Annotated[
int,
Query(
ge=1,
),
]

PaginationPageDep = Annotated[
int,
Query(
ge=1,
),
]
4 changes: 2 additions & 2 deletions app/dependencies/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Annotated

from aiokafka import AIOKafkaProducer
from core.constants import s3_bucket
from core.config.application import settings
from fastapi import Depends
from infrastructure.database.unit_of_work import UnitOfWork
from infrastructure.kafka import producer
Expand Down Expand Up @@ -33,7 +33,7 @@ async def get_input_file_uploader(
],
) -> AsyncGenerator[FileUploader]:
file_uploader = FileUploader(
bucket=s3_bucket.input_bucket,
bucket=settings.minio.bucket.input,
s3_client=s3_client,
unit_of_work=unit_of_work,
)
Expand Down
12 changes: 12 additions & 0 deletions app/infrastructure/database/repositories/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
__all__ = (
"FileRepository",
"OutboxRepository",
"ProjectRepository",
"RenderRepository",
"UserRepository",
)
from .file import FileRepository
from .outbox import OutboxRepository
from .project import ProjectRepository
from .render import RenderRepository
from .user import UserRepository
24 changes: 22 additions & 2 deletions app/infrastructure/database/repositories/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,28 @@ async def get_project_owner(self, project_id: int) -> User | None:
result = await self.session.execute(stmt)
return result.scalar()

async def get_user_projects(self, user_id: int) -> list[Project]:
stmt = select(Project).where(Project.user_id == user_id)
async def get_public_projects(self, size: int, page: int) -> list[Project]:
stmt = (
select(Project)
.where(Project.visibility == ProjectVisibility.public)
.limit(size)
.offset(size * (page - 1))
)
result = await self.session.execute(stmt)
return list(result.scalars().all())

async def get_user_projects(
self,
user_id: int,
size: int,
page: int,
) -> list[Project]:
stmt = (
select(Project)
.where(Project.user_id == user_id)
.limit(size)
.offset(size * (page - 1))
)
result = await self.session.execute(stmt)
return list(result.scalars().all())

Expand Down
3 changes: 1 addition & 2 deletions app/infrastructure/kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from aiokafka import AIOKafkaConsumer, AIOKafkaProducer, ConsumerRecord
from core.config.application import settings
from core.constants import kafka_topic
from core.logging import get_logger
from pydantic import BaseModel
from schemas.event import AddRenderProjectEvent
Expand Down Expand Up @@ -62,7 +61,7 @@ async def add_render_to_project_consume(consumer: AIOKafkaConsumer) -> None:


consumer = AIOKafkaConsumer(
kafka_topic.generate_model,
settings.kafka.topic.generate_render,
bootstrap_servers="kafka:9092",
group_id="app",
value_deserializer=deserialize_message,
Expand Down
7 changes: 4 additions & 3 deletions app/outbox_worker/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Self

from aiokafka import AIOKafkaProducer
from core.constants import kafka_topic
from core.config.application import settings
from core.interfaces.clients import AbstractUnitOfWorkClient
from core.logging import get_logger
from infrastructure.database.repositories.outbox import OutboxRepository
Expand Down Expand Up @@ -43,13 +43,14 @@ async def process_messages(self) -> None:
logger.info("Received message, message=%s", event.message)
message = self._serialize_message(event.message)
await self.outbox_repository.mark_event_as_sent(event.id)
topic = settings.kafka.topic.create_project
await self.producer.send(
topic=kafka_topic.create_project,
topic=topic,
value=message,
)
logger.info(
"Sent message, topic=%s, message=%s",
kafka_topic.create_project,
topic,
message,
)

Expand Down
17 changes: 17 additions & 0 deletions app/schemas/constraints/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Annotated

from core.constants import (
PROJECT_PAGINATION_PAGE_MIN_VALUE,
PROJECT_PAGINATION_SIZE_MIN_VALUE,
)
from pydantic import Field

SizeConstraint = Annotated[
int,
Field(ge=PROJECT_PAGINATION_SIZE_MIN_VALUE),
]

PageConstraint = Annotated[
int,
Field(ge=PROJECT_PAGINATION_PAGE_MIN_VALUE),
]
Loading
Loading