Skip to content

Commit 5443db8

Browse files
committed
Replace backend and update frontend
1 parent dec8fdc commit 5443db8

File tree

152 files changed

+2445
-1795
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

152 files changed

+2445
-1795
lines changed

.husky/pre-commit

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#!/bin/sh
22
. "$(dirname "$0")/_/husky.sh"
33

4-
./node_modules/.bin/prettier frontend/src/**/*.js --single-quote --trailing-comma es5 --write
5-
./node_modules/.bin/eslint frontend/src --ignore-pattern 'frontend/src/public/' --fix
4+
npm run format:backend
5+
npm run lint:backend
6+
7+
npm run format:frontend
8+
npm run lint:frontend
9+
610
git add .

.husky/pre-push

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#!/bin/sh
22
. "$(dirname "$0")/_/husky.sh"
33

4-
npm run test:frontend
54
npm run test:backend
6-
5+
npm run test:frontend

backend/.flake8

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[flake8]
2+
max-line-length = 88
3+
ignore =
4+
E203,
5+
W503

backend/.isort.cfg

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[settings]
2+
line_length=88
3+
multi_line_output=3
4+
include_trailing_comma=True
5+
use_parentheses=True
6+
force_grid_wrap=0

backend/api/__init__.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import asyncpg
2+
from asyncpg.pool import Pool
3+
4+
from api.utils import log_api_message
5+
from settings import DB_URL, ENV
6+
7+
8+
class DummyPool:
9+
async def close(self):
10+
pass
11+
12+
async def fetch(self, *args, **kwargs):
13+
pass
14+
15+
async def fetchval(self, *args, **kwargs):
16+
pass
17+
18+
19+
async def DbSession() -> Pool:
20+
if ENV == "test":
21+
# see the a mock pool implemented in specs/resolvers/__init__.py
22+
return DummyPool()
23+
24+
try:
25+
pool = await asyncpg.create_pool(DB_URL)
26+
return pool
27+
except Exception as e:
28+
message = f"Error initializing database connection: {e}"
29+
log_api_message(__name__, message)
30+
raise

backend/api/helpers.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from datetime import datetime
2+
3+
from api.resolvers.constants import INVALID_DATE_RANGE
4+
5+
6+
def check_date_validity(checkin_date: datetime, checkout_date: datetime) -> None:
7+
if checkout_date <= checkin_date:
8+
raise ValueError(INVALID_DATE_RANGE)
9+
10+
11+
def calculate_total_charge(
12+
daily_rate: int, checkin_date: datetime, checkout_date: datetime
13+
) -> int:
14+
calculated_days = (checkout_date - checkin_date).days
15+
return daily_rate * calculated_days

backend/api/models.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import datetime
2+
from typing import Any, Dict
3+
4+
from pydantic import BaseModel
5+
6+
from api import utils
7+
8+
9+
class Room(BaseModel):
10+
id: str
11+
num_beds: int
12+
allow_smoking: bool
13+
daily_rate: int
14+
cleaning_fee: int
15+
16+
17+
class Reservation(BaseModel):
18+
id: int
19+
room_id: str
20+
checkin_date: datetime.datetime
21+
checkout_date: datetime.datetime
22+
total_charge: int
23+
24+
25+
class ApiData(BaseModel):
26+
data: Dict[str, Any]
27+
status: utils.StatusCode
File renamed without changes.

backend/api/resolvers/constants.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
INVALID_DATE_RANGE = "Check-out date must be greater than check-in date."
2+
INVALID_DATE_CHECK_IN = "Check-in date must be greater than today."
3+
INVALID_DATE_OVERLAP = "Dates overlap with an existing reservations."
4+
RESERVATION_EXISTS = "Reservation already exists."

