Skip to content

Enhancement/sorted competition by upcoming and outdated #296

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 83 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
e443b17
Refactor server.py for improved readability and environment variable …
wilodorico May 16, 2025
45eb5bb
Add test configuration and fixtures; update .gitignore to include .fl…
wilodorico May 16, 2025
740c0b8
Add test for handling without email in login
wilodorico May 16, 2025
d60ef20
Add condition if email empty redirect index and flash message
wilodorico May 16, 2025
cc6c04b
display message flash in template index.html
wilodorico May 16, 2025
4d29e2d
Add test with non existent email in base and rename test for clarity
wilodorico May 16, 2025
e1c17a4
add try except to catch the IndexError exception and add flash message
wilodorico May 16, 2025
4b0de5f
Add test for loading valid JSON file return correct json
wilodorico May 16, 2025
032f3a9
Add JSONServices class with static method to load JSON files
wilodorico May 16, 2025
4203947
add test for non existent file return exception FileNotFoundError wit…
wilodorico May 16, 2025
2723254
Add error handling for FileNotFoundError in JSONServices.load method
wilodorico May 16, 2025
6e55e5f
Refacto code to use JsonService for load data clubs and competitions
wilodorico May 16, 2025
1528d11
Added test for club reservations exceeding their points limit
wilodorico May 23, 2025
c8f4b6c
Add a condition to check that the number of places requested does not…
wilodorico May 23, 2025
9637d7a
Display flash message in booking.html
wilodorico May 23, 2025
2e37cb2
Add LIMITED_BOOKING_PLACE constant and add test for club cannot book …
wilodorico May 23, 2025
5822bfc
Add validation for booking limit in purchasePlaces function
wilodorico May 23, 2025
69264b2
Refactor test setup by creating helper functions for mock clubs and c…
wilodorico May 23, 2025
c2e2d09
Add save method and corresponding test for JSONServices class
wilodorico Jun 6, 2025
f138791
Deduct competition places when a club books and save updated competit…
wilodorico Jun 6, 2025
12a623f
Add Club, Competition, and Reservation models with initial attributes…
wilodorico Jun 9, 2025
6f1b56c
Refactor Club, Competition, and Reservation models to use private att…
wilodorico Jun 9, 2025
8121b0f
Add has_enough_points method and corresponding tests for Club model
wilodorico Jun 9, 2025
5693c84
Add can_reserve method and tests for Competition model
wilodorico Jun 9, 2025
c1b35fd
Add max_places_per_reservation attribute and is_within_reservation_li…
wilodorico Jun 9, 2025
591137e
Add id uuid in club and competition and change key from numbrerOfPlac…
wilodorico Jun 9, 2025
f1fa413
Fix variable name in welcome template from numberOfPlaces to availabl…
wilodorico Jun 9, 2025
c9b65c3
Fix UUID generation in Club model and add serialization/deserializati…
wilodorico Jun 9, 2025
5a50711
Refactor Competition model to improve structure and add reserve_place…
wilodorico Jun 9, 2025
0202220
Refactor purchasePlaces route to use club and competition class models
wilodorico Jun 9, 2025
8d23f66
Refactor booking template for use instance variable
wilodorico Jun 9, 2025
b0798c2
Fix variable name in competition mock data from numberOfPlaces to ava…
wilodorico Jun 9, 2025
d4e4b33
Fix UUID generation in Reservation model and add serialization/deseri…
wilodorico Jun 13, 2025
c1630cc
Implement consume_points method in Club model and update reserve_plac…
wilodorico Jun 13, 2025
c5e4d0c
Refactor purchasePlaces route to include reservation handling and sav…
wilodorico Jun 13, 2025
31cd6da
Add reservations.json file and enhance purchasePlaces route to check …
wilodorico Jun 13, 2025
e80ad3e
Add ReservationJsonRepository to manage reservations and update purch…
wilodorico Jun 13, 2025
f4fb273
Add ClubJsonRepository to manage club data with JSON serialization an…
wilodorico Jun 13, 2025
4ff390e
Add CompetitionJsonRepository to manage competition data with JSON se…
wilodorico Jun 13, 2025
56edde3
Refactor purchasePlaces route to utilize CompetitionJsonRepository fo…
wilodorico Jun 13, 2025
f316279
Add find_by_email method to ClubJsonRepository for club email lookup
wilodorico Jun 13, 2025
fd15146
Add email property to Club class for better access to club email
wilodorico Jun 13, 2025
90c1cc8
Refactor showSummary route to use ClubJsonRepository for club email l…
wilodorico Jun 13, 2025
3a30b80
Fix competition iteration in welcome template to access properties di…
wilodorico Jun 13, 2025
aca068f
Refactor show_summary and purchase_places functions for consistency a…
wilodorico Jun 13, 2025
0a7f512
Refactor repository initializations to improve code organization and …
wilodorico Jun 13, 2025
4acb35c
Add check if place required = 0 and change message flash success
wilodorico Jun 19, 2025
2fb4092
Refactor ClubJsonRepository to implement ClubRepository protocol and …
wilodorico Jun 19, 2025
ed79b9e
Implement CompetitionRepository protocol in CompetitionJsonRepository…
wilodorico Jun 19, 2025
250d5e8
Implement ReservationRepository protocol and refactor ReservationJson…
wilodorico Jun 19, 2025
4c62141
Add id property to Club class and update reservation logic in Competi…
wilodorico Jun 20, 2025
905d3d1
Implement ReservePlaceUseCase for handling reservation logic and upda…
wilodorico Jun 20, 2025
6ba9b8a
Implement reload method in ClubJsonRepository and CompetitionJsonRepo…
wilodorico Jun 20, 2025
5516827
Refactor show_summary to simplify error handling for club email non e…
wilodorico Jun 20, 2025
6eb61fe
Add in-memory repository implementations for Club, Competition, and R…
wilodorico Jun 22, 2025
c060ac2
Refactor repository protocols to unify save and update methods; updat…
wilodorico Jun 22, 2025
60b4114
Refactor update methods in ClubJsonRepository and CompetitionJsonRepo…
wilodorico Jun 22, 2025
807ec8d
Refactor reservation handling in ReservePlaceUseCase; update execute …
wilodorico Jun 22, 2025
24863fc
Refactor in-memory repositories to accept initial data; implement upd…
wilodorico Jun 22, 2025
680bcfa
Add unit test for successful reservation for ReservePlaceUseCase
wilodorico Jun 22, 2025
5d4acf9
Add properties to Club, Competition, and Reservation models for bette…
wilodorico Jun 22, 2025
d602056
Add test for handling club not found scenario in ReservePlaceUseCase
wilodorico Jun 22, 2025
afe195b
Refactor error messages in ReservePlaceUseCase for club and add test …
wilodorico Jun 22, 2025
25207ea
Refactor tests in ReservePlaceUseCase to use fixtures for club, compe…
wilodorico Jun 22, 2025
eea9472
Add tests for handling zero and negative places in ReservePlaceUseCase
wilodorico Jun 22, 2025
17bde38
Add tests for error handling in ReservePlaceUseCase for insufficient …
wilodorico Jun 22, 2025
ca136b8
Fix #5 Booking places in past competitions.
wilodorico Jun 22, 2025
1f627fc
Remove redundant update call for competition in ReservePlaceUseCase
wilodorico Jun 22, 2025
37354d0
Add methods to get club reservations and total reserved places for co…
wilodorico Jun 26, 2025
3973797
Refactor is_within_reservation_limit to check total reserved places a…
wilodorico Jun 26, 2025
2f39138
Add methods to retrieve club reservations and total reserved places f…
wilodorico Jun 26, 2025
8009114
Fix #4 bug clubs shouldn't be able to book more than 12 places per co…
wilodorico Jun 26, 2025
6fcefd6
Refactor test_is_within_reservation_limit to use correct parameters f…
wilodorico Jun 26, 2025
d113265
Add Summer Challenge competition and corresponding reservation entry
wilodorico Jun 26, 2025
17f9a05
fix #7 Add points dashboard route and template for club points display
wilodorico Jun 26, 2025
31d2cd9
Refactor templates to extend base layout and improve structure for we…
wilodorico Jun 26, 2025
87af07c
Enhance session management and navigation: update showSummary route t…
wilodorico Jun 26, 2025
b40f3b2
Validate number of places in purchase_places function and handle errors
wilodorico Jun 26, 2025
67a90c6
Reload repositories in purchase_places function to ensure data consis…
wilodorico Jun 26, 2025
290f401
Refactor: Move models to entities and update imports across the codebase
wilodorico Jun 26, 2025
a0b4174
fix problems line too long 119
wilodorico Jun 26, 2025
791b698
Enhance documentation: Add class and method docstrings across entitie…
wilodorico Jun 26, 2025
ce9f536
Enhance competition display: Sort competitions by upcoming and outdat…
wilodorico Jun 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ bin
include
lib
.Python
tests/
.envrc
__pycache__
__pycache__
.env
.flake8
6 changes: 5 additions & 1 deletion clubs.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
{"clubs":[
{
"id": "25e97c3f-844b-4200-a3cb-7f0a09c0f623",
"name":"Simply Lift",
"email":"[email protected]",
"points":"13"
},
{
"id": "d5c2bc91-2171-40ef-a9bc-49af8e6518c9",
"name":"Iron Temple",
"email": "[email protected]",
"points":"4"
},
{ "name":"She Lifts",
{
"id": "006a09e0-9325-471c-970b-6fb81c3146d4",
"name":"She Lifts",
"email": "[email protected]",
"points":"12"
}
Expand Down
12 changes: 10 additions & 2 deletions competitions.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
{
"competitions": [
{
"id": "89bee937-7503-431a-bb1c-d2a05998170a",
"name": "Spring Festival",
"date": "2020-03-27 10:00:00",
"numberOfPlaces": "25"
"available_places": "25"
},
{
"id": "0684ecc2-80ca-4057-a144-3357298689d9",
"name": "Fall Classic",
"date": "2020-10-22 13:30:00",
"numberOfPlaces": "13"
"available_places": "13"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Summer Challenge",
"date": "2025-08-27 10:00:00",
"available_places": "20"
}
]
}
Empty file added entities/__init__.py
Empty file.
68 changes: 68 additions & 0 deletions entities/club.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import uuid


class Club:
"""Class representing a club with a name, email, and points system for reservation."""

def __init__(self, name: str, email: str, points: int):
self._id = str(uuid.uuid4())
self._name = name
self._email = email
self._points = points

def has_enough_points(self, points: int) -> bool:
"""Check if the club has enough points."""
return self._points >= points

def consume_points(self, points: int) -> None:
"""Consume points from the club."""
if not self.has_enough_points(points):
raise ValueError("Not enough points to consume.")
self._points -= points

def reserve(self, competition, places, date):
"""Reserve places for the club in a competition."""
return competition.reserve_places(self, places, date)

@property
def id(self) -> str:
"""Get the ID of the club."""
return self._id

@property
def name(self) -> str:
"""Get the name of the club."""
return self._name

@property
def email(self) -> str:
"""Get the email of the club."""
return self._email

@property
def points(self) -> int:
"""Get the points of the club."""
return self._points

@classmethod
def deserialize(cls, data: dict):
"""Deserialize data into the Club object."""
club = cls(
name=data.get("name"),
email=data.get("email"),
points=int(data.get("points")),
)
club._id = data.get("id")
return club

def serialize(self) -> dict:
"""Serialize the Club object into a dictionary."""
return {
"id": self._id,
"name": self._name,
"email": self._email,
"points": str(self._points),
}

def __str__(self) -> str:
return f"Club(id={self._id}, name={self._name}, email={self._email}, points={self._points})"
94 changes: 94 additions & 0 deletions entities/competition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import uuid
from datetime import datetime

from entities.reservation import Reservation


class Competition:
"""Class representing a competition with a name, date, available places and maximum places per reservation."""

MAX_PLACES_PER_RESERVATION = 12

def __init__(
self,
name: str,
date: datetime,
available_places: int,
max_places_per_reservation: int = MAX_PLACES_PER_RESERVATION,
):
self._id = str(uuid.uuid4())
self._name = name
self._date = date
self._available_places = available_places
self._max_places_per_reservation = max_places_per_reservation

def can_reserve(self, places: int) -> bool:
"""Check if there are enough available places to reserve."""
return self._available_places >= places

def is_within_reservation_limit(self, places: int, total_already_reserved: int) -> bool:
"""Check if the total reserved places stay within the allowed limit."""
return total_already_reserved + places <= self._max_places_per_reservation

def reserve_places(self, club, places, date):
"""Reserve places for a club in the competition."""
if not self.can_reserve(places):
raise ValueError("Not enough available places to reserve.")
if not club.has_enough_points(places):
raise ValueError("Not enough points to reserve places.")

club.consume_points(places)
self._available_places -= places

return Reservation(club.id, self._id, places, date)

@property
def id(self) -> str:
"""Get the ID of the competition."""
return self._id

@property
def name(self) -> str:
"""Get the name of the competition."""
return self._name

@property
def date(self) -> datetime:
"""Get the date of the competition."""
return self._date

@property
def available_places(self) -> int:
"""Get the number of available places."""
return self._available_places

@property
def max_places_per_reservation(self) -> int:
"""Get the maximum places per reservation."""
return self._max_places_per_reservation

@classmethod
def deserialize(cls, data: dict):
"""Deserialize data into the Competition object."""
competition = cls(
name=data.get("name"),
date=datetime.strptime(data.get("date"), "%Y-%m-%d %H:%M:%S"),
available_places=int(data.get("available_places")),
)
competition._id = data.get("id")
return competition

def serialize(self) -> dict:
"""Serialize the Competition object into a dictionary."""
return {
"id": self._id,
"name": self._name,
"date": self._date.strftime("%Y-%m-%d %H:%M:%S"),
"available_places": str(self._available_places),
}

def __str__(self) -> str:
return (
f"Competition(id={self._id}, name={self._name}, "
f"date={self._date}, available_places={self._available_places})"
)
61 changes: 61 additions & 0 deletions entities/reservation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import uuid
from datetime import datetime


class Reservation:
"""Class representing a reservation for a club in a competition."""

def __init__(self, club_id: str, competition_id: str, reserved_places: int, date: datetime):
self._id = str(uuid.uuid4())
self._club_id = club_id
self._competition_id = competition_id
self._reserved_places = reserved_places
self._date = date

@property
def club_id(self) -> str:
"""Get the ID of the club."""
return self._club_id

@property
def competition_id(self) -> str:
"""Get the ID of the competition."""
return self._competition_id

@property
def reserved_places(self) -> int:
"""Get the number of reserved places."""
return self._reserved_places

@property
def date(self) -> datetime:
"""Get the date of the reservation."""
return self._date

@classmethod
def deserialize(cls, data: dict):
"""Deserialize data into the Reservation object."""
reservation = cls(
club_id=data.get("club_id"),
competition_id=data.get("competition_id"),
reserved_places=int(data.get("reserved_places")),
date=datetime.strptime(data.get("date"), "%Y-%m-%d %H:%M:%S"),
)
reservation._id = data.get("id")
return reservation

def serialize(self) -> dict:
"""Serialize the Reservation object into a dictionary."""
return {
"id": self._id,
"club_id": self._club_id,
"competition_id": self._competition_id,
"reserved_places": str(self._reserved_places),
"date": self._date.strftime("%Y-%m-%d %H:%M:%S"),
}

def __str__(self) -> str:
return (
f"Reservation(id={self._id}, club_id={self._club_id}, "
f"competition_id={self._competition_id}, reserved_places={self._reserved_places}, date={self._date})"
)
1 change: 1 addition & 0 deletions globals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LIMITED_BOOKING_PLACE = 12
16 changes: 16 additions & 0 deletions json_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import json


class JSONServices:
@staticmethod
def load(filename: str):
try:
with open(filename) as file:
return json.load(file)
except FileNotFoundError:
raise FileNotFoundError(f"File {filename} not found.")

@staticmethod
def save(file_name, data):
with open(file_name, "w") as file:
json.dump(data, file, indent=4)
Empty file added repositories/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions repositories/club_json_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os

from entities.club import Club
from json_services import JSONServices
from repository_protocols.club_repository import ClubRepository


class ClubJsonRepository(ClubRepository):
"""Repository for managing clubs stored in a JSON file."""

def __init__(self, file_path: str):
self.file_path = file_path
self._clubs: list[Club] = self._load()

def _load(self) -> list[Club]:
"""Load clubs from the JSON file. If the file does not exist, return an empty list."""
if not os.path.exists(self.file_path):
return []

data = JSONServices.load(self.file_path).get("clubs", [])
return [Club.deserialize(club) for club in data]

def reload(self) -> None:
"""Reload the clubs from the JSON file."""
self._clubs = self._load()

def all(self) -> list[Club]:
"""Get all clubs from the repository."""
return self._clubs

def find_by_name(self, name: str) -> Club | None:
"""Find a club by its name."""
return next((club for club in self._clubs if club.name == name), None)

def find_by_email(self, email: str) -> Club | None:
"""Find a club by its email."""
return next((club for club in self._clubs if club.email == email), None)

def update(self, club: Club) -> None:
"""Update an existing club in the repository."""
for i, existing_club in enumerate(self._clubs):
if existing_club.name == club.name:
self._clubs[i] = club
break
serialized_data = [club.serialize() for club in self._clubs]
JSONServices.save(self.file_path, {"clubs": serialized_data})
46 changes: 46 additions & 0 deletions repositories/competition_json_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
from datetime import datetime

from entities.competition import Competition
from json_services import JSONServices
from repository_protocols.competition_repository import CompetitionRepository


class CompetitionJsonRepository(CompetitionRepository):
"""Repository for managing competitions stored in a JSON file."""

def __init__(self, file_path: str):
self.file_path = file_path
self._competitions: list[Competition] = self._load()

def _load(self) -> list[Competition]:
"""Load competitions from the JSON file. If the file does not exist, return an empty list."""
if not os.path.exists(self.file_path):
return []

data = JSONServices.load(self.file_path).get("competitions", [])
return [Competition.deserialize(competition) for competition in data]

def reload(self) -> None:
"""Reload the competitions from the JSON file."""
self._competitions = self._load()

def all(self) -> list[Competition]:
"""Get all competitions sorted by upcoming first, then outdated."""
now = datetime.now()
upcoming = [competition for competition in self._competitions if competition.date >= now]
outdated = [competition for competition in self._competitions if competition.date < now]
return sorted(upcoming, key=lambda c: c.date) + sorted(outdated, key=lambda c: c.date)

def find_by_name(self, name: str) -> Competition | None:
"""Find a competition by its name."""
return next((competition for competition in self._competitions if competition.name == name), None)

def update(self, competition: Competition) -> None:
"""Update an existing competition in the repository."""
for i, existing_competition in enumerate(self._competitions):
if existing_competition.name == competition.name:
self._competitions[i] = competition
break
serialized_data = [competition.serialize() for competition in self._competitions]
JSONServices.save(self.file_path, {"competitions": serialized_data})
Loading