diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4c2820c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.env/
+**/__pycache__/
\ No newline at end of file
diff --git a/data/max/wishlist.csv b/data/max/wishlist.csv
index 4d353b2..34a1d46 100644
--- a/data/max/wishlist.csv
+++ b/data/max/wishlist.csv
@@ -1,2 +1,3 @@
-"id","name","description","seller","image","price"
-"d3e203ba-9aab-4283-8c46-f4a46a5d1f62","Dune: Part 2 - 4K HDR Blu-Ray","Der zweite Film der Dune Reihe in bester Qualität auf Blu-Ray. Die Qualität ist für den Film wichtig, weshalb auf 4K HDR geachtet werden sollte. Das ist in der Regel gut auf der Verpackung gekennzeichnet.",,,29.99
\ No newline at end of file
+"id","name","description","shop","image","price"
+"d3e203ba-9aab-4283-8c46-f4a46a5d1f62","Dune: Part 2 - 4K HDR Blu-Ray","Der zweite Film der Dune Reihe in bester Qualität auf Blu-Ray. Die Qualität ist für den Film wichtig, weshalb auf 4K HDR geachtet werden sollte. Das ist in der Regel gut auf der Verpackung gekennzeichnet.","https://www.mediamarkt.de/de/product/_dune-part-two-blu-ray-2923536.html","https://assets.mmsrg.com/isr/166325/c1/-/ASSET_MMS_138728291?x=536&y=402&format=jpg&quality=80&sp=yes&strip=yes&trim&ex=536&ey=402&align=center&resizesource&unsharp=1.5x1+0.7+0.02&cox=0&coy=0&cdx=536&cdy=402",29.99
+"77b3c3a0-303b-482f-a1e0-67c29030ec69","Ex-Machina","","","",14.99
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..be08dc6
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+fastapi[standard]
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..0876a45
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,22 @@
+import uuid
+from fastapi import FastAPI, Request, Response
+from fastapi.responses import HTMLResponse
+from fastapi.templating import Jinja2Templates
+from wishlist import read_all_wishlists
+
+WISHLISTS_DIR = "../data"
+
+app = FastAPI()
+
+templates = Jinja2Templates(directory="templates")
+
+@app.get("/{id}/view", response_class=HTMLResponse)
+async def read_item(request: Request, id: uuid.UUID):
+ wishlists = read_all_wishlists(WISHLISTS_DIR)
+
+ if str(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
diff --git a/src/templates/wishlist_view.html b/src/templates/wishlist_view.html
new file mode 100644
index 0000000..d512123
--- /dev/null
+++ b/src/templates/wishlist_view.html
@@ -0,0 +1,177 @@
+
+
+
+
+
+ {{ wishlist.config.title }}
+
+
+
+ {{ wishlist.config.title }}
+
+
+
+ {% for item_id, item in wishlist.items.items() if not item.is_reserved %}
+ -
+
+
{{ item.name }}
+
{{ item.description }}
+
Price: €{{ "%.2f"|format(item.price) }}
+
Shop Link
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+
+ Reserved Items (click to expand/collapse)
+
+ {% for item_id, item in wishlist.items.items() if item.is_reserved %}
+ -
+
+
{{ item.name }} (Reserved)
+
{{ item.description }}
+
Price: €{{ "%.2f"|format(item.price) }}
+
Shop Link
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+
+
+
diff --git a/src/wishlist/__init__.py b/src/wishlist/__init__.py
new file mode 100644
index 0000000..8ebe8f8
--- /dev/null
+++ b/src/wishlist/__init__.py
@@ -0,0 +1,2 @@
+from .models import Wishlist, WishlistConfig, WishlistItem
+from .storage import read_all_wishlists
\ No newline at end of file
diff --git a/src/wishlist/models.py b/src/wishlist/models.py
new file mode 100644
index 0000000..b49ff0e
--- /dev/null
+++ b/src/wishlist/models.py
@@ -0,0 +1,33 @@
+import uuid
+from datetime import date
+
+class ItemReservation:
+ def __init__(self) -> None:
+ self.name: str = ""
+
+class WishlistItem:
+ def __init__(self) -> None:
+ self.id: 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.title: str = ""
+ self.deadlines: dict[str, 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
diff --git a/src/wishlist/storage.py b/src/wishlist/storage.py
new file mode 100644
index 0000000..4a17c54
--- /dev/null
+++ b/src/wishlist/storage.py
@@ -0,0 +1,60 @@
+from .models import Wishlist, WishlistConfig, WishlistItem
+from datetime import date
+import uuid
+import json
+import csv
+import os
+
+def read_wishlist_config(wishlist_dir: str) -> WishlistConfig:
+ path: str = f"{wishlist_dir}/config.json"
+ 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
+
+ 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]:
+ parent_dir, wishlist_dirs, _ = list(os.walk(directory))[0]
+
+ wishlists: dict[str, 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