490 lines
14 KiB
Python
490 lines
14 KiB
Python
from enum import Enum
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
from urllib.parse import urljoin
|
|
|
|
import config
|
|
import pendulum
|
|
from exceptions import MissingProperty
|
|
from pendulum import Date, Time
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
from pydantic_extra_types.pendulum_dt import DateTime
|
|
|
|
|
|
class User(BaseModel):
|
|
login: str
|
|
password: str = Field(repr=False)
|
|
club_id: Optional[str] = Field(default=None)
|
|
|
|
|
|
class BookingOpening(BaseModel):
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
|
|
days_before: Optional[int] = Field(default=7, alias="daysBefore")
|
|
opening_time: Optional[str] = Field(alias="time", default=None, repr=False)
|
|
time_after_booking: Optional[str] = Field(
|
|
alias="timeAfterBookingTime", default=None, repr=False
|
|
)
|
|
|
|
def __repr__(self):
|
|
base = super().__repr__()
|
|
time = f", time: {self.time})" if self.time else ""
|
|
time_after_booking = (
|
|
f", time_after_booking_time: {self.time_after_booking_time})"
|
|
if self.time_after_booking_time
|
|
else ""
|
|
)
|
|
|
|
return base.removesuffix(")") + time + time_after_booking
|
|
|
|
@property
|
|
def time(self):
|
|
return pendulum.parse(self.opening_time).time()
|
|
|
|
@property
|
|
def time_after_booking_time(self):
|
|
return (
|
|
pendulum.parse(self.time_after_booking).time()
|
|
if self.time_after_booking
|
|
else None
|
|
)
|
|
|
|
|
|
class TotalBookings(BaseModel):
|
|
peak_hours: int | str = Field(alias="peakHours")
|
|
off_peak_hours: int | str = Field(alias="offPeakHours")
|
|
|
|
|
|
class Court(BaseModel):
|
|
id: int
|
|
name: str
|
|
number: int
|
|
is_indoor: Optional[bool] = Field(alias="isIndoor")
|
|
|
|
|
|
class Sport(BaseModel):
|
|
name: str
|
|
id: int
|
|
duration: int
|
|
price: int
|
|
players: int
|
|
courts: list[Court]
|
|
|
|
|
|
class Url(BaseModel):
|
|
name: str
|
|
path: str
|
|
parameter: Optional[str] = Field(default=None)
|
|
payload_template: Optional[str] = Field(default=None, alias="payloadTemplate")
|
|
|
|
|
|
class BookingPlatform(BaseModel):
|
|
id: str
|
|
club_id: int = Field(alias="clubId")
|
|
url: str
|
|
hours_before_cancellation: int = Field(alias="hoursBeforeCancellation")
|
|
booking_opening: BookingOpening = Field(alias="bookingOpening")
|
|
total_bookings: TotalBookings = Field(alias="totalBookings")
|
|
sports: list[Sport]
|
|
urls: dict[str, Url]
|
|
|
|
def get_url_path(self, name: str) -> str:
|
|
"""
|
|
Get the URL path for the service with the given name
|
|
|
|
:param name: the name of the service
|
|
:return: the URL path
|
|
"""
|
|
self.check_url_path_exists(name)
|
|
|
|
return urljoin(self.url, self.urls.get(name).path)
|
|
|
|
def get_payload_template(self, name: str) -> Path:
|
|
"""
|
|
Get the path to the template file for the service with the given name
|
|
|
|
:param name: the name of the service
|
|
:return: the path to the template file
|
|
"""
|
|
self.check_payload_template_exists(name)
|
|
|
|
return config.get_resources_folder() / self.urls.get(name).payload_template
|
|
|
|
def get_url_parameter(self, name: str) -> str:
|
|
self.check_url_path_exists(name)
|
|
|
|
return self.urls.get(name).parameter
|
|
|
|
def check_url_path_exists(self, name: str) -> None:
|
|
"""
|
|
Check that the URL path for the given service is defined
|
|
|
|
:param name: the name of the service
|
|
"""
|
|
if (
|
|
self.urls is None
|
|
or self.urls.get(name) is None
|
|
or self.urls.get(name).path is None
|
|
):
|
|
raise MissingProperty(
|
|
f"The booking platform internal URL path for page {name} are not set"
|
|
)
|
|
|
|
def check_payload_template_exists(self, name: str) -> None:
|
|
"""
|
|
Check that the payload template for the given service is defined
|
|
|
|
:param name: the name of the service
|
|
"""
|
|
if (
|
|
self.urls is None
|
|
or self.urls.get(name) is None
|
|
or self.urls.get(name).path is None
|
|
):
|
|
raise ValueError(
|
|
f"The booking platform internal URL path for page {name} are not set"
|
|
)
|
|
|
|
@property
|
|
def landing_url(self) -> str:
|
|
"""
|
|
Get the URL to the landing page of Gestion-Sports
|
|
|
|
:return: the URL to the landing page
|
|
"""
|
|
return self.get_url_path("landing-page")
|
|
|
|
@property
|
|
def login_url(self) -> str:
|
|
"""
|
|
Get the URL to the connection login of Gestion-Sports
|
|
|
|
:return: the URL to the login page
|
|
"""
|
|
return self.get_url_path("login")
|
|
|
|
@property
|
|
def login_template(self) -> Path:
|
|
"""
|
|
Get the payload template to send to log in the website
|
|
|
|
:return: the payload template for logging in
|
|
"""
|
|
return self.get_payload_template("login")
|
|
|
|
@property
|
|
def booking_url(self) -> str:
|
|
"""
|
|
Get the URL to the booking page of Gestion-Sports
|
|
|
|
:return: the URL to the booking page
|
|
"""
|
|
return self.get_url_path("booking")
|
|
|
|
@property
|
|
def booking_template(self) -> Path:
|
|
"""
|
|
Get the payload template to send to book a court
|
|
|
|
:return: the payload template for booking a court
|
|
"""
|
|
return self.get_payload_template("booking")
|
|
|
|
@property
|
|
def user_bookings_url(self) -> str:
|
|
"""
|
|
Get the URL where all the user's bookings are available
|
|
|
|
:return: the URL to the user's bookings
|
|
"""
|
|
return self.get_url_path("user-bookings")
|
|
|
|
@property
|
|
def user_bookings_template(self) -> Path:
|
|
"""
|
|
Get the payload template to send to get all the user's bookings that are
|
|
available
|
|
|
|
:return: the payload template for the user's bookings
|
|
"""
|
|
return self.get_payload_template("user-bookings")
|
|
|
|
@property
|
|
def booking_cancellation_url(self) -> str:
|
|
"""
|
|
Get the URL where all the user's bookings are available
|
|
|
|
:return: the URL to the user's bookings
|
|
"""
|
|
return self.get_url_path("cancellation")
|
|
|
|
@property
|
|
def booking_cancel_template(self) -> Path:
|
|
"""
|
|
Get the payload template to send to get all the user's bookings that are
|
|
available
|
|
|
|
:return: the payload template for the user's bookings
|
|
"""
|
|
return self.get_payload_template("cancellation")
|
|
|
|
@property
|
|
def tournaments_sessions_url(self) -> str:
|
|
return self.get_url_path("tournament-sessions")
|
|
|
|
@property
|
|
def tournaments_sessions_template(self) -> Path:
|
|
return self.get_payload_template("tournament-sessions")
|
|
|
|
@property
|
|
def tournaments_list_url(self) -> str:
|
|
return self.get_url_path("tournaments-list")
|
|
|
|
@property
|
|
def available_sports(self) -> dict[str, Sport]:
|
|
"""
|
|
Get a dictionary of all sports, the key is the sport name lowered case
|
|
:return: the dictionary of all sports
|
|
"""
|
|
return {sport.name.lower(): sport for sport in self.sports}
|
|
|
|
|
|
class Club(BaseModel):
|
|
id: str
|
|
name: str
|
|
url: str
|
|
booking_platform: BookingPlatform = Field(alias="bookingPlatform")
|
|
|
|
@property
|
|
def landing_url(self) -> str:
|
|
"""
|
|
Get the URL to the landing page of Gestion-Sports
|
|
|
|
:return: the URL to the landing page
|
|
"""
|
|
return self.booking_platform.landing_url
|
|
|
|
@property
|
|
def login_url(self) -> str:
|
|
"""
|
|
Get the URL to the connection login of Gestion-Sports
|
|
|
|
:return: the URL to the login page
|
|
"""
|
|
return self.booking_platform.login_url
|
|
|
|
@property
|
|
def login_template(self) -> Path:
|
|
"""
|
|
Get the payload template to send to log in the website
|
|
|
|
:return: the payload template for logging in
|
|
"""
|
|
return self.booking_platform.login_template
|
|
|
|
@property
|
|
def booking_url(self) -> str:
|
|
"""
|
|
Get the URL to the booking page of Gestion-Sports
|
|
|
|
:return: the URL to the booking page
|
|
"""
|
|
return self.booking_platform.booking_url
|
|
|
|
@property
|
|
def booking_template(self) -> Path:
|
|
"""
|
|
Get the payload template to send to book a court
|
|
|
|
:return: the payload template for booking a court
|
|
"""
|
|
return self.booking_platform.booking_template
|
|
|
|
@property
|
|
def user_bookings_url(self) -> str:
|
|
"""
|
|
Get the URL where all the user's bookings are available
|
|
|
|
:return: the URL to the user's bookings
|
|
"""
|
|
return self.booking_platform.user_bookings_url
|
|
|
|
@property
|
|
def user_bookings_template(self) -> Path:
|
|
"""
|
|
Get the payload template to send to get all the user's bookings that are
|
|
available
|
|
|
|
:return: the payload template for the user's bookings
|
|
"""
|
|
return self.booking_platform.user_bookings_template
|
|
|
|
@property
|
|
def cancel_url(self) -> str:
|
|
"""
|
|
Get the URL where all the user's bookings are available
|
|
|
|
:return: the URL to the user's bookings
|
|
"""
|
|
return self.booking_platform.booking_cancellation_url
|
|
|
|
@property
|
|
def cancel_template(self) -> Path:
|
|
"""
|
|
Get the payload template to send to get all the user's bookings that are
|
|
available
|
|
|
|
:return: the payload template for the user's bookings
|
|
"""
|
|
return self.booking_platform.booking_cancel_template
|
|
|
|
@property
|
|
def sessions_url(self) -> str:
|
|
return self.booking_platform.tournaments_sessions_url
|
|
|
|
@property
|
|
def sessions_template(self) -> Path:
|
|
return self.booking_platform.tournaments_sessions_template
|
|
|
|
@property
|
|
def tournaments_url(self) -> str:
|
|
return self.booking_platform.tournaments_list_url
|
|
|
|
@property
|
|
def sports(self) -> dict[str, Sport]:
|
|
"""
|
|
Get a dictionary of all sports, the key is the sport name lowered case
|
|
:return: the dictionary of all sports
|
|
"""
|
|
return self.booking_platform.available_sports
|
|
|
|
|
|
class PlatformDefinition(BaseModel):
|
|
id: str
|
|
name: str
|
|
url: str
|
|
urls: list[Url]
|
|
|
|
|
|
class BookingFilter(BaseModel):
|
|
date: DateTime
|
|
sport_name: str
|
|
|
|
@field_validator("sport_name", mode="before")
|
|
@classmethod
|
|
def to_lower_case(cls, d: str) -> str:
|
|
return d.lower()
|
|
|
|
|
|
class Booking(BaseModel):
|
|
id: int
|
|
booking_date: DateTime = Field(alias="dateResa")
|
|
start_time: DateTime = Field(alias="startTime")
|
|
sport: str
|
|
court: str
|
|
game_creation: Optional[int] = Field(default=None, alias="creaPartie")
|
|
game_creation_limit: Optional[DateTime] = Field(
|
|
default=None, alias="limitCreaPartie"
|
|
)
|
|
cancel: Optional[bool] = Field(default=True)
|
|
block_player_replacement: Optional[int] = Field(
|
|
default=None, alias="bloquerRemplacementJoueur"
|
|
)
|
|
can_remove_parteners: bool = Field(default=True, alias="canRemovePartners")
|
|
end_time: Optional[DateTime] = Field(default=None, alias="endTime")
|
|
day_fr: Optional[str] = Field(default=None, alias="dayFr")
|
|
live_xperience_code: Optional[str] = Field(default=None, alias="codeLiveXperience")
|
|
spartime_qr_code: Optional[str] = Field(default=None, alias="qrCodeSpartime")
|
|
remaining_places: int = Field(default=3, alias="remainingPlaces")
|
|
is_captain: bool = Field(default=True, alias="isCaptain")
|
|
dt_start: Optional[DateTime] = Field(default=None, alias="dtStart")
|
|
credit_card_guaranty: Optional[str] = Field(default=None, alias="garantieCb")
|
|
certificate_validity_duration: Optional[int] = Field(
|
|
alias="dureeValidCertif", default=None
|
|
)
|
|
charge_id: Optional[str] = Field(default=None, alias="chargeId")
|
|
partners: Optional[list] = Field(default=[])
|
|
player_status: Optional[int] = Field(default=None, alias="playerStatus")
|
|
products: Optional[list] = Field(default=[])
|
|
|
|
@field_validator("booking_date", mode="before")
|
|
@classmethod
|
|
def validate_date(cls, d: str) -> DateTime:
|
|
return pendulum.from_format(
|
|
d, "DD/MM/YYYY", tz=pendulum.timezone("Europe/Paris")
|
|
)
|
|
|
|
@field_validator("start_time", "end_time", mode="before")
|
|
@classmethod
|
|
def validate_time(cls, t: str) -> DateTime:
|
|
return pendulum.from_format(t, "HH:mm", tz=pendulum.timezone("Europe/Paris"))
|
|
|
|
@field_validator("game_creation_limit", mode="before")
|
|
@classmethod
|
|
def validate_datetime_add_tz(cls, dt: str) -> DateTime:
|
|
return pendulum.parse(dt, tz=pendulum.timezone("Europe/Paris"))
|
|
|
|
@field_validator("dt_start", mode="before")
|
|
@classmethod
|
|
def validate_datetime(cls, dt: str) -> DateTime:
|
|
return pendulum.parse(dt)
|
|
|
|
@field_validator("sport", mode="before")
|
|
@classmethod
|
|
def to_lower_case(cls, d: str) -> str:
|
|
return d.lower()
|
|
|
|
def matches(self, booking_filter: BookingFilter) -> bool:
|
|
"""
|
|
Check if the booking matches the booking filter
|
|
|
|
:param booking_filter: the conditions the booking should meet
|
|
:return: true if the booking matches the conditions, false otherwise
|
|
"""
|
|
return (
|
|
self.is_same_sport(booking_filter.sport_name)
|
|
and self.is_same_date(booking_filter.date.date())
|
|
and self.is_same_time(booking_filter.date.time())
|
|
)
|
|
|
|
def is_same_sport(self, sport: str) -> bool:
|
|
"""
|
|
Check if the booking and the booking filter are about the same sport
|
|
|
|
:param sport: the sport to test
|
|
:return: true if the sport matches booking sport, false otherwise
|
|
"""
|
|
return self.sport == sport
|
|
|
|
def is_same_date(self, date: Date) -> bool:
|
|
"""
|
|
Check if the booking filter has the same date as the booking
|
|
|
|
:param date: the date to test
|
|
:return: true if the date matches the booking date, false otherwise
|
|
"""
|
|
return self.booking_date.date() == date
|
|
|
|
def is_same_time(self, time: Time) -> bool:
|
|
"""
|
|
Check if the booking filter has the same time as the booking
|
|
|
|
:param time: the time to test
|
|
:return: true if the time matches the booking time, false otherwise
|
|
"""
|
|
return self.start_time.time() == time
|
|
|
|
|
|
class Action(Enum):
|
|
BOOK = "book"
|
|
CANCEL = "cancel"
|
|
TOURNAMENTS = "tournaments"
|
|
|
|
|
|
class Tournament(BaseModel):
|
|
name: str
|
|
price: str
|
|
start_date: DateTime
|
|
end_date: DateTime
|
|
gender: str
|
|
places_left: str | int
|