Skip to content
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

Full changes log merge from beginning of repo #1

Open
wants to merge 25 commits into
base: pull_request_branch
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b4714fa
Added basic service/controller outlines.
Diamekod0221 Oct 18, 2023
f8f862c
Added ScreeningServiceTesting
Diamekod0221 Oct 19, 2023
07ac26b
Added logging config, Screening tests.
Diamekod0221 Oct 19, 2023
1a809c5
Started writing TicketServiceImpl, started ticket validation.
Diamekod0221 Oct 19, 2023
e2260cb
Restructured project, wrote validation for single ticket. Started for…
Diamekod0221 Oct 20, 2023
dc9b90e
Finished TicketValidator.java, added global exception handler.
Diamekod0221 Oct 24, 2023
946dc28
Finished core code. Time to start testing.
Diamekod0221 Oct 25, 2023
051a8c8
Added tests for cvontrollers and Unit tests for services and subservi…
Diamekod0221 Oct 26, 2023
b7cd42f
Added tests for TicketService. Reordered logic to fix bugs in TicketV…
Diamekod0221 Oct 28, 2023
bdcf05a
Fiished testing
Diamekod0221 Oct 31, 2023
a45ece7
Fixed postMapping problem, improved exception handling and status codes.
Diamekod0221 Oct 31, 2023
a2f33b0
Merge remote-tracking branch 'origin/master'
Diamekod0221 Oct 31, 2023
444d9a9
changed exception handling. Fixed test cases.
Diamekod0221 Oct 31, 2023
e071b25
Cleaned up tests.
Diamekod0221 Oct 31, 2023
fbbbbe3
Added compiled to git
Diamekod0221 Oct 31, 2023
b34036a
Added compiled to git
Diamekod0221 Nov 1, 2023
9606f52
Added sql scripts
Diamekod0221 Nov 1, 2023
4c8a000
Added sql scripts
Diamekod0221 Nov 1, 2023
123330b
Added sql scripts
Diamekod0221 Nov 1, 2023
6b3c3ac
Added sql scripts
Diamekod0221 Nov 1, 2023
602d154
Added sql scripts
Diamekod0221 Nov 1, 2023
f2e6ded
Added sql scripts
Diamekod0221 Nov 1, 2023
9582832
Added sql scripts
Diamekod0221 Nov 1, 2023
4d33a8c
Added sql scripts
Diamekod0221 Nov 1, 2023
4d05730
Added readme, how_to_run.txt, fixed script typos.
Diamekod0221 Nov 1, 2023
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
9 changes: 1 addition & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
Comment on lines -2 to -5

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Celowo się pozbyłeś tych linijek?


### STS ###
.apt_generated
Expand All @@ -24,10 +20,7 @@ target/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/
/logs/
50 changes: 50 additions & 0 deletions bash_scripts/app_preview.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash

BASE_URL="http://localhost:8080"

#values used in the requests
DATES=("2023-11-04T04:15:00" "2023-11-10T11:00:00")
SCREENING_IDS=(1 3)
POST_DATA_FILES=("./post_jsons/json_ticket1.json" "./post_jsons/json_ticket2.json")

HEADERS="-H 'Content-Type:application/json'"

