Skip to content

Commit

Permalink
feat(api/event-groups): add create, update and ics-related routes for…
Browse files Browse the repository at this point in the history
… event group router
  • Loading branch information
dantetemplar committed Aug 8, 2023
1 parent a3a8071 commit 03d4d6a
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 45 deletions.
13 changes: 12 additions & 1 deletion poetry.lock

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

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fastapi = { extras = ["all"], version = "^0.99.1" }
authlib = "^1.2.1"
cryptography = "^41.0.2"
icalendar = "^5.0.7"
aiofiles = "^23.1.0"

[tool.poetry.group.prod.dependencies]
gunicorn = "20.1.0"
Expand Down
199 changes: 155 additions & 44 deletions src/app/event_groups/routes.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,50 @@
from starlette.responses import FileResponse
from fastapi import UploadFile, HTTPException
from starlette.responses import FileResponse, JSONResponse

from src.app.dependencies import EVENT_GROUP_REPOSITORY_DEPENDENCY
from src.app.dependencies import EVENT_GROUP_REPOSITORY_DEPENDENCY, CURRENT_USER_ID_DEPENDENCY
from src.app.event_groups import router
from src.exceptions import EventGroupNotFoundException
from src.schemas.event_groups import ViewEventGroup, ListEventGroupsResponse
from src.exceptions import (
EventGroupNotFoundException,
OperationIsNotAllowed,
EventGroupWithMissingPath,
IcsFileIsNotModified,
)
from src.repositories.predefined.repository import PredefinedRepository
from src.schemas import ViewEventGroup, ListEventGroupsResponse, CreateEventGroup, UpdateEventGroup, OwnershipEnum

import aiofiles


@router.post(
"/", responses={201: {"description": "Event group created successfully", "model": ViewEventGroup}}, status_code=201
)
async def create_event_group(
event_group: CreateEventGroup,
event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY,
current_user_id: CURRENT_USER_ID_DEPENDENCY,
) -> ViewEventGroup:
created = await event_group_repository.create_or_read(event_group)
await event_group_repository.setup_ownership(created.id, current_user_id, OwnershipEnum.owner)
return created


@router.put(
"/{event_group_id}", responses={200: {"description": "Event group updated successfully", "model": ViewEventGroup}}
)
async def update_event_group(
event_group_id: int,
update_scheme: UpdateEventGroup,
event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY,
current_user_id: CURRENT_USER_ID_DEPENDENCY,
) -> ViewEventGroup:
event_group = await event_group_repository.read(event_group_id)
owners_and_moderators = {ownership.user_id for ownership in event_group.ownerships}

if current_user_id not in owners_and_moderators:
raise OperationIsNotAllowed()

event_group = await event_group_repository.update(event_group_id, update_scheme)
return event_group


