diff --git a/backend/app/api/routes/events.py b/backend/app/api/routes/events.py index f28c734..f4ec92c 100644 --- a/backend/app/api/routes/events.py +++ b/backend/app/api/routes/events.py @@ -16,6 +16,10 @@ from app.models.event import ( EventsPublic, EventUpdate, EventUserLink, + EventTeam, + EventTeamCreate, + EventTeamPublic, + EventTeamsPublic, ) from app.models.user import ( PermissionModule, @@ -231,3 +235,165 @@ def remove_user_from_event( # endregion + + +# region # Event / Teams ####################################################### + +@router.get("/{id}/teams", response_model=EventTeamsPublic, tags=router.tags + [ApiTags.TEAMS]) +def read_event_teams( + session: SessionDep, current_user: CurrentUser, id: RowId, skip: int = 0, limit: int = 100 +) -> Any: + """ + Retrieve event teams from a single event. + """ + + # Event permissions + event = session.get(Event, id) + if not event: + raise HTTPException(status_code=404, detail="Event not found") + + if not current_user.has_permission( + module=PermissionModule.EVENT, + part=PermissionPart.ADMIN, + rights=(PermissionRight.READ | PermissionRight.MANGE_TEAMS), + ) and ( event and (event.user_has_right(user=current_user, rights=(PermissionRight.READ | PermissionRight.MANGE_TEAMS)))): + raise HTTPException(status_code=400, detail="Not enough permissions") + + # Get list + count_statement = ( + select(func.count()) + .select_from(EventTeam) + .where(EventTeam.event_id == id) + ) + count = session.exec(count_statement).one() + statement = ( + select(EventTeam) + .where(EventTeam.event_id == id) + .offset(skip) + .limit(limit) + ) + event_teams = session.exec(statement).all() + + return EventTeamsPublic(data=event_teams, count=count) + + +@router.post("/{id}/teams", response_model=EventTeamPublic, tags=router.tags + [ApiTags.TEAMS]) +def create_event_team( + *, session: SessionDep, current_user: CurrentUser, id: RowId, event_team_in: EventTeamCreate +) -> Any: + """ + Create new team inside event. + """ + + event = session.get(Event, id) + + if not current_user.has_permission( + module=PermissionModule.EVENT, + part=PermissionPart.ADMIN, + rights=PermissionRight.MANGE_TEAMS, + ) and ( event and (event.user_has_rights(user=current_user, rights=PermissionRight.MANGE_TEAMS))): + raise HTTPException(status_code=400, detail="Not enough permissions") + + event_team = EventTeam.create(create_obj=event_team_in, event=event, session=session) + return event_team + + +@router.get("/teams", response_model=EventTeamsPublic, tags=router.tags + [ApiTags.TEAMS]) +def read_event_teams( + session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100 +) -> Any: + """ + Retrieve all event teams. + """ + + if not current_user.has_permission( + module=PermissionModule.EVENT, + part=PermissionPart.ADMIN, + rights=PermissionRight.MANGE_TEAMS, + ): + raise HTTPException(status_code=400, detail="Not enough permissions") + + # Get list + count_statement = ( + select(func.count()) + .select_from(EventTeam) + ) + count = session.exec(count_statement).one() + statement = ( + select(EventTeam) + .offset(skip) + .limit(limit) + ) + event_teams = session.exec(statement).all() + + return EventTeamsPublic(data=event_teams, count=count) + + +@router.get("/teams/{id}", response_model=EventTeamPublic, tags=router.tags + [ApiTags.TEAMS]) +def read_event_team(session: SessionDep, current_user: CurrentUser, id: RowId) -> Any: + """ + Get event team by ID. + """ + event_team = session.get(EventTeam, id) + if not event_team: + raise HTTPException(status_code=404, detail="Event team not found") + + event = session.get(Event, event_team.event_id) + + if not current_user.has_permission( + module=PermissionModule.EVENT, + part=PermissionPart.ADMIN, + rights=PermissionRight.MANGE_TEAMS, + ) and ( event and (event.user_has_rights(user=current_user, rights=PermissionRight.MANGE_TEAMS))): + raise HTTPException(status_code=400, detail="Not enough permissions") + + return event_team + + +@router.put("/teams/{id}", response_model=EventTeamPublic, tags=router.tags + [ApiTags.TEAMS]) +def create_event_team( + *, session: SessionDep, current_user: CurrentUser, id: RowId, event_team_in: EventTeamCreate +) -> Any: + """ + Update team. + """ + event_team = session.get(EventTeam, id) + if not event_team: + raise HTTPException(status_code=404, detail="Event team not found") + + event = session.get(Event, event_team.event_id) + + if not current_user.has_permission( + module=PermissionModule.EVENT, + part=PermissionPart.ADMIN, + rights=PermissionRight.MANGE_TEAMS, + ) and ( event and (event.user_has_rights(user=current_user, rights=PermissionRight.MANGE_TEAMS))): + raise HTTPException(status_code=400, detail="Not enough permissions") + + event_team = EventTeam.update(db_obj=event_team, in_obj=event_team_in, session=session) + return event_team + + +@router.delete("/teams/{id}", tags=router.tags + [ApiTags.TEAMS]) +def delete_event_team(session: SessionDep,current_user: CurrentUser, id: RowId) -> Message: + """ + Delete an event team. + """ + event_team = session.get(EventTeam, id) + if not event_team: + raise HTTPException(status_code=404, detail="Event team not found") + + event = session.get(Event, event_team.event_id) + + if not current_user.has_permission( + module=PermissionModule.EVENT, + part=PermissionPart.ADMIN, + rights=PermissionRight.MANGE_TEAMS, + ) and (event.user_has_rights(user=current_user, rights=PermissionRight.MANGE_TEAMS)): + raise HTTPException(status_code=400, detail="Not enough permissions") + + session.delete(event_team) + session.commit() + return Message(message="Event team deleted successfully") + +# endregion \ No newline at end of file diff --git a/backend/app/models/event.py b/backend/app/models/event.py index 91be399..23b030c 100644 --- a/backend/app/models/event.py +++ b/backend/app/models/event.py @@ -159,3 +159,70 @@ class EventsPublic(BaseSQLModel): # endregion + +# region EventTeam ############################################################ + + +class EventTeamBase(mixin.ThemeName, mixin.CheckInCheckOut, mixin.Canceled, BaseSQLModel): + # scouting_team_id: RowId | None = Field( + # foreign_key="ScoutingTeam.id", nullable=False, ondelete="CASCADE" + # ) + pass + + +# Properties to receive via API on creation +class EventTeamCreate(EventTeamBase): + pass + + +# Properties to receive via API on update, all are optional +class EventTeamUpdate(mixin.ThemeNameUpdate, EventTeamBase): + pass + + +class EventTeam(mixin.RowId, EventTeamBase, table=True): + # --- database only items -------------------------------------------------- + + # --- read only items ------------------------------------------------------ + event_id: RowId = Field( + foreign_key="event.id", nullable=False, ondelete="CASCADE" + ) + + # --- back_populates links ------------------------------------------------- + event: "Event" = Relationship(back_populates="team_links", cascade_delete=True) + # team: "ScoutingTeam" = Relationship(back_populates="event_links", cascade_delete=True) + + # --- CRUD actions --------------------------------------------------------- + @classmethod + def create(cls, *, session: Session, create_obj: EventTeamCreate, event: Event) -> "EventTeam": + data_obj = create_obj.model_dump(exclude_unset=True) + + db_obj = cls.model_validate(data_obj, update={"event_id": event.id}) + session.add(db_obj) + session.commit() + session.refresh(db_obj) + return db_obj + + @classmethod + def update( + cls, *, session: Session, db_obj: "EventTeam", in_obj: EventTeamUpdate + ) -> "EventTeam": + data_obj = in_obj.model_dump(exclude_unset=True) + db_obj.sqlmodel_update(data_obj) + session.add(db_obj) + session.commit() + session.refresh(db_obj) + return db_obj + + +# Properties to return via API, id is always required +class EventTeamPublic(mixin.RowIdPublic, EventTeamBase): + event_id: RowId + + +class EventTeamsPublic(BaseSQLModel): + data: list[EventTeamPublic] + count: int + + +# endregion \ No newline at end of file diff --git a/backend/app/models/mixin.py b/backend/app/models/mixin.py index ff9b335..15d3212 100644 --- a/backend/app/models/mixin.py +++ b/backend/app/models/mixin.py @@ -17,6 +17,14 @@ class FullName(BaseModel): full_name: str | None = Field(default=None, nullable=True, max_length=255) +class ThemeName(BaseModel): + theme_name: str = Field(index=True, max_length=255) + + +class ThemeNameUpdate(ThemeName): + theme_name: str | None = Field(default=None, max_length=255) + + class Contact(BaseModel): contact: str | None = Field(default=None, nullable=True, max_length=255) @@ -76,3 +84,10 @@ class Description(BaseModel): class StartEndDate: start_at: datetime | None = Field(default=None, nullable=True) end_at: datetime | None = Field(default=None, nullable=True) + + +class Canceled(BaseModel): + canceled_at: datetime | None = Field(default=None, nullable=True) + canceled_reason: str | None = Field(default=None, nullable=True, max_length=1024) + + diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 31aed7e..709f047 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -40,6 +40,7 @@ class PermissionRight(DocumentedIntFlag): DELETE = auto_enum() MANAGE_USERS = auto_enum() + MANGE_TEAMS = auto_enum() ADMIN = CREATE | READ | UPDATE | DELETE | MANAGE_USERS