app.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import os
  2. import datetime
  3. import itertools
  4. from typing import List, Union
  5. from enum import Enum
  6. from fastapi import FastAPI, status, Response
  7. from fastapi.middleware.cors import CORSMiddleware
  8. from pydantic import BaseModel
  9. import aiosqlite
  10. DB_FILE = os.environ.get("DB_LOCATION", "vacation.db")
  11. LAST_UPDATE = datetime.datetime.now()
  12. app = FastAPI()
  13. app.add_middleware(
  14. CORSMiddleware,
  15. allow_origins=["*"],
  16. allow_credentials=False,
  17. allow_methods=["*"],
  18. allow_headers=["*"],
  19. )
  20. def connect():
  21. return aiosqlite.connect(DB_FILE)
  22. @app.on_event("startup")
  23. async def setup():
  24. async with connect() as db:
  25. await db.execute("CREATE TABLE IF NOT EXISTS availability ( \
  26. name TEXT NOT NULL, \
  27. month INTEGER NOT NULL, \
  28. day INTEGER NOT NULL, \
  29. status TEXT CHECK(status = 'yes' OR status = 'no' OR status = 'maybe' OR status = 'unknown') DEFAULT 'unknown', \
  30. PRIMARY KEY (name, month, day)\
  31. );")
  32. await db.commit()
  33. @app.get("/")
  34. def health():
  35. return {"status": "healthy"}
  36. @app.get("/availability")
  37. async def get_all():
  38. results = []
  39. async with connect() as db:
  40. async with db.execute("SELECT name, month, day, status FROM availability ORDER BY month, day;") as cursor:
  41. results = await cursor.fetchall()
  42. return {
  43. "lastUpdated": LAST_UPDATE.isoformat(),
  44. "availability": [
  45. {
  46. "month": month,
  47. "day": day,
  48. "availability": [{
  49. "name": name,
  50. "status": status,
  51. } for (name, _, _, status) in avail],
  52. } for ((month, day), avail) in itertools.groupby(results, key=lambda t: (t[1], t[2]))
  53. ],
  54. }
  55. class AvailabilityStatus(Enum):
  56. yes = "yes"
  57. maybe = "maybe"
  58. no = "no"
  59. unknown = "unknown"
  60. class AvailabilityItem(BaseModel):
  61. month: int
  62. day: int
  63. status: Union[AvailabilityStatus, None]
  64. class Availability(BaseModel):
  65. name: str
  66. availability: List[AvailabilityItem]
  67. @app.post("/availability", status_code=status.HTTP_204_NO_CONTENT)
  68. async def set_availability(body: Availability):
  69. print(body)
  70. global LAST_UPDATE
  71. async with connect() as db:
  72. for a in body.availability:
  73. await db.execute(
  74. "INSERT INTO availability VALUES (:name, :month, :day, :status) \
  75. ON CONFLICT(name, month, day) DO \
  76. UPDATE SET name=:name, month=:month, day=:day, status=:status",
  77. {
  78. "name": body.name,
  79. "month": a.month,
  80. "day": a.day,
  81. "status": a.status.value,
  82. },
  83. )
  84. await db.commit()
  85. LAST_UPDATE = datetime.datetime.now()
  86. return Response(status_code=status.HTTP_204_NO_CONTENT)