Implement extra field for Guests and created timestamp for Parties, Add documentation for errors
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Kai Vogelgesang 2022-10-09 13:03:21 +02:00
parent e4e7497b3e
commit 0949a6c2ad
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879

View File

@ -1,3 +1,4 @@
from datetime import datetime
from typing import Literal
from fastapi import FastAPI, HTTPException, status, Depends
from pydantic import BaseModel
@ -7,6 +8,7 @@ from .db import MongoModel, PyObjectId, client
from .settings import settings
db = client["party"]
meta = client["party-meta"]
description = """
Party party \U0001F973
@ -44,25 +46,44 @@ Coming = Literal["yes", "no", "maybe"]
GrammaticalGender = Literal["m", "f", "d"]
class Guest(MongoModel):
class HTTPError(BaseModel):
detail: str
# error_responses = {status.HTTP_401_UNAUTHORIZED: {"model": HTTPError}}
def error_responses(*args):
return {arg: {"model": HTTPError} for arg in args}
class Guest(BaseModel):
token: str
name: str
coming: Coming | None
grammatical_gender: GrammaticalGender
extra: dict[str, str]
async def find_guest(party: str, token: str) -> Guest:
class DBGuest(Guest, MongoModel):
pass
async def find_guest(party: str, token: str) -> DBGuest:
guest = await db[party].find_one({"token": token})
if not guest:
raise HTTPException(status.HTTP_401_UNAUTHORIZED)
return Guest.parse_obj(guest)
return DBGuest.parse_obj(guest)
# Guest methods
@app.get("/{party}/{token}/me", response_model=Guest, tags=["guests"])
async def get_self(guest: Guest = Depends(find_guest)):
@app.get(
"/{party}/{token}/me",
response_model=Guest,
responses=error_responses(401),
tags=["guests"],
)
async def get_self(guest: DBGuest = Depends(find_guest)):
return guest
@ -70,13 +91,19 @@ class GuestUpdate(BaseModel):
coming: Coming
@app.patch("/{party}/{token}/me", tags=["guests"])
@app.patch(
"/{party}/{token}/me",
response_model=Guest,
responses=error_responses(401),
tags=["guests"],
)
async def update_self(
party: str, update: GuestUpdate, guest: Guest = Depends(find_guest)
party: str, update: GuestUpdate, guest: DBGuest = Depends(find_guest)
):
guest_dict = guest.dict()
guest_dict = guest.dict(exclude={"id"})
guest_dict.update(update.dict())
await db[party].replace_one({"_id": guest.id}, guest_dict)
return await db[party].find_one({"_id": guest.id})
class PartyStatus(BaseModel):
@ -84,7 +111,12 @@ class PartyStatus(BaseModel):
maybe_coming: int
@app.get("/{party}/{token}/status", response_model=PartyStatus, tags=["guests"])
@app.get(
"/{party}/{token}/status",
response_model=PartyStatus,
responses=error_responses(401),
tags=["guests"],
)
async def get_party_status(party: str, _=Depends(find_guest)):
definitely_coming = await db[party].count_documents({"coming": "yes"})
maybe_coming = await db[party].count_documents({"coming": "maybe"})
@ -103,17 +135,32 @@ async def auth_admin(admin_token: str):
raise HTTPException(status.HTTP_401_UNAUTHORIZED)
@app.get("/{admin_token}", response_model=list[str], tags=["admin"])
class Party(MongoModel):
name: str
created: datetime
@app.get(
"/{admin_token}",
response_model=list[Party],
responses=error_responses(401),
tags=["admin"],
)
async def list_parties(_=Depends(auth_admin)):
filter = {"name": {"$regex": r"^(?!system\.)"}}
return await db.list_collection_names(filter=filter)
return await meta["parties"].find().to_list(None)
class PartyCreate(BaseModel):
name: str
@app.post("/{admin_token}", status_code=status.HTTP_204_NO_CONTENT, tags=["admin"])
@app.post(
"/{admin_token}",
response_model=Party,
status_code=status.HTTP_201_CREATED,
responses=error_responses(400, 401),
tags=["admin"],
)
async def create_party(party: PartyCreate, _=Depends(auth_admin)):
try:
await db.create_collection(party.name)
@ -121,17 +168,37 @@ 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(),
}
)
return await meta["parties"].find_one({"_id": inserted.inserted_id})
@app.delete(
"/{admin_token}/{party}", status_code=status.HTTP_204_NO_CONTENT, tags=["admin"]
"/{admin_token}/{party}",
status_code=status.HTTP_204_NO_CONTENT,
responses=error_responses(401, 404),
tags=["admin"],
)
async def delete_party(party: str, _=Depends(auth_admin)):
deleted = await meta["parties"].delete_one({"name": party})
if deleted.deleted_count < 1:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
await db.drop_collection(party)
@app.get("/{admin_token}/{party}", response_model=list[Guest], tags=["admin"])
@app.get(
"/{admin_token}/{party}",
response_model=list[DBGuest],
responses=error_responses(401, 404),
tags=["admin"],
)
async def list_guests(party: str, _=Depends(auth_admin)):
if not await meta["parties"].find_one({"name": party}):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return await db[party].find().to_list(None)
@ -140,15 +207,20 @@ class GuestCreate(BaseModel):
name: str
coming: Coming | None
grammatical_gender: GrammaticalGender
extra: dict[str, str] = dict()
@app.post(
"/{admin_token}/{party}",
response_model=Guest,
response_model=DBGuest,
status_code=status.HTTP_201_CREATED,
responses=error_responses(400, 401, 404),
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)
existing = await db[party].find_one({"token": new_guest.token})
if existing:
raise HTTPException(
@ -165,12 +237,21 @@ class GuestModify(BaseModel):
name: str | None
coming: Coming | None
grammatical_gender: GrammaticalGender | None
extra: dict[str, str] | None
@app.patch("/{admin_token}/{party}/{id}", response_model=Guest, tags=["admin"])
@app.patch(
"/{admin_token}/{party}/{id}",
response_model=DBGuest,
responses=error_responses(401, 404),
tags=["admin"],
)
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)
existing = await db[party].find_one({"_id": id})
if not existing:
raise HTTPException(status.HTTP_404_NOT_FOUND)
@ -184,6 +265,7 @@ async def modify_guest(
@app.delete(
"/{admin_token}/{party}/{id}",
status_code=status.HTTP_204_NO_CONTENT,
responses=error_responses(401, 404),
tags=["admin"],
)
async def delete_guest(party: str, id: PyObjectId, _=Depends(auth_admin)):