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

pull request to show code #1

Open
wants to merge 65 commits into
base: automaticallyGeneratedCode
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
8e77819
added kotlin allopen
paw787878 May 3, 2023
1f97d4b
created domain entities
paw787878 May 3, 2023
faff851
movies inserted
paw787878 May 3, 2023
bc3c6d8
inserted seats
paw787878 May 3, 2023
f5b6cd3
added comment
paw787878 May 3, 2023
88191c8
added screenings
paw787878 May 3, 2023
5c90012
now can send date parameters
paw787878 May 3, 2023
e4124b0
stopped using old Date
paw787878 May 3, 2023
806ad87
criteria query for movie screenings
paw787878 May 3, 2023
5ab36dd
comment added
paw787878 May 3, 2023
11bcac7
added static metadata generator
paw787878 May 3, 2023
a5c58da
added paging capability
paw787878 May 3, 2023
1ce605c
added order by clause
paw787878 May 3, 2023
4ead81a
presented only requested data
paw787878 May 3, 2023
02d2544
rollback on all exceptions
paw787878 May 3, 2023
ef935b6
comments added
paw787878 May 4, 2023
a9bfa78
got screening info
paw787878 May 4, 2023
1ff2e8c
added service to create reservations
paw787878 May 4, 2023
e981aeb
comment
paw787878 May 4, 2023
3d1f79e
optimistic locking fix
paw787878 May 6, 2023
4bba25a
ticket type and price introduced
paw787878 May 6, 2023
d426b47
require at least one seat in reservation
paw787878 May 6, 2023
cacf9bb
added simple validation rules
paw787878 May 6, 2023
738152c
added capitalization validation
paw787878 May 6, 2023
4c2d361
added also regex validation for name and surname
paw787878 May 6, 2023
05ce075
added todo
paw787878 May 6, 2023
1fd39c2
polish characters handling
paw787878 May 6, 2023
9a86047
name validation tests
paw787878 May 7, 2023
1a7bcbd
used readonly transactions
paw787878 May 7, 2023
763992a
added custom error handling for rest endpoints
paw787878 May 7, 2023
568c5f6
send information if wrong id
paw787878 May 7, 2023
298ed23
replaced require with custom errors
paw787878 May 7, 2023
f2e9599
added exception message for hibernate validator
paw787878 May 7, 2023
567a28c
displayed optimistic lock exception
paw787878 May 7, 2023
b7f17c3
fixed and tested lazy loading
paw787878 May 7, 2023
571b75b
moved database initialization utils to separate service
paw787878 May 7, 2023
8d8f435
database is not initialized in test
paw787878 May 7, 2023
61ddf0b
tests use different data
paw787878 May 7, 2023
32c48ec
movie screening api test
paw787878 May 7, 2023
af65eba
added tests for reservation api
paw787878 May 7, 2023
02f0ff3
now one can book only 15 minutes before screening
paw787878 May 7, 2023
573b954
test reservation placed too late
paw787878 May 8, 2023
11e04ac
removed handled todos
paw787878 May 8, 2023
6cb9b2e
response in case of error is formatted as json
paw787878 May 8, 2023
c1e5c53
refactor
paw787878 May 8, 2023
5a0470d
style fixes
paw787878 May 8, 2023
1bacdd0
wip refactor of dto-s
paw787878 May 8, 2023
be27e61
refactored reservation creation
paw787878 May 8, 2023
470a046
refactor
paw787878 May 8, 2023
37060d2
refactor
paw787878 May 9, 2023
e320a83
rename
paw787878 May 9, 2023
91fe48c
added integration test for screenings
paw787878 May 9, 2023
6457b73
integration test for screening
paw787878 May 9, 2023
a14e602
screening post api test
paw787878 May 9, 2023
18ad083
added test for reservation web api
paw787878 May 9, 2023
536c798
build script added
paw787878 May 9, 2023
e3b08ce
not adding reservations when starting app
paw787878 May 9, 2023
1656155
removed bom
paw787878 May 9, 2023
268596f
first curl request
paw787878 May 9, 2023
ae9636e
bash script with scenario finished
paw787878 May 10, 2023
a23b92e
readme added
paw787878 May 10, 2023
60df68f
readme changes
paw787878 May 10, 2023
76e0250
style fixes
paw787878 May 10, 2023
2fa4ca3
validation fix
paw787878 May 10, 2023
002237f
old comment removal
paw787878 May 10, 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
13 changes: 13 additions & 0 deletions Readme.txt

Choose a reason for hiding this comment

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

