diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..87ca42d
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,16 @@
+{
+ "configurations": [
+ {
+ "name": "Python Debugger: FastAPI",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "uvicorn",
+ "args": [
+ "main:app",
+ "--reload"
+ ],
+ "jinja": true,
+ "cwd": "${workspaceFolder}/src"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data/max/reservations.json b/data/max/reservations.json
new file mode 100644
index 0000000..a62ccbe
--- /dev/null
+++ b/data/max/reservations.json
@@ -0,0 +1,8 @@
+{
+ "reservations": [
+ {
+ "item_id": "77b3c3a0-303b-482f-a1e0-67c29030ec69",
+ "name": "Max"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index 0876a45..9c3e6cd 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,8 +1,14 @@
import uuid
-from fastapi import FastAPI, Request, Response
+from fastapi import Depends, FastAPI, Request, Response
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
-from wishlist import read_all_wishlists
+from wishlist import (
+ read_all_wishlists,
+ ReservationRequest,
+ add_reservation,
+ remove_reservation,
+ ItemReservation,
+)
WISHLISTS_DIR = "../data"
@@ -10,13 +16,37 @@ app = FastAPI()
templates = Jinja2Templates(directory="templates")
-@app.get("/{id}/view", response_class=HTMLResponse)
-async def read_item(request: Request, id: uuid.UUID):
+wishlist_cache = {}
+
+
+@app.post("/reservation/update")
+async def update_reservation(
+ reservation_request: ReservationRequest = Depends(
+ ReservationRequest.parse_reservation_request
+ ),
+):
+ global wishlist_cache
+ wishlist_dir: str = wishlist_cache[reservation_request.wishlist_id].directory
+ if reservation_request.reserved:
+ new_reservation = ItemReservation()
+ new_reservation.name = reservation_request.name
+ new_reservation.item_id = str(reservation_request.item_id)
+ add_reservation(wishlist_dir, new_reservation)
+ else:
+ remove_reservation(wishlist_dir, reservation_request.item_id)
+
+
+@app.get("/{wishlist_id}/view", response_class=HTMLResponse)
+async def show_wishlist(request: Request, wishlist_id: uuid.UUID):
+ global wishlist_cache
wishlists = read_all_wishlists(WISHLISTS_DIR)
-
- if str(id) not in wishlists.keys():
+ wishlist_cache = wishlists
+
+ if wishlist_id not in wishlists.keys():
return Response(status_code=404)
-
+
return templates.TemplateResponse(
- request=request, name="wishlist_view.html", context={"wishlist": wishlists[str(id)]}
- )
\ No newline at end of file
+ request=request,
+ name="wishlist_view.html",
+ context={"wishlist": wishlists[wishlist_id]},
+ )
diff --git a/src/templates/components/wishlist_item.html b/src/templates/components/wishlist_item.html
new file mode 100644
index 0000000..88d3c1d
--- /dev/null
+++ b/src/templates/components/wishlist_item.html
@@ -0,0 +1,25 @@
+
+
+
+
{{ item.name }}{% if item.reservation %} (Reserved){% endif %}
+
{{ item.description }}
+
€{{ "%.2f"|format(item.price) }}
+
Shop Link
+
+
+
+
+
+
+
diff --git a/src/templates/wishlist_view.html b/src/templates/wishlist_view.html
index 6ed3534..bb37f8c 100644
--- a/src/templates/wishlist_view.html
+++ b/src/templates/wishlist_view.html
@@ -132,52 +132,16 @@
{{ wishlist.config.title }}
- {% for item_id, item in wishlist.items.items() if not item.reserved %}
- -
-
-
{{ item.name }}
-
{{ item.description }}
-
€{{ "%.2f"|format(item.price) }}
-
Shop Link
-
-
-
-
-
-
-
+ {% for item_id, item in wishlist.items.items() if not item.reservation %}
+ {% include 'components/wishlist_item.html' %}
{% endfor %}
Show Reserved Items
- {% for item_id, item in wishlist.items.items() if item.reserved %}
- -
-
-
{{ item.name }} (Reserved)
-
{{ item.description }}
-
€{{ "%.2f"|format(item.price) }}
-
Shop Link
-
-
-
-
-
-
-
+ {% for item_id, item in wishlist.items.items() if item.reservation %}
+ {% include 'components/wishlist_item.html' %}
{% endfor %}
diff --git a/src/wishlist/__init__.py b/src/wishlist/__init__.py
index 8ebe8f8..33bd8a4 100644
--- a/src/wishlist/__init__.py
+++ b/src/wishlist/__init__.py
@@ -1,2 +1,8 @@
-from .models import Wishlist, WishlistConfig, WishlistItem
-from .storage import read_all_wishlists
\ No newline at end of file
+from .models import (
+ Wishlist,
+ WishlistConfig,
+ WishlistItem,
+ ReservationRequest,
+ ItemReservation,
+)
+from .storage import read_all_wishlists, add_reservation, remove_reservation
diff --git a/src/wishlist/models.py b/src/wishlist/models.py
index b49ff0e..202f154 100644
--- a/src/wishlist/models.py
+++ b/src/wishlist/models.py
@@ -1,33 +1,54 @@
import uuid
from datetime import date
+from fastapi import Form
+from pydantic import BaseModel
+
+
+class ReservationRequest(BaseModel):
+ wishlist_id: uuid.UUID
+ item_id: uuid.UUID
+ reserved: bool
+ name: str
+
+ async def parse_reservation_request(
+ wishlist_id: uuid.UUID = Form(...),
+ item_id: uuid.UUID = Form(...),
+ reserved: bool = Form(...),
+ name: str = Form(...),
+ ) -> "ReservationRequest":
+ return ReservationRequest(
+ wishlist_id=wishlist_id, item_id=item_id, reserved=reserved, name=name
+ )
+
class ItemReservation:
def __init__(self) -> None:
+ self.item_id: uuid.UUID = uuid.uuid4()
self.name: str = ""
class WishlistItem:
def __init__(self) -> None:
- self.id: uuid = uuid.uuid4()
+ self.id: uuid.UUID = uuid.uuid4()
self.name: str = ""
self.description: str = ""
self.shop: str = ""
self.image: str = ""
self.price: float = 0
self.reservation: ItemReservation | None = None
-
+
@property
def is_reserved(self) -> bool:
return self.reservation is not None
class WishlistConfig:
def __init__(self) -> None:
- self.id: uuid = uuid.uuid4()
+ self.id: uuid.UUID = uuid.uuid4()
self.title: str = ""
- self.deadlines: dict[str, date] = {}
+ self.deadlines: dict[uuid.UUID, date] = {}
self.deadline_offset_days: int = 0
class Wishlist:
def __init__(self) -> None:
self.directory: str = ""
- self.items: dict[str, WishlistItem] = {} # Mapping Item ID to item
- self.config: WishlistConfig = None
\ No newline at end of file
+ self.items: dict[uuid.UUID, WishlistItem] = {} # Mapping Item ID to item
+ self.config: WishlistConfig = None
diff --git a/src/wishlist/storage.py b/src/wishlist/storage.py
index 4a17c54..ad51f9d 100644
--- a/src/wishlist/storage.py
+++ b/src/wishlist/storage.py
@@ -1,60 +1,125 @@
-from .models import Wishlist, WishlistConfig, WishlistItem
+from .models import Wishlist, WishlistConfig, WishlistItem, ItemReservation
from datetime import date
import uuid
import json
import csv
import os
+CONFIG_NAME = "config.json"
+WISHLIST_NAME = "wishlist.csv"
+RESERVATIONS_NAME = "reservations.json"
+
+
def read_wishlist_config(wishlist_dir: str) -> WishlistConfig:
- path: str = f"{wishlist_dir}/config.json"
+ path: str = f"{wishlist_dir}/{CONFIG_NAME}"
with open(path, "r", encoding="UTF-8") as fp:
json_config = json.load(fp)
-
+
config = WishlistConfig()
config.id = uuid.UUID(json_config["id"])
config.title = json_config["title"]
config.deadline_offset_days = json_config["deadlineOffsetDays"]
-
+
for label, timestamp in json_config["deadlines"].items():
config.deadlines[label] = date.fromisoformat(timestamp)
return config
-def read_wishlist_items(wishlist_dir: str) -> dict[str, WishlistItem]:
- items: dict[str, WishlistItem] = {}
- path: str = f"{wishlist_dir}/wishlist.csv"
- with open(path, "r", encoding = "UTF-8") as fp:
- csvreader = csv.DictReader(fp)
-
- for row in csvreader:
- item: WishlistItem = WishlistItem()
- item.id = uuid.UUID(row.get("id"))
- item.name = row.get("name")
- item.description = row.get("description")
- item.shop = row.get("shop")
- item.image = row.get("image")
- item.price = float(row.get("price"))
-
- items[str(item.id)] = item
-
+
+def read_wishlist_items(wishlist_dir: str) -> dict[uuid.UUID, WishlistItem]:
+ reservations = read_all_reservations(wishlist_dir)
+
+ items: dict[uuid.UUID, WishlistItem] = {}
+ path: str = f"{wishlist_dir}/{WISHLIST_NAME}"
+ with open(path, "r", encoding="UTF-8") as fp:
+ csvreader = csv.DictReader(fp)
+
+ for row in csvreader:
+ item: WishlistItem = WishlistItem()
+ item.id = uuid.UUID(row.get("id"))
+ item.name = row.get("name")
+ item.description = row.get("description")
+ item.shop = row.get("shop")
+ item.image = row.get("image")
+ item.price = float(row.get("price"))
+
+ if item.id in reservations.keys():
+ item.reservation = reservations[item.id]
+
+ items[item.id] = item
+
return items
-
+
def read_wishlist(wishlist_dir: str) -> Wishlist:
wishlist = Wishlist()
wishlist.directory = wishlist_dir
-
+
wishlist.items = read_wishlist_items(wishlist.directory)
wishlist.config = read_wishlist_config(wishlist.directory)
-
+
return wishlist
-def read_all_wishlists(directory: str) -> dict[str, Wishlist]:
+
+def read_all_wishlists(directory: str) -> dict[uuid.UUID, Wishlist]:
parent_dir, wishlist_dirs, _ = list(os.walk(directory))[0]
-
- wishlists: dict[str, Wishlist] = {}
+
+ wishlists: dict[uuid.UUID, Wishlist] = {}
for dir in wishlist_dirs:
wishlist: Wishlist = read_wishlist(f"{parent_dir}/{dir}")
- wishlists[str(wishlist.config.id)] = wishlist
-
- return wishlists
\ No newline at end of file
+ wishlists[wishlist.config.id] = wishlist
+
+ return wishlists
+
+
+def read_all_reservations(wishlist_dir: str) -> dict[uuid.UUID, ItemReservation]:
+ path: str = f"{wishlist_dir}/{RESERVATIONS_NAME}"
+ if not os.path.exists(path):
+ return {}
+
+ reservations_json = []
+ with open(path, "r", encoding="UTF-8") as fp:
+ reservations_json = json.load(fp)["reservations"]
+
+ reservations = {}
+ for r in reservations_json:
+ reservation = ItemReservation()
+ reservation.item_id = uuid.UUID(r["item_id"])
+ reservation.name = r["name"]
+
+ reservations[reservation.item_id] = reservation
+
+ return reservations
+
+
+def write_all_reservations(
+ wishlist_dir: str, reservations: dict[uuid.UUID, ItemReservation]
+):
+ path: str = f"{wishlist_dir}/{RESERVATIONS_NAME}"
+
+ reservations_json = []
+ for r in reservations.values():
+ reservations_json.append({"item_id": r.item_id, "name": r.name})
+
+ with open(path, "w", encoding="UTF-8") as fp:
+ json.dump({"reservations": reservations_json}, fp)
+
+
+def add_reservation(wishlist_dir: str, reservation: ItemReservation):
+ all_reservations = read_all_reservations(wishlist_dir)
+ if reservation.item_id in all_reservations.keys():
+ raise Exception(f"Item [{reservation.item_id}] already reserved.")
+
+ all_reservations[reservation.item_id] = reservation
+
+ write_all_reservations(wishlist_dir, all_reservations)
+
+
+def remove_reservation(wishlist_dir: str, item_id: uuid.UUID):
+ all_reservations = read_all_reservations(wishlist_dir)
+ if item_id not in all_reservations.keys():
+ return
+
+ del all_reservations[item_id]
+
+ write_all_reservations(wishlist_dir, all_reservations)