backend/api/resolvers/data.py

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from datetime import datetime
2+
from typing import Any, Dict
3+
4+
import api.utils as utils
5+
from api.helpers import calculate_total_charge
6+
from api.models import Reservation, Room
7+
from api.resolvers.constants import INVALID_DATE_CHECK_IN, INVALID_DATE_OVERLAP
8+
9+
10+
async def create_reservation(
11+
db, room_id: str, checkin_date: datetime, checkout_date: datetime
12+
) -> Dict[str, Any]:
13+
available = await is_room_available(
14+
db, room_id=room_id, checkin_date=checkin_date, checkout_date=checkout_date
15+
)
16+
if not available["success"]:
17+
for message in available["errors"]:
18+
utils.log_api_message(__name__, message)
19+
return {"success": False, "errors": available["errors"]}
20+
21+
room_result = await fetch_room(db, room_id)
22+
room = room_result["room"][0]
23+
daily_rate = room.daily_rate
24+
total_charge = calculate_total_charge(daily_rate, checkin_date, checkout_date)
25+
26+
reservation_insert_query = """
27+
INSERT INTO reservations
28+
(room_id, checkin_date, checkout_date, total_charge)
29+
VALUES ($1, $2, $3, $4)
30+
RETURNING id
31+
"""
32+
await db.fetchval(
33+
reservation_insert_query, room_id, checkin_date, checkout_date, total_charge
34+
)
35+
reservations = await fetch_all_rows(db, Reservation)
36+
return reservations
37+
38+
39+
async def delete_reservation(db, reservation_id: str) -> Dict[str, Any]:
40+
reservation = await fetch_reservation(db, reservation_id)
41+
if reservation:
42+
await db.execute("DELETE FROM reservations WHERE id = $1", reservation_id)
43+
return await fetch_all_rows(db, Reservation)
44+
else:
45+
errors = ["Reservation not found"]
46+
return {"success": False, "errors": errors, "reservations": None}
47+
48+
49+
async def is_room_available(
50+
db, room_id: str, checkin_date: datetime, checkout_date: datetime
51+
) -> Dict[str, Any]:
52+
try:
53+
if checkin_date < datetime.now():
54+
message: str = INVALID_DATE_CHECK_IN
55+
utils.log_api_message(__name__, message)
56+
return {"success": False, "errors": [message]}
57+
58+
query = """
59+
SELECT COUNT(*) FROM reservations
60+
WHERE room_id = $1
61+
AND (
62+
(checkin_date >= $2 AND checkin_date < $3)
63+
OR (checkout_date > $2 AND checkout_date <= $3)
64+
OR (checkin_date <= $2 AND checkout_date >= $3)
65+
)
66+
"""
67+
count = await db.fetchval(query, room_id, checkin_date, checkout_date)
68+
69+
if count == 0:
70+
return {"success": True, "errors": None}
71+
else:
72+
return {"success": False, "errors": [INVALID_DATE_OVERLAP]}
73+
74+
except Exception as error:
75+
utils.log_api_message(__name__, str(error))
76+
return {"success": False, "errors": [str(error)]}
77+
78+
79+
async def fetch_all_rows(db, entity_type) -> Dict[str, Any]:
80+
table_name = entity_type.__name__.lower() + "s"
81+
query = f"SELECT * FROM {table_name}"
82+
rows = await db.fetch(query)
83+
if rows:
84+
entities = [entity_type(**dict(row)) for row in rows]
85+
return {"success": True, f"{table_name}": entities}
86+
else:
87+
raise ValueError("No reserved rooms found")
88+
89+
90+
async def fetch_by_id(db, entity_type, id) -> Dict[str, Any]:
91+
table_name = entity_type.__name__.lower() + "s"
92+
query = f"SELECT * FROM {table_name} WHERE id = $1"
93+
rows = await db.fetch(query, id)
94+
if rows:
95+
entities = [entity_type(**dict(row)) for row in rows]
96+
return {"success": True, f"{entity_type.__name__.lower()}": entities}
97+
else:
98+
return {"success": False, f"{entity_type.__name__.lower()}": None}
99+
100+
101+
async def fetch_available_rooms(db, checkin_date, checkout_date) -> Dict[str, Any]:
102+
rooms = await fetch_all_rows(db, Room)
103+
available_rooms = [
104+
room
105+
for room in rooms
106+
if await is_room_available(db, room.id, checkin_date, checkout_date)
107+
]
108+
return {"success": True, "rooms": available_rooms}
109+
110+
111+
async def fetch_room(db, room_id: str) -> Room:
112+
result = await fetch_by_id(db, Room, id=room_id)
113+
if result["success"]:
114+
return result
115+
else:
116+
raise ValueError(f"Room with id {room_id} not found")
117+
118+
119+
async def fetch_reservation(db, reservation_id: id):
120+
result = await fetch_by_id(db, Reservation, id=reservation_id)
121+
if result["success"]:
122+
return result
123+
else:
124+
raise ValueError(f"Reservation with id of {reservation_id} not found")

