diff --git a/api_schemas/event_schemas.py b/api_schemas/event_schemas.py index 3b21e5c..b767c1e 100644 --- a/api_schemas/event_schemas.py +++ b/api_schemas/event_schemas.py @@ -3,7 +3,7 @@ from api_schemas.event_signup_schemas import EventSignupRead from db_models.priority_model import Priority_DB from helpers.constants import MAX_EVENT_DESC, MAX_EVENT_TITLE -from helpers.types import MEMBER_ROLES, datetime_utc +from helpers.types import ALCOHOL_EVENT_TYPES, EVENT_DOT_TYPES, MEMBER_ROLES, datetime_utc from pydantic import StringConstraints if TYPE_CHECKING: @@ -26,15 +26,18 @@ class EventRead(BaseSchema): max_event_users: int priorities: list[Priority_DB] all_day: bool - signup_not_opened_yet: bool recurring: bool - drink: bool food: bool - cash: bool closed: bool can_signup: bool drink_package: bool is_nollning_event: bool + alcohol_event_type: str + dress_code: str + price: int + signup_count: int + dot: str + lottery: bool # we dont need to be as strict about out data as in data. @@ -55,15 +58,17 @@ class EventCreate(BaseSchema): max_event_users: int priorities: list[MEMBER_ROLES] all_day: bool - signup_not_opened_yet: bool recurring: bool - drink: bool food: bool - cash: bool closed: bool can_signup: bool drink_package: bool is_nollning_event: bool + alcohol_event_type: ALCOHOL_EVENT_TYPES + dress_code: str + price: int + dot: EVENT_DOT_TYPES + lottery: bool class EventUpdate(BaseSchema): @@ -76,18 +81,19 @@ class EventUpdate(BaseSchema): title_en: Annotated[str, StringConstraints(max_length=MAX_EVENT_TITLE)] | None = None description_sv: Annotated[str, StringConstraints(max_length=MAX_EVENT_DESC)] | None = None description_en: Annotated[str, StringConstraints(max_length=MAX_EVENT_DESC)] | None = None - location: str + location: str | None = None max_event_users: int | None = None all_day: bool | None = None - signup_not_opened_yet: bool | None = None recurring: bool | None = None - drink: bool | None = None food: bool | None = None - cash: bool | None = None closed: bool | None = None can_signup: bool | None = None drink_package: bool | None = None is_nollning_event: bool | None = None + alcohol_event_type: ALCOHOL_EVENT_TYPES | None = None + dresscode: str | None = None + price: int | None = None + dot: EVENT_DOT_TYPES | None = None class AddEventTag(BaseSchema): diff --git a/api_schemas/event_signup_schemas.py b/api_schemas/event_signup_schemas.py index 05b1bc1..95b6167 100644 --- a/api_schemas/event_signup_schemas.py +++ b/api_schemas/event_signup_schemas.py @@ -18,6 +18,7 @@ class EventSignupRead(BaseSchema): priority: str group_name: str drinkPackage: DRINK_PACKAGES + confirmed_status: bool class EventSignupUpdate(BaseSchema): @@ -27,5 +28,5 @@ class EventSignupUpdate(BaseSchema): drinkPackage: DRINK_PACKAGES | None = None -class EventSignupDelete(BaseSchema): - user_id: int | None = None +# class EventSignupDelete(BaseSchema): +# user_id: int diff --git a/api_schemas/group_mission_schema.py b/api_schemas/group_mission_schema.py index 6fef50d..862d21a 100644 --- a/api_schemas/group_mission_schema.py +++ b/api_schemas/group_mission_schema.py @@ -13,11 +13,13 @@ class GroupMissionCreate(BaseSchema): class GroupMissionEdit(BaseSchema): - points: int - adventure_mission_id: int + points: int | None = None + adventure_mission_id: int | None = None + is_accepted: bool | None = None class GroupMissionRead(BaseSchema): points: int adventure_mission: AdventureMissionRead nollning_group: NollningGroupRead + is_accepted: bool diff --git a/db_models/event_model.py b/db_models/event_model.py index 38aa79d..2fbfc50 100644 --- a/db_models/event_model.py +++ b/db_models/event_model.py @@ -1,6 +1,6 @@ -from helpers.types import datetime_utc +from helpers.types import ALCOHOL_EVENT_TYPES, EVENT_DOT_TYPES, datetime_utc from typing import TYPE_CHECKING -from sqlalchemy import ForeignKey, String +from sqlalchemy import ForeignKey, String, Enum from sqlalchemy.orm import mapped_column, Mapped, relationship from helpers.constants import MAX_EVENT_DESC, MAX_EVENT_LOCATION, MAX_EVENT_TITLE from .base_model import BaseModel_DB @@ -35,6 +35,14 @@ class Event_DB(BaseModel_DB): location: Mapped[str] = mapped_column(String(MAX_EVENT_LOCATION)) + dress_code: Mapped[str] = mapped_column(String(MAX_EVENT_TITLE)) + + price: Mapped[int] = mapped_column() + + signup_count: Mapped[int] = mapped_column(init=False, default=0) + + alcohol_event_type: Mapped[ALCOHOL_EVENT_TYPES] = mapped_column() + max_event_users: Mapped[int] = mapped_column(default=0) event_users: Mapped[list["EventUser_DB"]] = relationship( @@ -50,11 +58,8 @@ class Event_DB(BaseModel_DB): ) all_day: Mapped[bool] = mapped_column(default=False) - signup_not_opened_yet: Mapped[bool] = mapped_column(default=True) recurring: Mapped[bool] = mapped_column(default=False) - drink: Mapped[bool] = mapped_column(default=False) food: Mapped[bool] = mapped_column(default=False) - cash: Mapped[bool] = mapped_column(default=False) closed: Mapped[bool] = mapped_column(default=False) can_signup: Mapped[bool] = mapped_column(default=False) drink_package: Mapped[bool] = mapped_column(default=False) @@ -64,3 +69,7 @@ class Event_DB(BaseModel_DB): ) is_nollning_event: Mapped[bool] = mapped_column(default=False) + + dot: Mapped[EVENT_DOT_TYPES] = mapped_column(default="None") + + lottery: Mapped[bool] = mapped_column(default=False) diff --git a/db_models/event_user_model.py b/db_models/event_user_model.py index 31ffa2d..b84b801 100644 --- a/db_models/event_user_model.py +++ b/db_models/event_user_model.py @@ -1,5 +1,5 @@ from typing import TYPE_CHECKING, Optional -from sqlalchemy import Column, DateTime, ForeignKey +from sqlalchemy import Column, DateTime, Enum, ForeignKey # from helpers.types import MEMBER_TYPE from .base_model import BaseModel_DB @@ -22,6 +22,10 @@ class EventUser_DB(BaseModel_DB): event: Mapped["Event_DB"] = relationship(back_populates="event_users") event_id: Mapped[int] = mapped_column(ForeignKey("event_table.id"), primary_key=True) + confirmed_status: Mapped[str] = mapped_column( + Enum("confirmed", "unconfirmed", name="confirmed_enum"), default="unconfirmed" + ) + group_name: Mapped[Optional[str]] = mapped_column(default=None) priority: Mapped[str] = mapped_column(default="Övrigt") drinkPackage: Mapped[str] = mapped_column(default="None") diff --git a/db_models/group_mission_model.py b/db_models/group_mission_model.py index 70c1c6f..91fbfd3 100644 --- a/db_models/group_mission_model.py +++ b/db_models/group_mission_model.py @@ -30,3 +30,5 @@ class GroupMission_DB(BaseModel_DB): nollning_group: Mapped["NollningGroup_DB"] = relationship(back_populates="group_missions") nollning_group_id: Mapped[int] = mapped_column(ForeignKey("nollning_group_table.id"), primary_key=True) + + is_accepted: Mapped[bool] = mapped_column(default=False, init=False) diff --git a/helpers/types.py b/helpers/types.py index b1f8997..d8b9e05 100644 --- a/helpers/types.py +++ b/helpers/types.py @@ -66,3 +66,7 @@ def force_utc(date: datetime): FOOD_PREFERENCES = Literal["Vegetarian", "Vegan", "Pescetarian", "Mjölkallergi", "Gluten"] DRINK_PACKAGES = Literal["None", "AlcoholFree", "Alcohol"] + +ALCOHOL_EVENT_TYPES = Literal["Alcohol", "Alcohol-Served", "None"] + +EVENT_DOT_TYPES = Literal["None", "Single", "Double"] diff --git a/routes/event_router.py b/routes/event_router.py index 17c0172..5693589 100644 --- a/routes/event_router.py +++ b/routes/event_router.py @@ -110,6 +110,32 @@ def get_random_event_signup(event_id: int, db: DB_dependency): return users +@event_router.patch( + "/event-confirm-event-users/{event_id}", + dependencies=[Permission.require("manage", "Event")], + response_model=EventRead, +) +def confirm_event_users(db: DB_dependency, event_id: int, confirmed_users: list[UserRead]): + event = db.query(Event_DB).filter_by(id=event_id).one_or_none() + + if not event: + raise HTTPException(404, detail="Event not found") + + if len(confirmed_users) > event.max_event_users: + raise HTTPException(400, detail="Too many users for chosen event") + + confirmed_user_ids = [user.id for user in confirmed_users] + + for event_user in event.event_users: + if event_user.user_id in confirmed_user_ids: + event_user.confirmed_status = "confirmed" + + db.commit() + db.refresh(event) + + return event + + @event_router.post("/add-tag", dependencies=[Permission.require("manage", "Event")], response_model=AddEventTag) def add_tag_to_event(data: AddEventTag, db: DB_dependency): diff --git a/routes/event_signup_router.py b/routes/event_signup_router.py index 9874ec1..8c6ca36 100644 --- a/routes/event_signup_router.py +++ b/routes/event_signup_router.py @@ -5,9 +5,9 @@ from db_models.event_model import Event_DB from db_models.event_user_model import EventUser_DB from db_models.user_model import User_DB -from services.event_signup_service import signup_to_event, signoff_from_event, update_event_signup +from services.event_signup_service import signup_to_event, signoff_from_event, update_event_signup, check_me_signup from user.permission import Permission -from api_schemas.event_signup_schemas import EventSignupCreate, EventSignupRead, EventSignupUpdate, EventSignupDelete +from api_schemas.event_signup_schemas import EventSignupCreate, EventSignupRead, EventSignupUpdate from pydantic_extra_types.phone_numbers import PhoneNumber from api_schemas.event_schemas import EventRead @@ -43,7 +43,7 @@ def event_signup_route( @event_signup_router.delete("/{event_id}", response_model=EventSignupRead) def event_signoff_route( event_id: int, - data: EventSignupDelete, + user_id: int, me: Annotated[User_DB, Permission.member()], manage_permission: Annotated[bool, Permission.check("manage", "Event")], db: DB_dependency, @@ -52,13 +52,13 @@ def event_signoff_route( if event is None: raise HTTPException(status.HTTP_404_NOT_FOUND) - if data.user_id is None or data.user_id == me.id: + if user_id == me.id: return signoff_from_event(event, me.id, manage_permission, db) if manage_permission == False: raise HTTPException(status.HTTP_400_BAD_REQUEST, "Check your permissions mate") - return signoff_from_event(event, data.user_id, manage_permission, db) + return signoff_from_event(event, user_id, manage_permission, db) @event_signup_router.patch("/{event_id}", response_model=EventSignupRead) @@ -82,6 +82,15 @@ def update_event_signup_route( return update_event_signup(event, data, data.user_id, manage_permission, db) +@event_signup_router.get("/me-signup/{event_id}", response_model=EventSignupRead) +def get_me_event_signup(event_id: int, me: Annotated[User_DB, Permission.member()], db: DB_dependency): + event = db.query(Event_DB).filter_by(id=event_id).one_or_none() + if event is None: + raise HTTPException(404, detail="Event not found") + + return check_me_signup(event_id, me, db) + + # @event_signup_router.get("/{event_id}", response_model=list[EventSignupRead]) # def get_all_signups(event_id: int, db: DB_dependency): # signups = db.query(EventUser_DB).filter(EventUser_DB.event_id == event_id).all() diff --git a/routes/nollning_router.py b/routes/nollning_router.py index 8c84e43..332ec27 100644 --- a/routes/nollning_router.py +++ b/routes/nollning_router.py @@ -1,5 +1,6 @@ -from fastapi import APIRouter -from sqlalchemy import desc +from fastapi import APIRouter, HTTPException +from sqlalchemy import and_, desc +from api_schemas.group_schema import GroupRead from api_schemas.nollning_schema import ( NollningCreate, NollningRead, @@ -9,6 +10,8 @@ ) from database import DB_dependency from db_models.nollning_model import Nollning_DB +from db_models.nollning_group_model import NollningGroup_DB +from db_models.group_mission_model import GroupMission_DB from services.nollning_service import ( add_g_to_nollning, create_nollning, @@ -70,3 +73,25 @@ def get_all_nollning_groups(db: DB_dependency, id: int): ) def delete_group_mission(db: DB_dependency, id: int, data: NollningDeleteMission): return delete_group_m(db, id, data) + + +@nollning_router.delete( + "/remove_group/{nollning_group_id}", + dependencies=[Permission.require("manage", "Nollning")], + response_model=NollningGroupRead, +) +def remove_group_from_nollning(db: DB_dependency, nollning_group_id: int): + nollning_group = db.query(NollningGroup_DB).filter_by(id=nollning_group_id).one_or_none() + + if not nollning_group: + raise HTTPException(404, detail="Group not in nollning") + + completed_missions = db.query(GroupMission_DB).filter_by(nollning_group_id=nollning_group.id).all() + + if completed_missions: + raise HTTPException(409, detail="Group has missions in nollning and therefore cannot be unlinked") + + db.delete(nollning_group) + db.commit() + + return nollning_group diff --git a/seed.py b/seed.py index fb0de14..c33878d 100644 --- a/seed.py +++ b/seed.py @@ -233,6 +233,11 @@ def seed_events(db: Session, one_council: Council_DB): signup_start=signup_start, signup_end=signup_end, location="Mattehuset", + dress_code="vad du vill", + price=123, + alcohol_event_type="Alcohol", + dot="Double", + lottery=False, ) db.add(event) db.commit() diff --git a/services/adventure_mission_service.py b/services/adventure_mission_service.py index 3dc7739..5d2fe95 100644 --- a/services/adventure_mission_service.py +++ b/services/adventure_mission_service.py @@ -25,8 +25,8 @@ def create_adventure_mission(db: Session, data: AdventureMissionCreate): if data.max_points < 1: raise HTTPException(400, detail="Max points has to be atleast 1") - if data.min_points < 1: - raise HTTPException(400, detail="Min points has to be atleast 1") + if data.min_points < 0: + raise HTTPException(400, detail="Min points has to be atleast 0") new_adventure_mission = AdventureMission_DB( nollning_id=data.nollning_id, diff --git a/services/event_service.py b/services/event_service.py index ba1c2b5..dc4909f 100644 --- a/services/event_service.py +++ b/services/event_service.py @@ -20,11 +20,17 @@ def create_new_event(data: EventCreate, db: Session): if start < datetime.now(UTC): raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Event start cannot be in the past, silly.") + if data.price < 0: + raise HTTPException(400, detail="price cannot be lower than 0") + # Check if council exists. It's just some extra validation council = db.query(Council_DB).filter_by(id=data.council_id).one_or_none() if council is None: raise HTTPException(status.HTTP_404_NOT_FOUND, "That council does not exist") + if data.price < 0: + raise HTTPException(400, detail="Price cannot be lower than 0") + event = Event_DB( starts_at=start, ends_at=end, @@ -37,16 +43,18 @@ def create_new_event(data: EventCreate, db: Session): signup_end=signup_end, max_event_users=data.max_event_users, all_day=data.all_day, - signup_not_opened_yet=data.signup_not_opened_yet, recurring=data.recurring, - drink=data.drink, food=data.food, - cash=data.cash, closed=data.closed, can_signup=data.can_signup, drink_package=data.drink_package, location=data.location, is_nollning_event=data.is_nollning_event, + dress_code=data.dress_code, + price=data.price, + alcohol_event_type=data.alcohol_event_type, + dot=data.dot, + lottery=data.lottery, ) db.add(event) # This adds the event itself to the session db.flush() # This is optional but can be helpful to ensure 'event.id' is set if used immediately after @@ -79,7 +87,10 @@ def update_event(event_id: int, data: EventUpdate, db: Session): event = db.query(Event_DB).filter_by(id=event_id).one_or_none() if not event: - raise HTTPException(status.HTTP_404_NOT_FOUND) + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Event not found") + + if data.price is not None and data.price < 0: + raise HTTPException(400, detail="Price cannot be lower than 0") for var, value in vars(data).items(): setattr(event, var, value) if value is not None else None diff --git a/services/event_signup_service.py b/services/event_signup_service.py index 74cfc39..e407c4d 100644 --- a/services/event_signup_service.py +++ b/services/event_signup_service.py @@ -10,7 +10,14 @@ def signup_to_event(event: Event_DB, user: User_DB, data: EventSignupCreate, manage_permission: bool, db: Session): now = datetime.now(UTC) - if event.signup_end < now and manage_permission == False: + + if (event.closed) and (manage_permission == False): + raise HTTPException(400, detail="Event is closed") + + if (event.signup_start > now) and (manage_permission == False): + raise HTTPException(400, detail="Event signup has not opened yet") + + if (event.signup_end < now) and (manage_permission == False): raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Event signup deadline is passed") if ( @@ -25,8 +32,15 @@ def signup_to_event(event: Event_DB, user: User_DB, data: EventSignupCreate, man for var, value in vars(data).items(): setattr(signup, var, value) if value else None + if event.lottery == False: + signup.confirmed_status = "confirmed" + db.add(signup) + + event.signup_count += 1 + db.commit() + return signup @@ -44,6 +58,9 @@ def signoff_from_event( raise HTTPException(status.HTTP_404_NOT_FOUND) db.delete(signup) + + event.signup_count -= 1 + db.commit() return signup @@ -62,3 +79,12 @@ def update_event_signup(event: Event_DB, data: EventSignupUpdate, user_id: int, db.commit() db.refresh(event) return signup + + +def check_me_signup(event_id: int, me: User_DB, db: Session): + signup = db.query(EventUser_DB).filter_by(user_id=me.id, event_id=event_id).one_or_none() + + if not signup: + raise HTTPException(404, detail="Signup not found") + + return signup