diff --git a/backend/app.py b/backend/app.py index d4545d2..43ea8bd 100644 --- a/backend/app.py +++ b/backend/app.py @@ -74,6 +74,23 @@ async def find_guest(party: str, token: str) -> DBGuest: return DBGuest.parse_obj(guest) +class Party(MongoModel): + name: str + created: datetime + allowed_extra: list[str] + + +async def find_party(name: str) -> Party: + party = await meta["parties"].find_one({"name": name}) + if not party: + raise HTTPException(status.HTTP_404_NOT_FOUND) + return Party.parse_obj(party) + + +def validate_extra(extra: dict[str, str], party: Party): + return all(k in party.allowed_extra and len(v) <= 64 for (k, v) in extra.items()) + + # Guest methods @@ -88,7 +105,8 @@ async def get_self(guest: DBGuest = Depends(find_guest)): class GuestUpdate(BaseModel): - coming: Coming + coming: Coming | None + extra: dict[str, str] | None @app.patch( @@ -100,8 +118,31 @@ class GuestUpdate(BaseModel): async def update_self( party: str, update: GuestUpdate, guest: DBGuest = Depends(find_guest) ): + try: + party_obj = await find_party(party) + except HTTPException: + # should not happen since find_guest in Depends already + # implies that the party/token combo is correct + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) + guest_dict = guest.dict(exclude={"id"}) - guest_dict.update(update.dict()) + update_dict = update.dict(exclude_unset=True) + + if "extra" in update_dict: + if not validate_extra(update_dict["extra"], party_obj): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) + + # overwrite allowed extra, but keep those that are not allowed/user-modifiable + update_dict["extra"].update( + { + k: v + for (k, v) in guest_dict["extra"].items() + if k not in party_obj.allowed_extra + } + ) + + guest_dict.update(update_dict) + await db[party].replace_one({"_id": guest.id}, guest_dict) return await db[party].find_one({"_id": guest.id}) @@ -135,11 +176,6 @@ async def auth_admin(admin_token: str): raise HTTPException(status.HTTP_401_UNAUTHORIZED) -class Party(MongoModel): - name: str - created: datetime - - @app.get( "/{admin_token}", response_model=list[Party], @@ -152,6 +188,7 @@ async def list_parties(_=Depends(auth_admin)): class PartyCreate(BaseModel): name: str + allowed_extra: list[str] = [] @app.post( @@ -168,12 +205,11 @@ async def create_party(party: PartyCreate, _=Depends(auth_admin)): raise HTTPException( status.HTTP_400_BAD_REQUEST, f"Party {party.name!r} already exists" ) - inserted = await meta["parties"].insert_one( - { - "name": party.name, - "created": datetime.now(), - } - ) + + party_dict = party.dict() + party_dict.update({"created": datetime.now()}) + + inserted = await meta["parties"].insert_one(party_dict) return await meta["parties"].find_one({"_id": inserted.inserted_id}) @@ -218,8 +254,7 @@ class GuestCreate(BaseModel): tags=["admin"], ) async def create_new_guest(party: str, new_guest: GuestCreate, _=Depends(auth_admin)): - if not await meta["parties"].find_one({"name": party}): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + await find_party(party) existing = await db[party].find_one({"token": new_guest.token}) if existing: @@ -232,6 +267,31 @@ async def create_new_guest(party: str, new_guest: GuestCreate, _=Depends(auth_ad return inserted +@app.get( + "/{admin_token}/{party}/userAllowedExtra", + response_model=list[str], + responses=error_responses(401, 404), + tags=["admin"], +) +async def get_allowed_extra_keys(party: str, _=Depends(auth_admin)): + party_obj = await find_party(party) + return party_obj.allowed_extra + + +@app.patch( + "/{admin_token}/{party}/userAllowedExtra", + response_model=Party, + responses=error_responses(401, 404), + tags=["admin"], +) +async def modify_allowed_extra_keys(party: str, keys: list[str], _=Depends(auth_admin)): + party_obj = await find_party(party) + party_dict = party_obj.dict(exclude={"id"}) + party_dict["allowed_extra"] = keys + await meta["parties"].replace_one({"_id": party_obj.id}, party_dict) + return await meta["parties"].find_one({"_id": party_obj.id}) + + class GuestModify(BaseModel): token: str | None name: str | None @@ -249,8 +309,7 @@ class GuestModify(BaseModel): async def modify_guest( party: str, id: PyObjectId, modified_guest: GuestModify, _=Depends(auth_admin) ): - if not await meta["parties"].find_one({"name": party}): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) + await find_party(party) existing = await db[party].find_one({"_id": id}) if not existing: