Fix several bugs and race conditions.

This commit is contained in:
Oleksandr Kozachuk 2025-06-04 10:46:51 +02:00
parent eb9b29d301
commit c66772fd67
2 changed files with 40 additions and 31 deletions

View File

@ -828,11 +828,11 @@
if (idea.start_time && idea.end_time) { if (idea.start_time && idea.end_time) {
const s = new Date(idea.start_time).toLocaleDateString('nb-NO', { const s = new Date(idea.start_time).toLocaleDateString('nb-NO', {
hour: '2-digit', minute: '2-digit', hour12: false hour: '2-digit', minute: '2-digit', hour12: false
}); }).replace(/ /g, ' ');
const e = new Date(idea.end_time).toLocaleDateString('nb-NO', { const e = new Date(idea.end_time).toLocaleDateString('nb-NO', {
hour: '2-digit', minute: '2-digit', hour12: false hour: '2-digit', minute: '2-digit', hour12: false
}); }).replace(/ /g, ' ');
timeCell = `<td>${s}${e}</td>`; timeCell = `<td>${s}<br/>&nbsp;${e}</td>`;
} else if (idea.start_time) { } else if (idea.start_time) {
const s = new Date(idea.start_time).toLocaleDateString('nb-NO', { const s = new Date(idea.start_time).toLocaleDateString('nb-NO', {
hour: '2-digit', minute: '2-digit', hour12: false hour: '2-digit', minute: '2-digit', hour12: false
@ -848,8 +848,8 @@
} }
html += `<tr>` html += `<tr>`
+ `<td><strong>${idea.name}</strong><br><small>${idea.description}</small></td>` + `<td><strong>${idea.name}</strong><br><small>${linkify(idea.description)}</small></td>`
+ timeCell; + timeCell;
allVoters.forEach(voter => { allVoters.forEach(voter => {
html += `<td class="vote-cell">${idea.voters.includes(voter) ? '✓' : ''}</td>`; html += `<td class="vote-cell">${idea.voters.includes(voter) ? '✓' : ''}</td>`;
}); });

View File

@ -5,6 +5,7 @@ from typing import Optional
from datetime import datetime from datetime import datetime
from fastapi import FastAPI, HTTPException, Path from fastapi import FastAPI, HTTPException, Path
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from filelock import FileLock
import argparse import argparse
import uvicorn import uvicorn
@ -49,7 +50,11 @@ class Vote(BaseModel):
DATA_DIR = "" # will be overridden via command line DATA_DIR = "" # will be overridden via command line
def get_tour_filepath(tour_id: str) -> str: 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 ───────────────────────────────────────────────────── # ─── ENDPOINT: CREATE TOUR ─────────────────────────────────────────────────────
@ -74,7 +79,7 @@ def create_tour(tour: TourCreate):
filepath = get_tour_filepath(tour_id) filepath = get_tour_filepath(tour_id)
with open(filepath, "w") as f: with open(filepath, "w") as f:
# Using default=str so that datetime objects become ISO strings # 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 return new_tour
@ -117,22 +122,24 @@ def add_idea(tour_id: str, idea: IdeaCreate):
if not os.path.exists(filepath): if not os.path.exists(filepath):
raise HTTPException(status_code=404, detail="Tour not found") raise HTTPException(status_code=404, detail="Tour not found")
with open(filepath) as f: lock = FileLock(filepath + ".lock")
data = json.load(f) with lock:
with open(filepath) as f:
data = json.load(f)
idea_id = str(uuid.uuid4()) idea_id = str(uuid.uuid4())
new_idea = Idea( new_idea = Idea(
id=idea_id, id=idea_id,
name=idea.name, name=idea.name,
description=idea.description, description=idea.description,
voters=[], voters=[],
start_time=idea.start_time, start_time=idea.start_time,
end_time=idea.end_time, end_time=idea.end_time,
) )
data["ideas"].append(new_idea.dict()) data["ideas"].append(new_idea.model_dump())
with open(filepath, "w") as f: with open(filepath, "w") as f:
json.dump(data, f, default=str) json.dump(data, f, default=str)
return new_idea return new_idea
@ -147,17 +154,19 @@ def vote_idea(tour_id: str, idea_id: str, vote: Vote):
if not os.path.exists(filepath): if not os.path.exists(filepath):
raise HTTPException(status_code=404, detail="Tour not found") raise HTTPException(status_code=404, detail="Tour not found")
with open(filepath) as f: lock = FileLock(filepath + ".lock")
data = json.load(f) with lock:
with open(filepath) as f:
data = json.load(f)
for idea in data["ideas"]: for idea in data["ideas"]:
if idea["id"] == idea_id: if idea["id"] == idea_id:
if vote.voterName not in idea["voters"]: if vote.voterName not in idea["voters"]:
idea["voters"].append(vote.voterName) idea["voters"].append(vote.voterName)
# Persist the change # Persist the change
with open(filepath, "w") as f: with open(filepath, "w") as f:
json.dump(data, f, default=str) json.dump(data, f, default=str)
return Idea(**idea) return Idea(**idea)
raise HTTPException(status_code=404, detail="Idea not found") raise HTTPException(status_code=404, detail="Idea not found")