Dlaczego akurat Readme.txt zamiast Readme.md?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Traktuję ten pr jako diff po którym można pisać komentarze, ten pr nigdy nie zostanie zmergowany. Z tego powodu nie zwracałem uwagi na to jak nazywa się target oraz source branch. W prawdziwej pracy zrobił bym odwrotnie tak, by po zmergowaniu tego pr zmienił się stan mastera.

Readme.md było by pewnie lepszym wyborem, bo pozwoliło by mi korzystać z markdownu, pewnie jest to też bardziej standardowy wybór. Nie zwróciłem na to uwagi.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
To build and run run build.sh

To run scenario
cd scenario
./scenario.sh

When sending third http request in curl one can use ticket types as follows:
adult: 1,
student: 2,
child: 3

Assumptions:
- I assumed, that reservation expiration date is equal to movie screening start time
20 changes: 20 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id "org.jetbrains.kotlin.kapt" version "1.8.21"
id 'org.springframework.boot' version '3.0.6'
id 'io.spring.dependency-management' version '1.1.0'
id 'org.jetbrains.kotlin.jvm' version '1.7.22'
id 'org.jetbrains.kotlin.plugin.spring' version '1.7.22'
id "org.jetbrains.kotlin.plugin.noarg" version "1.8.21"
id 'org.jetbrains.kotlin.plugin.jpa' version '1.7.22'

id "org.jetbrains.kotlin.plugin.allopen" version "1.8.21"
}

noArg {
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.Embeddable")
annotation("jakarta.persistence.MappedSuperclass")
}

allOpen {
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.Embeddable")
annotation("jakarta.persistence.MappedSuperclass")
}

group = 'com.example'
Expand All @@ -21,9 +37,13 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
implementation 'org.jetbrains.kotlin:kotlin-reflect'
implementation 'org.springframework.boot:spring-boot-starter-validation'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.8.21'

kapt "org.hibernate:hibernate-jpamodelgen:6.2.2.Final"
}

tasks.withType(KotlinCompile) {
Expand Down
3 changes: 3 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

./gradlew bootRun
Empty file modified gradlew
100644 → 100755
Empty file.
11 changes: 11 additions & 0 deletions scenario/reservationRequest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"movieScreeningId": 1,
"name": "Pawel",

Choose a reason for hiding this comment

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

A co z polskimi znakami ?:)

Copy link
Owner Author

Choose a reason for hiding this comment

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

Polskie znaki są obsługiwane, niestety nie wpisałem polskich znaków do tego "scenario" skryptu. Polskie znaki znajdują się w testach

"surname": "Czajka",
"seats": [
{
"seatId": 1,
"ticketType": 1
}
]
}
16 changes: 16 additions & 0 deletions scenario/scenario.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

curl -H "Content-Type: application/json" 'http://localhost:8080/api/screenings?minimalStartTime=2100-01-01T07:00:00.000%2B00:00&maximalEndTime=2100-01-02T23:40:00.000%2B00:00&offset=0&limit=1000' | json_pp

echo ''
echo ''

curl -H "Content-Type: application/json" 'http://localhost:8080/api/screening?screeningId=1' | json_pp

echo ''
echo ''

curl -H "Content-Type: application/json" -d "@reservationRequest.json" -X POST 'http://localhost:8080/api/createReservation' | json_pp

echo ''
echo ''
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
package com.example.ticketbookingapp

import com.example.ticketbookingapp.config.CustomConfigProperties
import com.example.ticketbookingapp.databaseInitialization.DatabaseInitializationForTest
import com.example.ticketbookingapp.databaseInitialization.DatabaseInitializationService
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Profile

@EnableConfigurationProperties(CustomConfigProperties::class)
@SpringBootApplication
class TicketBookingAppApplication
class TicketBookingAppApplication {

@Bean
@Profile("default")
fun dataLoader(
databaseInitializationService: DatabaseInitializationService
): CommandLineRunner {
return CommandLineRunner {
databaseInitializationService.initializeDatabase()
}
}

@Bean
@Profile("test")
fun dataLoaderTest(
databaseInitializationForTest: DatabaseInitializationForTest
): CommandLineRunner {
return CommandLineRunner {
databaseInitializationForTest.initializeDatabase()
}
}
}

fun main(args: Array<String>) {
runApplication<TicketBookingAppApplication>(*args)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.ticketbookingapp.config

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "custom")
data class CustomConfigProperties(
val minimalTimeBetweenBookingAndScreeningStart: Int

Choose a reason for hiding this comment

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

Nie wiadomo, czy ten czas jest w minutach czy w sekundach, jak byś to poprawił :) ?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Jedno rozwiązanie to zmiana nazwy na
minimalTimeBetweenBookingAndScreeningStartInMinutes

