From cfd59e9805bcaa85e3a9c7fae9b17c2b387daa9b Mon Sep 17 00:00:00 2001 From: VBas <70689135+Bastiaan27@users.noreply.github.com> Date: Sat, 21 Dec 2024 17:29:43 +0100 Subject: [PATCH] GET /questionnaires --- .codebuddy/.gitignore | 1 + .codebuddy/summary.md | 41 +++++++++++++++++ .env.example | 7 ++- pom.xml | 8 +++- .../controllers/QuestionnaireController.kt | 22 ++++++++++ .../dtos/responses/QuestionnaireResponse.kt | 10 +++++ .../core/facades/QuestionnaireFacade.kt | 16 +++++++ .../core/mappers/QuestionnaireMapper.kt | 17 +++++++ .../core/services/QuestionnaireService.kt | 22 ++++++++++ .../controllers/IQuestionnaireController.kt | 44 +++++++++++++++++++ .../queries/GetAllQuestionnairesQuery.kt | 3 ++ .../repositories/IQuestionnaireRepository.kt | 7 +++ .../support/domain/models/Questionnaire.kt | 26 +++++++++++ .../queries/GetAllQuestionnairesQuery.kt | 3 ++ .../infrastructure/config/PostgreSQLConfig.kt | 16 +++++++ .../GetAllQuestionnairesQueryHandler.kt | 17 +++++++ .../entities/QuestionnaireEntity.kt | 23 ++++++++++ .../repositories/BaseRepository.kt | 25 +++++++++++ .../QuestionnaireBaseRepository.kt | 7 +++ .../repositories/QuestionnaireRepository.kt | 36 +++++++++++++++ src/main/resources/application.properties | 2 +- src/main/resources/test_data.sql | 15 +++++++ 22 files changed, 364 insertions(+), 4 deletions(-) create mode 100644 .codebuddy/.gitignore create mode 100644 .codebuddy/summary.md create mode 100644 src/main/kotlin/org/ptss/support/api/controllers/QuestionnaireController.kt create mode 100644 src/main/kotlin/org/ptss/support/api/dtos/responses/QuestionnaireResponse.kt create mode 100644 src/main/kotlin/org/ptss/support/core/facades/QuestionnaireFacade.kt create mode 100644 src/main/kotlin/org/ptss/support/core/mappers/QuestionnaireMapper.kt create mode 100644 src/main/kotlin/org/ptss/support/core/services/QuestionnaireService.kt create mode 100644 src/main/kotlin/org/ptss/support/domain/interfaces/controllers/IQuestionnaireController.kt create mode 100644 src/main/kotlin/org/ptss/support/domain/interfaces/queries/GetAllQuestionnairesQuery.kt create mode 100644 src/main/kotlin/org/ptss/support/domain/interfaces/repositories/IQuestionnaireRepository.kt create mode 100644 src/main/kotlin/org/ptss/support/domain/models/Questionnaire.kt create mode 100644 src/main/kotlin/org/ptss/support/domain/queries/GetAllQuestionnairesQuery.kt create mode 100644 src/main/kotlin/org/ptss/support/infrastructure/config/PostgreSQLConfig.kt create mode 100644 src/main/kotlin/org/ptss/support/infrastructure/handlers/queries/questionnaire/GetAllQuestionnairesQueryHandler.kt create mode 100644 src/main/kotlin/org/ptss/support/infrastructure/persistence/entities/QuestionnaireEntity.kt create mode 100644 src/main/kotlin/org/ptss/support/infrastructure/repositories/BaseRepository.kt create mode 100644 src/main/kotlin/org/ptss/support/infrastructure/repositories/QuestionnaireBaseRepository.kt create mode 100644 src/main/kotlin/org/ptss/support/infrastructure/repositories/QuestionnaireRepository.kt create mode 100644 src/main/resources/test_data.sql diff --git a/.codebuddy/.gitignore b/.codebuddy/.gitignore new file mode 100644 index 0000000..9f4c740 --- /dev/null +++ b/.codebuddy/.gitignore @@ -0,0 +1 @@ +db/ \ No newline at end of file diff --git a/.codebuddy/summary.md b/.codebuddy/summary.md new file mode 100644 index 0000000..47f2194 --- /dev/null +++ b/.codebuddy/summary.md @@ -0,0 +1,41 @@ +# Project Summary + +## Overview +This project is developed using Kotlin as the primary programming language, and it leverages various frameworks and libraries commonly used in modern software development. The project appears to be a backend service that likely manages products, with functionalities for creating, retrieving, and managing product data. It also includes configurations for Docker and Helm, indicating a focus on containerization and deployment in cloud environments. + +### Languages, Frameworks, and Main Libraries Used +- **Language**: Kotlin +- **Build Tool**: Maven (indicated by `pom.xml`) +- **Containerization**: Docker (indicated by multiple Dockerfiles) +- **Documentation**: Markdown files for CI/CD and other related documentation +- **Testing Framework**: Presumably JUnit or similar, as indicated by the test files + +## Purpose of the Project +The purpose of the project is to provide a robust backend service that manages product-related operations, including creating and retrieving product information. It likely supports RESTful API endpoints for interaction with clients. The use of Docker and Helm suggests that the project is intended for deployment in cloud environments, facilitating scalability and maintainability. + +## Configuration and Build Files +Here are the relevant files for configuration and building the project: + +1. `/pom.xml` +2. `/mvnw` +3. `/mvnw.cmd` +4. `/Dockerfile` +5. `/Dockerfile.dev` +6. `/sonar-project.properties` +7. `/helm/Chart.yaml` +8. `/helm/values-dev.yaml` +9. `/helm/values-prod.yaml` +10. `/helm/values.yaml` + +## Source Files Location +The source files can be found in the following directory: +- `/src/main/kotlin` + +## Documentation Files Location +Documentation files are located in the following directory: +- `/docs` +- Additionally, there are README files located at: + - `/README.md` + - `/helm/README.md` + +This project structure indicates a well-organized codebase with clear separation of concerns, making it easier to maintain and extend in the future. \ No newline at end of file diff --git a/.env.example b/.env.example index 9c7e4bf..75319e1 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,9 @@ ACCESS_TOKEN_COOKIE_SECURE= ACCESS_TOKEN_COOKIE_SAME_SITE= ACCESS_TOKEN_COOKIE_DOMAIN= ACCESS_TOKEN_COOKIE_PATH= -ACCESS_TOKEN_COOKIE_MAX_AGE= \ No newline at end of file +ACCESS_TOKEN_COOKIE_MAX_AGE= + +# PostgreSQL Database Configuration +POSTGRESQL_DB_URL= +POSTGRESQL_DB_USERNAME= +POSTGRESQL_DB_PASSWORD= \ No newline at end of file diff --git a/pom.xml b/pom.xml index 919a241..07c01fd 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,10 @@ io.quarkus quarkus-micrometer-registry-prometheus + + io.quarkus + quarkus-jdbc-postgresql + io.quarkus quarkus-junit5 @@ -238,8 +242,8 @@ .gitignore - - + + true 4 diff --git a/src/main/kotlin/org/ptss/support/api/controllers/QuestionnaireController.kt b/src/main/kotlin/org/ptss/support/api/controllers/QuestionnaireController.kt new file mode 100644 index 0000000..e1d2b98 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/api/controllers/QuestionnaireController.kt @@ -0,0 +1,22 @@ +package org.ptss.support.api.controllers + +import jakarta.enterprise.context.ApplicationScoped +import jakarta.ws.rs.GET +import jakarta.ws.rs.Path +import jakarta.ws.rs.Produces +import jakarta.ws.rs.core.MediaType +import org.ptss.support.api.dtos.responses.QuestionnaireResponse +import org.ptss.support.core.facades.QuestionnaireFacade +import org.ptss.support.domain.interfaces.controllers.IQuestionnaireController + +@Path("/questionnaires") +@ApplicationScoped +@Produces(MediaType.APPLICATION_JSON) +class QuestionnaireController( + private val questionnaireFacade: QuestionnaireFacade, +) : IQuestionnaireController { + + @GET + override suspend fun getAllQuestionnaires(): List = + questionnaireFacade.getAllQuestionnaires() +} \ No newline at end of file diff --git a/src/main/kotlin/org/ptss/support/api/dtos/responses/QuestionnaireResponse.kt b/src/main/kotlin/org/ptss/support/api/dtos/responses/QuestionnaireResponse.kt new file mode 100644 index 0000000..e987084 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/api/dtos/responses/QuestionnaireResponse.kt @@ -0,0 +1,10 @@ +package org.ptss.support.api.dtos.responses + +data class QuestionnaireResponse( + val id: String, + val title: String, + val description: String, + val estimatedTimeOfCompletion: String, + val assignedAt: String, + val isFinished: Boolean, +) diff --git a/src/main/kotlin/org/ptss/support/core/facades/QuestionnaireFacade.kt b/src/main/kotlin/org/ptss/support/core/facades/QuestionnaireFacade.kt new file mode 100644 index 0000000..9239c1d --- /dev/null +++ b/src/main/kotlin/org/ptss/support/core/facades/QuestionnaireFacade.kt @@ -0,0 +1,16 @@ +package org.ptss.support.core.facades + +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject +import org.ptss.support.api.dtos.responses.QuestionnaireResponse +import org.ptss.support.core.mappers.QuestionnaireMapper +import org.ptss.support.core.services.QuestionnaireService + +@ApplicationScoped +class QuestionnaireFacade @Inject constructor( + private val questionnaireService: QuestionnaireService, +) { + suspend fun getAllQuestionnaires(): List = + questionnaireService.getAllQuestionnairesAsync() + .map(QuestionnaireMapper::toResponse) +} \ No newline at end of file diff --git a/src/main/kotlin/org/ptss/support/core/mappers/QuestionnaireMapper.kt b/src/main/kotlin/org/ptss/support/core/mappers/QuestionnaireMapper.kt new file mode 100644 index 0000000..1db3c37 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/core/mappers/QuestionnaireMapper.kt @@ -0,0 +1,17 @@ +package org.ptss.support.core.mappers + +import org.ptss.support.api.dtos.responses.QuestionnaireResponse +import org.ptss.support.domain.models.Questionnaire + +object QuestionnaireMapper { + fun toResponse(questionnaire: Questionnaire): QuestionnaireResponse { + return QuestionnaireResponse( + id = questionnaire.id, + title = questionnaire.title, + description = questionnaire.description, + estimatedTimeOfCompletion = questionnaire.estimatedTimeOfCompletion, + assignedAt = questionnaire.assignedAt, + isFinished = questionnaire.isFinished, + ) + } +} diff --git a/src/main/kotlin/org/ptss/support/core/services/QuestionnaireService.kt b/src/main/kotlin/org/ptss/support/core/services/QuestionnaireService.kt new file mode 100644 index 0000000..06f6427 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/core/services/QuestionnaireService.kt @@ -0,0 +1,22 @@ +package org.ptss.support.core.services + +import jakarta.enterprise.context.ApplicationScoped +import org.ptss.support.domain.models.Questionnaire +import org.ptss.support.domain.queries.GetAllQuestionnairesQuery +import org.ptss.support.infrastructure.handlers.queries.questionnaire.GetAllQuestionnairesQueryHandler +import org.ptss.support.infrastructure.util.executeWithExceptionLoggingAsync +import org.slf4j.LoggerFactory + +@ApplicationScoped +class QuestionnaireService( + private val getAllQuestionnairesQueryHandler: GetAllQuestionnairesQueryHandler, +) { + private val logger = LoggerFactory.getLogger(QuestionnaireService::class.java) + + suspend fun getAllQuestionnairesAsync(): List { + return logger.executeWithExceptionLoggingAsync( + operation = { getAllQuestionnairesQueryHandler.handleAsync(GetAllQuestionnairesQuery()) }, + logMessage = "Failed to get all questionnaires" + ) + } +} diff --git a/src/main/kotlin/org/ptss/support/domain/interfaces/controllers/IQuestionnaireController.kt b/src/main/kotlin/org/ptss/support/domain/interfaces/controllers/IQuestionnaireController.kt new file mode 100644 index 0000000..65f62f0 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/domain/interfaces/controllers/IQuestionnaireController.kt @@ -0,0 +1,44 @@ +package org.ptss.support.domain.interfaces.controllers + +import jakarta.ws.rs.GET +import org.eclipse.microprofile.openapi.annotations.Operation +import org.eclipse.microprofile.openapi.annotations.media.Content +import org.eclipse.microprofile.openapi.annotations.media.Schema +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses +import org.ptss.support.api.dtos.responses.QuestionnaireResponse +import org.ptss.support.common.exceptions.ServiceError + +// error 201, 400, 401, 403 and 500 +interface IQuestionnaireController { + @GET + @Operation(summary = "Get all questionnaires", description = "Retrieves a list of all questionnaires") + @APIResponses( + APIResponse( + responseCode = "201", + description = "List of questionnaires successfully retrieved", + content = [Content(schema = Schema(implementation = Array::class))], + ), + APIResponse( + responseCode = "400", + description = "Bad request", + content = [Content(schema = Schema(implementation = ServiceError::class))], + ), + APIResponse( + responseCode = "401", + description = "Unauthorized", + content = [Content(schema = Schema(implementation = ServiceError::class))], + ), + APIResponse( + responseCode = "403", + description = "Forbidden", + content = [Content(schema = Schema(implementation = ServiceError::class))], + ), + APIResponse( + responseCode = "500", + description = "Internal server error", + content = [Content(schema = Schema(implementation = ServiceError::class))], + ), + ) + suspend fun getAllQuestionnaires(): List +} diff --git a/src/main/kotlin/org/ptss/support/domain/interfaces/queries/GetAllQuestionnairesQuery.kt b/src/main/kotlin/org/ptss/support/domain/interfaces/queries/GetAllQuestionnairesQuery.kt new file mode 100644 index 0000000..50613bb --- /dev/null +++ b/src/main/kotlin/org/ptss/support/domain/interfaces/queries/GetAllQuestionnairesQuery.kt @@ -0,0 +1,3 @@ +package org.ptss.support.domain.interfaces.queries + +class GetAllQuestionnairesQuery() diff --git a/src/main/kotlin/org/ptss/support/domain/interfaces/repositories/IQuestionnaireRepository.kt b/src/main/kotlin/org/ptss/support/domain/interfaces/repositories/IQuestionnaireRepository.kt new file mode 100644 index 0000000..018076f --- /dev/null +++ b/src/main/kotlin/org/ptss/support/domain/interfaces/repositories/IQuestionnaireRepository.kt @@ -0,0 +1,7 @@ +package org.ptss.support.domain.interfaces.repositories + +import org.ptss.support.domain.models.Questionnaire + +interface IQuestionnaireRepository { + fun getAll(): List +} diff --git a/src/main/kotlin/org/ptss/support/domain/models/Questionnaire.kt b/src/main/kotlin/org/ptss/support/domain/models/Questionnaire.kt new file mode 100644 index 0000000..248f232 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/domain/models/Questionnaire.kt @@ -0,0 +1,26 @@ +package org.ptss.support.domain.models + +class Questionnaire() { + var id: String = "" + var title: String = "" + var description: String = "" + var estimatedTimeOfCompletion: String = "" + var assignedAt: String = "" + var isFinished: Boolean = false + + constructor( + id: String, + title: String, + description: String, + estimatedTimeOfCompletion: String, + assignedAt: String, + isFinished: Boolean + ) : this() { + this.id = id + this.title = title + this.description = description + this.estimatedTimeOfCompletion = estimatedTimeOfCompletion + this.assignedAt = assignedAt + this.isFinished = isFinished + } +} diff --git a/src/main/kotlin/org/ptss/support/domain/queries/GetAllQuestionnairesQuery.kt b/src/main/kotlin/org/ptss/support/domain/queries/GetAllQuestionnairesQuery.kt new file mode 100644 index 0000000..cf77975 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/domain/queries/GetAllQuestionnairesQuery.kt @@ -0,0 +1,3 @@ +package org.ptss.support.domain.queries + +class GetAllQuestionnairesQuery() \ No newline at end of file diff --git a/src/main/kotlin/org/ptss/support/infrastructure/config/PostgreSQLConfig.kt b/src/main/kotlin/org/ptss/support/infrastructure/config/PostgreSQLConfig.kt new file mode 100644 index 0000000..ffdb089 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/infrastructure/config/PostgreSQLConfig.kt @@ -0,0 +1,16 @@ +package org.ptss.support.infrastructure.config + +import io.smallrye.config.ConfigMapping +import io.smallrye.config.WithName + +@ConfigMapping(prefix = "quarkus.datasource") +interface PostgreSQLConfig { + @WithName("jdbc.url") + fun url(): String + + @WithName("username") + fun username(): String + + @WithName("password") + fun password(): String +} diff --git a/src/main/kotlin/org/ptss/support/infrastructure/handlers/queries/questionnaire/GetAllQuestionnairesQueryHandler.kt b/src/main/kotlin/org/ptss/support/infrastructure/handlers/queries/questionnaire/GetAllQuestionnairesQueryHandler.kt new file mode 100644 index 0000000..d61604b --- /dev/null +++ b/src/main/kotlin/org/ptss/support/infrastructure/handlers/queries/questionnaire/GetAllQuestionnairesQueryHandler.kt @@ -0,0 +1,17 @@ +package org.ptss.support.infrastructure.handlers.queries.questionnaire + +import jakarta.enterprise.context.ApplicationScoped +import org.ptss.support.domain.interfaces.queries.IQueryHandler +import org.ptss.support.domain.interfaces.repositories.IQuestionnaireRepository +import org.ptss.support.domain.models.Questionnaire +import org.ptss.support.domain.queries.GetAllQuestionnairesQuery + +@ApplicationScoped +class GetAllQuestionnairesQueryHandler( + private val questionnaireRepository: IQuestionnaireRepository, +) : IQueryHandler> { + + override suspend fun handleAsync(query: GetAllQuestionnairesQuery): List { + return questionnaireRepository.getAll() + } +} diff --git a/src/main/kotlin/org/ptss/support/infrastructure/persistence/entities/QuestionnaireEntity.kt b/src/main/kotlin/org/ptss/support/infrastructure/persistence/entities/QuestionnaireEntity.kt new file mode 100644 index 0000000..d0f0d96 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/infrastructure/persistence/entities/QuestionnaireEntity.kt @@ -0,0 +1,23 @@ +package org.ptss.support.infrastructure.persistence.entities + +import org.ptss.support.domain.models.Questionnaire + +data class QuestionnaireEntity( + val id: String, + val title: String, + val description: String, + val estimatedTimeOfCompletion: String, + val assignedAt: String, + val isFinished: Boolean, +) { + fun toDomain(): Questionnaire { + return Questionnaire( + id = id, + title = title, + description = description, + estimatedTimeOfCompletion = estimatedTimeOfCompletion, + assignedAt = assignedAt, + isFinished = isFinished, + ) + } +} diff --git a/src/main/kotlin/org/ptss/support/infrastructure/repositories/BaseRepository.kt b/src/main/kotlin/org/ptss/support/infrastructure/repositories/BaseRepository.kt new file mode 100644 index 0000000..04104ef --- /dev/null +++ b/src/main/kotlin/org/ptss/support/infrastructure/repositories/BaseRepository.kt @@ -0,0 +1,25 @@ +package org.ptss.support.infrastructure.repositories + +import jakarta.inject.Inject +import org.ptss.support.infrastructure.config.PostgreSQLConfig +import java.sql.Connection +import java.sql.DriverManager + +abstract class BaseRepository { + @Inject + protected lateinit var config: PostgreSQLConfig + + protected fun getConnection(): Connection { + return DriverManager.getConnection( + config.url(), + config.username(), + config.password(), + ) + } + + fun useConnection(block: (Connection) -> R): R { + return getConnection().use { conn -> + block(conn) + } + } +} diff --git a/src/main/kotlin/org/ptss/support/infrastructure/repositories/QuestionnaireBaseRepository.kt b/src/main/kotlin/org/ptss/support/infrastructure/repositories/QuestionnaireBaseRepository.kt new file mode 100644 index 0000000..41cf98c --- /dev/null +++ b/src/main/kotlin/org/ptss/support/infrastructure/repositories/QuestionnaireBaseRepository.kt @@ -0,0 +1,7 @@ +package org.ptss.support.infrastructure.repositories + +import jakarta.enterprise.context.ApplicationScoped +import org.ptss.support.domain.models.Questionnaire + +@ApplicationScoped +class QuestionnaireBaseRepository : BaseRepository() diff --git a/src/main/kotlin/org/ptss/support/infrastructure/repositories/QuestionnaireRepository.kt b/src/main/kotlin/org/ptss/support/infrastructure/repositories/QuestionnaireRepository.kt new file mode 100644 index 0000000..8714ba8 --- /dev/null +++ b/src/main/kotlin/org/ptss/support/infrastructure/repositories/QuestionnaireRepository.kt @@ -0,0 +1,36 @@ +package org.ptss.support.infrastructure.repositories + +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject +import org.ptss.support.domain.interfaces.repositories.IQuestionnaireRepository +import org.ptss.support.domain.models.Questionnaire +import org.ptss.support.infrastructure.persistence.entities.QuestionnaireEntity + +@ApplicationScoped +class QuestionnaireRepository @Inject constructor( + private val questionnaireBaseRepository: QuestionnaireBaseRepository +) : IQuestionnaireRepository { + override fun getAll(): List { + return questionnaireBaseRepository.useConnection { connection -> + val questionnaires = mutableListOf() + val query = + "SELECT id, title, description, estimatedTimeOfCompletion, assignedAt, isFinished FROM questionnaires" + + val result = connection.prepareStatement(query).executeQuery() + + while (result.next()) { + val questionnaire = QuestionnaireEntity( + id = result.getString("id"), + title = result.getString("title"), + description = result.getString("description"), + estimatedTimeOfCompletion = result.getString("estimatedTimeOfCompletion"), + assignedAt = result.getString("assignedAt"), + isFinished = result.getBoolean("isFinished"), + ).toDomain() + questionnaires.add(questionnaire) + } + + questionnaires + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 192e577..d60e648 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -45,4 +45,4 @@ quarkus.micrometer.export.prometheus.path=/metrics # Database Configuration azure.storage.connection-string=${AZURE_STORAGE_CONNECTION_STRING:UseDevelopmentStorage=true} -azure.storage.table-name=${AZURE_STORAGE_TABLE_NAME:products} +azure.storage.table-name=${AZURE_STORAGE_TABLE_NAME:products} \ No newline at end of file diff --git a/src/main/resources/test_data.sql b/src/main/resources/test_data.sql new file mode 100644 index 0000000..8251671 --- /dev/null +++ b/src/main/resources/test_data.sql @@ -0,0 +1,15 @@ +-- Create the questionnaires table +CREATE TABLE questionnaires ( + id INT PRIMARY KEY, + title VARCHAR(255), + description TEXT, + estimatedTimeOfCompletion VARCHAR(50), + assignedAt TIMESTAMP, + isFinished BOOLEAN +); + +-- Insert the data into the questionnaires table +INSERT INTO questionnaires (id, title, description, estimatedTimeOfCompletion, assignedAt, isFinished) VALUES + ('1', 'Questionnaire 1', 'Description 1', '30 minutes', '2023-01-01T00:00:00Z', false), + ('2', 'Questionnaire 2', 'Description 2', '45 minutes', '2023-02-01T00:00:00Z', true), + ('3', 'Questionnaire 3', 'Description 3', '60 minutes', '2023-03-01T00:00:00Z', false);