for ((i=0; i<${#DATES[@]}; i++)); do
# Make a GET request to the "/screening/find-screenings/{date}" endpoint
DATE="${DATES[i]}"
DATE_ENDPOINT="/screening/find-screenings/$DATE"
DATE_URL="$BASE_URL$DATE_ENDPOINT"
curl -X GET $HEADERS "$DATE_URL"
if [ $? -eq 0 ]; then
echo "GET request to $DATE_URL was successful."
else
echo "GET request to $DATE_URL failed."
exit 1
fi

# Make a GET request to the "/seat/get-seats/{screeningId}" endpoint
SCREENING_ID="${SCREENING_IDS[i]}"
SEAT_ENDPOINT="/seat/get-seats/$SCREENING_ID"
SEAT_URL="$BASE_URL$SEAT_ENDPOINT"
curl -X GET $HEADERS "$SEAT_URL"
if [ $? -eq 0 ]; then
echo "GET request to $SEAT_URL was successful."
else
echo "GET request to $SEAT_URL failed."
exit 1
fi

# Make a POST request to the "/tickets/book-tickets/" endpoint with JSON data from the specified file
POST_DATA_FILE="${POST_DATA_FILES[i]}"
POST_ENDPOINT="/tickets/book-tickets/"
POST_URL="$BASE_URL$POST_ENDPOINT"
curl -X POST $HEADERS -d "@$POST_DATA_FILE" "$POST_URL"
if [ $? -eq 0 ]; then
echo "POST request to $POST_URL was successful."
else
echo "POST request to $POST_URL failed."
exit 1
fi
done

echo "All requests completed."
41 changes: 41 additions & 0 deletions bash_scripts/db_setup.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

CONTAINER_NAME="mysql-container-nussknacker"

DB_USER="cinema-app"
DB_PASSWORD="EPqKjMPYhwJIeWKVRVQ7"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hasło do bazy danych zapisane jako plain text wygląda groźnie 😨 Czy hasło musi być w repozytorium? Jak mógłbyś to zapisać by nie było zapisane w git'cie ani widoczne dla kogoś kto tego hasła nie ma? 🤔

Copy link
Owner Author

@Diamekod0221 Diamekod0221 Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To prawda, że trochę się zastanawiałem, czy to tak wrzucać, ale to nie jest taki "prawdziwy" secret. Normalnie jest kilka rozwiązań, najbardziej popularne jakie znam, to (przy używaniu apki w dockerze, a ja tego nie zrobiłem), wrzucić secret jako env variable dla kontenera i ustawić w springu jako enviornment variable, np przy użyciu Property. Spring udostępnia też kilka bibliotek takich, jak jasypt czy spring vault pomocnych przy używaniu secretów.

DB_NAME="cinema"

SQL_SCRIPTS_DIR="../sql_scripts"

# Check if the Docker container is already running
if [ ! "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then
# Run the MySQL Docker container if it's not already running
docker run --ulimit nofile=6400:6400 -d --name "$CONTAINER_NAME" -e MYSQL_ROOT_PASSWORD="$DB_PASSWORD" -e MYSQL_DATABASE="$DB_NAME" -e MYSQL_USER="$DB_USER" -e MYSQL_PASSWORD="$DB_PASSWORD" -p 3306:3306 mysql:8.0

# Wait for the MySQL container to be up and running (adjust the timeout as needed)
timeout=60
while ! docker exec "$CONTAINER_NAME" mysql -u"$DB_USER" -p"$DB_PASSWORD" -e "SELECT 1;" &> /dev/null; do
timeout=$((timeout - 1))
if [ "$timeout" -le 0 ]; then
echo "Timeout waiting for MySQL container to start."
exit 1
fi
sleep 5
done
fi

for SCRIPT in "$SQL_SCRIPTS_DIR"/*.sql; do
SCRIPT_NAME=$(basename "$SCRIPT")
Comment on lines +28 to +29

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Znasz może jakieś narzędzia/biblioteki, które pomagają zarządzać wersjonowaniem bazy danych? Np. gdyby zadeklarowane przez ciebie create_schemas.sql się zmieniło, musiałbyś jakoś tą zmianę dodać, jak byś to zrobił? Co gwarantuje Ci, że pliki zostaną wykonane w prawidłowej kolejności?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Znam flyway i Liquibase, korzystałem trochę z Liquibase z tych dwóch. Nie wiem, czy jest tam jakaś opcja, żeby bezpośrednio zmienić plik, chyba nie. Można np zrobić changeset liquibase, dodać ten skrypcik jako referencję i wtedy już zmieniać rzeczy za pomocą liquibase. Bazowy skrypt pozostaje jako podstawa.

Aktualnie gwarancję, że pliki będą wykonane w prawidłowej kolejności daje to, że akurat ułożyły się alfabetycznie. Narzędziami do wersjonowania można sobie to zapewnić niezależnie od tego.


docker exec -i "$CONTAINER_NAME" mysql -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" < "$SCRIPT"

if [ $? -eq 0 ]; then
echo "Script $SCRIPT_NAME executed successfully."
else
echo "Error executing script $SCRIPT_NAME."
exit 1
fi
done

echo "All SQL scripts executed successfully."
18 changes: 18 additions & 0 deletions bash_scripts/post_jsons/json_ticket1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[{
"name":"John",
"surname":"Doe",
"screeningId":1,
"screeningDate":"2023-11-04T04:15:00",
"seatId":1,
"type":"ADULT"
},
{
"name":"Andrzej",
"surname":"Brzęczyszczykiewicz",
"screeningId":1,
"screeningDate":"2023-11-04T04:15:00",
"seatId":2,
"type":"CHILD"
}
]

8 changes: 8 additions & 0 deletions bash_scripts/post_jsons/json_ticket2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[{
"name":"John",
"surname":"Doe",
"screeningId":3,
"screeningDate":"2023-11-10T11:00:00",
"seatId":11,
"type":"ADULT"
}]
17 changes: 17 additions & 0 deletions how_to_run.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
To run the app:

Please install docker, java 21, maven.

Go to /bash_scripts.
First run the db_setup.bash script - it should start a docker container with mysql 8.0 running. The container is

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fajnie, że napisałeś skrypt :) a dlaczego nie zdecydowałeś się na użycie docker-compose?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mvn spring-boot::run to jest moje preferowane podejście przy takich szybkich odpaleniach, nie trzeba nic konfigurować na classpath, zwłaszcza jeśli chodzi o zewnętrzne pliki. Budowanie jara i docker-compose czasem wymagają jakiegoś grzebania, a nie bardzo miałem na to czas. Myślę, że w tym przypadku są to równie eleganckie rozwiązania, maven odpala się zwykle szybciej niż docker i bez problemu.

scheduled for 3306 port (default docker). Then the script will attempt to populate the db with initial tables and values.
If everything is ok, "All SQL scripts executed successfully." Should be printed.

If the docker container runs properly and the db is filled, run (consider a detached terminal)
mvn spring-boot::run
in the repo directory, it should start the app on localhost:8080.

For a live use case, please run app_preview.bash - it will run two sequences of how the app could be used. If everything
is ok, the respective curl requests and responses should appear, ending with "All requests completed."

Feel free to fiddle around with the app, hopefully you break something :)
34 changes: 33 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,25 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
Expand All @@ -41,7 +59,21 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fajnie, że używasz in-memory db - h2 👍

</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
Expand Down
69 changes: 69 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
About the app:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dlaczego wybrałeś README jako readme.txt a nie readme.md? Jaka jest różnica i co by Tobie (i użytkownikom) to dało? :)
Chyba, że celowo zamysł był na taki low-tech vibe 🤓

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lubię .txt :) Różnica jest taka, że w markdownie można dodać lepszy formatting i np hyperlinki. Trochę ładniej możnaby zrobić.


I have treated this task more as a prototype app than production-grade code. Following this philosophy, I wanted to
have a cleanly designed app that is decoupled and open for extension, but only provides minimal, required, functionality.

This approach is also applicable to testing - I have written some sanity tests for the main logic, places where stuff can go wrong
and some integration tests for the database. I've tried to come up with test cases that could break the app,
but please don't expect 100% coverage and a bulletproof app, as that would take a lot of time and I felt it wouldn't
really make sense.

I implemented all the functionalities required in the pdf file (TouK_recruitment_task_ticket_booking_app_basic).
I assumed that reserving tickets is different from buying tickets, so the app provides 3 states for a seat -
AVAILABLE, RESERVED, BOUGHT. Reservations are valid for 24 hours. Every day at 2AM, a task runner deletes outdated
reservations from the database and frees the reserved seats.

As for the database - I have used a relational db (MySQL, but this is no personal preference). The entities are pretty
heavily constrained and I don't think there's much redundancy, but some logic is not enforced by the db - for instance,
there is a RoomRowEntity which has a numberOfSeats field, but the number of actual seats referencing that RoomRow isn't
bound by that value. In a production app this should of course be changed. I've put some indexes on columns that are
frequently looked up.


Usage:
The app exposes 3 rest endpoints -

/screening/find-screenings/{date} - get
/seat/get-seats/{screeningId} - get
/tickets/book-tickets/ - post

The user calls the screenings endpoint with a date and time and receives a sorted list of screenings in the time period
(date - 30mins, date + 4h).

The user then calls the /get-seats/ endpoint with a particular screeningId and receives a list of seats for this particular
screening - as there cannot be a single place left over in a row between two already reserved places, the user receives
full list - available and unavailable seats.

The user then calls the post /book-tickets/ endpoint with a json list of tickets. Tickets are of the format:
{
"name":"John",
"surname":"Doe",
"screeningId":1,
"screeningDate":"2023-11-02 04:15:00",
"seatId":1,
"type":"ADULT"
}

I have wondered a bit about how to realize that part - I've thought about implementing some simple session mechanism or mock
that would enable the user to book tickets in a timeframe, but, as the pdf requirements stated, operations were to be
exposed as REST, so that would be in conflict. It would also take a significant amount of work to provide some security
user-based session mechanisms and test them. In the end I've chosen a completely stateless approach where the whole
request is processed in one server call.

As the business use case states that the user chooses a particular screening, and I think it makes sense to only book for
one screening, all tickets have to be for the same screening.

Tickets can have different holders - it makes sense in a real world scenario (someone can pick up his ticket without
the person making the reservation) and was cleaner to implement :).

The holder's name and surname should obey the specification provided in the .pdf file. If the surname is two-part,
dashed, both parts must obey the minimum 3 letter length and other rules.

If the tickets are valid, the system logs them into the db, stamps a reservation expiration date and returns a .json
Reservation object of the form List<Ticket>, ticketCostSum, expirationDate.

I've also provided a
screenings/find-screenings/{movie}/{date} - get endpoint
to find screenings of a particular movie, seemed natural, but I didn't test that very much.

Please read how_to_run.txt for running manual.
Loading