Lepsze rozwiązanie to by było zmienić typ tego na Duration. Wówczas mogę w properties napisać
custom.minimalTimeBetweenBookingAndScreeningStart=15m
i to tam wybieram czy to są sekundy czy minuty

)
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.example.ticketbookingapp.databaseInitialization

import com.example.ticketbookingapp.domain.*
import com.example.ticketbookingapp.service.MovieReservationService
import com.example.ticketbookingapp.service.SeatAndTicketType
import jakarta.persistence.EntityManager
import jakarta.persistence.PersistenceContext
import org.springframework.transaction.annotation.Transactional
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.time.LocalDateTime
import java.time.ZoneOffset

@Service
class DatabaseInitializationForTest(
private val reservationService: MovieReservationService,
private val databaseInitializationUtils: DatabaseInitializationUtils,
) {
@PersistenceContext
private lateinit var entityManager: EntityManager

@Transactional(rollbackFor = [Exception::class])
fun initializeDatabase() {
val adultTicketType = databaseInitializationUtils.initializeTicketType("adult", BigDecimal("25"))
val studentTicketType = databaseInitializationUtils.initializeTicketType("student", BigDecimal("18"))
val childTicketType = databaseInitializationUtils.initializeTicketType("child", BigDecimal("12.5"))

val room1 = databaseInitializationUtils.initializeRoom("room 1", 5, 5)
val room2 = databaseInitializationUtils.initializeRoom("room 2", 10, 10)
val room3 = databaseInitializationUtils.initializeRoom("room 3", 15, 15)

val movie1 = databaseInitializationUtils.initializeMovie("Schindler's List", 90)
val movie2 = databaseInitializationUtils.initializeMovie("The Shawshank Redemption", 90)
val movie3 = databaseInitializationUtils.initializeMovie("The Lord of the Rings: The Return of the King", 90)

val screening_1_1 = initializeScreening(room1, movie1, 10)
val screening_1_2 = initializeScreening(room1, movie2, 20)

val screening_2_2 = initializeScreening(room2, movie2, 9)
databaseInitializationUtils.initializeScreening(
room1,
movie1,
LocalDateTime.of(2100, 1, 1, 0, 0, 0),
30
)
initializeScreening(room2, movie3, 21)

initializeScreening(room3, movie3, 11)
initializeScreening(room3, movie1, 19)

// to make sure ids of movie screenings are accessible
entityManager.flush()

val instantInThePast = LocalDateTime.of(1999, 1, 1, 0, 0).toInstant(ZoneOffset.UTC)

reservationService.createReservation(
screening_1_1,
User("Name", "Surname"),
screening_1_1.screeningRoom.seats
.filter { seat -> seat.columnName == "1" }
.map { e -> SeatAndTicketType(e, studentTicketType) },
instantInThePast
)

reservationService.createReservation(
screening_1_1,
User(
"Namee",
"Surnamee",
),
screening_1_1.screeningRoom.seats
.filter { seat -> seat.columnName == "4" }
.map { e -> SeatAndTicketType(e, adultTicketType) },
instantInThePast
)

reservationService.createReservation(
screening_1_2,
User(
"Nameee",
"Surnameee",
),
screening_1_2.screeningRoom.seats
.filter { seat -> seat.columnName == "5" }
.map { e -> SeatAndTicketType(e, childTicketType) },
instantInThePast
)

reservationService.createReservation(
screening_2_2,
User(
"Nameee",
"Surnameee",
),
screening_2_2.screeningRoom.seats
.filter { seat -> seat.columnName == "2" }
.map { e -> SeatAndTicketType(e, childTicketType) },
instantInThePast
)
}

private fun initializeScreening(
screeningRoom: ScreeningRoom,
movie: Movie,
hour: Int,
): MovieScreening {
return databaseInitializationUtils.initializeScreening(
screeningRoom,
movie,
LocalDateTime.of(2000, 1, 1, hour, 0, 0),
30
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.example.ticketbookingapp.databaseInitialization

import com.example.ticketbookingapp.domain.*
import jakarta.persistence.EntityManager
import jakarta.persistence.PersistenceContext
import org.springframework.transaction.annotation.Transactional
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.time.LocalDateTime

@Service
class DatabaseInitializationService(
private val databaseInitializationUtils: DatabaseInitializationUtils,
) {
@PersistenceContext
private lateinit var entityManager: EntityManager

@Transactional(rollbackFor = [Exception::class])
fun initializeDatabase() {
databaseInitializationUtils.initializeTicketType("adult", BigDecimal("25"))
databaseInitializationUtils.initializeTicketType("student", BigDecimal("18"))
databaseInitializationUtils.initializeTicketType("child", BigDecimal("12.5"))

val room1 = databaseInitializationUtils.initializeRoom("room 1", 3, 3)
val room2 = databaseInitializationUtils.initializeRoom("room 2", 15, 15)
val room3 = databaseInitializationUtils.initializeRoom("room 3", 20, 20)

val movie1 = databaseInitializationUtils.initializeMovie("Schindler's List ęóąśłżźćń", 100)
val movie2 = databaseInitializationUtils.initializeMovie("The Shawshank Redemption", 120)
val movie3 = databaseInitializationUtils.initializeMovie("The Lord of the Rings: The Return of the King", 140)

initializeScreening(room1, movie1, 10)
initializeScreening(room1, movie2, 20)

initializeScreening(room2, movie2, 9)
initializeScreening(room2, movie3, 21)

initializeScreening(room3, movie3, 11)
initializeScreening(room3, movie1, 19)

// to make sure ids of movie screenings are accessible
entityManager.flush()

Choose a reason for hiding this comment

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

Wydaje mi się, że to nie byłoby konieczne, jeżeli metody initialize* zwracałyby obiekt z metody save

Copy link
Owner Author

@paw787878 paw787878 May 12, 2023

Choose a reason for hiding this comment

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

Korzystam z metody save() z JpaRepository i te metody initialize* zwracają wynik tej metody.

Nie zdawałem sobie sprawy, że metoda save() zwraca obiekty z ustawionym id, wiedziałem że może być z tym problem jak się używa entityManager.persist i stąd się zabezpieczyłem.

Swoją drogą, nawet jakby metoda save() zwracała obiekty z nie ustawionym id to i tak to flush() tutaj i w klasie DatabaseInitializationForTest nie było by potrzebne, ponieważ nie korzystam z wygenerowanego id, zamiast tego korzystam z referencji do java obiektów. Kiedyś odnosiłem się do id w tutaj poniżej tego flush()

}

private fun initializeScreening(
screeningRoom: ScreeningRoom,
movie: Movie,
hour: Int
): MovieScreening {
return databaseInitializationUtils.initializeScreening(
screeningRoom,
movie,
LocalDateTime.of(2100, 1, 1, hour, 0, 0),
30
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.example.ticketbookingapp.databaseInitialization

import com.example.ticketbookingapp.domain.*
import com.example.ticketbookingapp.repositories.*
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.time.LocalDateTime
import java.time.ZoneOffset

@Service
class DatabaseInitializationUtils(
private val movieRepository: MovieRepository,
private val screeningRoomRepository: ScreeningRoomRepository,
private val seatRepository: SeatRepository,
private val movieScreeningRepository: MovieScreeningRepository,
private val ticketTypeRepository: TicketTypeRepository,
) {
fun initializeTicketType(name: String, price: BigDecimal): TicketType {
return TicketType(name, price).let { ticketTypeRepository.save(it) }
}

fun initializeRoom(roomName: String, numberOfRows: Int, numberOfColumns: Int): ScreeningRoom {
val screeningRoom = ScreeningRoom(roomName).let { screeningRoomRepository.save(it) }
val rowNames = ('A'..'Z').toList()
val seatsArray = buildList {
repeat(numberOfRows) { row ->
add(buildList {
repeat(numberOfColumns) { column ->
add(
Seat(
screeningRoom,
rowNames[row].toString(),
(column + 1).toString()
).let { seatRepository.save(it) }
)
}
})
}
}

for (row in seatsArray) {
for ((rowIndex, seat) in row.withIndex()) {
seat.seatToLeft = row.getOrNull(rowIndex - 1)
seat.seatToRight = row.getOrNull(rowIndex + 1)
}
}

return screeningRoom
}

fun initializeMovie(
movieName: String, movieLengthInMinutes: Int
): Movie {
return Movie(movieName, movieLengthInMinutes)
.let { movieRepository.save(it) }
}

fun initializeScreening(
screeningRoom: ScreeningRoom,
movie: Movie,
dateTimeOfStart: LocalDateTime,
commercialsLengthInMinutes: Int,
): MovieScreening {
val instantOfStart = dateTimeOfStart
.toInstant(ZoneOffset.UTC)
val instantOfEnd =
instantOfStart.plusSeconds((commercialsLengthInMinutes + movie.lengthInMinutes).toLong() * 60)

return MovieScreening(movie, screeningRoom, instantOfStart, instantOfEnd)
.let { movieScreeningRepository.save(it) }
}
}
Loading