-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tacs 44 map gateway to scheduler (#33)
- Loading branch information
Showing
21 changed files
with
1,140 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
""" | ||
Scheduler Commands | ||
""" | ||
import datetime | ||
|
||
from pydantic import Field | ||
|
||
from app.domain.schemas import CamelCaseModel | ||
|
||
|
||
class ProposeOption(CamelCaseModel): | ||
""" | ||
Command to propose a meeting option. | ||
""" | ||
|
||
date: datetime.date = Field(description="The date of the meeting.", example="2021-01-01") | ||
hour: int = Field(description="The hour of the meeting.", example="12", max=23, min=0) | ||
minute: int = Field(description="The minute of the meeting.", example="30", max=59, min=0) | ||
|
||
|
||
class ScheduleMeeting(CamelCaseModel): | ||
""" | ||
Command to schedule a meeting. | ||
""" | ||
|
||
organizer: str = Field(description="Responsible for the meeting's username.", example="johndoe") | ||
title: str | None = Field(description="The meeting's title.", example="Coffee with John Doe") | ||
description: str | None = Field(description="The meeting's description.", | ||
example="A meeting to discuss the project.") | ||
location: str | None = Field(description="The meeting's location.", example="Floor 3, Cafeteria") | ||
options: list[ProposeOption] = Field(description="A list of options to schedule the meeting.", min_items=1) | ||
guests: set[str] = Field(description="A list of guests to invite to the meeting.", | ||
example=["frank", "jane"], default_factory=set) | ||
|
||
|
||
class ToggleVoting(CamelCaseModel): | ||
""" | ||
Command to toggle voting on a meeting. | ||
""" | ||
username: str = Field(description="Responsible for the meeting's username.", example="johndoe") | ||
voting: bool = Field(description="Whether voting is enabled", example=True, default=True) | ||
|
||
|
||
class JoinMeeting(CamelCaseModel): | ||
""" | ||
Command to join a meeting. | ||
""" | ||
username: str = Field(description="Username of who wants to join a meeting", example="johndoe") | ||
|
||
|
||
class VoteOption(CamelCaseModel): | ||
""" | ||
Command to vote on a meeting option. | ||
""" | ||
username: str = Field(description="Username of who wants to vote on a meeting option", example="johndoe") | ||
option: ProposeOption = Field(description="The option to vote on.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
""" | ||
Events from scheduler service. | ||
""" | ||
import datetime | ||
|
||
from pydantic import Field | ||
|
||
from app.domain.schemas import CamelCaseModel | ||
|
||
|
||
class OptionVoted(CamelCaseModel): | ||
""" | ||
Event emitted when an option is voted. | ||
""" | ||
date: datetime.datetime = Field(description="A tentative date for a meeting") | ||
votes: list[str] = Field(description="A list of usernames that voted for the option.", example=["johndoe"]) | ||
|
||
|
||
class MeetingScheduled(CamelCaseModel): | ||
""" | ||
Event emitted when a meeting is scheduled. | ||
""" | ||
|
||
id: str = Field(description="The meeting's unique identifier.", example="6442ee3291a1304d4c88ffc9") | ||
organizer: str = Field(description="Username of who scheduled the meeting.", example="johndoe") | ||
voting: bool = Field(description="Whether mode voting is enabled.", example=True) | ||
title: str = Field(description="The meeting's title.", example="Sprint Planning") | ||
description: str | None = Field(description="The meeting's description.", example="Planning the next sprint.") | ||
location: str | None = Field(description="The meeting's location.", example="Room 1") | ||
date: datetime.datetime | None = Field(description="The meeting's date.", example="2021-01-01T09:30:00Z") | ||
guests: list[str] = Field(description="A list of guests.", example=["johndoe", "clarasmith"], default_factory=list) | ||
options: list[OptionVoted] = Field(description="A list of options.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
""" | ||
Scheduler Service Gateway | ||
""" | ||
from typing import Annotated | ||
|
||
from fastapi import APIRouter, Path, Request, Response | ||
from starlette.status import HTTP_200_OK, HTTP_201_CREATED | ||
|
||
from app.adapters.http_client import AsyncHttpClient | ||
from app.adapters.network import gateway | ||
from app.dependencies import AsyncHttpClientDependency, ServiceProvider | ||
from app.domain.commands.scheduler_service import JoinMeeting, ScheduleMeeting, ToggleVoting, VoteOption | ||
from app.domain.events.scheduler_service import MeetingScheduled | ||
from app.domain.models import Service | ||
from app.domain.schemas import ResponseModel, ResponseModels | ||
from app.service_layer.gateway import api_v1_url, get_service, verify_scheduling_meeting, verify_status, \ | ||
verify_user_existence | ||
|
||
router = APIRouter(prefix="/scheduler-service", tags=["Scheduler"]) | ||
|
||
|
||
@router.get("/schedules", | ||
status_code=HTTP_200_OK, | ||
summary="Finds all schedules", | ||
tags=["Queries"] | ||
) | ||
async def query_schedules( | ||
services: ServiceProvider, | ||
client: AsyncHttpClientDependency, | ||
) -> ResponseModels[MeetingScheduled]: | ||
""" | ||
Retrieves schedules from the Database. | ||
""" | ||
|
||
service = await get_service(service_name="scheduler", services=services) | ||
|
||
response, code = await gateway(service_url=service.base_url, path=f"{api_v1_url}/schedules", | ||
client=client, method="GET") | ||
|
||
verify_status(response=response, status_code=code) | ||
|
||
return ResponseModels[MeetingScheduled](**response) | ||
|
||
|
||
@router.get("/schedules/{schedule_id}", | ||
status_code=HTTP_200_OK, | ||
summary="Finds schedule by id", | ||
tags=["Queries"] | ||
) | ||
async def query_schedule_by_id( | ||
schedule_id: Annotated[str, Path(description="The schedule's id.", example="b455f6t63t7")], | ||
services: ServiceProvider, | ||
client: AsyncHttpClientDependency, | ||
) -> ResponseModel[MeetingScheduled]: | ||
""" | ||
Retrieves a specific schedule from the Database. | ||
""" | ||
|
||
service = await get_service(service_name="scheduler", services=services) | ||
|
||
response, code = await gateway(service_url=service.base_url, path=f"{api_v1_url}/schedules/{schedule_id}", | ||
client=client, method="GET") | ||
|
||
verify_status(response=response, status_code=code) | ||
|
||
return ResponseModel[MeetingScheduled](**response) | ||
|
||
|
||
@router.post("/schedules", | ||
status_code=HTTP_201_CREATED, | ||
summary="Creates a schedule", | ||
tags=["Commands"], | ||
) | ||
async def schedule( | ||
command: ScheduleMeeting, | ||
services: ServiceProvider, | ||
client: AsyncHttpClientDependency, | ||
request: Request, | ||
response: Response, | ||
) -> ResponseModel[MeetingScheduled]: | ||
""" | ||
Schedules a meeting. | ||
""" | ||
|
||
updated_command = await verify_scheduling_meeting(command=command, | ||
service=await get_service(service_name="auth", | ||
services=services), | ||
client=client) | ||
|
||
service_response, status_code = await gateway( | ||
service_url=(await get_service(service_name="scheduler", services=services)).base_url, | ||
path=f"{api_v1_url}/schedules", | ||
client=client, | ||
method="POST", | ||
request_body=updated_command.json() | ||
) | ||
|
||
verify_status(response=service_response, status_code=status_code, status_codes=[HTTP_201_CREATED]) | ||
|
||
response_body = ResponseModel[MeetingScheduled](**service_response) | ||
|
||
response.headers["Location"] = f"{request.base_url}api/v1/scheduler-service/schedules/{response_body.data.id}" | ||
|
||
return response_body | ||
|
||
|
||
@router.patch("/schedules/{schedule_id}/voting", | ||
status_code=HTTP_200_OK, | ||
summary="Toggles voting on a schedule", | ||
tags=["Commands"], | ||
) | ||
async def toggle_voting( | ||
schedule_id: Annotated[str, Path(description="The schedule's id.", example="b455f6t63t7")], | ||
command: ToggleVoting, | ||
services: ServiceProvider, | ||
client: AsyncHttpClientDependency, | ||
) -> ResponseModel[MeetingScheduled]: | ||
""" | ||
Toggles voting on a schedule. | ||
""" | ||
service_response, status_code = await gateway( | ||
service_url=(await get_service(service_name="scheduler", services=services)).base_url, | ||
path=f"{api_v1_url}/schedules/{schedule_id}/voting", | ||
client=client, | ||
method="PATCH", | ||
request_body=command.json() | ||
) | ||
|
||
verify_status(response=service_response, status_code=status_code, status_codes=[HTTP_200_OK]) | ||
|
||
return ResponseModel[MeetingScheduled](**service_response) | ||
|
||
|
||
@router.patch("/schedules/{schedule_id}/relationships/guests", | ||
status_code=HTTP_200_OK, | ||
summary="Adds a guest to a schedule", | ||
tags=["Commands"], | ||
) | ||
async def join_meeting( | ||
schedule_id: Annotated[str, Path(description="The schedule's id.", example="b455f6t63t7")], | ||
command: JoinMeeting, | ||
services: ServiceProvider, | ||
client: AsyncHttpClientDependency, | ||
) -> ResponseModel[MeetingScheduled]: | ||
""" | ||
Allows a valid user to join a meeting. | ||
""" | ||
return await command_with_user_validation( | ||
path=f"{schedule_id}/relationships/guests", | ||
command=command, | ||
services=services, | ||
client=client, | ||
) | ||
|
||
|
||
@router.patch("/schedules/{schedule_id}/options", | ||
status_code=HTTP_200_OK, | ||
summary="Votes for an option", | ||
tags=["Commands"], | ||
) | ||
async def vote_for_option( | ||
schedule_id: Annotated[str, Path(description="The schedule's id.", example="b455f6t63t7")], | ||
command: VoteOption, | ||
services: ServiceProvider, | ||
client: AsyncHttpClientDependency, | ||
) -> ResponseModel[MeetingScheduled]: | ||
return await command_with_user_validation( | ||
path=f"{schedule_id}/options", | ||
command=command, | ||
services=services, | ||
client=client, | ||
) | ||
|
||
|
||
######################################################################################################################## | ||
# Helper functions | ||
######################################################################################################################## | ||
|
||
async def command_with_user_validation( | ||
path: str, | ||
command: JoinMeeting | VoteOption, | ||
services: list[Service], | ||
client: AsyncHttpClient, | ||
method: str = "PATCH", | ||
) -> ResponseModel[MeetingScheduled]: | ||
""" | ||
Validates the user and then sends the command to the scheduler service. | ||
""" | ||
await verify_user_existence(username=command.username, | ||
client=client, | ||
service=await get_service(service_name="auth", services=services), | ||
) | ||
|
||
service_response, status_code = await gateway( | ||
service_url=(await get_service(service_name="scheduler", services=services)).base_url, | ||
client=client, | ||
path=f"{api_v1_url}/schedules/{path}", | ||
method=method, | ||
request_body=command.json() | ||
) | ||
|
||
verify_status(response=service_response, status_code=status_code, status_codes=[HTTP_200_OK]) | ||
|
||
return ResponseModel[MeetingScheduled](**service_response) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.