backend/api/resolvers/mutations.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from typing import Any, Dict, cast
2+
3+
import api.utils as utils
4+
from api import DbSession
5+
from api.helpers import check_date_validity
6+
from api.resolvers.data import create_reservation, delete_reservation
7+
8+
9+
async def create_reservation_resolver(obj, info, input: dict) -> Dict[str, Any]:
10+
db = await DbSession()
11+
try:
12+
room_id = cast(str, input.get("room_id"))
13+
checkin_date = utils.convert_to_local_date_from_str(
14+
str(input.get("checkin_date"))
15+
)
16+
checkout_date = utils.convert_to_local_date_from_str(
17+
str(input.get("checkout_date"))
18+
)
19+
20+
check_date_validity(checkin_date, checkout_date)
21+
22+
result = await create_reservation(db, room_id, checkin_date, checkout_date)
23+
return result
24+
except ValueError as error:
25+
utils.log_api_message(__name__, str(error))
26+
return {"success": False, "errors": [str(error)]}
27+
except Exception as e:
28+
error = f"Unexpected error: {str(e)}"
29+
utils.log_api_message(__name__, f"Unexpected error: {error}")
30+
return {"success": False, "errors": [error]}
31+
finally:
32+
await db.close()
33+
34+
35+
async def delete_reservation_resolver(obj, info, reservationId: int) -> Dict[str, Any]:
36+
db = await DbSession()
37+
try:
38+
result = await delete_reservation(db, reservationId)
39+
return result
40+
except ValueError as error:
41+
utils.log_api_message(__name__, str(error))
42+
return {"success": False, "errors": [str(error)]}
43+
except Exception as e:
44+
error = f"Unexpected error: {str(e)}"
45+
utils.log_api_message(__name__, f"Unexpected error: {error}")
46+
return {"success": False, "errors": [error]}
47+
finally:
48+
await db.close()

backend/api/resolvers/queries.py

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from typing import Any, Dict
2+
3+
import api.utils as utils
4+
from api import DbSession
5+
from api.models import Reservation, Room
6+
from api.resolvers.data import fetch_all_rows, fetch_available_rooms, fetch_reservation
7+
8+
9+
async def get_reservation_resolver(obj, info, id) -> Dict[str, Any]:
10+
try:
11+
db = await DbSession()
12+
13+
result = await fetch_reservation(db, id)
14+
return result
15+
except ValueError as error:
16+
message = f"Error retrieving reservation: {str(error)}"
17+
utils.log_api_message(__name__, message)
18+
return {
19+
"success": False,
20+
"errors": [message],
21+
}
22+
except Exception as e:
23+
error = f"Unexpected error: {str(e)}"
24+
utils.log_api_message(__name__, f"Unexpected error: {error}")
25+
return {"success": False, "errors": [error]}
26+
finally:
27+
await db.close()
28+
29+
30+
async def get_all_reservations_resolver(obj, info) -> Dict[str, Any]:
31+
db = await DbSession()
32+
try:
33+
return await fetch_all_rows(db, Reservation)
34+
except ValueError as error:
35+
utils.log_api_message(__name__, str(error))
36+
return {"success": False, "errors": [str(error)]}
37+
except Exception as e:
38+
error = f"Unexpected error: {str(e)}"
39+
utils.log_api_message(__name__, f"Unexpected error: {error}")
40+
return {"success": False, "errors": [error]}
41+
finally:
42+
await db.close()
43+
44+
45+
async def get_all_rooms_resolver(obj, info) -> Dict[str, Any]:
46+
db = await DbSession()
47+
try:
48+
return await fetch_all_rows(db, Room)
49+
except ValueError as error:
50+
utils.log_api_message(__name__, str(error))
51+
return {"success": False, "errors": [str(error)]}
52+
except Exception as e:
53+
error = f"Unexpected error: {str(e)}"
54+
utils.log_api_message(__name__, f"Unexpected error: {error}")
55+
return {"success": False, "errors": [error]}
56+
finally:
57+
await db.close()
58+
59+
60+
async def get_available_rooms_resolver(obj, info, input: dict) -> Dict[str, Any]:
61+
db = await DbSession()
62+
try:
63+
checkin_date = utils.convert_to_local_date_from_str(
64+
str(input.get("checkin_date"))
65+
)
66+
checkout_date = utils.convert_to_local_date_from_str(
67+
str(input.get("checkout_date"))
68+
)
69+
70+
return await fetch_available_rooms(db, checkin_date, checkout_date)
71+
except ValueError as error:
72+
utils.log_api_message(__name__, str(error))
73+
return {"success": False, "errors": [str(error)]}
74+
except Exception as e:
75+
error = f"Unexpected error: {str(e)}"
76+
utils.log_api_message(__name__, f"Unexpected error: {error}")
77+
return {"success": False, "errors": [error]}
78+
finally:
79+
await db.close()

0 commit comments

Comments
 (0)