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