@router.get(
Expand All @@ -29,6 +69,28 @@ async def find_event_group_by_path(
return event_group


@router.get(
"/by-alias",
responses={
200: {"description": "Event group info", "model": ViewEventGroup},
404: {"description": "Event group not found"},
},
)
async def find_event_group_by_alias(
alias: str,
event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY,
) -> ViewEventGroup:
"""
Get event group info by alias
"""

event_group = await event_group_repository.read_by_alias(alias)

if event_group is None:
raise EventGroupNotFoundException()
return event_group


@router.get(
"/{event_group_id}",
responses={
Expand All @@ -51,14 +113,79 @@ async def get_event_group(
return event_group


@router.get(
"/",
responses={
200: {"description": "List of event groups", "model": ListEventGroupsResponse},
},
)
async def list_event_groups(
event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY,
) -> ListEventGroupsResponse:
"""
Get a list of all event groups
"""
groups = await event_group_repository.read_all()
return ListEventGroupsResponse.from_iterable(groups)


# ------------------ ICS-related ------------------- #
@router.put(
"/{event_group_id}/ics",
responses={
201: {"description": ".ics file updated successfully"},
# 304: {"description": ".ics file already exists and content is the same"},
400: {"description": "Path is not defined for this event group"},
403: {"description": "This user can not execute this operation"},
404: {"description": "Event group not found"},
},
)
async def set_event_group_ics(
event_group_id: int,
ics_file: UploadFile,
event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY,
current_user_id: CURRENT_USER_ID_DEPENDENCY,
):
"""
Load .ics file to event group by event group id and save file to predefined path
"""

event_group = await event_group_repository.read(event_group_id)

if event_group is None:
raise EventGroupNotFoundException()

if event_group.path is None:
raise EventGroupWithMissingPath()

owners_and_moderators = {ownership.user_id for ownership in event_group.ownerships}

if current_user_id not in owners_and_moderators:
raise OperationIsNotAllowed()

ics_path = PredefinedRepository.locate_ics_by_path(event_group.path)

# TODO: add validation for ics file(return 400 if file is not valid)
if False:
return JSONResponse(status_code=400, content={"message": "File is not valid"})
# TODO: compare ics files if one already exists and return 200 if they are the same
if False:
return IcsFileIsNotModified()
async with aiofiles.open(ics_path, "wb") as f:
content = await ics_file.read()
await f.write(content)

return JSONResponse(status_code=201, content={"message": "File uploaded successfully"})


@router.get(
"/{event_group_id}/ics",
responses={
200: {"description": ".ics file"},
404: {"description": "Event group not found"},
},
)
async def get_event_group_ics(event_group_id: int, event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY):
async def get_event_group_ics(event_group_id: str, event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY):
"""
Get event group .ics file by id
"""
Expand All @@ -71,52 +198,36 @@ async def get_event_group_ics(event_group_id: int, event_group_repository: EVENT
return FileResponse(ics_path)
else:
# TODO: create ics file on the fly from events connected to event group
raise NotImplementedError()
raise HTTPException(
status_code=501, detail="Can not create .ics file on the fly (set static .ics file for the event group"
)


@router.get(
"/",
"/ics/{event_group_alias}.ics",
responses={
200: {"description": "List of event groups", "model": ListEventGroupsResponse},
200: {"description": ".ics file"},
404: {"description": "Event group not found"},
},
)
async def list_event_groups(
event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY,
) -> ListEventGroupsResponse:
async def get_event_group_ics_by_alias(
event_group_alias: str, event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY
):
"""
Get a list of all event groups
Get event group .ics file by id
"""
groups = await event_group_repository.read_all()
return ListEventGroupsResponse.from_iterable(groups)
event_group = await event_group_repository.read_by_alias(event_group_alias)

if event_group is None:
raise EventGroupNotFoundException()
if event_group.path:
ics_path = PredefinedRepository.locate_ics_by_path(event_group.path)
return FileResponse(ics_path)
else:
# TODO: create ics file on the fly from events connected to event group
raise HTTPException(
status_code=501, detail="Can not create .ics file on the fly (set static .ics file for the event group"
)


# TODO: create event group through form
# class EventGroupUpload(BaseModel):
# path: str = Form(...)
# name: str = Form(...)
# satellite: Json = Form(...)
# ics_file: UploadFile = None
#
# # fix error with Json (pydantic)
# @validator("satellite", pre=True, always=True)
# def _validate_satellite(cls, v):
# if isinstance(v, dict):
# return json.dumps(v)
# return v
# @router.post(
# "/",
# responses={
# 200: {"description": "Event group info", "model": ViewEventGroup},
# 401: {"description": "No credentials provided"},
# 403: {"description": "Could not validate credentials"},
# },
# )
# async def create_event_group(
# _is_verified: IS_VERIFIED_PARSER_DEPENDENCY,
# event_group_repository: EVENT_GROUP_REPOSITORY_DEPENDENCY,
# event_group: EventGroupUpload = Depends()
# ) -> ViewEventGroup:
# """
# Create a new event group if it does not exist
# """
# return await event_group_repository.create_group_if_not_exists(event_group)
# ^^^^^^^^^^^^^ ICS-related ^^^^^^^^^^^^^^^^^^^^^^^^ #

1 comment on commit 03d4d6a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src
   exceptions.py31874%11, 24, 36, 48, 60, 72, 84, 96
   main.py84199%153
src/app/auth
   common.py281739%12–21, 25–32, 36–44, 49–50
   dependencies.py16662%33–38, 42
   jwt.py452936%25–27, 31–35, 39–43, 47–51, 55–63, 67–74
src/app/event_groups
   routes.py785233%26–28, 40–47, 65–69, 87–91, 108–113, 128–129, 153–178, 192–201, 219–228
src/app/users
   routes.py261350%35–37, 56–61, 79–81, 102–107
src/repositories
   crud.py93990%86–91, 126, 139–140
src/repositories/event_groups
   repository.py57296%44–45
src/schemas
   events.py71692%48, 56, 79, 98–100
   tags.py40198%36
src/storages/sql
   storage.py23196%43
TOTAL123714588% 

Tests Skipped Failures Errors Time
47 0 💤 0 ❌ 0 🔥 10.023s ⏱️

Please sign in to comment.