Skip to content

Commit 209d4d0

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/frontend/main/bootstrap-5.3.3
2 parents a971cf0 + 74ab1b5 commit 209d4d0

30 files changed

Lines changed: 153 additions & 142 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ cython_debug/
157157

158158
# VS Code
159159
.vscode/
160+
.claude/
160161

161162
.envrc
162163
serverless.yaml

README.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
**JavaScript, Vite, Reactjs, Redux Toolkit, Redux Sagas, Python, FastAPI, GraphQL, AsyncPG, Postgres**
44

5-
[![Application Unit Tests](https://github.com/WillSams/example-js-react-with-python/actions/workflows/pr-validate.yml/badge.svg)](https://github.com/WillSams/eexample-js-react-with-python/actions/workflows/pr-validate.yml)
5+
[![Application Unit Tests](https://github.com/WillSams/example-js-react-with-python/actions/workflows/pr-validate.yml/badge.svg)](https://github.com/WillSams/example-js-react-with-python/actions/workflows/pr-validate.yml)
66

77
This example contains a frontend and backend:
88

99
- The frontend is a [React](https://react.dev) application using [Bootstrap4](https://getbootstrap.com/docs/4.6/getting-started/introduction/) for view designs.
10-
- The backend is a [GraphQL API](https://graphql.org) providing the ability to create, delete, and list reservatios plus available rooms for a given date range.
10+
- The backend is a [GraphQL API](https://graphql.org) providing the ability to create, delete, and list reservations plus available rooms for a given date range.
1111

1212
React [Typescript](https://github.com/WillSams/example-ts-react-with-python) and [Express MVC](https://github.com/WillSams/example-mvc-expressjs-with-python) versions of this same idea are available.
1313

@@ -17,7 +17,7 @@ An [abandoned](https://github.com/WillSams/example-mvc-expressjs-with-python/tre
1717

1818
- When a room is reserved, it cannot be reserved by another guest on overlapping dates.
1919
- Whenever there are multiple available rooms for a request, the room with the lower final price is assigned.
20-
- Whenever a request is made for a single room, a double bed room may be assigned (if no single is available?).
20+
- Whenever a request is made for a single room, a double bed room may be assigned if no single is available.
2121
- Smokers are not placed in non-smoking rooms.
2222
- Non-smokers are not placed in allowed smoking rooms.
2323
- Final price for reservations are determined by daily price * num of days requested, plus the cleaning fee.
@@ -77,11 +77,12 @@ The below are optional but highly recommended:
7777

7878
- [nvm](https://github.com/nvm-sh/nvm) - Used to manage NodeJS versions.
7979
- [Direnv](https://direnv.net/) - Used to manage environment variables.
80-
- Install [direnv](https://direnv.net) for persisting environment variables needed for development.
8180

8281
## Getting Started
8382

84-
First, we'll need to set up our environment variables. You can do this by either any of the methods mentioned in [/tools/ENV.md](./tools/ENV.md) but I recommend using [Direnv](https://direnv.net/).
83+
First, we'll need to set up our environment variables. You can do this by any of the methods mentioned in [/tools/ENV.md](./tools/ENV.md) but I recommend using [Direnv](https://direnv.net/).
84+
85+
Key backend variables include `SECRET_KEY`, `REFRESH_SECRET_KEY`, `PG_URL`, and `ALLOWED_ORIGINS` (a comma-separated list of allowed frontend origins, e.g. `http://localhost:3000`).
8586

8687
### Install Python Packages
8788

@@ -99,11 +100,11 @@ pip install -r requirements.txt
99100
Execute the following within your terminal:
100101

101102
```bash
102-
nvm use # To eliminate any issues, install/use the version listed in .nvmrc.
103-
npm i # install the packages needed for project
104-
cd ../frontend && npm i # install the packages needed for the frontend
105-
cd ../db && npm i # install the packages needed for database migrations
106-
cd .. # navigate back to the root of the repostiory
103+
nvm use # To eliminate any issues, install/use the version listed in .nvmrc.
104+
npm i # install the packages needed for project
105+
cd frontend && npm i # install the packages needed for the frontend
106+
cd ../db && npm i # install the packages needed for database migrations
107+
cd .. # navigate back to the root of the repository
107108
```
108109

109110
### Create the database
@@ -112,10 +113,11 @@ Finally, let's create and seed the databases and our Reservations and Rooms tabl
112113

113114
```bash
114115
# Create the databases and seed them
115-
NODE_ENV=development | npm run refresh && npm run seed
116+
cd db
117+
NODE_ENV=development npm run refresh && NODE_ENV=development npm run seed
116118
```
117119

118-
During development, you can just execute `npm run dev:db-baseline` to refresh the database back to the original seed data.
120+
During development, you can just execute `npm run dev:db-baseline` in the root of the project to refresh the database back to the original seed data.
119121

120122
## Development
121123

@@ -126,17 +128,17 @@ docker-compose up -d # runs the database in the background
126128
npm run dev
127129
```
128130

129-
Also, you just execute the backend via `npm run dev:backend`. to verify the backend is working:
131+
Also, you can just execute the backend via `npm run dev:backend`. To verify the backend is working:
130132

131133
```bash
132134
curl http://localhost:$RESERVATION_PORT/$ENV/about
133135
```
134136

135-
You can also acces the Ariadne GraphiQL (interactive test playground) instance at [http://localhost:$RESERVATION_PORT/$ENV/graphql](http://localhost:$PLAYGROUND_PORT/$ENV/graphql).
137+
You can also access the Ariadne GraphiQL (interactive test playground) instance at [http://localhost:$RESERVATION_PORT/$ENV/graphql](http://localhost:$RESERVATION_PORT/$ENV/graphql).
136138

137139
## Testing
138140

139-
The backend uses [Pytest](https://docs.pytest.org) and the frontend uses [Jest](https://jestjs.io/). To run these tests, simply execute `npm run test:backend` or `npm run test:frontend', respectively.
141+
The backend uses [Pytest](https://docs.pytest.org) and the frontend uses [Jest](https://jestjs.io/). To run these tests, simply execute `npm run test:backend` or `npm run test:frontend`, respectively.
140142

141143
## Containerization
142144

backend/requirements-docker.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
ariadne==0.22
22
asyncpg==0.29.0
33
fastapi==0.109.2
4-
httpx==0.26.0
4+
httpx==0.27.0
55
pydantic==2.6.1
66
python-multipart==0.0.9
77
python-jose==3.3.0

backend/schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type RoomsResult {
4949
}
5050

5151
type Query {
52-
getReservation(roomId: String!): ReservationResult!
52+
getReservation(id: ID!): ReservationResult!
5353
getAllReservations: ReservationsResult!
5454
getAllRooms: RoomsResult!
5555
getAvailableRooms(input: AvailableRoomInput!): RoomsResult!

backend/specs/resolvers/when_cancelling_reservations.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,6 @@
1010

1111

1212
class DescribeDeleteReservationResolver:
13-
def find_by_id(self, reservation_id):
14-
return [
15-
reservation
16-
for reservation in self.reservations
17-
if reservation.id != reservation_id
18-
]
19-
2013
@pytest.mark.asyncio
2114
async def should_delete_existing_reservation(self, mocker):
2215
original_reservations = [
@@ -60,7 +53,7 @@ async def should_delete_existing_reservation(self, mocker):
6053
assert result["reservations"] == expected_reservations
6154

6255
@pytest.mark.asyncio
63-
async def test_should_give_reservation_not_found(self, mocker):
56+
async def should_give_reservation_not_found(self, mocker):
6457
result = await delete_reservation_resolver(
6558
None, MOCK_EXECUTION_CONTEXT, reservationId=999
6659
)

backend/specs/resolvers/when_querying_for_available_rooms.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ async def should_get_available_rooms_all_available(self, mocker):
3131
mocker.AsyncMock(return_value=mock_pool),
3232
)
3333
mocker.patch(
34-
"api.resolvers.data.fetch_all_rows", mocker.AsyncMock(return_value=rooms)
34+
"api.resolvers.data.fetch_all_rows",
35+
mocker.AsyncMock(return_value={"success": True, "rooms": rooms}),
3536
)
3637
mocker.patch(
3738
"api.resolvers.data.fetch_room",

backend/specs/when_authorizing.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import pytest
44
from jose import jwt
55

6-
from auth import create_access_token, create_refresh_token
7-
from settings import ALGORITHM, REFRESH_SECRET_KEY, SECRET_KEY
6+
from auth import create_access_token
7+
from settings import ALGORITHM, SECRET_KEY
88

99

1010
class DescribeAuthorization:
@@ -22,12 +22,3 @@ def should_create_access_token(self, subject, expires_delta):
2222

2323
decoded_token = jwt.decode(generated_token, SECRET_KEY, algorithms=[ALGORITHM])
2424
assert decoded_token["sub"] == subject
25-
26-
def should_create_refresh_token(self, subject, expires_delta):
27-
generated_token = create_refresh_token(subject, expires_delta)
28-
assert generated_token is not None
29-
30-
decoded_token = jwt.decode(
31-
generated_token, REFRESH_SECRET_KEY, algorithms=[ALGORITHM]
32-
)
33-
assert decoded_token["sub"] == subject

backend/src/api/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ class DummyPool:
99
async def close(self):
1010
pass
1111

12+
async def execute(self, *args, **kwargs):
13+
pass
14+
1215
async def fetch(self, *args, **kwargs):
1316
pass
1417

backend/src/api/resolvers/data.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,10 @@ async def create_reservation(
3636
return reservations
3737

3838

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}
39+
async def delete_reservation(db, reservation_id: int) -> Dict[str, Any]:
40+
await fetch_reservation(db, reservation_id)
41+
await db.execute("DELETE FROM reservations WHERE id = $1", reservation_id)
42+
return await fetch_all_rows(db, Reservation)
4743

4844

4945
async def is_room_available(
@@ -80,11 +76,8 @@ async def fetch_all_rows(db, entity_type) -> Dict[str, Any]:
8076
table_name = entity_type.__name__.lower() + "s"
8177
query = f"SELECT * FROM {table_name}"
8278
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")
79+
entities = [entity_type(**dict(row)) for row in rows] if rows else []
80+
return {"success": True, f"{table_name}": entities}
8881

8982

9083
async def fetch_by_id(db, entity_type, id) -> Dict[str, Any]:
@@ -99,11 +92,14 @@ async def fetch_by_id(db, entity_type, id) -> Dict[str, Any]:
9992

10093

10194
async def fetch_available_rooms(db, checkin_date, checkout_date) -> Dict[str, Any]:
102-
rooms = await fetch_all_rows(db, Room)
95+
result = await fetch_all_rows(db, Room)
96+
rooms = result.get("rooms", [])
10397
available_rooms = [
10498
room
10599
for room in rooms
106-
if await is_room_available(db, room.id, checkin_date, checkout_date)
100+
if (await is_room_available(db, room.id, checkin_date, checkout_date))[
101+
"success"
102+
]
107103
]
108104
return {"success": True, "rooms": available_rooms}
109105

@@ -116,7 +112,7 @@ async def fetch_room(db, room_id: str) -> Room:
116112
raise ValueError(f"Room with id {room_id} not found")
117113

118114

119-
async def fetch_reservation(db, reservation_id: id):
115+
async def fetch_reservation(db, reservation_id: int):
120116
result = await fetch_by_id(db, Reservation, id=reservation_id)
121117
if result["success"]:
122118
return result

backend/src/api/resolvers/queries.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77

88

99
async def get_reservation_resolver(obj, info, id) -> Dict[str, Any]:
10+
db = await DbSession()
1011
try:
11-
db = await DbSession()
12-
1312
result = await fetch_reservation(db, id)
1413
return result
1514
except ValueError as error:

0 commit comments

Comments
 (0)