Skip to content
This repository has been archived by the owner on Apr 14, 2024. It is now read-only.

Hotfix/disable state #225

Merged
merged 2 commits into from
Nov 28, 2023
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
**/*_pb2.py
**/*_pb2_grpc.py
**/*.proto
**/*.pyc
*.db
*.pyc
*.sqlite3
.env
.idea
Expand Down
44 changes: 22 additions & 22 deletions autotests/flow/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,47 +477,47 @@ async def test_waiting_email_about_accept_third_request_to_join(

@pytest.mark.dependency(depends=["TestProjectFlow::test_get_third_accepted_request_to_join"])
@pytest.mark.asyncio
async def test_create_random_request_to_join(
async def test_create_owner_request_to_join(
self,
random_id: uuid.UUID,
random_projects_rest_client: ProjectsRestClient,
oleg_id: uuid.UUID,
oleg_projects_rest_client: ProjectsRestClient,
):
position_id: uuid.UUID = self.CONTEXT["position_id"]

participant = await random_projects_rest_client.create_request_to_join_position(
participant = await oleg_projects_rest_client.create_request_to_join_position(
position_id=position_id,
)

self.CONTEXT["new_participant_id"] = participant.id

assert participant.position_id == position_id
assert participant.user_id == random_id
assert participant.user_id == oleg_id
assert participant.status == ParticipantStatusEnum.REQUEST

@pytest.mark.dependency(depends=["TestProjectFlow::test_create_random_request_to_join"])
@pytest.mark.dependency(depends=["TestProjectFlow::test_create_owner_request_to_join"])
@pytest.mark.asyncio
async def test_get_random_request_to_join(
async def test_get_owner_request_to_join(
self,
random_id: uuid.UUID,
random_projects_rest_client: ProjectsRestClient,
oleg_id: uuid.UUID,
oleg_projects_rest_client: ProjectsRestClient,
):
position_id: uuid.UUID = self.CONTEXT["position_id"]
participant_id: uuid.UUID = self.CONTEXT["new_participant_id"]

participant = await random_projects_rest_client.get_participant(
participant = await oleg_projects_rest_client.get_participant(
participant_id=participant_id,
)

assert participant.id == participant_id
assert participant.position_id == position_id
assert participant.user_id == random_id
assert participant.user_id == oleg_id
assert participant.status == ParticipantStatusEnum.REQUEST

@pytest.mark.dependency(depends=["TestProjectFlow::test_get_random_request_to_join"])
@pytest.mark.dependency(depends=["TestProjectFlow::test_get_owner_request_to_join"])
@pytest.mark.asyncio
async def test_random_accept_request_to_join(
async def test_owner_accept_request_to_join(
self,
random_id: uuid.UUID,
oleg_id: uuid.UUID,
oleg_projects_rest_client: ProjectsRestClient,
):
position_id: uuid.UUID = self.CONTEXT["position_id"]
Expand All @@ -530,29 +530,29 @@ async def test_random_accept_request_to_join(

assert participant.id == participant_id
assert participant.position_id == position_id
assert participant.user_id == random_id
assert participant.user_id == oleg_id
assert participant.status == ParticipantStatusEnum.JOINED

@pytest.mark.dependency(depeds=["TestProjectFlow::test_random_accept_request_to_join"])
@pytest.mark.dependency(depeds=["TestProjectFlow::test_owner_accept_request_to_join"])
@pytest.mark.asyncio
async def test_get_random_accepted_request_to_join(
async def test_get_owner_accepted_request_to_join(
self,
random_id: uuid.UUID,
random_projects_rest_client: ProjectsRestClient,
oleg_id: uuid.UUID,
oleg_projects_rest_client: ProjectsRestClient,
):
position_id: uuid.UUID = self.CONTEXT["position_id"]
participant_id: uuid.UUID = self.CONTEXT["new_participant_id"]

participant = await random_projects_rest_client.get_participant(
participant = await oleg_projects_rest_client.get_participant(
participant_id=participant_id,
)

assert participant.id == participant_id
assert participant.position_id == position_id
assert participant.user_id == random_id
assert participant.user_id == oleg_id
assert participant.status == ParticipantStatusEnum.JOINED

@pytest.mark.dependency(depends=["TestProjectFlow::test_get_random_accepted_request_to_join"])
@pytest.mark.dependency(depends=["TestProjectFlow::test_get_owner_accepted_request_to_join"])
@pytest.mark.asyncio
async def test_leave_position_by_participant(
self,
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ services:
ROOT_PATH: ${PROJECTS_ROOT_PATH:-/projects}
ALLOWED_ORIGINS: ${PROJECTS_ALLOWED_ORIGINS:-["http://localhost:3000"]}
PRODUCER_SERVERS: '["kafka:9091"]'
USERS_GRPC_HOST: users
USERS_GRPC_PORT: 50051
secrets:
- jwt_access_token_public_key
- jwt_access_token_private_key
Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ httpx = "^0.25.0"
typer = {version = "^0.9.0", extras = ["all"]}
redis = ">=4.2.0rc1"
pydantic = {version = "^2.5.1", extras = ["email"]}
py-fast-grpc = "0.1.10"
py-fast-grpc = "^0.3.2"

[tool.poetry.extras]
sqlite = ["aiosqlite"]
Expand Down
10 changes: 5 additions & 5 deletions sapphire/common/api/schemas/paginated.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import Any

from pydantic import BaseModel
from pydantic import BaseModel, NonNegativeInt, PositiveInt


class PaginatedResponse(BaseModel):
data: list[Any]
page: int
per_page: int
total_pages: int
total_items: int
page: PositiveInt
per_page: PositiveInt
total_pages: NonNegativeInt
total_items: NonNegativeInt
10 changes: 6 additions & 4 deletions sapphire/common/broker/models/projects.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import enum
import uuid

from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, ConfigDict, EmailStr


class ParticipantNotificationData(BaseModel):
model_config = ConfigDict(from_attributes=True)

user_id: uuid.UUID
position_id: uuid.UUID
project_id: uuid.UUID
owner_id: uuid.UUID
project_name: str
position_id: uuid.UUID
participant_id: uuid.UUID
participant_email: EmailStr
owner_id: uuid.UUID
owner_email: EmailStr


class ParticipantNotificationType(str, enum.Enum):
Expand Down
3 changes: 1 addition & 2 deletions sapphire/common/cache/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ async def start(self):

async def stop(self):
if self.redis:
self.redis.close()
await self.redis.wait_closed()
await self.redis.close()
self.redis = None

async def set(self, key: str, value: Any):
Expand Down
14 changes: 14 additions & 0 deletions sapphire/common/internal_api/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pydantic import BaseModel, NonNegativeInt, PositiveInt


class ListBaseRequest(BaseModel):
page: PositiveInt = 1
per_page: PositiveInt = 10


class ListBaseResponse(BaseModel):
data: list
page: PositiveInt
per_page: PositiveInt
total_items: NonNegativeInt
total_pages: NonNegativeInt
5 changes: 4 additions & 1 deletion sapphire/projects/api/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from sapphire.common.jwt import get_jwt_methods
from sapphire.projects.broker.service import get_service as get_broker_service
from sapphire.projects.database import get_service as get_database_service
from sapphire.users.internal_api.client import get_client as get_users_internal_api_client

from .service import get_service

Expand All @@ -18,11 +19,13 @@ def run(ctx: typer.Context):
database_service = get_database_service(settings=settings)
jwt_methods = get_jwt_methods(settings=settings)
broker_service = get_broker_service(loop=loop, settings=settings)
users_internal_api_client = get_users_internal_api_client(settings=settings)
api_service = get_service(
database=database_service,
jwt_methods=jwt_methods,
settings=settings,
broker_service=broker_service
broker_service=broker_service,
users_internal_api_client=users_internal_api_client,
)

loop.run_until_complete(api_service.run())
Expand Down
24 changes: 23 additions & 1 deletion sapphire/projects/api/rest/participants/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from sapphire.projects.broker.service import ProjectsBrokerService
from sapphire.projects.database.models import Participant, ParticipantStatusEnum
from sapphire.projects.database.service import ProjectsDatabaseService
from sapphire.users.internal_api.client.service import UsersInternalAPIClient

from .dependencies import get_path_participant
from .schemas import (
Expand All @@ -27,6 +28,9 @@ async def create_participant(
) -> ParticipantResponse:
broker_service: ProjectsBrokerService = request.app.service.broker
database_service: ProjectsDatabaseService = request.app.service.database
users_internal_api_client: UsersInternalAPIClient = (
request.app.service.users_internal_api_client
)

async with database_service.transaction() as session:
position = await database_service.get_position(
Expand All @@ -53,6 +57,9 @@ async def create_participant(
detail="Participant already send request to project or joined in project",
)

participant_data = await users_internal_api_client.get_user(user_id=jwt_data.user_id)
owner_data = await users_internal_api_client.get_user(user_id=position.project.owner_id)

async with database_service.transaction() as session:
participant = await database_service.create_participant(
session=session,
Expand All @@ -62,6 +69,8 @@ async def create_participant(
await broker_service.send_participant_requested(
project=position.project,
participant=participant,
participant_email=participant_data.email,
owner_email=owner_data.email,
)
await broker_service.send_create_chat(
is_personal=True,
Expand All @@ -85,6 +94,9 @@ async def update_participant(
) -> ParticipantResponse:
broker_service: ProjectsBrokerService = request.app.service.broker
database_service: ProjectsDatabaseService = request.app.service.database
users_internal_api_client: UsersInternalAPIClient = (
request.app.service.users_internal_api_client
)

project_owner_nodes = {
# New expected status : Required current statuses
Expand All @@ -108,6 +120,11 @@ async def update_participant(
if participant.status not in required_statuses:
raise HTTPForbidden()

participant_data = await users_internal_api_client.get_user(user_id=participant.user_id)
owner_data = await users_internal_api_client.get_user(
user_id=participant.position.project.owner_id,
)

async with database_service.transaction() as session:
participant = await database_service.update_participant_status(
session=session,
Expand Down Expand Up @@ -137,7 +154,12 @@ async def update_participant(
.get(jwt_data.user_id, None)
)
if participant_notification_send:
await participant_notification_send(project=project, participant=participant)
await participant_notification_send(
project=project,
participant=participant,
participant_email=participant_data.email,
owner_email=owner_data.email,
)

return ParticipantResponse.model_validate(participant)

Expand Down
11 changes: 10 additions & 1 deletion sapphire/projects/api/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from sapphire.projects.broker.service import ProjectsBrokerService
from sapphire.projects.database.service import ProjectsDatabaseService
from sapphire.projects.settings import ProjectsSettings
from sapphire.users.internal_api.client.service import UsersInternalAPIClient

from . import health, router

Expand All @@ -20,6 +21,7 @@ def __init__(
database: ProjectsDatabaseService,
jwt_methods: JWTMethods,
broker_service: ProjectsBrokerService,
users_internal_api_client: UsersInternalAPIClient,
media_dir_path: pathlib.Path = pathlib.Path("/media"),
load_file_chunk_size: int = 1024 * 1024, # 1 Mb
version: str = "0.0.0.0",
Expand All @@ -31,6 +33,7 @@ def __init__(
self._database = database
self._jwt_methods = jwt_methods
self._broker_service = broker_service
self._users_internal_api_client = users_internal_api_client
self._media_dir_path = media_dir_path
self._load_file_chunk_size = load_file_chunk_size

Expand Down Expand Up @@ -65,6 +68,10 @@ def jwt_methods(self) -> JWTMethods:
def broker(self) -> ProjectsBrokerService:
return self._broker_service

@property
def users_internal_api_client(self) -> UsersInternalAPIClient:
return self._users_internal_api_client

@property
def media_dir_path(self) -> pathlib.Path:
return self._media_dir_path
Expand All @@ -78,12 +85,14 @@ def get_service(
database: ProjectsDatabaseService,
jwt_methods: JWTMethods,
settings: ProjectsSettings,
broker_service: ProjectsBrokerService
broker_service: ProjectsBrokerService,
users_internal_api_client: UsersInternalAPIClient,
) -> ProjectsAPIService:
return ProjectsAPIService(
database=database,
jwt_methods=jwt_methods,
broker_service=broker_service,
users_internal_api_client=users_internal_api_client,
media_dir_path=settings.media_dir_path,
load_file_chunk_size=settings.load_file_chunk_size,
version=get_version() or "0.0.0",
Expand Down
Loading