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
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
e4e7497b3e
commit
0949a6c2ad
116
backend/app.py
116
backend/app.py
@ -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)):
|
||||||
|
Loading…
Reference in New Issue
Block a user