From c66772fd6731c6dbc55870d6e0ed89e6b1e419ba Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Wed, 4 Jun 2025 10:46:51 +0200 Subject: [PATCH] Fix several bugs and race conditions. --- index.html | 10 ++++----- server.py | 61 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/index.html b/index.html index f47c3be..a6c9f34 100644 --- a/index.html +++ b/index.html @@ -828,11 +828,11 @@ if (idea.start_time && idea.end_time) { const s = new Date(idea.start_time).toLocaleDateString('nb-NO', { hour: '2-digit', minute: '2-digit', hour12: false - }); + }).replace(/ /g, ' '); const e = new Date(idea.end_time).toLocaleDateString('nb-NO', { hour: '2-digit', minute: '2-digit', hour12: false - }); - timeCell = `${s} → ${e}`; + }).replace(/ /g, ' '); + timeCell = `${s}
→ ${e}`; } else if (idea.start_time) { const s = new Date(idea.start_time).toLocaleDateString('nb-NO', { hour: '2-digit', minute: '2-digit', hour12: false @@ -848,8 +848,8 @@ } html += `` - + `${idea.name}
${idea.description}` - + timeCell; + + `${idea.name}
${linkify(idea.description)}` + + timeCell; allVoters.forEach(voter => { html += `${idea.voters.includes(voter) ? '✓' : ''}`; }); diff --git a/server.py b/server.py index e16b8b6..6ef2181 100644 --- a/server.py +++ b/server.py @@ -5,6 +5,7 @@ from typing import Optional from datetime import datetime from fastapi import FastAPI, HTTPException, Path from pydantic import BaseModel, Field +from filelock import FileLock import argparse import uvicorn @@ -49,7 +50,11 @@ class Vote(BaseModel): DATA_DIR = "" # will be overridden via command line def get_tour_filepath(tour_id: str) -> str: - return os.path.join(DATA_DIR, f"{tour_id}.json") + fname = f"{tour_id}.json" + full = os.path.abspath(os.path.join(DATA_DIR, fname)) + if not full.startswith(os.path.abspath(DATA_DIR) + os.sep): + raise HTTPException(status_code=400, detail="Invalid tour ID") + return full # ─── ENDPOINT: CREATE TOUR ───────────────────────────────────────────────────── @@ -74,7 +79,7 @@ def create_tour(tour: TourCreate): filepath = get_tour_filepath(tour_id) with open(filepath, "w") as f: # Using default=str so that datetime objects become ISO strings - json.dump(new_tour.dict(), f, default=str) + json.dump(new_tour.model_dump(), f, default=str) return new_tour @@ -117,22 +122,24 @@ def add_idea(tour_id: str, idea: IdeaCreate): if not os.path.exists(filepath): raise HTTPException(status_code=404, detail="Tour not found") - with open(filepath) as f: - data = json.load(f) + lock = FileLock(filepath + ".lock") + with lock: + with open(filepath) as f: + data = json.load(f) - idea_id = str(uuid.uuid4()) - new_idea = Idea( - id=idea_id, - name=idea.name, - description=idea.description, - voters=[], - start_time=idea.start_time, - end_time=idea.end_time, - ) + idea_id = str(uuid.uuid4()) + new_idea = Idea( + id=idea_id, + name=idea.name, + description=idea.description, + voters=[], + start_time=idea.start_time, + end_time=idea.end_time, + ) - data["ideas"].append(new_idea.dict()) - with open(filepath, "w") as f: - json.dump(data, f, default=str) + data["ideas"].append(new_idea.model_dump()) + with open(filepath, "w") as f: + json.dump(data, f, default=str) return new_idea @@ -147,17 +154,19 @@ def vote_idea(tour_id: str, idea_id: str, vote: Vote): if not os.path.exists(filepath): raise HTTPException(status_code=404, detail="Tour not found") - with open(filepath) as f: - data = json.load(f) + lock = FileLock(filepath + ".lock") + with lock: + with open(filepath) as f: + data = json.load(f) - for idea in data["ideas"]: - if idea["id"] == idea_id: - if vote.voterName not in idea["voters"]: - idea["voters"].append(vote.voterName) - # Persist the change - with open(filepath, "w") as f: - json.dump(data, f, default=str) - return Idea(**idea) + for idea in data["ideas"]: + if idea["id"] == idea_id: + if vote.voterName not in idea["voters"]: + idea["voters"].append(vote.voterName) + # Persist the change + with open(filepath, "w") as f: + json.dump(data, f, default=str) + return Idea(**idea) raise HTTPException(status_code=404, detail="Idea not found")