From d6e7a74cfcf1078a0c9105c682e690ac05ae1542 Mon Sep 17 00:00:00 2001 From: PhilippFaber <46455902+PhilippFaber@users.noreply.github.com> Date: Fri, 20 Jan 2023 11:46:36 +0100 Subject: [PATCH] Datenbankmigration (#67) * add liquibase * add model with some hacks test * wip * update Backend * update backend * update timeformat in db * update build gradle * update file urls * Added basic database model * fix frontend + migrations * update readme * remove andrena plans Co-authored-by: Daniel Thoma --- README.md | 20 +--- application/build.gradle.kts | 6 +- .../kotlin/replace/dto/BookableEntityDto.kt | 61 ++++++---- .../replace/dto/BookableEntityTypeDto.kt | 23 ++-- .../jvmMain/kotlin/replace/dto/BookingDto.kt | 44 +++++--- .../replace/dto/CreateBookableEntityDto.kt | 14 +++ .../dto/CreateBookableEntityTypeDto.kt | 6 +- .../kotlin/replace/dto/CreateBookingDto.kt | 10 ++ .../kotlin/replace/dto/CreateFloorDto.kt | 10 ++ .../kotlin/replace/dto/CreateSiteDto.kt | 6 +- .../src/jvmMain/kotlin/replace/dto/Dto.kt | 4 +- .../src/jvmMain/kotlin/replace/dto/FileDto.kt | 18 +-- .../kotlin/replace/dto/FileUploadDto.kt | 16 +-- .../jvmMain/kotlin/replace/dto/FloorDto.kt | 68 ++++------- .../jvmMain/kotlin/replace/dto/ModelDto.kt | 5 + .../src/jvmMain/kotlin/replace/dto/SiteDto.kt | 23 ++-- .../replace/dto/TemporaryFileUploadDto.kt | 20 +--- .../replace/dto/UpdateBookableEntityDto.kt | 16 +++ .../dto/UpdateBookableEntityTypeDto.kt | 9 ++ .../kotlin/replace/dto/UpdateBookingDto.kt | 12 ++ .../kotlin/replace/dto/UpdateFloorDto.kt | 11 ++ .../kotlin/replace/dto/UpdateSiteDto.kt | 9 ++ .../src/jvmMain/kotlin/replace/dto/UserDto.kt | 33 ++++-- .../CreateBookableEntityUseCase.kt | 32 +++--- .../UpdateBookableEntityUseCase.kt | 27 +++-- .../CreateBookableEntityTypeUseCase.kt | 23 ++-- .../UpdateBookableEntityTypeUseCase.kt | 22 ++-- .../usecase/booking/CreateBookingUseCase.kt | 37 +++--- .../replace/usecase/file/CreateFileUseCase.kt | 44 ++++---- .../replace/usecase/file/DeleteFileUseCase.kt | 22 ++-- .../usecase/floor/CreateFloorUseCase.kt | 43 +++---- .../usecase/floor/SaveFloorPlanFileUseCase.kt | 34 ------ .../usecase/floor/UpdateFloorUseCase.kt | 34 +++--- .../replace/usecase/site/CreateSiteUseCase.kt | 17 +-- .../replace/usecase/site/UpdateSiteUseCase.kt | 19 ++-- .../CreateTemporaryFileUploadUseCase.kt | 33 +++--- .../DeleteTemporaryFileUploadUseCase.kt | 36 +++--- ...aveTemporaryFileUploadPersistentUseCase.kt | 29 ++--- build.gradle.kts | 2 +- domain/build.gradle.kts | 8 +- .../datastore/BookableEntityRepository.kt | 8 -- .../datastore/BookableEntityTypeRepository.kt | 7 -- .../replace/datastore/FloorRepository.kt | 8 -- .../kotlin/replace/datastore/Repository.kt | 12 -- .../datastore/TemporaryFileRepository.kt | 8 -- .../replace/datastore/UserRepository.kt | 7 -- .../kotlin/replace/datastore/aliases.kt | 11 -- .../kotlin/replace/model/BookableEntities.kt | 29 +++++ .../kotlin/replace/model/BookableEntity.kt | 13 --- .../replace/model/BookableEntityTypes.kt | 15 +++ .../kotlin/replace/model/BookedEntities.kt | 19 ++++ .../jvmMain/kotlin/replace/model/Booking.kt | 13 --- .../jvmMain/kotlin/replace/model/Bookings.kt | 25 +++++ .../src/jvmMain/kotlin/replace/model/File.kt | 12 -- .../src/jvmMain/kotlin/replace/model/Files.kt | 21 ++++ .../src/jvmMain/kotlin/replace/model/Floor.kt | 12 -- .../jvmMain/kotlin/replace/model/Floors.kt | 23 ++++ .../jvmMain/kotlin/replace/model/Models.kt | 21 ++++ .../kotlin/replace/model/ObjectWithId.kt | 13 --- .../src/jvmMain/kotlin/replace/model/Sites.kt | 15 +++ .../kotlin/replace/model/TemporaryFile.kt | 15 --- .../kotlin/replace/model/TemporaryFiles.kt | 27 +++++ .../src/jvmMain/kotlin/replace/model/User.kt | 11 -- .../kotlin/replace/model/UserSession.kt | 11 +- .../src/jvmMain/kotlin/replace/model/Users.kt | 21 ++++ gradle/libs.versions.toml | 14 ++- infrastructure/build.gradle.kts | 77 ++++++++++++- .../src/jvmMain/kotlin/replace/KtorBackend.kt | 13 +-- .../MongoBookableEntityRepository.kt | 11 -- .../MongoBookableEntityTypeRepository.kt | 11 -- .../replace/datastore/MongoFloorRepository.kt | 12 -- .../replace/datastore/MongoRepository.kt | 22 ---- .../datastore/MongoTemporaryFileRepository.kt | 13 --- .../replace/datastore/MongoUserRepository.kt | 11 -- .../kotlin/replace/http/ApplicationModule.kt | 76 +++++++------ .../replace/http/AuthenticationModule.kt | 7 +- .../replace/http/AuthenticationRouting.kt | 60 +++++----- .../kotlin/replace/http/ControllerRouting.kt | 18 ++- .../kotlin/replace/http/RepositoryModule.kt | 52 ++++----- .../controller/BookableEntityController.kt | 23 ++-- .../BookableEntityTypeController.kt | 15 ++- .../http/controller/BookingController.kt | 41 +++++-- .../replace/http/controller/FileController.kt | 31 +++-- .../http/controller/FloorController.kt | 48 +++----- .../replace/http/controller/SiteController.kt | 37 +++--- .../TemporaryFileUploadController.kt | 33 +++--- .../replace/http/controller/UserController.kt | 9 +- .../job/DeleteOldTemporaryFileUploadsJob.kt | 24 ++-- .../replace/serializer/InstantSerializer.kt | 16 --- .../replace/serializer/ObjectIdSerializer.kt | 15 --- .../resources/application.conf.example | 19 ++-- .../jvmMain/resources/db/changelog-root.json | 8 ++ .../jvmMain/resources/db/changelog-stub.json | 13 +++ ...023-01-17_23-50-46_create_users_table.json | 70 ++++++++++++ ...18-28-14_create_temporary_files_table.json | 82 ++++++++++++++ ...023-01-19_18-38-52_create_files_table.json | 73 ++++++++++++ ...023-01-19_18-46-48_create_sites_table.json | 40 +++++++ ...23-01-19_18-48-19_create_floors_table.json | 61 ++++++++++ ...21_create_bookable_entity_types_table.json | 40 +++++++ ...-01-19_19-02-00_create_bookings_table.json | 59 ++++++++++ ...19-06-14_create_bookable_entity_table.json | 70 ++++++++++++ ...19-10-08_create_booked_entities_table.json | 51 +++++++++ .../app/core/openapi/.openapi-generator/FILES | 9 ++ .../app/core/openapi/api/default.service.ts | 106 +++++++++--------- .../core/openapi/model/bookableEntityDto.ts | 4 + .../openapi/model/bookableEntityTypeDto.ts | 2 + .../src/app/core/openapi/model/bookingDto.ts | 14 ++- .../openapi/model/createBookableEntityDto.ts | 20 ++++ .../model/createBookableEntityTypeDto.ts | 17 +++ .../core/openapi/model/createBookingDto.ts | 19 ++++ .../app/core/openapi/model/createFloorDto.ts | 20 ++++ .../app/core/openapi/model/createSiteDto.ts | 17 +++ .../src/app/core/openapi/model/fileDto.ts | 23 ++++ .../app/core/openapi/model/fileUploadDto.ts | 2 +- .../src/app/core/openapi/model/floorDto.ts | 10 +- .../src/app/core/openapi/model/instant.ts | 19 ++++ .../src/app/core/openapi/model/models.ts | 9 ++ .../src/app/core/openapi/model/siteDto.ts | 2 + .../openapi/model/temporaryFileUploadDto.ts | 1 + .../openapi/model/updateBookableEntityDto.ts | 21 ++++ .../app/core/openapi/model/updateFloorDto.ts | 21 ++++ .../app/core/openapi/model/updateSiteDto.ts | 18 +++ .../src/app/core/openapi/model/userDto.ts | 7 +- ...ate-or-update-bookable-entity.component.ts | 8 +- .../floor/pages/edit/edit.component.ts | 24 +++- .../reservation/reservation.component.html | 51 +-------- .../reservation/reservation.component.ts | 47 ++++---- .../create-or-update-floor.component.ts | 10 +- .../site/pages/create/create.component.ts | 4 +- .../site/pages/edit/edit.component.ts | 11 +- .../file-upload/file-upload.component.ts | 12 +- .../user-layout/user-layout.component.ts | 2 +- web/src/angular/src/app/util/FileUtil.ts | 4 +- .../angular/src/assets/andrena_Darmstadt.png | Bin 3332785 -> 0 bytes .../angular/src/assets/andrena_Frankfurt.png | Bin 2532577 -> 0 bytes 135 files changed, 1983 insertions(+), 1109 deletions(-) create mode 100644 application/src/jvmMain/kotlin/replace/dto/CreateBookableEntityDto.kt rename domain/src/jvmMain/kotlin/replace/model/BookableEntityType.kt => application/src/jvmMain/kotlin/replace/dto/CreateBookableEntityTypeDto.kt (52%) create mode 100644 application/src/jvmMain/kotlin/replace/dto/CreateBookingDto.kt create mode 100644 application/src/jvmMain/kotlin/replace/dto/CreateFloorDto.kt rename domain/src/jvmMain/kotlin/replace/model/Site.kt => application/src/jvmMain/kotlin/replace/dto/CreateSiteDto.kt (57%) create mode 100644 application/src/jvmMain/kotlin/replace/dto/ModelDto.kt create mode 100644 application/src/jvmMain/kotlin/replace/dto/UpdateBookableEntityDto.kt create mode 100644 application/src/jvmMain/kotlin/replace/dto/UpdateBookableEntityTypeDto.kt create mode 100644 application/src/jvmMain/kotlin/replace/dto/UpdateBookingDto.kt create mode 100644 application/src/jvmMain/kotlin/replace/dto/UpdateFloorDto.kt create mode 100644 application/src/jvmMain/kotlin/replace/dto/UpdateSiteDto.kt delete mode 100644 application/src/jvmMain/kotlin/replace/usecase/floor/SaveFloorPlanFileUseCase.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/datastore/BookableEntityRepository.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/datastore/BookableEntityTypeRepository.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/datastore/FloorRepository.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/datastore/Repository.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/datastore/TemporaryFileRepository.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/datastore/UserRepository.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/datastore/aliases.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/BookableEntities.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/model/BookableEntity.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/BookableEntityTypes.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/BookedEntities.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/model/Booking.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/Bookings.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/model/File.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/Files.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/model/Floor.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/Floors.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/Models.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/model/ObjectWithId.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/Sites.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/model/TemporaryFile.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/TemporaryFiles.kt delete mode 100644 domain/src/jvmMain/kotlin/replace/model/User.kt create mode 100644 domain/src/jvmMain/kotlin/replace/model/Users.kt delete mode 100644 infrastructure/src/jvmMain/kotlin/replace/datastore/MongoBookableEntityRepository.kt delete mode 100644 infrastructure/src/jvmMain/kotlin/replace/datastore/MongoBookableEntityTypeRepository.kt delete mode 100644 infrastructure/src/jvmMain/kotlin/replace/datastore/MongoFloorRepository.kt delete mode 100644 infrastructure/src/jvmMain/kotlin/replace/datastore/MongoRepository.kt delete mode 100644 infrastructure/src/jvmMain/kotlin/replace/datastore/MongoTemporaryFileRepository.kt delete mode 100644 infrastructure/src/jvmMain/kotlin/replace/datastore/MongoUserRepository.kt delete mode 100644 infrastructure/src/jvmMain/kotlin/replace/serializer/InstantSerializer.kt delete mode 100644 infrastructure/src/jvmMain/kotlin/replace/serializer/ObjectIdSerializer.kt create mode 100644 infrastructure/src/jvmMain/resources/db/changelog-root.json create mode 100644 infrastructure/src/jvmMain/resources/db/changelog-stub.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-17_23-50-46_create_users_table.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-28-14_create_temporary_files_table.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-38-52_create_files_table.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-46-48_create_sites_table.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-48-19_create_floors_table.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-00-21_create_bookable_entity_types_table.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-02-00_create_bookings_table.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-06-14_create_bookable_entity_table.json create mode 100644 infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-10-08_create_booked_entities_table.json create mode 100644 web/src/angular/src/app/core/openapi/model/createBookableEntityDto.ts create mode 100644 web/src/angular/src/app/core/openapi/model/createBookableEntityTypeDto.ts create mode 100644 web/src/angular/src/app/core/openapi/model/createBookingDto.ts create mode 100644 web/src/angular/src/app/core/openapi/model/createFloorDto.ts create mode 100644 web/src/angular/src/app/core/openapi/model/createSiteDto.ts create mode 100644 web/src/angular/src/app/core/openapi/model/fileDto.ts create mode 100644 web/src/angular/src/app/core/openapi/model/instant.ts create mode 100644 web/src/angular/src/app/core/openapi/model/updateBookableEntityDto.ts create mode 100644 web/src/angular/src/app/core/openapi/model/updateFloorDto.ts create mode 100644 web/src/angular/src/app/core/openapi/model/updateSiteDto.ts delete mode 100644 web/src/angular/src/assets/andrena_Darmstadt.png delete mode 100644 web/src/angular/src/assets/andrena_Frankfurt.png diff --git a/README.md b/README.md index 5413b421..ce0ed447 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Technologien - Backend: [Ktor](https://ktor.io/) -- Datatabase: [MongoDB](https://www.mongodb.com) +- Datatabase: [PostgreSQL](https://www.postgresql.org/) - Frontend: - [Angular](https://angular.io/) - [Angular Material](https://material.angular.io/) @@ -25,19 +25,6 @@ - In `/infrastructure/src/jvmMain/resources/application.conf` configure your database settings - If you want to contribute to the project run `./gradlew addKtlintCheckGitPreCommitHook` beforehand - execute `yarn install` in `/web/src/angular` to install frontend packages -- add a `user` collection in your database and add an initial user. Copy-Paste Example: - -```json -{ - "_id": { - "$oid": "6391c928a8d80e8729177c7f" - }, - "username": "user", - "password": "password", - "firstName": "Max", - "lastName": "Mustermann" -} -```` ### Run @@ -46,8 +33,9 @@ ### Notes +- When running ktor in Dev Mode, it will attempt to seed a dev user. See Console output for more information. - Frontend runs on port 4200, backend on port 8000. Frontend requests are getting proxied by angular to port 8000, configure in `/web/src/angular/proxy.conf.json` -- Frontend hot reloads, backends needs to get restartet. +- Frontend hot reloads, backends needs to get restarted. - For faster starting time of the backend, comment out `commonMainRuntimeOnly(project(":replace-web"))` in `/build.gradle.kts`. - Should look like this: @@ -61,5 +49,5 @@ ``` - - frontend won't be compiled and served anymore by Ktor, which is irrelevant in Dev, since we are using the angular dev server +- frontend won't be compiled and served anymore by Ktor, which is irrelevant in Dev, since we are using the angular dev server - You can execute `./gradlew ktlintCheck` and `./gradlew ktlintFormat` to check for backend formatting (alternatively ctrl + ctrl in intellij ot execute gradle commands) diff --git a/application/build.gradle.kts b/application/build.gradle.kts index f52b34c7..92152159 100644 --- a/application/build.gradle.kts +++ b/application/build.gradle.kts @@ -11,5 +11,9 @@ dependencies { jvmMainImplementation(libs.logging.impl) jvmMainImplementation(libs.logging.core) jvmMainImplementation(libs.kotlinx.serialization) - commonMainImplementation(libs.kotlinx.datetime) + jvmMainImplementation(libs.exposed.core) + jvmMainImplementation(libs.exposed.dao) + jvmMainImplementation(libs.exposed.jdbc) + jvmMainImplementation(libs.exposed.java.time) + jvmMainImplementation(libs.kotlinx.datetime) } diff --git a/application/src/jvmMain/kotlin/replace/dto/BookableEntityDto.kt b/application/src/jvmMain/kotlin/replace/dto/BookableEntityDto.kt index 71264955..2db6be45 100644 --- a/application/src/jvmMain/kotlin/replace/dto/BookableEntityDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/BookableEntityDto.kt @@ -1,30 +1,53 @@ package replace.dto import kotlinx.serialization.Serializable -import org.bson.types.ObjectId import replace.model.BookableEntity +import kotlin.reflect.KProperty1 @Serializable data class BookableEntityDto( - override val id: String? = null, + override val id: String, + val name: String, + val floorId: String, + val parentId: String? = null, - val type: BookableEntityTypeDto? = null -) : Dto - -fun BookableEntity.toDto() = BookableEntityDto( - id = id?.toHexString(), - name = name, - floorId = floorId.toHexString(), - parentId = parentId?.toHexString(), - type = type?.toDto() -) - -fun BookableEntityDto.toModel() = - BookableEntity( - name, - type?.toModel(), - ObjectId(floorId), - if (parentId != null) ObjectId(parentId) else null + + val typeId: String? = null, + + val floor: FloorDto? = null, + val parent: BookableEntityDto? = null, + val type: BookableEntityTypeDto? = null, +) : ModelDto + +fun BookableEntity.toDto(with: List> = emptyList()): BookableEntityDto { + val floor = if (with.contains(BookableEntity::floor)) { + floor.toDto() + } else { + null + } + + val parent = if (with.contains(BookableEntity::parent)) { + parent?.toDto() + } else { + null + } + + val type = if (with.contains(BookableEntity::type)) { + type?.toDto() + } else { + null + } + + return BookableEntityDto( + id = id.value, + name = name, + floorId = floorId.value, + parentId = parentId?.value, + typeId = typeId?.value, + floor = floor, + parent = parent, + type = type, ) +} diff --git a/application/src/jvmMain/kotlin/replace/dto/BookableEntityTypeDto.kt b/application/src/jvmMain/kotlin/replace/dto/BookableEntityTypeDto.kt index 71fe7ae3..e10285f3 100644 --- a/application/src/jvmMain/kotlin/replace/dto/BookableEntityTypeDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/BookableEntityTypeDto.kt @@ -2,16 +2,25 @@ package replace.dto import kotlinx.serialization.Serializable import replace.model.BookableEntityType +import kotlin.reflect.KProperty1 @Serializable data class BookableEntityTypeDto( - override val id: String? = null, + override val id: String, val name: String, -) : Dto + val bookableEntities: List? = null, +) : ModelDto -fun BookableEntityType.toDto() = BookableEntityTypeDto( - id = id?.toHexString(), - name = name, -) +fun BookableEntityType.toDto(with: List> = emptyList()): BookableEntityTypeDto { + val bookableEntities = if (with.contains(BookableEntityType::bookableEntities)) { + bookableEntities.map { it.toDto() } + } else { + null + } -fun BookableEntityTypeDto.toModel() = BookableEntityType(name) + return BookableEntityTypeDto( + id = id.value, + name = name, + bookableEntities = bookableEntities, + ) +} diff --git a/application/src/jvmMain/kotlin/replace/dto/BookingDto.kt b/application/src/jvmMain/kotlin/replace/dto/BookingDto.kt index 362774bf..49629957 100644 --- a/application/src/jvmMain/kotlin/replace/dto/BookingDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/BookingDto.kt @@ -1,23 +1,39 @@ package replace.dto -import kotlinx.datetime.Instant import kotlinx.serialization.Serializable -import org.bson.types.ObjectId import replace.model.Booking +import kotlin.reflect.KProperty1 @Serializable data class BookingDto( - override val id: String? = null, - val bookedEntities: List, - val startDateTime: String, - val endDateTime: String, -) : Dto + override val id: String, + val userId: String, + val user: UserDto? = null, + val bookedEntities: List? = null, + val start: String, + val end: String +) : ModelDto -fun Booking.toDto() = BookingDto( - id = id?.toHexString(), - bookedEntities = bookedEntities.map { it.toHexString() }, - startDateTime = startDateTime.toString(), - endDateTime = endDateTime.toString(), -) +fun Booking.toDto(with: List> = emptyList()): BookingDto { -fun BookingDto.toModel() = Booking(bookedEntities.map { ObjectId(it) }, Instant.parse(startDateTime), Instant.parse(endDateTime)) + val bookedEntities = if (with.contains(Booking::bookedEntities)) { + bookedEntities.map { it.toDto() } + } else { + null + } + + val user = if (with.contains(Booking::user)) { + user.toDto() + } else { + null + } + + return BookingDto( + id = id.value, + userId = userId.value, + start = start.toString(), + end = end.toString(), + bookedEntities = bookedEntities, + user = user, + ) +} diff --git a/application/src/jvmMain/kotlin/replace/dto/CreateBookableEntityDto.kt b/application/src/jvmMain/kotlin/replace/dto/CreateBookableEntityDto.kt new file mode 100644 index 00000000..60c837ae --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/CreateBookableEntityDto.kt @@ -0,0 +1,14 @@ +package replace.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class CreateBookableEntityDto( + val name: String, + + val floorId: String, + + val parentId: String? = null, + + val typeId: String? = null, +) diff --git a/domain/src/jvmMain/kotlin/replace/model/BookableEntityType.kt b/application/src/jvmMain/kotlin/replace/dto/CreateBookableEntityTypeDto.kt similarity index 52% rename from domain/src/jvmMain/kotlin/replace/model/BookableEntityType.kt rename to application/src/jvmMain/kotlin/replace/dto/CreateBookableEntityTypeDto.kt index 8c49c706..52a3f1b5 100644 --- a/domain/src/jvmMain/kotlin/replace/model/BookableEntityType.kt +++ b/application/src/jvmMain/kotlin/replace/dto/CreateBookableEntityTypeDto.kt @@ -1,8 +1,8 @@ -package replace.model +package replace.dto import kotlinx.serialization.Serializable @Serializable -data class BookableEntityType( +data class CreateBookableEntityTypeDto( val name: String, -) : ObjectWithId() +) diff --git a/application/src/jvmMain/kotlin/replace/dto/CreateBookingDto.kt b/application/src/jvmMain/kotlin/replace/dto/CreateBookingDto.kt new file mode 100644 index 00000000..1f407f12 --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/CreateBookingDto.kt @@ -0,0 +1,10 @@ +package replace.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class CreateBookingDto( + val bookedEntityIds: List, + val start: String, + val end: String +) diff --git a/application/src/jvmMain/kotlin/replace/dto/CreateFloorDto.kt b/application/src/jvmMain/kotlin/replace/dto/CreateFloorDto.kt new file mode 100644 index 00000000..42389fd0 --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/CreateFloorDto.kt @@ -0,0 +1,10 @@ +package replace.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class CreateFloorDto( + val name: String, + val siteId: String, + val planFile: FileUploadDto? = null, +) diff --git a/domain/src/jvmMain/kotlin/replace/model/Site.kt b/application/src/jvmMain/kotlin/replace/dto/CreateSiteDto.kt similarity index 57% rename from domain/src/jvmMain/kotlin/replace/model/Site.kt rename to application/src/jvmMain/kotlin/replace/dto/CreateSiteDto.kt index e2be1add..27c798f6 100644 --- a/domain/src/jvmMain/kotlin/replace/model/Site.kt +++ b/application/src/jvmMain/kotlin/replace/dto/CreateSiteDto.kt @@ -1,8 +1,8 @@ -package replace.model +package replace.dto import kotlinx.serialization.Serializable @Serializable -data class Site( +class CreateSiteDto( val name: String, -) : ObjectWithId() +) diff --git a/application/src/jvmMain/kotlin/replace/dto/Dto.kt b/application/src/jvmMain/kotlin/replace/dto/Dto.kt index c2e57d39..2e1e423c 100644 --- a/application/src/jvmMain/kotlin/replace/dto/Dto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/Dto.kt @@ -1,5 +1,5 @@ package replace.dto -interface Dto { - val id: String? +abstract class Dto { + fun validate() {} } diff --git a/application/src/jvmMain/kotlin/replace/dto/FileDto.kt b/application/src/jvmMain/kotlin/replace/dto/FileDto.kt index 93d581da..9dfaa726 100644 --- a/application/src/jvmMain/kotlin/replace/dto/FileDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/FileDto.kt @@ -4,28 +4,22 @@ import kotlinx.serialization.Serializable import replace.model.File @Serializable -class FileDto( - override val id: String? = null, +data class FileDto( + override val id: String, val name: String, val path: String, val extension: String, val sizeInBytes: Long, val mime: String? = null, -) : Dto + val url: String, +) : ModelDto fun File.toDto() = FileDto( - id = id?.toHexString(), - name = name, - path = path, - extension = extension, - sizeInBytes = sizeInBytes, - mime = mime, -) - -fun FileDto.toModel() = File( + id = id.value, name = name, path = path, extension = extension, sizeInBytes = sizeInBytes, mime = mime, + url = "/api/file/$id" ) diff --git a/application/src/jvmMain/kotlin/replace/dto/FileUploadDto.kt b/application/src/jvmMain/kotlin/replace/dto/FileUploadDto.kt index ca9607a6..5e7d256c 100644 --- a/application/src/jvmMain/kotlin/replace/dto/FileUploadDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/FileUploadDto.kt @@ -1,20 +1,16 @@ package replace.dto import kotlinx.serialization.Serializable -import replace.datastore.FileRepository import replace.datastore.FileStorage -import replace.datastore.TemporaryFileRepository import replace.usecase.temporaryfileupload.SaveTemporaryFileUploadPersistentUseCase @Serializable -class FileUploadDto( - override val id: String, +data class FileUploadDto( + val fileId: String, val temporary: Boolean, -) : Dto +) suspend fun FileUploadDto.save( - temporaryFileRepository: TemporaryFileRepository, - fileRepository: FileRepository, fileStorage: FileStorage, ): FileUploadDto { if (!temporary) { @@ -22,16 +18,14 @@ suspend fun FileUploadDto.save( } val file = SaveTemporaryFileUploadPersistentUseCase.execute( - id, - temporaryFileRepository, - fileRepository, + fileId, fileStorage, ) checkNotNull(file.id) { "Could not save file" } return FileUploadDto( - id = file.id, + fileId = file.id, temporary = false, ) } diff --git a/application/src/jvmMain/kotlin/replace/dto/FloorDto.kt b/application/src/jvmMain/kotlin/replace/dto/FloorDto.kt index 2aa0baa4..182e4706 100644 --- a/application/src/jvmMain/kotlin/replace/dto/FloorDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/FloorDto.kt @@ -1,61 +1,41 @@ package replace.dto import kotlinx.serialization.Serializable -import org.bson.types.ObjectId -import replace.datastore.FileRepository -import replace.datastore.FileStorage -import replace.datastore.TemporaryFileRepository +import org.jetbrains.exposed.sql.transactions.transaction import replace.model.Floor +import kotlin.reflect.KProperty1 @Serializable class FloorDto( - override val id: String? = null, + override val id: String, val name: String, val siteId: String, - val planFile: FileUploadDto? = null, -) : Dto + val site: SiteDto? = null, + val planFileId: String? = null, + val planFile: FileDto? = null, + val bookableEntities: List? = null, +) : ModelDto -fun Floor.toDto(): FloorDto { +fun Floor.toDto(with: List> = emptyList()): FloorDto { - val fileId = planFileId?.toHexString() - ?: return FloorDto( - id = id?.toHexString(), - name = name, - siteId = siteId.toHexString(), - ) + val siteDto = if (with.contains(Floor::site)) { + site.toDto() + } else { + null + } - return FloorDto( - id = id?.toHexString(), - name = name, - siteId = siteId.toHexString(), - planFile = FileUploadDto( - id = fileId, - temporary = false, - ), - ) -} - -fun FloorDto.toModel(): Floor { - return Floor( - name = name, - siteId = ObjectId(siteId), - planFileId = planFile?.let { ObjectId(planFile.id) }, - ) -} + val bookableEntities = if (with.contains(Floor::bookableEntities)) { + bookableEntities.map { it.toDto() } + } else { + null + } -suspend fun FloorDto.saveFiles( - temporaryFileRepository: TemporaryFileRepository, - fileRepository: FileRepository, - fileStorage: FileStorage -): FloorDto { return FloorDto( - id = id, + id = id.value, name = name, - siteId = siteId, - planFile = planFile?.save( - temporaryFileRepository = temporaryFileRepository, - fileRepository = fileRepository, - fileStorage = fileStorage, - ), + siteId = siteId.value, + planFile = transaction { planFile?.toDto() }, + site = siteDto, + bookableEntities = bookableEntities, ) } diff --git a/application/src/jvmMain/kotlin/replace/dto/ModelDto.kt b/application/src/jvmMain/kotlin/replace/dto/ModelDto.kt new file mode 100644 index 00000000..b919ef20 --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/ModelDto.kt @@ -0,0 +1,5 @@ +package replace.dto + +interface ModelDto { + val id: String +} diff --git a/application/src/jvmMain/kotlin/replace/dto/SiteDto.kt b/application/src/jvmMain/kotlin/replace/dto/SiteDto.kt index 7938b237..9d8a692a 100644 --- a/application/src/jvmMain/kotlin/replace/dto/SiteDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/SiteDto.kt @@ -2,16 +2,25 @@ package replace.dto import kotlinx.serialization.Serializable import replace.model.Site +import kotlin.reflect.KProperty1 @Serializable class SiteDto( - override val id: String? = null, + override val id: String, val name: String, -) : Dto + val floors: List? = null, +) : ModelDto -fun Site.toDto() = SiteDto( - id = id?.toHexString(), - name = name, -) +fun Site.toDto(with: List> = emptyList()): SiteDto { + val floors = if (with.contains(Site::floors)) { + floors.map { it.toDto() } + } else { + null + } -fun SiteDto.toModel() = Site(name) + return SiteDto( + id = id.value, + name = name, + floors = floors, + ) +} diff --git a/application/src/jvmMain/kotlin/replace/dto/TemporaryFileUploadDto.kt b/application/src/jvmMain/kotlin/replace/dto/TemporaryFileUploadDto.kt index 23079df4..73be5e53 100644 --- a/application/src/jvmMain/kotlin/replace/dto/TemporaryFileUploadDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/TemporaryFileUploadDto.kt @@ -2,34 +2,26 @@ package replace.dto import kotlinx.serialization.Serializable import replace.model.TemporaryFile -import java.time.LocalDateTime @Serializable class TemporaryFileUploadDto( - override val id: String? = null, + override val id: String, val name: String, val path: String, val mime: String? = null, val extension: String, val sizeInBytes: Long, val createdAt: String, -) : Dto + val url: String, +) : ModelDto fun TemporaryFile.toDto() = TemporaryFileUploadDto( - id = id?.toHexString(), + id = id.value, name = name, path = path, mime = mime, extension = extension, sizeInBytes = sizeInBytes, - createdAt = createdAt.toString() -) - -fun TemporaryFileUploadDto.toModel() = TemporaryFile( - name = name, - path = path, - extension = extension, - mime = mime, - sizeInBytes = sizeInBytes, - createdAt = LocalDateTime.parse(createdAt) + createdAt = createdAt.toString(), + url = "/api/temporary-file-upload/$id", ) diff --git a/application/src/jvmMain/kotlin/replace/dto/UpdateBookableEntityDto.kt b/application/src/jvmMain/kotlin/replace/dto/UpdateBookableEntityDto.kt new file mode 100644 index 00000000..250eb348 --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/UpdateBookableEntityDto.kt @@ -0,0 +1,16 @@ +package replace.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateBookableEntityDto( + val id: String, + + val name: String, + + val floorId: String, + + val parentId: String? = null, + + val typeId: String? = null, +) diff --git a/application/src/jvmMain/kotlin/replace/dto/UpdateBookableEntityTypeDto.kt b/application/src/jvmMain/kotlin/replace/dto/UpdateBookableEntityTypeDto.kt new file mode 100644 index 00000000..161496b8 --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/UpdateBookableEntityTypeDto.kt @@ -0,0 +1,9 @@ +package replace.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateBookableEntityTypeDto( + val id: String, + val name: String, +) diff --git a/application/src/jvmMain/kotlin/replace/dto/UpdateBookingDto.kt b/application/src/jvmMain/kotlin/replace/dto/UpdateBookingDto.kt new file mode 100644 index 00000000..948c7c8b --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/UpdateBookingDto.kt @@ -0,0 +1,12 @@ +package replace.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateBookingDto( + val id: String, + val userId: String, + val bookedEntityIds: List, + val start: String, + val end: String +) diff --git a/application/src/jvmMain/kotlin/replace/dto/UpdateFloorDto.kt b/application/src/jvmMain/kotlin/replace/dto/UpdateFloorDto.kt new file mode 100644 index 00000000..04b23a7e --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/UpdateFloorDto.kt @@ -0,0 +1,11 @@ +package replace.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateFloorDto( + val id: String, + val name: String, + val siteId: String, + val planFile: FileUploadDto? = null, +) diff --git a/application/src/jvmMain/kotlin/replace/dto/UpdateSiteDto.kt b/application/src/jvmMain/kotlin/replace/dto/UpdateSiteDto.kt new file mode 100644 index 00000000..aab31eef --- /dev/null +++ b/application/src/jvmMain/kotlin/replace/dto/UpdateSiteDto.kt @@ -0,0 +1,9 @@ +package replace.dto + +import kotlinx.serialization.Serializable + +@Serializable +class UpdateSiteDto( + val id: String, + val name: String, +) diff --git a/application/src/jvmMain/kotlin/replace/dto/UserDto.kt b/application/src/jvmMain/kotlin/replace/dto/UserDto.kt index 4c9ac1cc..d1a8985b 100644 --- a/application/src/jvmMain/kotlin/replace/dto/UserDto.kt +++ b/application/src/jvmMain/kotlin/replace/dto/UserDto.kt @@ -2,18 +2,31 @@ package replace.dto import kotlinx.serialization.Serializable import replace.model.User +import kotlin.reflect.KProperty1 @Serializable class UserDto( - override val id: String? = null, + override val id: String, val username: String, - val firstName: String, - val lastName: String, -) : Dto + val firstname: String, + val password: String, + val lastname: String, + val bookings: List? = null, +) : ModelDto -fun User.toDto() = UserDto( - id?.toHexString(), - username, - firstName, - lastName, -) +fun User.toDto(with: List> = emptyList()): UserDto { + val bookings = if (with.contains(User::bookings)) { + bookings.map { it.toDto() } + } else { + null + } + + return UserDto( + id = id.value, + username = username, + password = password, + firstname = firstname, + lastname = lastname, + bookings = bookings, + ) +} diff --git a/application/src/jvmMain/kotlin/replace/usecase/bookableentity/CreateBookableEntityUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/bookableentity/CreateBookableEntityUseCase.kt index 5af93861..70c1d79e 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/bookableentity/CreateBookableEntityUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/bookableentity/CreateBookableEntityUseCase.kt @@ -1,29 +1,29 @@ package replace.usecase.bookableentity -import org.bson.types.ObjectId -import replace.datastore.Repository +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.transactions.transaction import replace.dto.BookableEntityDto +import replace.dto.CreateBookableEntityDto import replace.dto.toDto -import replace.dto.toModel +import replace.model.BookableEntities import replace.model.BookableEntity -import replace.model.BookableEntityType +import replace.model.BookableEntityTypes +import replace.model.Floors object CreateBookableEntityUseCase { - suspend fun execute( - bookableEntityDto: BookableEntityDto, - bookableEntityRepository: Repository, - bookableEntityTypeRepository: Repository, + fun execute( + bookableEntityDto: CreateBookableEntityDto, ): BookableEntityDto { - val bookableEntityTypeId = bookableEntityDto.type?.id + return transaction { + val bookableEntity = BookableEntity.new { + name = bookableEntityDto.name + floorId = EntityID(bookableEntityDto.floorId, Floors) + parentId = bookableEntityDto.parentId?.let { EntityID(it, BookableEntities) } + typeId = bookableEntityDto.typeId?.let { EntityID(it, BookableEntityTypes) } + } - if (bookableEntityTypeId !== null) { - val bookableEntityType = bookableEntityTypeRepository.findOneById(ObjectId(bookableEntityDto.type.id)) - checkNotNull(bookableEntityType) { " Bookable Entity Type $bookableEntityTypeId does not exists" } + bookableEntity.toDto() } - - val insertedBookableEntity = bookableEntityRepository.insertOne(bookableEntityDto.toModel()) - checkNotNull(insertedBookableEntity) { "Could not insert BookableEntity" } - return insertedBookableEntity.toDto() } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/bookableentity/UpdateBookableEntityUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/bookableentity/UpdateBookableEntityUseCase.kt index c107e706..d9eb9a83 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/bookableentity/UpdateBookableEntityUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/bookableentity/UpdateBookableEntityUseCase.kt @@ -1,21 +1,30 @@ package replace.usecase.bookableentity -import org.bson.types.ObjectId -import replace.datastore.Repository +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.transactions.transaction import replace.dto.BookableEntityDto +import replace.dto.UpdateBookableEntityDto import replace.dto.toDto -import replace.dto.toModel +import replace.model.BookableEntities import replace.model.BookableEntity +import replace.model.Floors object UpdateBookableEntityUseCase { suspend fun execute( - bookableEntityDto: BookableEntityDto, - bookableEntityRepository: Repository, + bookableEntityDto: UpdateBookableEntityDto, ): BookableEntityDto { - val bookableEntityId = ObjectId(bookableEntityDto.id) - val updatedBookableEntity = bookableEntityRepository.updateOne(bookableEntityId, bookableEntityDto.toModel()) - checkNotNull(updatedBookableEntity) - return updatedBookableEntity.toDto() + return transaction { + val bookableEntity = BookableEntity.findById(bookableEntityDto.id) + + checkNotNull(bookableEntity) { "BookableEntity with id ${bookableEntityDto.id} not found" } + println(bookableEntityDto.name) + bookableEntity.name = bookableEntityDto.name + bookableEntity.floorId = EntityID(bookableEntityDto.floorId, Floors) + bookableEntity.parentId = bookableEntityDto.parentId?.let { EntityID(it, BookableEntities) } + bookableEntity.typeId = bookableEntityDto.typeId?.let { EntityID(it, BookableEntities) } + + bookableEntity.toDto() + } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/bookableentitytype/CreateBookableEntityTypeUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/bookableentitytype/CreateBookableEntityTypeUseCase.kt index 8e8edae4..19e3b28f 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/bookableentitytype/CreateBookableEntityTypeUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/bookableentitytype/CreateBookableEntityTypeUseCase.kt @@ -1,23 +1,24 @@ package replace.usecase.bookableentitytype -import replace.datastore.BookableEntityTypeRepository +import org.jetbrains.exposed.sql.transactions.transaction import replace.dto.BookableEntityTypeDto +import replace.dto.CreateBookableEntityTypeDto import replace.dto.toDto import replace.model.BookableEntityType object CreateBookableEntityTypeUseCase { - suspend fun execute( - bookableEntityTypeDto: BookableEntityTypeDto, - bookableEntityTypeRepository: BookableEntityTypeRepository, + fun execute( + createBookableEntityTypeDto: CreateBookableEntityTypeDto, ): BookableEntityTypeDto { - val foundType = bookableEntityTypeRepository.findByName(bookableEntityTypeDto.name) - if (foundType != null) { - throw IllegalStateException("BookableEntityType with given name already exists") + + return transaction { + + val bookableEntityType = BookableEntityType.new { + name = createBookableEntityTypeDto.name + } + + bookableEntityType.toDto() } - val type = BookableEntityType(bookableEntityTypeDto.name) - val insertedBookableEntityType = bookableEntityTypeRepository.insertOne(type) - checkNotNull(insertedBookableEntityType) { "Could not insert BookableEntityType" } - return insertedBookableEntityType.toDto() } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/bookableentitytype/UpdateBookableEntityTypeUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/bookableentitytype/UpdateBookableEntityTypeUseCase.kt index 78ce734d..146bd33b 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/bookableentitytype/UpdateBookableEntityTypeUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/bookableentitytype/UpdateBookableEntityTypeUseCase.kt @@ -1,20 +1,24 @@ package replace.usecase.bookableentitytype -import org.bson.types.ObjectId -import replace.datastore.BookableEntityTypeRepository +import org.jetbrains.exposed.sql.transactions.transaction import replace.dto.BookableEntityTypeDto +import replace.dto.UpdateBookableEntityTypeDto import replace.dto.toDto -import replace.dto.toModel +import replace.model.BookableEntityType object UpdateBookableEntityTypeUseCase { suspend fun execute( - bookableEntityTypeDto: BookableEntityTypeDto, - bookableEntityTypeRepository: BookableEntityTypeRepository, + updateBookableEntityTypeDto: UpdateBookableEntityTypeDto, ): BookableEntityTypeDto { - val bookableEntityTypeId = ObjectId(bookableEntityTypeDto.id) + return transaction { - val updatedBookableEntityType = bookableEntityTypeRepository.updateOne(bookableEntityTypeId, bookableEntityTypeDto.toModel()) - checkNotNull(updatedBookableEntityType) { "Could not update BookableEntityType" } - return updatedBookableEntityType.toDto() + val bookableEntityType = BookableEntityType.findById(updateBookableEntityTypeDto.id) + + checkNotNull(bookableEntityType) { "BookableEntityType with id ${updateBookableEntityTypeDto.id} not found" } + + bookableEntityType.name = updateBookableEntityTypeDto.name + + bookableEntityType.toDto() + } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/booking/CreateBookingUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/booking/CreateBookingUseCase.kt index 30100222..7112cbdf 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/booking/CreateBookingUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/booking/CreateBookingUseCase.kt @@ -1,31 +1,36 @@ package replace.usecase.booking import kotlinx.datetime.Instant -import org.bson.types.ObjectId -import replace.datastore.BookableEntityRepository -import replace.datastore.Repository +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.transactions.transaction import replace.dto.BookingDto +import replace.dto.CreateBookingDto import replace.dto.toDto +import replace.model.BookableEntities +import replace.model.BookableEntity import replace.model.Booking +import replace.model.Users object CreateBookingUseCase { suspend fun execute( - bookingDto: BookingDto, - bookingRepository: Repository, - bookableEntityRepository: BookableEntityRepository, + createBookingDto: CreateBookingDto, + userId: String ): BookingDto { - val bookedEntities = bookingDto.bookedEntities.map { ObjectId(it) } - val startDateTime = bookingDto.startDateTime - val endDateTime = bookingDto.endDateTime + return transaction { + if (createBookingDto.bookedEntityIds.isEmpty()) { + throw IllegalArgumentException("BookedEntities must not be empty") + } - // ensure that each booked entity exists - // TODO: Clean up tree if parent + child are booked? + val newBookedEntities = BookableEntity.forEntityIds(createBookingDto.bookedEntityIds.map { EntityID(it, BookableEntities) }) - bookedEntities.forEach { - bookableEntityRepository.findOneById(it) ?: throw IllegalStateException("Entity with id $it does not exist") + val booking = Booking.new { + start = Instant.parse(createBookingDto.start) + end = Instant.parse(createBookingDto.end) + this.userId = EntityID(userId, Users) + bookedEntities = newBookedEntities + } + + booking.toDto() } - val insertedBooking = bookingRepository.insertOne(Booking(bookedEntities, Instant.parse(startDateTime), Instant.parse(endDateTime))) - checkNotNull(insertedBooking) { "Could not insert booking" } - return insertedBooking.toDto() } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/file/CreateFileUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/file/CreateFileUseCase.kt index 8d898061..aa037b88 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/file/CreateFileUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/file/CreateFileUseCase.kt @@ -1,47 +1,41 @@ package replace.usecase.file -import org.bson.types.ObjectId -import replace.datastore.FileRepository +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import replace.datastore.FileStorage -import replace.datastore.TemporaryFileRepository import replace.dto.FileDto import replace.dto.toDto import replace.model.File +import replace.model.TemporaryFile import java.util.UUID object CreateFileUseCase { suspend fun execute( temporaryFileUploadId: String, - temporaryFileRepository: TemporaryFileRepository, - fileRepository: FileRepository, fileStorage: FileStorage, ): FileDto { - if (!ObjectId.isValid(temporaryFileUploadId)) { - throw IllegalArgumentException("Id $temporaryFileUploadId is not a valid ObjectId") - } - - val temporaryFileUpload = temporaryFileRepository.findOneById(ObjectId(temporaryFileUploadId)) - ?: throw IllegalArgumentException("Temporary file upload with id $temporaryFileUploadId not found") + return newSuspendedTransaction { - val newFilePath = "uploads/${UUID.randomUUID()}/base.${temporaryFileUpload.extension}" + val temporaryFileUpload = TemporaryFile.findById(temporaryFileUploadId) + ?: throw IllegalArgumentException("Temporary file upload with id $temporaryFileUploadId not found") - if (!fileStorage.copyFile(temporaryFileUpload.path, newFilePath)) { - throw IllegalStateException("Could not copy file from ${temporaryFileUpload.path} to $newFilePath") - } + val newFilePath = "uploads/${UUID.randomUUID()}/base.${temporaryFileUpload.extension}" - val file = File( - name = temporaryFileUpload.name, - path = newFilePath, - mime = temporaryFileUpload.mime, - extension = temporaryFileUpload.extension, - sizeInBytes = fileStorage.getFileSize(newFilePath), - ) + if (!fileStorage.copyFile(temporaryFileUpload.path, newFilePath)) { + throw IllegalStateException("Could not copy file from ${temporaryFileUpload.path} to $newFilePath") + } - val insertedFile = fileRepository.insertOne(file) + val fileSize = fileStorage.getFileSize(newFilePath) - checkNotNull(insertedFile) { "Could not insert File into Database" } + val insertedFile = File.new { + name = temporaryFileUpload.name + path = newFilePath + mime = temporaryFileUpload.mime + extension = temporaryFileUpload.extension + sizeInBytes = fileSize + } - return insertedFile.toDto() + insertedFile.toDto() + } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/file/DeleteFileUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/file/DeleteFileUseCase.kt index 2f6fcf74..b7d54c6a 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/file/DeleteFileUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/file/DeleteFileUseCase.kt @@ -1,27 +1,25 @@ package replace.usecase.file -import org.bson.types.ObjectId -import replace.datastore.FileRepository +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import replace.datastore.FileStorage +import replace.model.File object DeleteFileUseCase { suspend fun execute( fileId: String, - fileRepository: FileRepository, fileStorage: FileStorage, ) { - if (!ObjectId.isValid(fileId)) { - throw IllegalArgumentException("Id $fileId: is not a valid ObjectId") - } + return newSuspendedTransaction { + val file = File.findById(fileId) - val file = fileRepository.findOneById(ObjectId(fileId)) - ?: throw IllegalArgumentException("File with id $fileId not found") + checkNotNull(file) { "File with id $fileId not found" } - if (fileStorage.deleteFile(file.path)) { - fileRepository.deleteOneById(ObjectId(fileId)) - } else { - throw IllegalStateException("Could not delete file with id $fileId") + if (fileStorage.deleteFile(file.path)) { + file.delete() + } else { + throw IllegalStateException("Could not delete file with id $fileId") + } } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/floor/CreateFloorUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/floor/CreateFloorUseCase.kt index 6c0ff916..98b50b55 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/floor/CreateFloorUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/floor/CreateFloorUseCase.kt @@ -1,40 +1,31 @@ package replace.usecase.floor -import org.bson.types.ObjectId -import replace.datastore.FileRepository +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import replace.datastore.FileStorage -import replace.datastore.FloorRepository -import replace.datastore.SiteRepository -import replace.datastore.TemporaryFileRepository +import replace.dto.CreateFloorDto import replace.dto.FloorDto +import replace.dto.save import replace.dto.toDto -import replace.dto.toModel +import replace.model.File +import replace.model.Floor +import replace.model.Sites object CreateFloorUseCase { suspend fun execute( - floorDto: FloorDto, - floorRepository: FloorRepository, - siteRepository: SiteRepository, - temporaryFileRepository: TemporaryFileRepository, - fileRepository: FileRepository, + createFloorDto: CreateFloorDto, fileStorage: FileStorage, ): FloorDto { - val siteId = ObjectId(floorDto.siteId) - val site = siteRepository.findOneById(siteId) - checkNotNull(site) { "Site with id $siteId not found" } + return newSuspendedTransaction { + val file = createFloorDto.planFile?.save(fileStorage)?.let { File.findById(it.fileId) } - val floorDtoWithPlan = SaveFloorPlanFileUseCase.execute( - floorDto, - floorRepository, - temporaryFileRepository, - fileRepository, - fileStorage, - ) + val insertedFloor = Floor.new { + name = createFloorDto.name + siteId = EntityID(createFloorDto.siteId, Sites) + planFile = file + } - val insertedFloor = floorRepository.insertOne(floorDtoWithPlan.toModel()) - - checkNotNull(insertedFloor) { "Could not insert BookableEntity" } - - return insertedFloor.toDto() + insertedFloor.toDto() + } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/floor/SaveFloorPlanFileUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/floor/SaveFloorPlanFileUseCase.kt deleted file mode 100644 index 15569bfe..00000000 --- a/application/src/jvmMain/kotlin/replace/usecase/floor/SaveFloorPlanFileUseCase.kt +++ /dev/null @@ -1,34 +0,0 @@ -package replace.usecase.floor - -import org.bson.types.ObjectId -import replace.datastore.FileRepository -import replace.datastore.FileStorage -import replace.datastore.FloorRepository -import replace.datastore.TemporaryFileRepository -import replace.dto.FloorDto -import replace.dto.saveFiles -import replace.usecase.file.DeleteFileUseCase - -object SaveFloorPlanFileUseCase { - suspend fun execute( - floorDto: FloorDto, - floorRepository: FloorRepository, - temporaryFileRepository: TemporaryFileRepository, - fileRepository: FileRepository, - fileStorage: FileStorage, - ): FloorDto { - val oldPlanFileId = floorDto.id?.let { floorRepository.findOneById(ObjectId(it)) }?.planFileId?.toHexString() - - val saved = floorDto.saveFiles( - temporaryFileRepository = temporaryFileRepository, - fileRepository = fileRepository, - fileStorage = fileStorage, - ) - - if (oldPlanFileId != saved.planFile?.id) { - oldPlanFileId?.let { DeleteFileUseCase.execute(it, fileRepository, fileStorage) } - } - - return saved - } -} diff --git a/application/src/jvmMain/kotlin/replace/usecase/floor/UpdateFloorUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/floor/UpdateFloorUseCase.kt index e0ce0ef8..77c2b847 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/floor/UpdateFloorUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/floor/UpdateFloorUseCase.kt @@ -1,36 +1,30 @@ package replace.usecase.floor -import org.bson.types.ObjectId -import replace.datastore.FileRepository +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import replace.datastore.FileStorage -import replace.datastore.FloorRepository -import replace.datastore.TemporaryFileRepository import replace.dto.FloorDto +import replace.dto.UpdateFloorDto +import replace.dto.save import replace.dto.toDto -import replace.dto.toModel +import replace.model.File +import replace.model.Floor object UpdateFloorUseCase { suspend fun execute( - dto: FloorDto, - repository: FloorRepository, - temporaryFileRepository: TemporaryFileRepository, - fileRepository: FileRepository, + updateFloorDto: UpdateFloorDto, fileStorage: FileStorage, ): FloorDto { - val floorId = ObjectId(dto.id) + return newSuspendedTransaction { + val floor = Floor.findById(updateFloorDto.id) - val floorDtoWithPlan = SaveFloorPlanFileUseCase.execute( - dto, - repository, - temporaryFileRepository, - fileRepository, - fileStorage, - ) + checkNotNull(floor) { "Floor with id ${updateFloorDto.id} not found" } - val updatedModel = repository.updateOne(floorId, floorDtoWithPlan.toModel()) + val file = updateFloorDto.planFile?.save(fileStorage)?.let { File.findById(it.fileId) } - checkNotNull(updatedModel) { "Could not update Floor with id $floorId\n$floorDtoWithPlan" } + floor.name = updateFloorDto.name + floor.planFile = file - return updatedModel.toDto() + floor.toDto() + } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/site/CreateSiteUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/site/CreateSiteUseCase.kt index 05f0c58e..0ca683a5 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/site/CreateSiteUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/site/CreateSiteUseCase.kt @@ -1,18 +1,21 @@ package replace.usecase.site -import replace.datastore.SiteRepository +import org.jetbrains.exposed.sql.transactions.transaction +import replace.dto.CreateSiteDto import replace.dto.SiteDto import replace.dto.toDto import replace.model.Site object CreateSiteUseCase { suspend fun execute( - siteDto: SiteDto, - siteRepository: SiteRepository, + createSiteDto: CreateSiteDto, ): SiteDto { - val site = Site(siteDto.name) - val insertedSite = siteRepository.insertOne(site) - checkNotNull(insertedSite) { "Could not insert Site" } - return insertedSite.toDto() + return transaction { + val site = Site.new { + name = createSiteDto.name + } + + site.toDto() + } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/site/UpdateSiteUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/site/UpdateSiteUseCase.kt index 025eea20..dfa4e4da 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/site/UpdateSiteUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/site/UpdateSiteUseCase.kt @@ -1,22 +1,23 @@ package replace.usecase.site -import org.bson.types.ObjectId -import replace.datastore.SiteRepository +import org.jetbrains.exposed.sql.transactions.transaction import replace.dto.SiteDto +import replace.dto.UpdateSiteDto import replace.dto.toDto -import replace.dto.toModel +import replace.model.Site object UpdateSiteUseCase { suspend fun execute( - siteDto: SiteDto, - siteRepository: SiteRepository, + updateSiteDto: UpdateSiteDto, ): SiteDto { - val siteId = ObjectId(siteDto.id) + return transaction { + val site = Site.findById(updateSiteDto.id) - val updatedSite = siteRepository.updateOne(siteId, siteDto.toModel()) + checkNotNull(site) { "Site with id ${updateSiteDto.id} not found" } - checkNotNull(updatedSite) { "Could not update Site" } + site.name = updateSiteDto.name - return updatedSite.toDto() + site.toDto() + } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/CreateTemporaryFileUploadUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/CreateTemporaryFileUploadUseCase.kt index cde9d765..5d14c584 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/CreateTemporaryFileUploadUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/CreateTemporaryFileUploadUseCase.kt @@ -1,7 +1,8 @@ package replace.usecase.temporaryfileupload +import kotlinx.datetime.Clock +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import replace.datastore.FileStorage -import replace.datastore.TemporaryFileRepository import replace.dto.TemporaryFileUploadDto import replace.dto.toDto import replace.model.TemporaryFile @@ -14,27 +15,25 @@ object CreateTemporaryFileUploadUseCase { suspend fun execute( fileName: String, input: InputStream, - temporaryFileRepository: TemporaryFileRepository, fileStorage: FileStorage, ): TemporaryFileUploadDto { + return newSuspendedTransaction { + val temporaryFileUploadPath = "temporary_uploads/${randomUUID()}" - val temporaryFileUploadPath = "temporary_uploads/${randomUUID()}" + fileStorage.saveFile(temporaryFileUploadPath, input) - fileStorage.saveFile(temporaryFileUploadPath, input) + val fileSize = fileStorage.getFileSize(temporaryFileUploadPath) - val insertedTemporaryFile = temporaryFileRepository.insertOne( - TemporaryFile( - name = fileName.substringBeforeLast("."), - path = temporaryFileUploadPath, - mime = URLConnection.guessContentTypeFromName(fileName.lowercase()), - extension = fileName.substringAfterLast("."), - sizeInBytes = fileStorage.getFileSize(temporaryFileUploadPath), - createdAt = java.time.LocalDateTime.now(), - ) - ) + val temporaryFile = TemporaryFile.new { + name = fileName.substringBeforeLast(".") + path = temporaryFileUploadPath + mime = URLConnection.guessContentTypeFromName(fileName.lowercase()) + extension = fileName.substringAfterLast(".") + sizeInBytes = fileSize + createdAt = Clock.System.now() + } - checkNotNull(insertedTemporaryFile) { "Could not insert TemporaryFileUpload into Database" } - - return insertedTemporaryFile.toDto() + temporaryFile.toDto() + } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/DeleteTemporaryFileUploadUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/DeleteTemporaryFileUploadUseCase.kt index cc962d59..57454bdc 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/DeleteTemporaryFileUploadUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/DeleteTemporaryFileUploadUseCase.kt @@ -1,28 +1,38 @@ package replace.usecase.temporaryfileupload -import org.bson.types.ObjectId +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import replace.datastore.FileStorage -import replace.datastore.TemporaryFileRepository +import replace.model.TemporaryFile object DeleteTemporaryFileUploadUseCase { suspend fun execute( - temporaryFileUploadId: String, - temporaryFileRepository: TemporaryFileRepository, + temporaryFileId: String, fileStorage: FileStorage, ) { + newSuspendedTransaction { + val temporaryFile = TemporaryFile.findById(temporaryFileId) - if (!ObjectId.isValid(temporaryFileUploadId)) { - throw IllegalArgumentException("Id $temporaryFileUploadId is not a valid ObjectId") - } + checkNotNull(temporaryFile) { "TemporaryFileUpload with id $temporaryFileId not found" } - val temporaryFileUpload = temporaryFileRepository.findOneById(ObjectId(temporaryFileUploadId)) - ?: throw IllegalArgumentException("Temporary file upload with id $temporaryFileUploadId not found") + if (fileStorage.deleteFile(temporaryFile.path)) { + temporaryFile.delete() + } else { + throw IllegalStateException("Could not delete temporary file upload with id $temporaryFileId") + } + } + } - if (fileStorage.deleteFile(temporaryFileUpload.path)) { - temporaryFileRepository.deleteOneById(ObjectId(temporaryFileUploadId)) - } else { - throw IllegalStateException("Could not delete temporary file upload with id $temporaryFileUploadId") + suspend fun execute( + temporaryFile: TemporaryFile, + fileStorage: FileStorage, + ) { + newSuspendedTransaction { + if (fileStorage.deleteFile(temporaryFile.path)) { + temporaryFile.delete() + } else { + throw IllegalStateException("Could not delete temporary file upload with id $temporaryFile.id") + } } } } diff --git a/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/SaveTemporaryFileUploadPersistentUseCase.kt b/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/SaveTemporaryFileUploadPersistentUseCase.kt index 2690e1bb..9deb0d51 100644 --- a/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/SaveTemporaryFileUploadPersistentUseCase.kt +++ b/application/src/jvmMain/kotlin/replace/usecase/temporaryfileupload/SaveTemporaryFileUploadPersistentUseCase.kt @@ -1,8 +1,7 @@ package replace.usecase.temporaryfileupload -import replace.datastore.FileRepository +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import replace.datastore.FileStorage -import replace.datastore.TemporaryFileRepository import replace.dto.FileDto import replace.usecase.file.CreateFileUseCase @@ -10,24 +9,20 @@ object SaveTemporaryFileUploadPersistentUseCase { suspend fun execute( temporaryFileUploadId: String, - temporaryFileRepository: TemporaryFileRepository, - fileRepository: FileRepository, fileStorage: FileStorage, ): FileDto { + return newSuspendedTransaction { + val file = CreateFileUseCase.execute( + temporaryFileUploadId, + fileStorage, + ) - val file = CreateFileUseCase.execute( - temporaryFileUploadId, - temporaryFileRepository, - fileRepository, - fileStorage, - ) + DeleteTemporaryFileUploadUseCase.execute( + temporaryFileUploadId, + fileStorage, + ) - DeleteTemporaryFileUploadUseCase.execute( - temporaryFileUploadId, - temporaryFileRepository, - fileStorage, - ) - - return file + file + } } } diff --git a/build.gradle.kts b/build.gradle.kts index f95c155a..f392d08f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,7 +20,7 @@ application { dependencies { jvmMainImplementation(project(":replace-application")) jvmMainImplementation(project(":replace-infrastructure")) - commonMainRuntimeOnly(project(":replace-web")) + // commonMainRuntimeOnly(project(":replace-web")) } tasks { diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index a6ec92ea..ed7478d1 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -6,7 +6,11 @@ plugins { dependencies { jvmMainApi(libs.bundles.kmongo) - jvmTestImplementation(libs.kotest) jvmMainImplementation(libs.ktor.server.auth) - commonMainImplementation(libs.kotlinx.datetime) + jvmTestImplementation(libs.kotest) + jvmMainImplementation(libs.exposed.core) + jvmMainImplementation(libs.exposed.dao) + jvmMainImplementation(libs.exposed.jdbc) + jvmMainImplementation(libs.exposed.java.time) + jvmMainImplementation(libs.kotlinx.datetime) } diff --git a/domain/src/jvmMain/kotlin/replace/datastore/BookableEntityRepository.kt b/domain/src/jvmMain/kotlin/replace/datastore/BookableEntityRepository.kt deleted file mode 100644 index b6e723cb..00000000 --- a/domain/src/jvmMain/kotlin/replace/datastore/BookableEntityRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package replace.datastore - -import org.bson.types.ObjectId -import replace.model.BookableEntity - -interface BookableEntityRepository : Repository { - suspend fun forFloor(floorId: ObjectId): List -} diff --git a/domain/src/jvmMain/kotlin/replace/datastore/BookableEntityTypeRepository.kt b/domain/src/jvmMain/kotlin/replace/datastore/BookableEntityTypeRepository.kt deleted file mode 100644 index 7fbb07bc..00000000 --- a/domain/src/jvmMain/kotlin/replace/datastore/BookableEntityTypeRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package replace.datastore - -import replace.model.BookableEntityType - -interface BookableEntityTypeRepository : Repository { - suspend fun findByName(name: String): BookableEntityType? -} diff --git a/domain/src/jvmMain/kotlin/replace/datastore/FloorRepository.kt b/domain/src/jvmMain/kotlin/replace/datastore/FloorRepository.kt deleted file mode 100644 index 2bea1bc3..00000000 --- a/domain/src/jvmMain/kotlin/replace/datastore/FloorRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package replace.datastore - -import org.bson.types.ObjectId -import replace.model.Floor - -interface FloorRepository : Repository { - suspend fun findBySiteId(siteId: ObjectId): List -} diff --git a/domain/src/jvmMain/kotlin/replace/datastore/Repository.kt b/domain/src/jvmMain/kotlin/replace/datastore/Repository.kt deleted file mode 100644 index c378f88b..00000000 --- a/domain/src/jvmMain/kotlin/replace/datastore/Repository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package replace.datastore - -import org.bson.types.ObjectId -import replace.model.ObjectWithId - -interface Repository { - suspend fun insertOne(item: T): T? - suspend fun updateOne(id: ObjectId, item: T): T? - suspend fun findOneById(id: ObjectId): T? - suspend fun deleteOneById(id: ObjectId): Boolean - suspend fun getAll(): List -} diff --git a/domain/src/jvmMain/kotlin/replace/datastore/TemporaryFileRepository.kt b/domain/src/jvmMain/kotlin/replace/datastore/TemporaryFileRepository.kt deleted file mode 100644 index 302d94b9..00000000 --- a/domain/src/jvmMain/kotlin/replace/datastore/TemporaryFileRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package replace.datastore - -import replace.model.TemporaryFile -import java.time.LocalDateTime - -interface TemporaryFileRepository : Repository { - suspend fun findOlderThan(datetime: LocalDateTime): List -} diff --git a/domain/src/jvmMain/kotlin/replace/datastore/UserRepository.kt b/domain/src/jvmMain/kotlin/replace/datastore/UserRepository.kt deleted file mode 100644 index a27892d3..00000000 --- a/domain/src/jvmMain/kotlin/replace/datastore/UserRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package replace.datastore - -import replace.model.User - -interface UserRepository : Repository { - suspend fun findByUsername(username: String): User? -} diff --git a/domain/src/jvmMain/kotlin/replace/datastore/aliases.kt b/domain/src/jvmMain/kotlin/replace/datastore/aliases.kt deleted file mode 100644 index a119d49e..00000000 --- a/domain/src/jvmMain/kotlin/replace/datastore/aliases.kt +++ /dev/null @@ -1,11 +0,0 @@ -package replace.datastore - -import replace.model.Booking -import replace.model.File -import replace.model.Site - -// repositories that don't have any special methods - -typealias BookingRepository = Repository -typealias SiteRepository = Repository -typealias FileRepository = Repository diff --git a/domain/src/jvmMain/kotlin/replace/model/BookableEntities.kt b/domain/src/jvmMain/kotlin/replace/model/BookableEntities.kt new file mode 100644 index 00000000..4ff32893 --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/BookableEntities.kt @@ -0,0 +1,29 @@ +package replace.model + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID + +object BookableEntities : Models("bookable_entities") { + val name = varchar("name", 255) + val floor_id = reference("floor_id", Floors) + val type_id = reference("type_id", BookableEntityTypes).nullable() + val parent_id = reference("parent_id", BookableEntities).nullable() +} + +class BookableEntity(id: EntityID) : Model(id) { + companion object : EntityClass(BookableEntities) + var name by BookableEntities.name + + var floorId by BookableEntities.floor_id + var floor by Floor referencedOn BookableEntities.floor_id + + var parentId by BookableEntities.parent_id + var parent by BookableEntity optionalReferencedOn BookableEntities.parent_id + + var typeId by BookableEntities.type_id + var type by BookableEntityType optionalReferencedOn BookableEntities.type_id + + val children by BookableEntity optionalReferrersOn BookableEntities.parent_id + + val bookings by Booking via BookedEntities +} diff --git a/domain/src/jvmMain/kotlin/replace/model/BookableEntity.kt b/domain/src/jvmMain/kotlin/replace/model/BookableEntity.kt deleted file mode 100644 index d465999f..00000000 --- a/domain/src/jvmMain/kotlin/replace/model/BookableEntity.kt +++ /dev/null @@ -1,13 +0,0 @@ -package replace.model - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable -import org.bson.types.ObjectId - -@Serializable -data class BookableEntity( - val name: String, - val type: BookableEntityType? = null, - @Contextual val floorId: ObjectId, - @Contextual val parentId: ObjectId? = null, -) : ObjectWithId() diff --git a/domain/src/jvmMain/kotlin/replace/model/BookableEntityTypes.kt b/domain/src/jvmMain/kotlin/replace/model/BookableEntityTypes.kt new file mode 100644 index 00000000..715d73e9 --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/BookableEntityTypes.kt @@ -0,0 +1,15 @@ +package replace.model + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID + +object BookableEntityTypes : Models("bookable_entity_types") { + val name = varchar("name", 255) +} + +class BookableEntityType(id: EntityID) : Model(id) { + companion object : EntityClass(BookableEntityTypes) + var name by BookableEntityTypes.name + + val bookableEntities by BookableEntity optionalReferrersOn BookableEntities.type_id +} diff --git a/domain/src/jvmMain/kotlin/replace/model/BookedEntities.kt b/domain/src/jvmMain/kotlin/replace/model/BookedEntities.kt new file mode 100644 index 00000000..0b5907f4 --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/BookedEntities.kt @@ -0,0 +1,19 @@ +package replace.model + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID + +object BookedEntities : Models("booked_entities") { + val bookable_entity_id = reference("bookable_entity_id", BookableEntities) + val booking_id = reference("booking_id", Bookings) +} + +class BookedEntity(id: EntityID) : Model(id) { + companion object : EntityClass(BookedEntities) + + var bookableEntityId by BookedEntities.bookable_entity_id + var bookableEntity by BookableEntity referencedOn BookedEntities.bookable_entity_id + + var bookingId by BookedEntities.booking_id + var booking by Booking referencedOn BookedEntities.booking_id +} diff --git a/domain/src/jvmMain/kotlin/replace/model/Booking.kt b/domain/src/jvmMain/kotlin/replace/model/Booking.kt deleted file mode 100644 index 51155669..00000000 --- a/domain/src/jvmMain/kotlin/replace/model/Booking.kt +++ /dev/null @@ -1,13 +0,0 @@ -package replace.model - -import kotlinx.datetime.Instant -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable -import org.bson.types.ObjectId - -@Serializable -data class Booking( - val bookedEntities: List<@Contextual ObjectId>, - val startDateTime: Instant, - val endDateTime: Instant, -) : ObjectWithId() diff --git a/domain/src/jvmMain/kotlin/replace/model/Bookings.kt b/domain/src/jvmMain/kotlin/replace/model/Bookings.kt new file mode 100644 index 00000000..0d85db07 --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/Bookings.kt @@ -0,0 +1,25 @@ +package replace.model + +import kotlinx.datetime.toJavaInstant +import kotlinx.datetime.toKotlinInstant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.javatime.timestamp +import java.time.Instant + +object Bookings : Models("bookings") { + val start = timestamp("start").default(Instant.now()) + val end = timestamp("end").default(Instant.now()) + val user_id = reference("user_id", Users) +} + +class Booking(id: EntityID) : Model(id) { + companion object : EntityClass(Bookings) + var start by Bookings.start.transform({ it.toJavaInstant() }, { it.toKotlinInstant() }) + var end by Bookings.end.transform({ it.toJavaInstant() }, { it.toKotlinInstant() }) + + var userId by Bookings.user_id + var user by User referencedOn Bookings.user_id + + var bookedEntities by BookableEntity via BookedEntities +} diff --git a/domain/src/jvmMain/kotlin/replace/model/File.kt b/domain/src/jvmMain/kotlin/replace/model/File.kt deleted file mode 100644 index df0d87ed..00000000 --- a/domain/src/jvmMain/kotlin/replace/model/File.kt +++ /dev/null @@ -1,12 +0,0 @@ -package replace.model - -import kotlinx.serialization.Serializable - -@Serializable -data class File( - val name: String, - val path: String, - val extension: String, - val sizeInBytes: Long, - val mime: String? = null, -) : ObjectWithId() diff --git a/domain/src/jvmMain/kotlin/replace/model/Files.kt b/domain/src/jvmMain/kotlin/replace/model/Files.kt new file mode 100644 index 00000000..e482670e --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/Files.kt @@ -0,0 +1,21 @@ +package replace.model + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID + +object Files : Models("files") { + val name = varchar("name", 255) + val path = varchar("path", 65535) + val extension = varchar("extension", 255) + val sizeInBytes = long("size_in_bytes") + val mime = varchar("mime", 255).nullable() +} + +class File(id: EntityID) : Model(id) { + companion object : EntityClass(Files) + var name by Files.name + var path by Files.path + var extension by Files.extension + var sizeInBytes by Files.sizeInBytes + var mime by Files.mime +} diff --git a/domain/src/jvmMain/kotlin/replace/model/Floor.kt b/domain/src/jvmMain/kotlin/replace/model/Floor.kt deleted file mode 100644 index e6eaf317..00000000 --- a/domain/src/jvmMain/kotlin/replace/model/Floor.kt +++ /dev/null @@ -1,12 +0,0 @@ -package replace.model - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable -import org.bson.types.ObjectId - -@Serializable -data class Floor( - val name: String, - @Contextual val siteId: ObjectId, - @Contextual val planFileId: ObjectId? = null, -) : ObjectWithId() diff --git a/domain/src/jvmMain/kotlin/replace/model/Floors.kt b/domain/src/jvmMain/kotlin/replace/model/Floors.kt new file mode 100644 index 00000000..4da633c4 --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/Floors.kt @@ -0,0 +1,23 @@ +package replace.model + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID + +object Floors : Models("floors") { + val name = varchar("name", 255) + val site_id = reference("site_id", Sites) + val plan_file_id = reference("plan_file_id", Files).nullable() +} + +class Floor(id: EntityID) : Model(id) { + companion object : EntityClass(Floors) + var name by Floors.name + + var siteId by Floors.site_id + var site by Site referencedOn Floors.site_id + + var planFileId by Floors.plan_file_id + var planFile by File optionalReferencedOn Floors.plan_file_id + + val bookableEntities by BookableEntity referrersOn BookableEntities.floor_id +} diff --git a/domain/src/jvmMain/kotlin/replace/model/Models.kt b/domain/src/jvmMain/kotlin/replace/model/Models.kt new file mode 100644 index 00000000..a0df6af4 --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/Models.kt @@ -0,0 +1,21 @@ +package replace.model + +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IdTable +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.CustomFunction +import org.jetbrains.exposed.sql.VarCharColumnType + +open class Models : IdTable { + + constructor() : super() + constructor(name: String) : super(name) + + final override val id: Column> = varchar("id", 36).defaultExpression(CustomFunction("gen_random_uuid()", VarCharColumnType())).entityId() + override val primaryKey = PrimaryKey(id, name = "pk_${tableName}_id") +} + +abstract class Model(id: EntityID) : Entity(id) + +// TODO: add https://github.com/JetBrains/Exposed/issues/497#issuecomment-520266191 diff --git a/domain/src/jvmMain/kotlin/replace/model/ObjectWithId.kt b/domain/src/jvmMain/kotlin/replace/model/ObjectWithId.kt deleted file mode 100644 index 5cb31c8c..00000000 --- a/domain/src/jvmMain/kotlin/replace/model/ObjectWithId.kt +++ /dev/null @@ -1,13 +0,0 @@ -package replace.model - -import kotlinx.serialization.Contextual -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import org.bson.types.ObjectId - -@Serializable -sealed class ObjectWithId { - @Contextual - @SerialName("_id") - var id: ObjectId? = null -} diff --git a/domain/src/jvmMain/kotlin/replace/model/Sites.kt b/domain/src/jvmMain/kotlin/replace/model/Sites.kt new file mode 100644 index 00000000..37f0288d --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/Sites.kt @@ -0,0 +1,15 @@ +package replace.model + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID + +object Sites : Models("sites") { + val name = varchar("name", 255) +} + +class Site(id: EntityID) : Model(id) { + companion object : EntityClass(Sites) + var name by Sites.name + + val floors by Floor referrersOn Floors.site_id +} diff --git a/domain/src/jvmMain/kotlin/replace/model/TemporaryFile.kt b/domain/src/jvmMain/kotlin/replace/model/TemporaryFile.kt deleted file mode 100644 index 17ae3270..00000000 --- a/domain/src/jvmMain/kotlin/replace/model/TemporaryFile.kt +++ /dev/null @@ -1,15 +0,0 @@ -package replace.model - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable -import java.time.LocalDateTime - -@Serializable -data class TemporaryFile( - val name: String, - val path: String, - val extension: String, - val sizeInBytes: Long, - val mime: String? = null, - @Contextual val createdAt: LocalDateTime, -) : ObjectWithId() diff --git a/domain/src/jvmMain/kotlin/replace/model/TemporaryFiles.kt b/domain/src/jvmMain/kotlin/replace/model/TemporaryFiles.kt new file mode 100644 index 00000000..c6257918 --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/TemporaryFiles.kt @@ -0,0 +1,27 @@ +package replace.model + +import kotlinx.datetime.toJavaInstant +import kotlinx.datetime.toKotlinInstant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.javatime.timestamp +import java.time.Instant + +object TemporaryFiles : Models("temporary_files") { + val name = varchar("name", 255) + val path = varchar("path", 65535) + val extension = varchar("extension", 255) + val sizeInBytes = long("size_in_bytes") + val mime = varchar("mime", 255).nullable() + val createdAt = timestamp("created_at").default(Instant.now()) +} + +class TemporaryFile(id: EntityID) : Model(id) { + companion object : EntityClass(TemporaryFiles) + var name by TemporaryFiles.name + var path by TemporaryFiles.path + var extension by TemporaryFiles.extension + var sizeInBytes by TemporaryFiles.sizeInBytes + var mime by TemporaryFiles.mime + var createdAt by TemporaryFiles.createdAt.transform({ it.toJavaInstant() }, { it.toKotlinInstant() }) +} diff --git a/domain/src/jvmMain/kotlin/replace/model/User.kt b/domain/src/jvmMain/kotlin/replace/model/User.kt deleted file mode 100644 index 6930df2b..00000000 --- a/domain/src/jvmMain/kotlin/replace/model/User.kt +++ /dev/null @@ -1,11 +0,0 @@ -package replace.model - -import kotlinx.serialization.Serializable - -@Serializable -data class User( - val username: String, - val password: String, - val firstName: String, - val lastName: String, -) : ObjectWithId() diff --git a/domain/src/jvmMain/kotlin/replace/model/UserSession.kt b/domain/src/jvmMain/kotlin/replace/model/UserSession.kt index 91e2a71e..bd32241a 100644 --- a/domain/src/jvmMain/kotlin/replace/model/UserSession.kt +++ b/domain/src/jvmMain/kotlin/replace/model/UserSession.kt @@ -1,18 +1,17 @@ package replace.model import io.ktor.server.auth.Principal +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant import kotlinx.serialization.Serializable -import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneOffset @Serializable data class UserSession( val userId: String?, - val startUtcFormatted: String = OffsetDateTime.now(ZoneOffset.UTC).toString(), + val startTimeString: String = Clock.System.now().toString(), ) : Principal -val UserSession.startUtc: Instant - get() = OffsetDateTime.parse(startUtcFormatted).toInstant() +val UserSession.start: Instant + get() = Instant.parse(startTimeString) fun User.createSession(): UserSession = UserSession(userId = id.toString()) diff --git a/domain/src/jvmMain/kotlin/replace/model/Users.kt b/domain/src/jvmMain/kotlin/replace/model/Users.kt new file mode 100644 index 00000000..20352108 --- /dev/null +++ b/domain/src/jvmMain/kotlin/replace/model/Users.kt @@ -0,0 +1,21 @@ +package replace.model + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID + +object Users : Models() { + val username = varchar("username", 255).uniqueIndex() + val password = varchar("password", 255) + val firstname = varchar("firstname", 255) + val lastname = varchar("lastname", 255) +} + +class User(id: EntityID) : Model(id) { + companion object : EntityClass(Users) + var username by Users.username + var password by Users.password + var firstname by Users.firstname + var lastname by Users.lastname + + val bookings by Booking referrersOn Bookings.user_id +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3dbf35c0..e0069a9e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,11 @@ kotlin = "1.8.0" ktor = "2.2.2" log4j = "2.19.0" tegral = "0.0.3" +liquibase = "2.1.1" +liquibase-core = "4.18.0" +liquibase-picocli = "4.7.0" +exposed = "0.40.1" +postgresql = "42.5.1" [libraries] kmongo-core = { module = "org.litote.kmongo:kmongo", version.ref = "kmongo" } @@ -27,7 +32,13 @@ logging-impl = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.r logging-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } tegral-openapi-base = { module ="guru.zoroark.tegral:tegral-openapi-ktor", version.ref = "tegral" } tegral-openapi-swagger = { module ="guru.zoroark.tegral:tegral-openapi-ktorui", version.ref = "tegral" } - +exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" } +exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" } +exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } +exposed-java-time = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" } +postgrsql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } +liquibase-core = { module = "org.liquibase:liquibase-core", version.ref = "liquibase-core" } +liquibase-picocli = { module = "info.picocli:picocli", version.ref = "liquibase-picocli" } [bundles] kmongo = [ "kmongo.core", "kmongo.async", "kmongo.coroutine" ] @@ -42,3 +53,4 @@ node = { id = "com.github.node-gradle.node", version = "3.5.0" } shadow = "com.github.johnrengelman.shadow:7.1.2" spring-boot = { id = "org.springframework.boot", version = "3.0.0-SNAPSHOT" } spring-dependency-management = { id = "io.spring.dependency-management", version = "1.1.0" } +liquibase = { id = "org.liquibase.gradle", version.ref = "liquibase" } diff --git a/infrastructure/build.gradle.kts b/infrastructure/build.gradle.kts index 781e0a66..8d1dcb3d 100644 --- a/infrastructure/build.gradle.kts +++ b/infrastructure/build.gradle.kts @@ -1,7 +1,18 @@ +import com.typesafe.config.ConfigFactory +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +buildscript { + dependencies { + classpath("com.typesafe:config:1.4.2") + } +} + @Suppress("DSL_SCOPE_VIOLATION") // https://youtrack.jetbrains.com/issue/KTIJ-19369 plugins { id("kotlin-jvm.base-conventions") kotlin("plugin.serialization") + alias(libs.plugins.liquibase) } dependencies { @@ -9,7 +20,6 @@ dependencies { jvmMainImplementation(project(":replace-domain")) jvmMainImplementation(libs.kotlinx.coroutines) jvmMainImplementation(libs.kotlinx.serialization) - commonMainImplementation(libs.kotlinx.datetime) jvmMainImplementation(libs.ktor.serialization) jvmMainImplementation(libs.ktor.server.auth) jvmMainImplementation(libs.ktor.server.content.negotiation) @@ -21,10 +31,75 @@ dependencies { jvmMainImplementation(libs.tegral.openapi.base) jvmMainImplementation(libs.tegral.openapi.swagger) jvmTestImplementation(libs.kotest) + liquibaseRuntime(libs.liquibase.core) + liquibaseRuntime(libs.liquibase.picocli) + liquibaseRuntime(libs.postgrsql) + jvmMainImplementation(libs.exposed.core) + jvmMainImplementation(libs.exposed.dao) + jvmMainImplementation(libs.exposed.jdbc) + jvmMainImplementation(libs.exposed.java.time) + jvmMainImplementation(libs.postgrsql) + jvmMainImplementation(libs.kotlinx.datetime) } +apply(plugin = "liquibase") + tasks { test { useJUnitPlatform() } } + +val migrationDir = File("infrastructure/src/jvmMain/resources/db/migrations") +val migrationRoot = File("infrastructure/src/jvmMain/resources/db/changelog-root.json") +val configFile = File("infrastructure/src/jvmMain/resources/application.conf") + +liquibase { + activities { + register("main") { + + if (!configFile.exists()) { + throw IllegalArgumentException("Config file does not exist") + } + + val config = ConfigFactory.parseString(File("infrastructure/src/jvmMain/resources/application.conf").readText()) + + val dbUrl = config.getString("ktor.db.url") ?: throw IllegalArgumentException("Database URL not set") + val dbUser = config.getString("ktor.db.user") ?: "" + val dbPass = config.getString("ktor.db.password") ?: "" + + this.arguments = mapOf( + "logLevel" to "info", + "changeLogFile" to migrationRoot.path, + "url" to dbUrl, + "username" to dbUser, + "password" to dbPass + ) + } + } + + runList = "main" +} + +val migrationStub = File("infrastructure/src/jvmMain/resources/db/changelog-stub.json") + +tasks { + register("make-migration") { + doLast { + val migrationPrefix = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")) + val migrationName = "${migrationPrefix}_${project.findProperty("id") ?: ""}" + val migrationFile = File("${migrationDir.path}/$migrationName.json") + + migrationStub.copyTo(migrationFile) + + println("Created migration file: ${migrationFile.path}") + + val newText = migrationStub.readText().replace("{{id}}", migrationName) + migrationFile.writeText(newText) + } + } + register("fresh",) { + dependsOn("dropAll") + dependsOn("update") + } +} diff --git a/infrastructure/src/jvmMain/kotlin/replace/KtorBackend.kt b/infrastructure/src/jvmMain/kotlin/replace/KtorBackend.kt index 11df33ee..81785719 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/KtorBackend.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/KtorBackend.kt @@ -1,16 +1,5 @@ package replace -import io.ktor.server.application.Application -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import replace.http.applicationModule - object KtorBackend : Backend { - override fun start(args: Array) { - embeddedServer( - Netty, - port = 8000, - module = Application::applicationModule - ).start(wait = true) - } + override fun start(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) } diff --git a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoBookableEntityRepository.kt b/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoBookableEntityRepository.kt deleted file mode 100644 index 87844902..00000000 --- a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoBookableEntityRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package replace.datastore - -import org.bson.types.ObjectId -import org.litote.kmongo.coroutine.CoroutineCollection -import org.litote.kmongo.eq -import replace.model.BookableEntity - -class MongoBookableEntityRepository(collection: CoroutineCollection) : - MongoRepository(collection), BookableEntityRepository { - override suspend fun forFloor(floorId: ObjectId): List = collection.find(BookableEntity::floorId eq floorId).toList() -} diff --git a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoBookableEntityTypeRepository.kt b/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoBookableEntityTypeRepository.kt deleted file mode 100644 index 764f5d42..00000000 --- a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoBookableEntityTypeRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package replace.datastore - -import org.litote.kmongo.coroutine.CoroutineCollection -import org.litote.kmongo.eq -import replace.model.BookableEntityType - -class MongoBookableEntityTypeRepository(collection: CoroutineCollection) : - MongoRepository(collection), BookableEntityTypeRepository { - override suspend fun findByName(name: String): BookableEntityType? = - collection.findOne(BookableEntityType::name eq name) -} diff --git a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoFloorRepository.kt b/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoFloorRepository.kt deleted file mode 100644 index 26c0ac09..00000000 --- a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoFloorRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package replace.datastore - -import org.bson.types.ObjectId -import org.litote.kmongo.coroutine.CoroutineCollection -import org.litote.kmongo.eq -import replace.model.Floor - -class MongoFloorRepository(collection: CoroutineCollection) : - MongoRepository(collection), FloorRepository { - override suspend fun findBySiteId(siteId: ObjectId): List = - collection.find(Floor::siteId eq siteId).toList() -} diff --git a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoRepository.kt b/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoRepository.kt deleted file mode 100644 index cabbefe6..00000000 --- a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoRepository.kt +++ /dev/null @@ -1,22 +0,0 @@ -package replace.datastore - -import org.bson.types.ObjectId -import org.litote.kmongo.coroutine.CoroutineCollection -import replace.model.ObjectWithId - -open class MongoRepository(protected val collection: CoroutineCollection) : Repository { - override suspend fun insertOne(item: T): T? = - if (collection.insertOne(item).wasAcknowledged()) item else null - - override suspend fun updateOne(id: ObjectId, item: T): T? = - if (collection.updateOneById(id, item).matchedCount > 0) item else null - - override suspend fun findOneById(id: ObjectId): T? = - collection.findOneById(id) - - override suspend fun deleteOneById(id: ObjectId): Boolean = - collection.deleteOneById(id).deletedCount > 0 - - override suspend fun getAll(): List = - collection.find().toList() -} diff --git a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoTemporaryFileRepository.kt b/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoTemporaryFileRepository.kt deleted file mode 100644 index 1cf82c0d..00000000 --- a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoTemporaryFileRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package replace.datastore - -import org.litote.kmongo.coroutine.CoroutineCollection -import org.litote.kmongo.lt -import replace.model.TemporaryFile -import java.time.LocalDateTime - -class MongoTemporaryFileRepository(collection: CoroutineCollection) : - MongoRepository(collection), TemporaryFileRepository { - - override suspend fun findOlderThan(datetime: LocalDateTime): List = - collection.find(TemporaryFile::createdAt lt datetime).toList() -} diff --git a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoUserRepository.kt b/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoUserRepository.kt deleted file mode 100644 index 8f3c3180..00000000 --- a/infrastructure/src/jvmMain/kotlin/replace/datastore/MongoUserRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package replace.datastore - -import org.litote.kmongo.coroutine.CoroutineCollection -import org.litote.kmongo.eq -import replace.model.User - -class MongoUserRepository(collection: CoroutineCollection) : - MongoRepository(collection), UserRepository { - override suspend fun findByUsername(username: String): User? = - collection.findOne(User::username eq username) -} diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/ApplicationModule.kt b/infrastructure/src/jvmMain/kotlin/replace/http/ApplicationModule.kt index 7676bf4d..f5eecfd7 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/ApplicationModule.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/ApplicationModule.kt @@ -1,6 +1,5 @@ package replace.http -import com.typesafe.config.ConfigFactory import guru.zoroark.tegral.openapi.ktor.TegralOpenApiKtor import guru.zoroark.tegral.openapi.ktor.openApiEndpoint import guru.zoroark.tegral.openapi.ktorui.TegralSwaggerUiKtor @@ -10,24 +9,20 @@ import io.ktor.http.HttpMethod import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.Application import io.ktor.server.application.install -import io.ktor.server.config.HoconApplicationConfig import io.ktor.server.config.tryGetString import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.cors.routing.CORS import io.ktor.server.resources.Resources import io.ktor.server.routing.routing import kotlinx.serialization.json.Json -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.contextual -import org.litote.kmongo.coroutine.CoroutineDatabase -import org.litote.kmongo.coroutine.coroutine -import org.litote.kmongo.reactivestreams.KMongo +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.DatabaseConfig +import org.jetbrains.exposed.sql.transactions.transaction import replace.datastore.LocalFileStorage -import replace.datastore.MongoTemporaryFileRepository -import replace.datastore.MongoUserRepository import replace.job.DeleteOldTemporaryFileUploadsJob +import replace.model.User +import replace.model.Users import replace.plugin.SinglePageApplication -import replace.serializer.ObjectIdSerializer fun Application.applicationModule() { install(CORS) { @@ -44,9 +39,6 @@ fun Application.applicationModule() { Json { encodeDefaults = false ignoreUnknownKeys = true - serializersModule = SerializersModule { - contextual(ObjectIdSerializer) - } } ) } @@ -61,13 +53,24 @@ fun Application.applicationModule() { install(TegralSwaggerUiKtor) - val config = HoconApplicationConfig(ConfigFactory.load()) - val db = getDB(config) + val databaseConfig = DatabaseConfig { + keepLoadedReferencesOutOfTransaction = true + } - val userRepository = MongoUserRepository(db.getCollection()) + Database.connect( + environment.config.tryGetString("ktor.db.url") ?: "", + driver = "org.postgresql.Driver", + user = environment.config.tryGetString("ktor.db.user") ?: "", + password = environment.config.tryGetString("ktor.db.password") ?: "", + databaseConfig = databaseConfig + ) + + if (environment.developmentMode) { + devSeeder() + } sessionModule() - authenticationModule(userRepository) + authenticationModule() install(SinglePageApplication) { folderPath = "static" @@ -81,35 +84,34 @@ fun Application.applicationModule() { val storage = LocalFileStorage() - routeControllers(db, storage) + routeControllers(storage) val deleteOldTemporaryFileUploadsJob = DeleteOldTemporaryFileUploadsJob( 1000 * 60 * 60 * 12, // 12 hours 1000 * 60 * 60 * 24, // 24 hours - MongoTemporaryFileRepository(db.getCollection()), storage ) deleteOldTemporaryFileUploadsJob.dispatch() } -fun getDB(config: HoconApplicationConfig): CoroutineDatabase { - val host = config.tryGetString("database.host") ?: "localhost" - val port = config.tryGetString("database.port") ?: "27017" - val user = config.tryGetString("database.user") ?: "" - val password = config.tryGetString("database.password")?.prependIndent(":") ?: "" - val database = config.tryGetString("database.database") ?: "replace-app" - - var credentials = "$user$password" - - if (credentials.isNotBlank()) { - credentials += "@" +fun devSeeder() { + transaction { + val message = "Seeding Dev User! (This should only happen in dev). Username: user, Password: password" + println() + println("-".repeat(message.length)) + println(message) + println("-".repeat(message.length)) + println() + val devUser = User.find { Users.username eq "user" }.firstOrNull() + + if (devUser == null) { + User.new { + username = "user" + password = "password" + firstname = "John" + lastname = "Doe" + } + } } - - val mongoUri = config.tryGetString("database.uri") - ?: "mongodb://$credentials$host:$port" - - val client = KMongo.createClient(mongoUri).coroutine - - return client.getDatabase(database) } diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/AuthenticationModule.kt b/infrastructure/src/jvmMain/kotlin/replace/http/AuthenticationModule.kt index 52f7c22d..4bdf3134 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/AuthenticationModule.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/AuthenticationModule.kt @@ -6,12 +6,9 @@ import io.ktor.server.auth.authentication import io.ktor.server.auth.session import io.ktor.server.response.respondText import io.ktor.server.routing.routing -import replace.datastore.UserRepository import replace.model.UserSession -fun Application.authenticationModule( - userRepository: UserRepository, -) { +fun Application.authenticationModule() { authentication { session { validate { session -> @@ -29,6 +26,6 @@ fun Application.authenticationModule( } routing { - routeAuthentication(userRepository) + routeAuthentication() } } diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/AuthenticationRouting.kt b/infrastructure/src/jvmMain/kotlin/replace/http/AuthenticationRouting.kt index dc4514ec..6cd1db48 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/AuthenticationRouting.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/AuthenticationRouting.kt @@ -30,30 +30,32 @@ import io.ktor.server.sessions.clear import io.ktor.server.sessions.get import io.ktor.server.sessions.sessions import io.ktor.server.sessions.set -import org.bson.types.ObjectId -import replace.datastore.UserRepository +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.transactions.transaction import replace.dto.LoginRequest +import replace.dto.toDto import replace.model.User import replace.model.UserSession +import replace.model.Users import replace.model.createSession -fun Route.routeAuthentication(userRepository: UserRepository) { +fun Route.routeAuthentication() { get("/api/current-user") { val session = call.sessions.get() - if (session?.userId === null) { + val userId = session?.userId + if (userId === null) { call.respondText("Not authenticated", status = HttpStatusCode.Unauthorized) return@get } - val user: User = try { - userRepository.findOneById(ObjectId(session.userId)) - ?: error("User with id ${session.userId} not found in database") - } catch (e: Exception) { - return@get call.respondText( - "Unable to get current user: ${e.message}", - status = HttpStatusCode.InternalServerError, - ) + + val user = transaction { User.findById(userId) } + + if (user === null) { + call.respondText("Not authenticated", status = HttpStatusCode.Unauthorized) + return@get } - call.respond(user) + + call.respond(user.toDto()) } post("/api/logout") { call.sessions.clear() @@ -73,26 +75,18 @@ fun Route.routeAuthentication(userRepository: UserRepository) { status = HttpStatusCode.BadRequest ) } - val userFromDb = userRepository.findByUsername(user.username) - when { - userFromDb == null -> { - call.respondText( - "User ${user.username} does not exist", - status = HttpStatusCode.BadRequest - ) - } - userFromDb.password != user.password -> { - // TODO: Replace with OAuth - // Never store plaintext passwords in a production DB - call.respondText( - "Wrong password for user ${user.username}", - status = HttpStatusCode.BadRequest - ) - } - else -> { - call.sessions.set(userFromDb.createSession()) - call.respond(userFromDb) - } + // TODO: Replace with OAuth + + val userFromDb = transaction { + User.find { Users.username eq user.username and(Users.password eq user.password) }.firstOrNull() + } + + if (userFromDb === null) { + call.respondText("Could not sign in: User not found", status = HttpStatusCode.BadRequest) + return@post } + call.sessions.clear("X-SESSION-TOKEN") + call.sessions.set(userFromDb.createSession()) + call.respond(userFromDb.toDto()) } } diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/ControllerRouting.kt b/infrastructure/src/jvmMain/kotlin/replace/http/ControllerRouting.kt index 463b92f3..402291f4 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/ControllerRouting.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/ControllerRouting.kt @@ -3,7 +3,6 @@ package replace.http import io.ktor.server.application.Application import io.ktor.server.auth.authenticate import io.ktor.server.routing.routing -import org.litote.kmongo.coroutine.CoroutineDatabase import replace.datastore.FileStorage import replace.http.controller.registerBookableEntityRoutes import replace.http.controller.registerBookableEntityTypeRoutes @@ -15,19 +14,18 @@ import replace.http.controller.registerTemporaryFileUploadRoutes import replace.http.controller.registerUserRoutes fun Application.routeControllers( - db: CoroutineDatabase, fileStorage: FileStorage ) { routing { authenticate { - registerBookableEntityRoutes(db) - registerBookableEntityTypeRoutes(db) - registerBookingRoutes(db) - registerFloorRoutes(db, fileStorage) - registerSiteRoutes(db) - registerUserRoutes(db) - registerFileRoutes(db, fileStorage) - registerTemporaryFileUploadRoutes(db, fileStorage) + registerBookableEntityRoutes() + registerBookableEntityTypeRoutes() + registerBookingRoutes() + registerFloorRoutes(fileStorage) + registerSiteRoutes() + registerUserRoutes() + registerFileRoutes(fileStorage) + registerTemporaryFileUploadRoutes(fileStorage) } } } diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/RepositoryModule.kt b/infrastructure/src/jvmMain/kotlin/replace/http/RepositoryModule.kt index 4466d3a9..bf459507 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/RepositoryModule.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/RepositoryModule.kt @@ -10,22 +10,18 @@ import io.ktor.server.response.respond import io.ktor.server.response.respondText import io.ktor.server.routing.Route import io.ktor.server.routing.get -import org.bson.types.ObjectId -import replace.datastore.Repository -import replace.dto.Dto +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.sql.transactions.transaction +import replace.dto.ModelDto import replace.http.controller.Routing -import replace.model.ObjectWithId +import replace.model.Model import kotlin.reflect.typeOf -inline fun Route.routeRepository(repository: Repository, crossinline toDto: (T) -> D) { +inline fun Route.routeRepository(repository: EntityClass, crossinline toDto: (T) -> D) { val listType = typeOf>() get { - try { - call.respond(repository.getAll().map(toDto)) - } catch (e: Exception) { - e.printStackTrace() - } + call.respond(transaction { repository.all().map(toDto) }) } describe { description = "Gets all ${T::class.simpleName}s" 200 response { @@ -37,20 +33,19 @@ inline fun Route.routeRepository(rep } get { route -> - if (!ObjectId.isValid(route.id)) { - return@get call.respondText("Id ${route.id} is not a valid ObjectId", status = HttpStatusCode.BadRequest) - } - val dbResult = repository.findOneById(ObjectId(route.id)) - if (dbResult == null) { - call.respondText("No document with id ${route.id}", status = HttpStatusCode.NotFound) - } else { - call.respond(toDto(dbResult)) + val model = transaction { repository.findById(route.id) } + + if (model === null) { + call.respondText("No Model with id ${route.id} found", status = HttpStatusCode.NotFound) + return@get } + + call.respond(toDto(model)) } describe { description = "Gets a ${T::class.simpleName} by id" "id" pathParameter { description = "The id of the ${T::class.simpleName}" - schema(ObjectId().toString()) + schema("") } 200 response { description = "The ${T::class.simpleName} with the given id" @@ -58,24 +53,26 @@ inline fun Route.routeRepository(rep schema() } } - 400 response { - description = "The id is not a valid ObjectId" - } 404 response { description = "No ${T::class.simpleName} with the given id exists" } } delete { route -> - if (!ObjectId.isValid(route.id)) { - return@delete call.respondText("Id ${route.id} is not a valid ObjectId", status = HttpStatusCode.BadRequest) + val model = transaction { repository.findById(route.id) } + + if (model === null) { + return@delete } - call.respond(repository.deleteOneById(ObjectId(route.id))) + + model.delete() + + call.respondText("Deleted Model with id ${route.id}", status = HttpStatusCode.OK) } describe { description = "Deletes a ${T::class.simpleName} by id" "id" pathParameter { description = "The id of the ${T::class.simpleName}" - schema(ObjectId().toString()) + schema("") } 200 response { description = "The deleted ${T::class.simpleName} with the given id" @@ -83,8 +80,5 @@ inline fun Route.routeRepository(rep schema(true) } } - 400 response { - description = "The id is not a valid ObjectId" - } } } diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookableEntityController.kt b/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookableEntityController.kt index c85af091..62050915 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookableEntityController.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookableEntityController.kt @@ -6,34 +6,31 @@ import io.ktor.server.routing.Route import io.ktor.server.routing.post import io.ktor.server.routing.put import io.ktor.server.routing.route -import org.litote.kmongo.coroutine.CoroutineDatabase -import replace.datastore.MongoRepository import replace.dto.BookableEntityDto +import replace.dto.CreateBookableEntityDto +import replace.dto.UpdateBookableEntityDto import replace.dto.toDto import replace.http.routeRepository import replace.model.BookableEntity -import replace.model.BookableEntityType import replace.usecase.bookableentity.CreateBookableEntityUseCase import replace.usecase.bookableentity.UpdateBookableEntityUseCase -fun Route.registerBookableEntityRoutes(db: CoroutineDatabase) { - val bookableEntityRepository = MongoRepository(db.getCollection()) - val bookableEntityTypeRepository = MongoRepository(db.getCollection()) +fun Route.registerBookableEntityRoutes() { route("/api/bookable-entity") { - routeRepository(bookableEntityRepository) { + routeRepository(BookableEntity.Companion) { it.toDto() } - post { + post { executeUseCase { - CreateBookableEntityUseCase.execute(it, bookableEntityRepository, bookableEntityTypeRepository) + CreateBookableEntityUseCase.execute(it) } } describe { description = "Creates a new bookable entity" body { json { - schema() + schema() } } 200 response { @@ -44,15 +41,15 @@ fun Route.registerBookableEntityRoutes(db: CoroutineDatabase) { } } - put { + put { executeUseCase { - UpdateBookableEntityUseCase.execute(it, bookableEntityRepository) + UpdateBookableEntityUseCase.execute(it) } } describe { description = "Updates a bookable entity" body { json { - schema() + schema() } } 200 response { diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookableEntityTypeController.kt b/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookableEntityTypeController.kt index 1f8cfc92..08739b7e 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookableEntityTypeController.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookableEntityTypeController.kt @@ -5,29 +5,28 @@ import guru.zoroark.tegral.openapi.ktor.describe import io.ktor.server.routing.Route import io.ktor.server.routing.post import io.ktor.server.routing.route -import org.litote.kmongo.coroutine.CoroutineDatabase -import replace.datastore.MongoBookableEntityTypeRepository import replace.dto.BookableEntityTypeDto +import replace.dto.CreateBookableEntityTypeDto import replace.dto.toDto import replace.http.routeRepository +import replace.model.BookableEntityType import replace.usecase.bookableentitytype.CreateBookableEntityTypeUseCase -fun Route.registerBookableEntityTypeRoutes(db: CoroutineDatabase) { - val bookableEntityTypeRepository = MongoBookableEntityTypeRepository(db.getCollection()) +fun Route.registerBookableEntityTypeRoutes() { route("/api/bookable-entity-type") { - routeRepository(bookableEntityTypeRepository) { + routeRepository(BookableEntityType.Companion) { it.toDto() } - post { + post { executeUseCase { - CreateBookableEntityTypeUseCase.execute(it, bookableEntityTypeRepository) + CreateBookableEntityTypeUseCase.execute(it) } } describe { description = "Creates a new bookable entity type" body { json { - schema() + schema() } } 200 response { diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookingController.kt b/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookingController.kt index 9d08eb94..f5a4c069 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookingController.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/controller/BookingController.kt @@ -2,30 +2,49 @@ package replace.http.controller import guru.zoroark.tegral.openapi.dsl.schema import guru.zoroark.tegral.openapi.ktor.describe +import io.ktor.server.application.call +import io.ktor.server.response.respond import io.ktor.server.routing.Route +import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.route -import org.litote.kmongo.coroutine.CoroutineDatabase -import replace.datastore.MongoBookableEntityRepository -import replace.datastore.MongoRepository +import io.ktor.server.sessions.get +import io.ktor.server.sessions.sessions +import org.jetbrains.exposed.dao.with +import org.jetbrains.exposed.sql.transactions.transaction import replace.dto.BookingDto +import replace.dto.CreateBookingDto import replace.dto.toDto import replace.http.routeRepository import replace.model.Booking +import replace.model.UserSession import replace.usecase.booking.CreateBookingUseCase +import kotlin.reflect.typeOf -fun Route.registerBookingRoutes(db: CoroutineDatabase) { - val bookingRepository = MongoRepository(db.getCollection()) - val bookableEntityRepository = MongoBookableEntityRepository(db.getCollection()) - +fun Route.registerBookingRoutes() { route("/api/booking") { - routeRepository(bookingRepository) { + val listType = typeOf>() + + get { + call.respond(transaction { Booking.all().with(Booking::bookedEntities).map { it.toDto(listOf(Booking::bookedEntities)) } }) + } describe { + description = "Gets all Bookings" + 200 response { + description = "All Bookings" + json { + schema(listType) + } + } + } + routeRepository(Booking.Companion) { it.toDto() } - post { + post { executeUseCase { - CreateBookingUseCase.execute(it, bookingRepository, bookableEntityRepository) + val userId = call.sessions.get()?.userId!! + + CreateBookingUseCase.execute(it, userId) } } describe { body { @@ -36,7 +55,7 @@ fun Route.registerBookingRoutes(db: CoroutineDatabase) { description = "Creates a new booking" body { json { - schema() + schema() } } 200 response { diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/controller/FileController.kt b/infrastructure/src/jvmMain/kotlin/replace/http/controller/FileController.kt index 89efbe2f..34e32449 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/controller/FileController.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/controller/FileController.kt @@ -14,43 +14,40 @@ import io.ktor.server.response.respondText import io.ktor.server.routing.Route import io.ktor.server.routing.route import io.swagger.v3.oas.models.media.FileSchema -import org.bson.types.ObjectId -import org.litote.kmongo.coroutine.CoroutineDatabase +import org.jetbrains.exposed.sql.transactions.transaction import replace.datastore.FileStorage -import replace.datastore.MongoRepository import replace.model.File -fun Route.registerFileRoutes(db: CoroutineDatabase, fileStorage: FileStorage) { - val fileRepository = MongoRepository(db.getCollection()) - +fun Route.registerFileRoutes(fileStorage: FileStorage) { route("/api/file") { get { route -> - if (!ObjectId.isValid(route.id)) { - return@get call.respondText("Id ${route.id} is not a valid ObjectId", status = HttpStatusCode.BadRequest) - } + val file = transaction { File.findById(route.id) } - val dbResult = fileRepository.findOneById(ObjectId(route.id)) - ?: return@get call.respondText("Temporary file upload with id ${route.id} not found", status = HttpStatusCode.NotFound) + if (file === null) { + call.respondText("No File with id ${route.id} found", status = HttpStatusCode.NotFound) + return@get + } - if (!fileStorage.exists(dbResult.path)) { - return@get call.respondText("File with id ${route.id} not found", status = HttpStatusCode.NotFound) + if (!fileStorage.exists(file.path)) { + call.respondText("File with id ${route.id} not found", status = HttpStatusCode.NotFound) + return@get } call.response.header( HttpHeaders.ContentDisposition, ContentDisposition.Inline.withParameter( - ContentDisposition.Parameters.FileName, "${dbResult.name}.${dbResult.extension}" + ContentDisposition.Parameters.FileName, "${file.name}.${file.extension}" ).toString() ) - val mime = dbResult.mime ?: ContentType.Application.OctetStream.toString() + val mime = file.mime ?: ContentType.Application.OctetStream.toString() - call.respondBytes(ContentType.parse(mime)) { fileStorage.readFile(dbResult.path).readBytes() } + call.respondBytes(ContentType.parse(mime)) { fileStorage.readFile(file.path).readBytes() } } describe { description = "Gets a temporary file upload by id" "id" pathParameter { description = "The id of the file" - schema(ObjectId().toString()) + schema("") } 200 response { description = "The temporary file upload" diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/controller/FloorController.kt b/infrastructure/src/jvmMain/kotlin/replace/http/controller/FloorController.kt index f9de8d88..56aedc1d 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/controller/FloorController.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/controller/FloorController.kt @@ -11,41 +11,31 @@ import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.put import io.ktor.server.routing.route -import org.bson.types.ObjectId -import org.litote.kmongo.coroutine.CoroutineDatabase +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.transaction import replace.datastore.FileStorage -import replace.datastore.MongoBookableEntityRepository -import replace.datastore.MongoFloorRepository -import replace.datastore.MongoRepository -import replace.datastore.MongoTemporaryFileRepository +import replace.dto.CreateFloorDto import replace.dto.FloorDto +import replace.dto.UpdateFloorDto import replace.dto.toDto import replace.http.routeRepository -import replace.model.File -import replace.model.Site +import replace.model.BookableEntities +import replace.model.BookableEntity +import replace.model.Floor import replace.usecase.floor.CreateFloorUseCase import replace.usecase.floor.UpdateFloorUseCase -fun Route.registerFloorRoutes(db: CoroutineDatabase, fileStorage: FileStorage) { - val floorRepository = MongoFloorRepository(db.getCollection()) - val siteRepository = MongoRepository(db.getCollection()) - val bookableEntityRepository = MongoBookableEntityRepository(db.getCollection()) - val fileRepository = MongoRepository(db.getCollection()) - val temporaryFileUploadRepository = MongoTemporaryFileRepository(db.getCollection()) +fun Route.registerFloorRoutes(fileStorage: FileStorage) { route("/api/floor") { - routeRepository(floorRepository) { + routeRepository(Floor.Companion) { it.toDto() } - post { + post { executeUseCase { CreateFloorUseCase.execute( it, - floorRepository, - siteRepository, - temporaryFileUploadRepository, - fileRepository, fileStorage, ) } @@ -53,7 +43,7 @@ fun Route.registerFloorRoutes(db: CoroutineDatabase, fileStorage: FileStorage) { description = "Creates a new floor" body { json { - schema() + schema() } } 200 response { @@ -67,17 +57,13 @@ fun Route.registerFloorRoutes(db: CoroutineDatabase, fileStorage: FileStorage) { get("/{floorId}/bookable-entity") { val floorId = call.parameters["floorId"] ?: return@get call.respondText("Missing id", status = HttpStatusCode.BadRequest) - if (!ObjectId.isValid(floorId)) { - return@get call.respondText("Id $floorId is not a valid ObjectId", status = HttpStatusCode.BadRequest) - } - - val bookableEntities = bookableEntityRepository.forFloor(ObjectId(floorId)).map() { it.toDto() } + val bookableEntityDtos = transaction { BookableEntity.find(BookableEntities.floor_id eq floorId).map { it.toDto() } } - call.respond(bookableEntities) + call.respond(bookableEntityDtos) } describe { "floorId" pathParameter { description = "The id of the floor" - schema(ObjectId().toString()) + schema("") } description = "Gets all bookable entities for a floor" 200 response { @@ -88,15 +74,15 @@ fun Route.registerFloorRoutes(db: CoroutineDatabase, fileStorage: FileStorage) { } } - put { + put { executeUseCase { - UpdateFloorUseCase.execute(it, floorRepository, temporaryFileUploadRepository, fileRepository, fileStorage) + UpdateFloorUseCase.execute(it, fileStorage) } } describe { description = "Updates a floor" body { json { - schema() + schema() } } 200 response { diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/controller/SiteController.kt b/infrastructure/src/jvmMain/kotlin/replace/http/controller/SiteController.kt index bdbb8061..af05b292 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/controller/SiteController.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/controller/SiteController.kt @@ -11,35 +11,34 @@ import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.put import io.ktor.server.routing.route -import org.bson.types.ObjectId -import org.litote.kmongo.coroutine.CoroutineDatabase -import replace.datastore.MongoFloorRepository -import replace.datastore.MongoRepository +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.transaction +import replace.dto.CreateSiteDto import replace.dto.SiteDto +import replace.dto.UpdateSiteDto import replace.dto.toDto import replace.http.routeRepository +import replace.model.Floor +import replace.model.Floors import replace.model.Site import replace.usecase.site.CreateSiteUseCase import replace.usecase.site.UpdateSiteUseCase -fun Route.registerSiteRoutes(db: CoroutineDatabase) { - val siteRepository = MongoRepository(db.getCollection()) - val floorRepository = MongoFloorRepository(db.getCollection()) - +fun Route.registerSiteRoutes() { route("/api/site") { - routeRepository(siteRepository) { + routeRepository(Site.Companion) { it.toDto() } - post { + post { executeUseCase { - CreateSiteUseCase.execute(it, siteRepository) + CreateSiteUseCase.execute(it) } } describe { description = "Creates a new site" body { json { - schema() + schema() } } 200 response { @@ -50,15 +49,15 @@ fun Route.registerSiteRoutes(db: CoroutineDatabase) { } } - put { + put { executeUseCase { - UpdateSiteUseCase.execute(it, siteRepository) + UpdateSiteUseCase.execute(it) } } describe { description = "Updates a site" body { json { - schema() + schema() } } 200 response { @@ -72,18 +71,14 @@ fun Route.registerSiteRoutes(db: CoroutineDatabase) { get("/{siteId}/floor") { val siteId = call.parameters["siteId"] ?: return@get call.respondText("Missing id", status = HttpStatusCode.BadRequest) - if (!ObjectId.isValid(siteId)) { - return@get call.respondText("Id $siteId is not a valid ObjectId", status = HttpStatusCode.BadRequest) - } - - val floors = floorRepository.findBySiteId(ObjectId(siteId)).map() { it.toDto() } + val floors = transaction { Floor.find { Floors.site_id eq siteId }.toList().map { it.toDto() } } call.respond(floors) } describe { description = "Gets all floors for a site" "siteId" pathParameter { description = "The id of the site" - schema(ObjectId().toString()) + schema("") } 200 response { description = "The floors for the site" diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/controller/TemporaryFileUploadController.kt b/infrastructure/src/jvmMain/kotlin/replace/http/controller/TemporaryFileUploadController.kt index a8130e84..c762b641 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/controller/TemporaryFileUploadController.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/controller/TemporaryFileUploadController.kt @@ -22,18 +22,15 @@ import io.ktor.server.routing.post import io.ktor.server.routing.route import io.swagger.v3.oas.models.media.FileSchema import io.swagger.v3.oas.models.media.MapSchema -import org.bson.types.ObjectId -import org.litote.kmongo.coroutine.CoroutineDatabase +import org.jetbrains.exposed.sql.transactions.transaction import replace.datastore.FileStorage -import replace.datastore.MongoTemporaryFileRepository import replace.dto.TemporaryFileUploadDto +import replace.model.TemporaryFile import replace.usecase.temporaryfileupload.CreateTemporaryFileUploadUseCase import replace.usecase.temporaryfileupload.DeleteTemporaryFileUploadUseCase import java.util.UUID -fun Route.registerTemporaryFileUploadRoutes(db: CoroutineDatabase, fileStorage: FileStorage) { - val temporaryFileUploadRepository = MongoTemporaryFileRepository(db.getCollection()) - +fun Route.registerTemporaryFileUploadRoutes(fileStorage: FileStorage) { route("/api/temporary-file-upload") { post { executeUseCase { @@ -51,7 +48,6 @@ fun Route.registerTemporaryFileUploadRoutes(db: CoroutineDatabase, fileStorage: val newFile = CreateTemporaryFileUploadUseCase.execute( name, it.streamProvider(), - temporaryFileUploadRepository, fileStorage ) temporaryFileUploadDtos.add(newFile) @@ -77,32 +73,29 @@ fun Route.registerTemporaryFileUploadRoutes(db: CoroutineDatabase, fileStorage: } get { route -> - if (!ObjectId.isValid(route.id)) { - return@get call.respondText("Id ${route.id} is not a valid ObjectId", status = HttpStatusCode.BadRequest) - } - val dbResult = temporaryFileUploadRepository.findOneById(ObjectId(route.id)) - ?: return@get call.respondText("Temporary file upload with id ${route.id} not found", status = HttpStatusCode.NotFound) + val file = transaction { TemporaryFile.findById(route.id) } - if (!fileStorage.exists(dbResult.path)) { - return@get call.respondText("Temporary file upload with id ${route.id} not found", status = HttpStatusCode.NotFound) + if (file === null) { + call.respondText("No File with id ${route.id} found", status = HttpStatusCode.NotFound) + return@get } call.response.header( HttpHeaders.ContentDisposition, ContentDisposition.Inline.withParameter( - ContentDisposition.Parameters.FileName, "${dbResult.name}.${dbResult.extension}" + ContentDisposition.Parameters.FileName, "${file.name}.${file.extension}" ).toString() ) - val mime = dbResult.mime ?: ContentType.Application.OctetStream.toString() + val mime = file.mime ?: ContentType.Application.OctetStream.toString() - call.respondBytes(ContentType.parse(mime)) { fileStorage.readFile(dbResult.path).readBytes() } + call.respondBytes(ContentType.parse(mime)) { fileStorage.readFile(file.path).readBytes() } } describe { description = "Gets a temporary file upload by id" "id" pathParameter { description = "The id of the temporary file" - schema(ObjectId().toString()) + schema("") } 200 response { description = "The temporary file upload" @@ -114,13 +107,13 @@ fun Route.registerTemporaryFileUploadRoutes(db: CoroutineDatabase, fileStorage: delete { route -> executeUseCase { - DeleteTemporaryFileUploadUseCase.execute(route.id, temporaryFileUploadRepository, fileStorage) + DeleteTemporaryFileUploadUseCase.execute(route.id, fileStorage) return@delete call.respond(HttpStatusCode.NoContent) } } describe { "id" pathParameter { description = "The id of the temporary file" - schema(ObjectId().toString()) + schema("") } description = "Deletes a temporary file upload by id" 204 response { diff --git a/infrastructure/src/jvmMain/kotlin/replace/http/controller/UserController.kt b/infrastructure/src/jvmMain/kotlin/replace/http/controller/UserController.kt index 47c6bf5b..1459677a 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/http/controller/UserController.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/http/controller/UserController.kt @@ -2,16 +2,13 @@ package replace.http.controller import io.ktor.server.routing.Route import io.ktor.server.routing.route -import org.litote.kmongo.coroutine.CoroutineDatabase -import replace.datastore.MongoUserRepository import replace.dto.toDto import replace.http.routeRepository +import replace.model.User -fun Route.registerUserRoutes(db: CoroutineDatabase) { - val userRepository = MongoUserRepository(db.getCollection()) - +fun Route.registerUserRoutes() { route("/api/user") { - routeRepository(userRepository) { + routeRepository(User.Companion) { it.toDto() } } diff --git a/infrastructure/src/jvmMain/kotlin/replace/job/DeleteOldTemporaryFileUploadsJob.kt b/infrastructure/src/jvmMain/kotlin/replace/job/DeleteOldTemporaryFileUploadsJob.kt index d16fbc10..a6373919 100644 --- a/infrastructure/src/jvmMain/kotlin/replace/job/DeleteOldTemporaryFileUploadsJob.kt +++ b/infrastructure/src/jvmMain/kotlin/replace/job/DeleteOldTemporaryFileUploadsJob.kt @@ -1,25 +1,31 @@ package replace.job +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.minus +import kotlinx.datetime.toJavaInstant +import org.jetbrains.exposed.sql.SqlExpressionBuilder.less +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import replace.datastore.FileStorage -import replace.datastore.TemporaryFileRepository +import replace.model.TemporaryFile +import replace.model.TemporaryFiles import replace.usecase.temporaryfileupload.DeleteTemporaryFileUploadUseCase -import java.time.LocalDateTime -import java.time.temporal.ChronoUnit class DeleteOldTemporaryFileUploadsJob( interval: Long, private val fileMaxAgeInMilliseconds: Long, - private val temporaryFileRepository: TemporaryFileRepository, private val fileStorage: FileStorage, ) : SchedulableJob(interval) { override suspend fun run() { - + val fileStorage = fileStorage try { - val oldTemporaryFileUploads = this.temporaryFileRepository.findOlderThan(LocalDateTime.now().minus(fileMaxAgeInMilliseconds, ChronoUnit.MILLIS)) + newSuspendedTransaction { + val oldTemporaryFiles = TemporaryFile.find( + TemporaryFiles.createdAt less Clock.System.now().minus(fileMaxAgeInMilliseconds, DateTimeUnit.MILLISECOND).toJavaInstant() + ) - oldTemporaryFileUploads.forEach { temporaryFileUpload -> - temporaryFileUpload.id?.let { - DeleteTemporaryFileUploadUseCase.execute(it.toHexString(), this.temporaryFileRepository, this.fileStorage) + oldTemporaryFiles.forEach { temporaryFile -> + DeleteTemporaryFileUploadUseCase.execute(temporaryFile, fileStorage) } } } catch (e: Exception) { diff --git a/infrastructure/src/jvmMain/kotlin/replace/serializer/InstantSerializer.kt b/infrastructure/src/jvmMain/kotlin/replace/serializer/InstantSerializer.kt deleted file mode 100644 index 9352067f..00000000 --- a/infrastructure/src/jvmMain/kotlin/replace/serializer/InstantSerializer.kt +++ /dev/null @@ -1,16 +0,0 @@ -package replace.serializer - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import java.time.Instant -import java.time.OffsetDateTime - -object InstantSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) - override fun deserialize(decoder: Decoder): Instant = OffsetDateTime.parse(decoder.decodeString()).toInstant() - override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeString(value.toString()) -} diff --git a/infrastructure/src/jvmMain/kotlin/replace/serializer/ObjectIdSerializer.kt b/infrastructure/src/jvmMain/kotlin/replace/serializer/ObjectIdSerializer.kt deleted file mode 100644 index 7dceb2ff..00000000 --- a/infrastructure/src/jvmMain/kotlin/replace/serializer/ObjectIdSerializer.kt +++ /dev/null @@ -1,15 +0,0 @@ -package replace.serializer - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import org.bson.types.ObjectId - -object ObjectIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ObjectId", PrimitiveKind.STRING) - override fun deserialize(decoder: Decoder): ObjectId = ObjectId(decoder.decodeString()) - override fun serialize(encoder: Encoder, value: ObjectId) = encoder.encodeString(value.toString()) -} diff --git a/infrastructure/src/jvmMain/resources/application.conf.example b/infrastructure/src/jvmMain/resources/application.conf.example index acd62cc4..48ba00a0 100644 --- a/infrastructure/src/jvmMain/resources/application.conf.example +++ b/infrastructure/src/jvmMain/resources/application.conf.example @@ -1,11 +1,14 @@ -database: { - user: "user" - password: "password" - host: "localhost" - port: "27017" - database: "replace-app" - uri: null -} ktor { development = false + deployment { + port = 8000 + } + db { + url = "jdbc:postgresql://localhost:5432/replace-app" + user = "postgres" + password = "postgres" + } + application { + modules = [ replace.http.ApplicationModuleKt.applicationModule ] + } } diff --git a/infrastructure/src/jvmMain/resources/db/changelog-root.json b/infrastructure/src/jvmMain/resources/db/changelog-root.json new file mode 100644 index 00000000..35532d75 --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/changelog-root.json @@ -0,0 +1,8 @@ +{ + "databaseChangeLog": [ + { + "includeAll": { "path": "infrastructure/src/jvmMain/resources/db/migrations" } + } + ] +} + diff --git a/infrastructure/src/jvmMain/resources/db/changelog-stub.json b/infrastructure/src/jvmMain/resources/db/changelog-stub.json new file mode 100644 index 00000000..6598486d --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/changelog-stub.json @@ -0,0 +1,13 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "{{id}}", + "author": "re-place", + "changes": [ + + ] + } + }] +} + diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-17_23-50-46_create_users_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-17_23-50-46_create_users_table.json new file mode 100644 index 00000000..a88b4479 --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-17_23-50-46_create_users_table.json @@ -0,0 +1,70 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-17_23-50-46_create_users_table", + "author": "re-place", + "changes": [ + { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_users_id", + "unique": true + } + } + }, + { + "column": { + "name": "username", + "type": "varchar(255)", + "constraints": { + "nullable": false, + "unique": true + } + } + }, + { + "column": { + "name": "password", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "firstname", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "lastname", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + } + ], + "schemaName": "public", + "tableName": "users" + } + } + ] + } + }] +} + diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-28-14_create_temporary_files_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-28-14_create_temporary_files_table.json new file mode 100644 index 00000000..20c01d13 --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-28-14_create_temporary_files_table.json @@ -0,0 +1,82 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-19_18-28-14_create_temporary_files_table", + "author": "re-place", + "changes": { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_temporary_files_id", + "unique": true + } + } + }, + { + "column": { + "name": "name", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "path", + "type": "varchar(65535)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "extension", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "size_in_bytes", + "type": "bigint", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "mime", + "type": "varchar(255)" + } + }, + { + "column": { + "name": "created_at", + "type": "timestamp", + "constraints": { + "nullable": false + } + } + } + ], + "schemaName": "public", + "tableName": "temporary_files" + } + } + } + } + ] +} diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-38-52_create_files_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-38-52_create_files_table.json new file mode 100644 index 00000000..fe3ba2ca --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-38-52_create_files_table.json @@ -0,0 +1,73 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-19_18-38-52_create_files_table", + "author": "re-place", + "changes": { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_files_id", + "unique": true + } + } + }, + { + "column": { + "name": "name", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "path", + "type": "varchar(65535)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "extension", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "size_in_bytes", + "type": "bigint", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "mime", + "type": "varchar(255)" + } + } + ], + "schemaName": "public", + "tableName": "files" + } + } + } + } + ] +} diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-46-48_create_sites_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-46-48_create_sites_table.json new file mode 100644 index 00000000..325a27aa --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-46-48_create_sites_table.json @@ -0,0 +1,40 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-19_18-46-48_create_sites_table", + "author": "re-place", + "changes": { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_sites_id", + "unique": true + } + } + }, + { + "column": { + "name": "name", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + } + ], + "schemaName": "public", + "tableName": "sites" + } + } + } + } + ] +} diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-48-19_create_floors_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-48-19_create_floors_table.json new file mode 100644 index 00000000..b66e84e8 --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_18-48-19_create_floors_table.json @@ -0,0 +1,61 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-19_18-48-19_create_floors_table", + "author": "re-place", + "changes": { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_floors_id", + "unique": true + } + } + }, + { + "column": { + "name": "name", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "site_id", + "type": "varchar(36)", + "constraints": { + "nullable": false, + "foreignKeyName": "fk_floors_sites", + "references": "sites(id)" + } + } + }, + { + "column": { + "name": "plan_file_id", + "type": "varchar(36)", + "constraints": { + "foreignKeyName": "fk_floors_files", + "references": "files(id)" + } + } + } + ], + "schemaName": "public", + "tableName": "floors" + } + } + } + } + ] +} diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-00-21_create_bookable_entity_types_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-00-21_create_bookable_entity_types_table.json new file mode 100644 index 00000000..d1191f20 --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-00-21_create_bookable_entity_types_table.json @@ -0,0 +1,40 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-19_19-00-21_create_bookable_entity_types_table", + "author": "re-place", + "changes": { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_bookable_entity_types_id", + "unique": true + } + } + }, + { + "column": { + "name": "name", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + } + ], + "schemaName": "public", + "tableName": "bookable_entity_types" + } + } + } + } + ] +} diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-02-00_create_bookings_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-02-00_create_bookings_table.json new file mode 100644 index 00000000..e4544e83 --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-02-00_create_bookings_table.json @@ -0,0 +1,59 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-19_19-02-00_create_bookings_table", + "author": "re-place", + "changes": { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_bookings_id", + "unique": true + } + } + }, + { + "column": { + "name": "start", + "type": "timestamp", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "end", + "type": "timestamp", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "user_id", + "type": "varchar(36)", + "constraints": { + "foreignKeyName": "fk_bookings_user", + "references": "users(id)" + } + } + } + ], + "schemaName": "public", + "tableName": "bookings" + } + } + } + } + ] +} diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-06-14_create_bookable_entity_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-06-14_create_bookable_entity_table.json new file mode 100644 index 00000000..33882eed --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-06-14_create_bookable_entity_table.json @@ -0,0 +1,70 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-19_19-06-14_create_bookable_entity_table", + "author": "re-place", + "changes": { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_bookable_entity_id", + "unique": true + } + } + }, + { + "column": { + "name": "name", + "type": "varchar(255)", + "constraints": { + "nullable": false + } + } + }, + { + "column": { + "name": "floor_id", + "type": "varchar(36)", + "constraints": { + "foreignKeyName": "fk_bookable_entities_floor", + "references": "floors(id)" + } + } + }, + { + "column": { + "name": "type_id", + "type": "varchar(36)", + "constraints": { + "foreignKeyName": "fk_bookable_entities_entity_types", + "references": "bookable_entity_types(id)" + } + } + }, + { + "column": { + "name": "parent_id", + "type": "varchar(36)", + "constraints": { + "foreignKeyName": "fk_bookable_entities_bookable_entities", + "references": "bookable_entities(id)" + } + } + } + ], + "schemaName": "public", + "tableName": "bookable_entities" + } + } + } + } + ] +} diff --git a/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-10-08_create_booked_entities_table.json b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-10-08_create_booked_entities_table.json new file mode 100644 index 00000000..192c4045 --- /dev/null +++ b/infrastructure/src/jvmMain/resources/db/migrations/2023-01-19_19-10-08_create_booked_entities_table.json @@ -0,0 +1,51 @@ +{ + "databaseChangeLog": [ + { + "changeSet": { + "id": "2023-01-19_19-10-08_create_booked_entities_table", + "author": "re-place", + "changes": { + "createTable": { + "columns": [ + { + "column": { + "name": "id", + "type": "varchar(36)", + "defaultValueComputed": "gen_random_uuid()", + "constraints": { + "nullable": false, + "primaryKey": true, + "primaryKeyName": "pk_booked_entity_id", + "unique": true + } + } + }, + { + "column": { + "name": "bookable_entity_id", + "type": "varchar(36)", + "constraints": { + "foreignKeyName": "fk_booked_entities_bookable_entities", + "references": "bookable_entities(id)" + } + } + }, + { + "column": { + "name": "booking_id", + "type": "varchar(36)", + "constraints": { + "foreignKeyName": "fk_booked_entities_bookings", + "references": "bookings(id)" + } + } + } + ], + "schemaName": "public", + "tableName": "booked_entities" + } + } + } + } + ] +} diff --git a/web/src/angular/src/app/core/openapi/.openapi-generator/FILES b/web/src/angular/src/app/core/openapi/.openapi-generator/FILES index 2094160b..b0375136 100644 --- a/web/src/angular/src/app/core/openapi/.openapi-generator/FILES +++ b/web/src/angular/src/app/core/openapi/.openapi-generator/FILES @@ -10,11 +10,20 @@ index.ts model/bookableEntityDto.ts model/bookableEntityTypeDto.ts model/bookingDto.ts +model/createBookableEntityDto.ts +model/createBookableEntityTypeDto.ts +model/createBookingDto.ts +model/createFloorDto.ts +model/createSiteDto.ts +model/fileDto.ts model/fileUploadDto.ts model/floorDto.ts model/models.ts model/siteDto.ts model/temporaryFileUploadDto.ts +model/updateBookableEntityDto.ts +model/updateFloorDto.ts +model/updateSiteDto.ts model/userDto.ts param.ts variables.ts diff --git a/web/src/angular/src/app/core/openapi/api/default.service.ts b/web/src/angular/src/app/core/openapi/api/default.service.ts index aab80004..aa214197 100644 --- a/web/src/angular/src/app/core/openapi/api/default.service.ts +++ b/web/src/angular/src/app/core/openapi/api/default.service.ts @@ -22,9 +22,17 @@ import { CustomHttpParameterCodec } from "../encoder" import { BookableEntityDto } from "../model/bookableEntityDto" import { BookableEntityTypeDto } from "../model/bookableEntityTypeDto" import { BookingDto } from "../model/bookingDto" +import { CreateBookableEntityDto } from "../model/createBookableEntityDto" +import { CreateBookableEntityTypeDto } from "../model/createBookableEntityTypeDto" +import { CreateBookingDto } from "../model/createBookingDto" +import { CreateFloorDto } from "../model/createFloorDto" +import { CreateSiteDto } from "../model/createSiteDto" import { FloorDto } from "../model/floorDto" import { SiteDto } from "../model/siteDto" import { TemporaryFileUploadDto } from "../model/temporaryFileUploadDto" +import { UpdateBookableEntityDto } from "../model/updateBookableEntityDto" +import { UpdateFloorDto } from "../model/updateFloorDto" +import { UpdateSiteDto } from "../model/updateSiteDto" import { UserDto } from "../model/userDto" import { BASE_PATH, COLLECTION_FORMATS } from "../variables" @@ -64,7 +72,6 @@ export class DefaultService { httpParams = this.addToHttpParamsRecursive(httpParams, value, key) } return httpParams - } private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { @@ -153,7 +160,6 @@ export class DefaultService { * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiBookableEntityIdDelete(id: string, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "text/plain", context?: HttpContext}): Observable; public apiBookableEntityIdDelete(id: string, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "text/plain", context?: HttpContext}): Observable>; public apiBookableEntityIdDelete(id: string, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "text/plain", context?: HttpContext}): Observable>; @@ -266,14 +272,14 @@ export class DefaultService { /** * Creates a new bookable entity - * @param bookableEntityDto + * @param createBookableEntityDto * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiBookableEntityPost(bookableEntityDto?: BookableEntityDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; - public apiBookableEntityPost(bookableEntityDto?: BookableEntityDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiBookableEntityPost(bookableEntityDto?: BookableEntityDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiBookableEntityPost(bookableEntityDto?: BookableEntityDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { + public apiBookableEntityPost(createBookableEntityDto?: CreateBookableEntityDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; + public apiBookableEntityPost(createBookableEntityDto?: CreateBookableEntityDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiBookableEntityPost(createBookableEntityDto?: CreateBookableEntityDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiBookableEntityPost(createBookableEntityDto?: CreateBookableEntityDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { let localVarHeaders = this.defaultHeaders @@ -319,7 +325,7 @@ export class DefaultService { return this.httpClient.request("post", `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, - body: bookableEntityDto, + body: createBookableEntityDto, responseType: responseType_, withCredentials: this.configuration.withCredentials, headers: localVarHeaders, @@ -331,14 +337,14 @@ export class DefaultService { /** * Updates a bookable entity - * @param bookableEntityDto + * @param updateBookableEntityDto * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiBookableEntityPut(bookableEntityDto?: BookableEntityDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; - public apiBookableEntityPut(bookableEntityDto?: BookableEntityDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiBookableEntityPut(bookableEntityDto?: BookableEntityDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiBookableEntityPut(bookableEntityDto?: BookableEntityDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { + public apiBookableEntityPut(updateBookableEntityDto?: UpdateBookableEntityDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; + public apiBookableEntityPut(updateBookableEntityDto?: UpdateBookableEntityDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiBookableEntityPut(updateBookableEntityDto?: UpdateBookableEntityDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiBookableEntityPut(updateBookableEntityDto?: UpdateBookableEntityDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { let localVarHeaders = this.defaultHeaders @@ -384,7 +390,7 @@ export class DefaultService { return this.httpClient.request("put", `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, - body: bookableEntityDto, + body: updateBookableEntityDto, responseType: responseType_, withCredentials: this.configuration.withCredentials, headers: localVarHeaders, @@ -566,14 +572,14 @@ export class DefaultService { /** * Creates a new bookable entity type - * @param bookableEntityTypeDto + * @param createBookableEntityTypeDto * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiBookableEntityTypePost(bookableEntityTypeDto?: BookableEntityTypeDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; - public apiBookableEntityTypePost(bookableEntityTypeDto?: BookableEntityTypeDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiBookableEntityTypePost(bookableEntityTypeDto?: BookableEntityTypeDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiBookableEntityTypePost(bookableEntityTypeDto?: BookableEntityTypeDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { + public apiBookableEntityTypePost(createBookableEntityTypeDto?: CreateBookableEntityTypeDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; + public apiBookableEntityTypePost(createBookableEntityTypeDto?: CreateBookableEntityTypeDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiBookableEntityTypePost(createBookableEntityTypeDto?: CreateBookableEntityTypeDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiBookableEntityTypePost(createBookableEntityTypeDto?: CreateBookableEntityTypeDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { let localVarHeaders = this.defaultHeaders @@ -619,7 +625,7 @@ export class DefaultService { return this.httpClient.request("post", `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, - body: bookableEntityTypeDto, + body: createBookableEntityTypeDto, responseType: responseType_, withCredentials: this.configuration.withCredentials, headers: localVarHeaders, @@ -801,14 +807,14 @@ export class DefaultService { /** * Creates a new booking - * @param bookingDto + * @param createBookingDto * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiBookingPost(bookingDto?: BookingDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; - public apiBookingPost(bookingDto?: BookingDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiBookingPost(bookingDto?: BookingDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiBookingPost(bookingDto?: BookingDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { + public apiBookingPost(createBookingDto?: CreateBookingDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; + public apiBookingPost(createBookingDto?: CreateBookingDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiBookingPost(createBookingDto?: CreateBookingDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiBookingPost(createBookingDto?: CreateBookingDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { let localVarHeaders = this.defaultHeaders @@ -854,7 +860,7 @@ export class DefaultService { return this.httpClient.request("post", `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, - body: bookingDto, + body: createBookingDto, responseType: responseType_, withCredentials: this.configuration.withCredentials, headers: localVarHeaders, @@ -1141,14 +1147,14 @@ export class DefaultService { /** * Creates a new floor - * @param floorDto + * @param createFloorDto * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiFloorPost(floorDto?: FloorDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; - public apiFloorPost(floorDto?: FloorDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiFloorPost(floorDto?: FloorDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiFloorPost(floorDto?: FloorDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { + public apiFloorPost(createFloorDto?: CreateFloorDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; + public apiFloorPost(createFloorDto?: CreateFloorDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiFloorPost(createFloorDto?: CreateFloorDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiFloorPost(createFloorDto?: CreateFloorDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { let localVarHeaders = this.defaultHeaders @@ -1194,7 +1200,7 @@ export class DefaultService { return this.httpClient.request("post", `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, - body: floorDto, + body: createFloorDto, responseType: responseType_, withCredentials: this.configuration.withCredentials, headers: localVarHeaders, @@ -1206,14 +1212,14 @@ export class DefaultService { /** * Updates a floor - * @param floorDto + * @param updateFloorDto * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiFloorPut(floorDto?: FloorDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; - public apiFloorPut(floorDto?: FloorDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiFloorPut(floorDto?: FloorDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiFloorPut(floorDto?: FloorDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { + public apiFloorPut(updateFloorDto?: UpdateFloorDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; + public apiFloorPut(updateFloorDto?: UpdateFloorDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiFloorPut(updateFloorDto?: UpdateFloorDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiFloorPut(updateFloorDto?: UpdateFloorDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { let localVarHeaders = this.defaultHeaders @@ -1259,7 +1265,7 @@ export class DefaultService { return this.httpClient.request("put", `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, - body: floorDto, + body: updateFloorDto, responseType: responseType_, withCredentials: this.configuration.withCredentials, headers: localVarHeaders, @@ -1441,14 +1447,14 @@ export class DefaultService { /** * Creates a new site - * @param siteDto + * @param createSiteDto * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiSitePost(siteDto?: SiteDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; - public apiSitePost(siteDto?: SiteDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiSitePost(siteDto?: SiteDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiSitePost(siteDto?: SiteDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { + public apiSitePost(createSiteDto?: CreateSiteDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; + public apiSitePost(createSiteDto?: CreateSiteDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiSitePost(createSiteDto?: CreateSiteDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiSitePost(createSiteDto?: CreateSiteDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { let localVarHeaders = this.defaultHeaders @@ -1494,7 +1500,7 @@ export class DefaultService { return this.httpClient.request("post", `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, - body: siteDto, + body: createSiteDto, responseType: responseType_, withCredentials: this.configuration.withCredentials, headers: localVarHeaders, @@ -1506,14 +1512,14 @@ export class DefaultService { /** * Updates a site - * @param siteDto + * @param updateSiteDto * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public apiSitePut(siteDto?: SiteDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; - public apiSitePut(siteDto?: SiteDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiSitePut(siteDto?: SiteDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; - public apiSitePut(siteDto?: SiteDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { + public apiSitePut(updateSiteDto?: UpdateSiteDto, observe?: "body", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable; + public apiSitePut(updateSiteDto?: UpdateSiteDto, observe?: "response", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiSitePut(updateSiteDto?: UpdateSiteDto, observe?: "events", reportProgress?: boolean, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable>; + public apiSitePut(updateSiteDto?: UpdateSiteDto, observe: any = "body", reportProgress = false, options?: {httpHeaderAccept?: "application/json", context?: HttpContext}): Observable { let localVarHeaders = this.defaultHeaders @@ -1559,7 +1565,7 @@ export class DefaultService { return this.httpClient.request("put", `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, - body: siteDto, + body: updateSiteDto, responseType: responseType_, withCredentials: this.configuration.withCredentials, headers: localVarHeaders, diff --git a/web/src/angular/src/app/core/openapi/model/bookableEntityDto.ts b/web/src/angular/src/app/core/openapi/model/bookableEntityDto.ts index ce4298a8..ea127875 100644 --- a/web/src/angular/src/app/core/openapi/model/bookableEntityDto.ts +++ b/web/src/angular/src/app/core/openapi/model/bookableEntityDto.ts @@ -9,6 +9,7 @@ * https://openapi-generator.tech * Do not edit the class manually. */ +import { FloorDto } from './floorDto'; import { BookableEntityTypeDto } from './bookableEntityTypeDto'; @@ -17,6 +18,9 @@ export interface BookableEntityDto { name?: string; floorId?: string; parentId?: string; + typeId?: string; + floor?: FloorDto; + parent?: BookableEntityDto; type?: BookableEntityTypeDto; } diff --git a/web/src/angular/src/app/core/openapi/model/bookableEntityTypeDto.ts b/web/src/angular/src/app/core/openapi/model/bookableEntityTypeDto.ts index 530ba400..57b9878a 100644 --- a/web/src/angular/src/app/core/openapi/model/bookableEntityTypeDto.ts +++ b/web/src/angular/src/app/core/openapi/model/bookableEntityTypeDto.ts @@ -9,10 +9,12 @@ * https://openapi-generator.tech * Do not edit the class manually. */ +import { BookableEntityDto } from './bookableEntityDto'; export interface BookableEntityTypeDto { id?: string; name?: string; + bookableEntities?: Array; } diff --git a/web/src/angular/src/app/core/openapi/model/bookingDto.ts b/web/src/angular/src/app/core/openapi/model/bookingDto.ts index 971e88a6..761b76bd 100644 --- a/web/src/angular/src/app/core/openapi/model/bookingDto.ts +++ b/web/src/angular/src/app/core/openapi/model/bookingDto.ts @@ -3,18 +3,22 @@ * API for the Replace application * * The version of the OpenAPI document: 2022.1-SNAPSHOT - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech * Do not edit the class manually. */ +import { BookableEntityDto } from './bookableEntityDto'; +import { UserDto } from './userDto'; -export interface BookingDto { +export interface BookingDto { id?: string; - bookedEntities?: Array; - startDateTime?: string; - endDateTime?: string; + userId?: string; + user?: UserDto; + bookedEntities?: Array; + start?: string; + end?: string; } diff --git a/web/src/angular/src/app/core/openapi/model/createBookableEntityDto.ts b/web/src/angular/src/app/core/openapi/model/createBookableEntityDto.ts new file mode 100644 index 00000000..c554f341 --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/createBookableEntityDto.ts @@ -0,0 +1,20 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface CreateBookableEntityDto { + name?: string; + floorId?: string; + parentId?: string; + typeId?: string; +} + diff --git a/web/src/angular/src/app/core/openapi/model/createBookableEntityTypeDto.ts b/web/src/angular/src/app/core/openapi/model/createBookableEntityTypeDto.ts new file mode 100644 index 00000000..6d23ada6 --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/createBookableEntityTypeDto.ts @@ -0,0 +1,17 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface CreateBookableEntityTypeDto { + name?: string; +} + diff --git a/web/src/angular/src/app/core/openapi/model/createBookingDto.ts b/web/src/angular/src/app/core/openapi/model/createBookingDto.ts new file mode 100644 index 00000000..2158491b --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/createBookingDto.ts @@ -0,0 +1,19 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface CreateBookingDto { + bookedEntityIds?: Array; + start?: string; + end?: string; +} + diff --git a/web/src/angular/src/app/core/openapi/model/createFloorDto.ts b/web/src/angular/src/app/core/openapi/model/createFloorDto.ts new file mode 100644 index 00000000..9e974d82 --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/createFloorDto.ts @@ -0,0 +1,20 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { FileUploadDto } from './fileUploadDto'; + + +export interface CreateFloorDto { + name?: string; + siteId?: string; + planFile?: FileUploadDto; +} + diff --git a/web/src/angular/src/app/core/openapi/model/createSiteDto.ts b/web/src/angular/src/app/core/openapi/model/createSiteDto.ts new file mode 100644 index 00000000..a7f487df --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/createSiteDto.ts @@ -0,0 +1,17 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface CreateSiteDto { + name?: string; +} + diff --git a/web/src/angular/src/app/core/openapi/model/fileDto.ts b/web/src/angular/src/app/core/openapi/model/fileDto.ts new file mode 100644 index 00000000..40b12441 --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/fileDto.ts @@ -0,0 +1,23 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface FileDto { + id?: string; + name?: string; + path?: string; + extension?: string; + sizeInBytes?: number; + mime?: string; + url?: string; +} + diff --git a/web/src/angular/src/app/core/openapi/model/fileUploadDto.ts b/web/src/angular/src/app/core/openapi/model/fileUploadDto.ts index 9bcc663c..2bdea1ec 100644 --- a/web/src/angular/src/app/core/openapi/model/fileUploadDto.ts +++ b/web/src/angular/src/app/core/openapi/model/fileUploadDto.ts @@ -12,7 +12,7 @@ export interface FileUploadDto { - id?: string; + fileId?: string; temporary?: boolean; } diff --git a/web/src/angular/src/app/core/openapi/model/floorDto.ts b/web/src/angular/src/app/core/openapi/model/floorDto.ts index 6dee8935..123876cd 100644 --- a/web/src/angular/src/app/core/openapi/model/floorDto.ts +++ b/web/src/angular/src/app/core/openapi/model/floorDto.ts @@ -9,12 +9,18 @@ * https://openapi-generator.tech * Do not edit the class manually. */ -import { FileUploadDto } from './fileUploadDto'; +import { FileDto } from './fileDto'; +import { BookableEntityDto } from './bookableEntityDto'; +import { SiteDto } from './siteDto'; + export interface FloorDto { id?: string; name?: string; siteId?: string; - planFile?: FileUploadDto; + site?: SiteDto; + planFileId?: string; + planFile?: FileDto; + bookableEntities?: Array; } diff --git a/web/src/angular/src/app/core/openapi/model/instant.ts b/web/src/angular/src/app/core/openapi/model/instant.ts new file mode 100644 index 00000000..2f44c417 --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/instant.ts @@ -0,0 +1,19 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface Instant { + value$kotlinx_datetime?: string; + epochSeconds?: number; + nanosecondsOfSecond?: number; +} + diff --git a/web/src/angular/src/app/core/openapi/model/models.ts b/web/src/angular/src/app/core/openapi/model/models.ts index cd0d366c..bc2d4ce0 100644 --- a/web/src/angular/src/app/core/openapi/model/models.ts +++ b/web/src/angular/src/app/core/openapi/model/models.ts @@ -1,8 +1,17 @@ export * from './bookableEntityDto'; export * from './bookableEntityTypeDto'; export * from './bookingDto'; +export * from './createBookableEntityDto'; +export * from './createBookableEntityTypeDto'; +export * from './createBookingDto'; +export * from './createFloorDto'; +export * from './createSiteDto'; +export * from './fileDto'; export * from './fileUploadDto'; export * from './floorDto'; export * from './siteDto'; export * from './temporaryFileUploadDto'; +export * from './updateBookableEntityDto'; +export * from './updateFloorDto'; +export * from './updateSiteDto'; export * from './userDto'; diff --git a/web/src/angular/src/app/core/openapi/model/siteDto.ts b/web/src/angular/src/app/core/openapi/model/siteDto.ts index b0210b24..3ad59f42 100644 --- a/web/src/angular/src/app/core/openapi/model/siteDto.ts +++ b/web/src/angular/src/app/core/openapi/model/siteDto.ts @@ -9,10 +9,12 @@ * https://openapi-generator.tech * Do not edit the class manually. */ +import { FloorDto } from './floorDto'; export interface SiteDto { id?: string; name?: string; + floors?: Array; } diff --git a/web/src/angular/src/app/core/openapi/model/temporaryFileUploadDto.ts b/web/src/angular/src/app/core/openapi/model/temporaryFileUploadDto.ts index 068aa1f4..28824769 100644 --- a/web/src/angular/src/app/core/openapi/model/temporaryFileUploadDto.ts +++ b/web/src/angular/src/app/core/openapi/model/temporaryFileUploadDto.ts @@ -19,5 +19,6 @@ export interface TemporaryFileUploadDto { extension?: string; sizeInBytes?: number; createdAt?: string; + url?: string; } diff --git a/web/src/angular/src/app/core/openapi/model/updateBookableEntityDto.ts b/web/src/angular/src/app/core/openapi/model/updateBookableEntityDto.ts new file mode 100644 index 00000000..072997da --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/updateBookableEntityDto.ts @@ -0,0 +1,21 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface UpdateBookableEntityDto { + id?: string; + name?: string; + floorId?: string; + parentId?: string; + typeId?: string; +} + diff --git a/web/src/angular/src/app/core/openapi/model/updateFloorDto.ts b/web/src/angular/src/app/core/openapi/model/updateFloorDto.ts new file mode 100644 index 00000000..0269ba5e --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/updateFloorDto.ts @@ -0,0 +1,21 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { FileUploadDto } from './fileUploadDto'; + + +export interface UpdateFloorDto { + id?: string; + name?: string; + siteId?: string; + planFile?: FileUploadDto; +} + diff --git a/web/src/angular/src/app/core/openapi/model/updateSiteDto.ts b/web/src/angular/src/app/core/openapi/model/updateSiteDto.ts new file mode 100644 index 00000000..e8ffe9b6 --- /dev/null +++ b/web/src/angular/src/app/core/openapi/model/updateSiteDto.ts @@ -0,0 +1,18 @@ +/** + * Replace API + * API for the Replace application + * + * The version of the OpenAPI document: 2022.1-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface UpdateSiteDto { + id?: string; + name?: string; +} + diff --git a/web/src/angular/src/app/core/openapi/model/userDto.ts b/web/src/angular/src/app/core/openapi/model/userDto.ts index 1285739f..a0beea22 100644 --- a/web/src/angular/src/app/core/openapi/model/userDto.ts +++ b/web/src/angular/src/app/core/openapi/model/userDto.ts @@ -9,12 +9,15 @@ * https://openapi-generator.tech * Do not edit the class manually. */ +import { BookingDto } from './bookingDto'; export interface UserDto { id?: string; username?: string; - firstName?: string; - lastName?: string; + firstname?: string; + password?: string; + lastname?: string; + bookings?: Array; } diff --git a/web/src/angular/src/app/features/floor/components/create-or-update-bookable-entity/create-or-update-bookable-entity.component.ts b/web/src/angular/src/app/features/floor/components/create-or-update-bookable-entity/create-or-update-bookable-entity.component.ts index b5ea2afb..9a6e4ef9 100644 --- a/web/src/angular/src/app/features/floor/components/create-or-update-bookable-entity/create-or-update-bookable-entity.component.ts +++ b/web/src/angular/src/app/features/floor/components/create-or-update-bookable-entity/create-or-update-bookable-entity.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core" import { SetOptional } from "type-fest" -import { BookableEntityDto } from "src/app/core/openapi" +import { BookableEntityDto, CreateBookableEntityDto, UpdateBookableEntityDto } from "src/app/core/openapi" @Component({ selector: "create-or-update-bookable-entity [bookableEntity]", @@ -9,8 +9,8 @@ import { BookableEntityDto } from "src/app/core/openapi" styles: [], }) export class CreateOrUpdateBookableEntityComponent implements OnChanges { - @Input() bookableEntity!: SetOptional - bookableEntityToEdit: SetOptional = { name: "", type: undefined } + @Input() bookableEntity!: CreateBookableEntityDto | UpdateBookableEntityDto + bookableEntityToEdit: CreateBookableEntityDto | UpdateBookableEntityDto = { name: "", typeId: undefined } @Output() submitBookableEntity = new EventEmitter>() @@ -21,7 +21,7 @@ export class CreateOrUpdateBookableEntityComponent implements OnChanges { } get saveText() { - return this.bookableEntity.id === undefined ? "Hinzufügen" : "Speichern" + return (this.bookableEntity as UpdateBookableEntityDto).id === undefined ? "Hinzufügen" : "Speichern" } public onSubmit(event: SubmitEvent) { diff --git a/web/src/angular/src/app/features/floor/pages/edit/edit.component.ts b/web/src/angular/src/app/features/floor/pages/edit/edit.component.ts index 46b30891..b865c6d1 100644 --- a/web/src/angular/src/app/features/floor/pages/edit/edit.component.ts +++ b/web/src/angular/src/app/features/floor/pages/edit/edit.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute } from "@angular/router" import { Subscription } from "rxjs" import { SetOptional } from "type-fest" -import { BookableEntityDto, DefaultService, FileUploadDto, FloorDto } from "src/app/core/openapi" +import { BookableEntityDto, DefaultService, FileUploadDto, FloorDto, UpdateFloorDto } from "src/app/core/openapi" import { DataLoader, Form } from "src/app/util" @Component({ @@ -14,7 +14,7 @@ import { DataLoader, Form } from "src/app/util" }) export class EditComponent implements OnDestroy { title = "" - form: Form | undefined = undefined + form: Form | undefined = undefined floor = new DataLoader() bookableEntities = new DataLoader() editingBookableEntity: SetOptional | undefined = undefined @@ -27,7 +27,15 @@ export class EditComponent implements OnDestroy { private readonly snackBar: MatSnackBar, ) { this.floor.subscribe((floor) => { - this.form = new Form(floor) + this.form = new Form({ + id: floor.id, + name: floor.name, + planFile: floor.planFile ? { + fileId: floor.planFile.id, + temporary: false, + } : undefined, + siteId: floor.siteId, + }) this.form.useSnackbar(snackBar) this.title = `Stockwerk ${this.form.data.name} bearbeiten` }) @@ -105,11 +113,17 @@ export class EditComponent implements OnDestroy { public get initialFiles(): string[] { const planFile = this.form?.data.planFile - if (planFile === undefined || planFile === null || planFile.temporary === true || planFile.id === undefined) { + if (planFile === undefined || planFile.temporary === true) { + return [] + } + + const fileId = planFile.fileId + + if (fileId === undefined) { return [] } - return [planFile.id] + return [fileId] } public onFilesUploaded(files: FileUploadDto[]) { diff --git a/web/src/angular/src/app/features/reservation/pages/reservation/reservation.component.html b/web/src/angular/src/app/features/reservation/pages/reservation/reservation.component.html index 8edfa583..ddaad6ff 100644 --- a/web/src/angular/src/app/features/reservation/pages/reservation/reservation.component.html +++ b/web/src/angular/src/app/features/reservation/pages/reservation/reservation.component.html @@ -3,11 +3,11 @@

Buchen

-
+
-
+
@@ -29,47 +29,6 @@

Buchen

- Standort Buchen --> -