feat: Add ktlint to backend and format all files

This commit is contained in:
2025-10-31 10:25:12 +01:00
parent be5017fcd4
commit 841341857d
53 changed files with 496 additions and 402 deletions

View File

@@ -0,0 +1,29 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true
[*.{kt,kts}]
indent_size = 4
max_line_length = 120
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ktlint_standard_package-name = disabled
[*.{yaml,yml}]
indent_size = 2
[*.gradle]
indent_size = 4
[*.md]
trim_trailing_whitespace = false

View File

@@ -5,6 +5,7 @@ plugins {
id 'io.spring.dependency-management' version '1.1.7'
id 'org.jetbrains.kotlin.plugin.jpa' version '1.9.25'
id 'org.openapi.generator' version '7.11.0'
id 'org.jlleitschuh.gradle.ktlint' version '13.1.0'
}
group = 'com.betriebsratkanzlei'
@@ -24,12 +25,6 @@ ext {
openHtmlVersion = '1.0.10'
}
//dependencyManagement {
// imports {
// mavenBom 'eu.europa.ec.joinup.sd-dss:dss-bom:6.2'
// }
//}
dependencies {
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
implementation 'org.jetbrains.kotlin:kotlin-reflect'
@@ -69,6 +64,16 @@ allOpen {
annotation 'jakarta.persistence.Embeddable'
}
ktlint {
version = "1.5.0"
android = false
ignoreFailures = false
filter {
exclude("**/generated/**")
exclude { element -> element.file.path.contains("generated/") }
}
}
def generatedSourcesServerLegalconsenthubDir = "$buildDir/generated/server".toString()
sourceSets {

View File

@@ -9,5 +9,5 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing
class LegalconsenthubApplication
fun main(args: Array<String>) {
runApplication<LegalconsenthubApplication>(*args)
runApplication<LegalconsenthubApplication>(*args)
}

View File

@@ -3,18 +3,17 @@ package com.betriebsratkanzlei.legalconsenthub.application_form
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSection
import com.betriebsratkanzlei.legalconsenthub.user.User
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.CascadeType
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.Enumerated
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.OneToMany
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
@@ -27,35 +26,26 @@ class ApplicationForm(
@Id
@GeneratedValue
var id: UUID? = null,
@Column(nullable = false)
var name: String = "",
@OneToMany(mappedBy = "applicationForm", cascade = [CascadeType.ALL], orphanRemoval = true)
var formElementSections: MutableList<FormElementSection> = mutableListOf(),
@Column(nullable = false)
var isTemplate: Boolean,
var organizationId: String = "",
@Enumerated(EnumType.STRING)
@Column(nullable = false)
var status: ApplicationFormStatus = ApplicationFormStatus.DRAFT,
@ManyToOne
@JoinColumn(name = "created_by_id", nullable = false)
var createdBy: User,
@ManyToOne
@JoinColumn(name = "last_modified_by_id", nullable = false)
var lastModifiedBy: User,
@CreatedDate
@Column(nullable = false)
var createdAt: LocalDateTime? = null,
@LastModifiedDate
@Column(nullable = false)
var modifiedAt: LocalDateTime? = null
var modifiedAt: LocalDateTime? = null,
)

View File

@@ -18,80 +18,94 @@ class ApplicationFormController(
val applicationFormService: ApplicationFormService,
val pagedApplicationFormMapper: PagedApplicationFormMapper,
val applicationFormMapper: ApplicationFormMapper,
val applicationFormFormatService: ApplicationFormFormatService
val applicationFormFormatService: ApplicationFormFormatService,
) : ApplicationFormApi {
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
override fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ResponseEntity<ApplicationFormDto> {
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun createApplicationForm(
createApplicationFormDto: CreateApplicationFormDto,
): ResponseEntity<ApplicationFormDto> {
val updatedCreateApplicationFormDto = createApplicationFormDto.copy(isTemplate = false)
return ResponseEntity.ok(
applicationFormMapper.toApplicationFormDto(
applicationFormService.createApplicationForm(updatedCreateApplicationFormDto)
)
applicationFormService.createApplicationForm(updatedCreateApplicationFormDto),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')")
override fun getAllApplicationForms(organizationId: String?): ResponseEntity<PagedApplicationFormDto> {
return ResponseEntity.ok(
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
)
override fun getAllApplicationForms(organizationId: String?): ResponseEntity<PagedApplicationFormDto> =
ResponseEntity.ok(
pagedApplicationFormMapper.toPagedApplicationFormDto(
applicationFormService.getApplicationForms(organizationId)
)
applicationFormService.getApplicationForms(organizationId),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')")
override fun getApplicationFormById(id: UUID): ResponseEntity<ApplicationFormDto> {
return ResponseEntity.ok(
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
)
override fun getApplicationFormById(id: UUID): ResponseEntity<ApplicationFormDto> =
ResponseEntity.ok(
applicationFormMapper.toApplicationFormDto(
applicationFormService.getApplicationFormById(id)
)
applicationFormService.getApplicationFormById(id),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')")
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
)
override fun getApplicationFormHtml(id: UUID): ResponseEntity<String> {
val applicationForm = applicationFormService.getApplicationFormById(id)
return ResponseEntity.ok(
applicationFormFormatService.generateHtml(applicationForm)
applicationFormFormatService.generateHtml(applicationForm),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')")
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
)
override fun getApplicationFormPdf(id: UUID): ResponseEntity<Resource> {
val applicationForm = applicationFormService.getApplicationFormById(id)
val pdfBytes = applicationFormFormatService.generatePdf(applicationForm)
val resource = ByteArrayResource(pdfBytes)
return ResponseEntity.ok()
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"form-$id.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(resource)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun updateApplicationForm(
id: UUID,
applicationFormDto: ApplicationFormDto
): ResponseEntity<ApplicationFormDto> {
return ResponseEntity.ok(
applicationFormDto: ApplicationFormDto,
): ResponseEntity<ApplicationFormDto> =
ResponseEntity.ok(
applicationFormMapper.toApplicationFormDto(
applicationFormService.updateApplicationForm(applicationFormDto)
)
applicationFormService.updateApplicationForm(applicationFormDto),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun deleteApplicationForm(id: UUID): ResponseEntity<Unit> {
applicationFormService.deleteApplicationFormByID(id)
return ResponseEntity.noContent().build()
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
override fun submitApplicationForm(id: UUID): ResponseEntity<ApplicationFormDto> {
return ResponseEntity.ok(
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun submitApplicationForm(id: UUID): ResponseEntity<ApplicationFormDto> =
ResponseEntity.ok(
applicationFormMapper.toApplicationFormDto(
applicationFormService.submitApplicationForm(id)
)
applicationFormService.submitApplicationForm(id),
),
)
}
}

View File

@@ -1,20 +1,21 @@
package com.betriebsratkanzlei.legalconsenthub.application_form
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder
import org.springframework.stereotype.Service
import org.thymeleaf.TemplateEngine
import org.thymeleaf.context.Context
import java.io.ByteArrayOutputStream
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder
@Service
class ApplicationFormFormatService(
private val templateEngine: TemplateEngine
private val templateEngine: TemplateEngine,
) {
fun generatePdf(applicationForm: ApplicationForm): ByteArray {
val htmlContent = generateHtml(applicationForm)
val outputStream = ByteArrayOutputStream()
PdfRendererBuilder().useFastMode()
PdfRendererBuilder()
.useFastMode()
.withHtmlContent(htmlContent, null)
.toStream(outputStream)
.run()
@@ -23,9 +24,10 @@ class ApplicationFormFormatService(
}
fun generateHtml(applicationForm: ApplicationForm): String {
val context = Context().apply {
setVariable("applicationForm", applicationForm)
}
val context =
Context().apply {
setVariable("applicationForm", applicationForm)
}
return templateEngine.process("application_form_template", context)
}
}

View File

@@ -1,7 +1,6 @@
package com.betriebsratkanzlei.legalconsenthub.application_form
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSectionMapper
import com.betriebsratkanzlei.legalconsenthub.user.User
import com.betriebsratkanzlei.legalconsenthub.user.UserMapper
import com.betriebsratkanzlei.legalconsenthub.user.UserService
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
@@ -13,52 +12,64 @@ import java.time.LocalDateTime
class ApplicationFormMapper(
private val formElementSectionMapper: FormElementSectionMapper,
private val userMapper: UserMapper,
private val userService: UserService
private val userService: UserService,
) {
fun toApplicationFormDto(applicationForm: ApplicationForm): ApplicationFormDto {
return ApplicationFormDto(
fun toApplicationFormDto(applicationForm: ApplicationForm): ApplicationFormDto =
ApplicationFormDto(
id = applicationForm.id ?: throw IllegalStateException("ApplicationForm ID must not be null!"),
name = applicationForm.name,
formElementSections = applicationForm.formElementSections.map { formElementSectionMapper.toFormElementSectionDto(it) },
formElementSections =
applicationForm.formElementSections.map {
formElementSectionMapper
.toFormElementSectionDto(
it,
)
},
isTemplate = applicationForm.isTemplate,
organizationId = applicationForm.organizationId,
createdBy = userMapper.toUserDto(applicationForm.createdBy),
lastModifiedBy = userMapper.toUserDto(applicationForm.lastModifiedBy),
createdAt = applicationForm.createdAt ?: LocalDateTime.now(),
modifiedAt = applicationForm.modifiedAt ?: LocalDateTime.now(),
status = applicationForm.status
status = applicationForm.status,
)
}
fun toApplicationForm(applicationForm: ApplicationFormDto): ApplicationForm {
return ApplicationForm(
fun toApplicationForm(applicationForm: ApplicationFormDto): ApplicationForm =
ApplicationForm(
id = applicationForm.id,
name = applicationForm.name,
formElementSections = applicationForm.formElementSections.map { formElementSectionMapper.toFormElementSection(it) }.toMutableList(),
formElementSections =
applicationForm.formElementSections
.map {
formElementSectionMapper.toFormElementSection(it)
}.toMutableList(),
isTemplate = applicationForm.isTemplate,
organizationId = applicationForm.organizationId,
status = applicationForm.status,
createdBy = userMapper.toUser(applicationForm.createdBy),
lastModifiedBy = userMapper.toUser(applicationForm.lastModifiedBy),
createdAt = applicationForm.createdAt,
modifiedAt = applicationForm.modifiedAt
modifiedAt = applicationForm.modifiedAt,
)
}
fun toApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm {
val currentUser = userService.getCurrentUser()
val applicationForm = ApplicationForm(
name = createApplicationFormDto.name,
isTemplate = createApplicationFormDto.isTemplate,
organizationId = createApplicationFormDto.organizationId ?: "",
status = createApplicationFormDto.status ?: com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus.DRAFT,
createdBy = currentUser,
lastModifiedBy = currentUser,
)
applicationForm.formElementSections = createApplicationFormDto.formElementSections
.map { formElementSectionMapper.toFormElementSection(it, applicationForm) }
.toMutableList()
val applicationForm =
ApplicationForm(
name = createApplicationFormDto.name,
isTemplate = createApplicationFormDto.isTemplate,
organizationId = createApplicationFormDto.organizationId ?: "",
status =
createApplicationFormDto.status
?: com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus.DRAFT,
createdBy = currentUser,
lastModifiedBy = currentUser,
)
applicationForm.formElementSections =
createApplicationFormDto.formElementSections
.map { formElementSectionMapper.toFormElementSection(it, applicationForm) }
.toMutableList()
return applicationForm
}
}

View File

@@ -11,6 +11,11 @@ import java.util.UUID
interface ApplicationFormRepository : JpaRepository<ApplicationForm, UUID> {
fun findAllByIsTemplateTrue(page: Pageable): Page<ApplicationForm>
@Query("SELECT c FROM ApplicationForm c WHERE (c.isTemplate IS false) AND (:organizationId is null or c.organizationId = :organizationId)")
fun findAllByIsTemplateFalseAndOrganizationId(organizationId: String?, page: Pageable): Page<ApplicationForm>
@Query(
"SELECT c FROM ApplicationForm c WHERE (c.isTemplate IS false) AND (:organizationId is null or c.organizationId = :organizationId)",
)
fun findAllByIsTemplateFalseAndOrganizationId(
organizationId: String?,
page: Pageable,
): Page<ApplicationForm>
}

View File

@@ -5,15 +5,14 @@ import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotCreatedExc
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotDeletedException
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundException
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotUpdatedException
// import com.betriebsratkanzlei.legalconsenthub.notification.NotificationService
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto
import com.betriebsratkanzlei.legalconsenthub_api.model.NotificationType
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import java.util.UUID
// import com.betriebsratkanzlei.legalconsenthub.notification.NotificationService
@Service
class ApplicationFormService(
@@ -21,7 +20,6 @@ class ApplicationFormService(
private val applicationFormMapper: ApplicationFormMapper,
// private val notificationService: NotificationService
) {
fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm {
val applicationForm = applicationFormMapper.toApplicationForm(createApplicationFormDto)
val savedApplicationForm: ApplicationForm
@@ -34,9 +32,10 @@ class ApplicationFormService(
return savedApplicationForm
}
fun getApplicationFormById(id: UUID): ApplicationForm {
return applicationFormRepository.findById(id).orElseThrow { ApplicationFormNotFoundException(id) }
}
fun getApplicationFormById(id: UUID): ApplicationForm =
applicationFormRepository.findById(id).orElseThrow {
ApplicationFormNotFoundException(id)
}
fun getApplicationForms(organizationId: String?): Page<ApplicationForm> {
val pageable = PageRequest.of(0, 10)
@@ -73,17 +72,18 @@ class ApplicationFormService(
applicationFormId = id,
currentState = applicationForm.status,
expectedState = ApplicationFormStatus.DRAFT,
operation = "submit"
operation = "submit",
)
}
applicationForm.status = ApplicationFormStatus.SUBMITTED
val savedApplicationForm = try {
applicationFormRepository.save(applicationForm)
} catch (e: Exception) {
throw ApplicationFormNotUpdatedException(e, id)
}
val savedApplicationForm =
try {
applicationFormRepository.save(applicationForm)
} catch (e: Exception) {
throw ApplicationFormNotUpdatedException(e, id)
}
// Create notifications for relevant users
createSubmissionNotifications(savedApplicationForm)
@@ -93,7 +93,9 @@ class ApplicationFormService(
private fun createSubmissionNotifications(applicationForm: ApplicationForm) {
val title = "Neuer Mitbestimmungsantrag eingereicht"
val message = "Ein neuer Mitbestimmungsantrag '${applicationForm.name}' wurde von ${applicationForm.createdBy.name} eingereicht und wartet auf Ihre Bearbeitung."
val message =
"Ein neuer Mitbestimmungsantrag '${applicationForm.name}' wurde von " +
"${applicationForm.createdBy.name} eingereicht und wartet auf Ihre Bearbeitung."
val clickTarget = "/application-forms/${applicationForm.id}/0"
// // Create separate notification for each role that should be notified

View File

@@ -1,15 +1,15 @@
package com.betriebsratkanzlei.legalconsenthub.application_form
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto
import com.betriebsratkanzlei.legalconsenthub_api.model.PagedApplicationFormDto
import org.springframework.data.domain.Page
import org.springframework.stereotype.Component
@Component
class PagedApplicationFormMapper(private val applicationFormMapper: ApplicationFormMapper) {
fun toPagedApplicationFormDto(pagedApplicationForm: Page<ApplicationForm>): PagedApplicationFormDto {
return PagedApplicationFormDto(
class PagedApplicationFormMapper(
private val applicationFormMapper: ApplicationFormMapper,
) {
fun toPagedApplicationFormDto(pagedApplicationForm: Page<ApplicationForm>): PagedApplicationFormDto =
PagedApplicationFormDto(
content = pagedApplicationForm.content.map { applicationFormMapper.toApplicationFormDto(it) },
number = pagedApplicationForm.number,
propertySize = pagedApplicationForm.size,
@@ -20,5 +20,4 @@ class PagedApplicationFormMapper(private val applicationFormMapper: ApplicationF
empty = pagedApplicationForm.isEmpty,
first = pagedApplicationForm.isFirst,
)
}
}

View File

@@ -17,47 +17,54 @@ class ApplicationFormTemplateController(
val pagedApplicationFormMapper: PagedApplicationFormMapper,
val applicationFormMapper: ApplicationFormMapper,
) : ApplicationFormTemplateApi {
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
override fun createApplicationFormTemplate(createApplicationFormDto: CreateApplicationFormDto): ResponseEntity<ApplicationFormDto> {
return ResponseEntity.ok(
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun createApplicationFormTemplate(
createApplicationFormDto: CreateApplicationFormDto,
): ResponseEntity<ApplicationFormDto> =
ResponseEntity.ok(
applicationFormMapper.toApplicationFormDto(
applicationFormTemplateService.createApplicationFormTemplate(createApplicationFormDto)
)
applicationFormTemplateService.createApplicationFormTemplate(createApplicationFormDto),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
override fun getAllApplicationFormTemplates(): ResponseEntity<PagedApplicationFormDto> {
return ResponseEntity.ok(
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun getAllApplicationFormTemplates(): ResponseEntity<PagedApplicationFormDto> =
ResponseEntity.ok(
pagedApplicationFormMapper.toPagedApplicationFormDto(
applicationFormTemplateService.getApplicationFormTemplates()
)
applicationFormTemplateService.getApplicationFormTemplates(),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
override fun getApplicationFormTemplateById(id: UUID): ResponseEntity<ApplicationFormDto> {
return ResponseEntity.ok(
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun getApplicationFormTemplateById(id: UUID): ResponseEntity<ApplicationFormDto> =
ResponseEntity.ok(
applicationFormMapper.toApplicationFormDto(
applicationFormTemplateService.getApplicationFormTemplateById(id)
)
applicationFormTemplateService.getApplicationFormTemplateById(id),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun updateApplicationFormTemplate(
id: UUID,
applicationFormDto: ApplicationFormDto
): ResponseEntity<ApplicationFormDto> {
return ResponseEntity.ok(
applicationFormDto: ApplicationFormDto,
): ResponseEntity<ApplicationFormDto> =
ResponseEntity.ok(
applicationFormMapper.toApplicationFormDto(
applicationFormTemplateService.updateApplicationFormTemplate(applicationFormDto)
)
applicationFormTemplateService.updateApplicationFormTemplate(applicationFormDto),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun deleteApplicationFormTemplate(id: UUID): ResponseEntity<Unit> {
applicationFormTemplateService.deleteApplicationFormTemplateByID(id)
return ResponseEntity.noContent().build()

View File

@@ -17,9 +17,8 @@ import java.util.UUID
@Service
class ApplicationFormTemplateService(
private val applicationFormRepository: ApplicationFormRepository,
private val applicationFormMapper: ApplicationFormMapper
private val applicationFormMapper: ApplicationFormMapper,
) {
fun createApplicationFormTemplate(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm {
val applicationForm = applicationFormMapper.toApplicationForm(createApplicationFormDto)
val savedApplicationForm: ApplicationForm
@@ -32,9 +31,10 @@ class ApplicationFormTemplateService(
return savedApplicationForm
}
fun getApplicationFormTemplateById(id: UUID): ApplicationForm {
return applicationFormRepository.findById(id).orElseThrow { ApplicationFormNotFoundException(id) }
}
fun getApplicationFormTemplateById(id: UUID): ApplicationForm =
applicationFormRepository.findById(id).orElseThrow {
ApplicationFormNotFoundException(id)
}
fun getApplicationFormTemplates(): Page<ApplicationForm> {
val pageable = PageRequest.of(0, 10)

View File

@@ -1,4 +1,4 @@
package com.betriebsratkanzlei.legalconsenthub.comment;
package com.betriebsratkanzlei.legalconsenthub.comment
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElement
@@ -6,16 +6,15 @@ import com.betriebsratkanzlei.legalconsenthub.user.User
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.LocalDateTime
import java.util.UUID;
import java.util.UUID
@Entity
@EntityListeners(AuditingEntityListener::class)
@@ -23,26 +22,20 @@ class Comment(
@Id
@GeneratedValue
var id: UUID? = null,
@Column(nullable = false)
var message: String = "",
@ManyToOne
@JoinColumn(name = "created_by_id", nullable = false)
var createdBy: User,
@CreatedDate
@Column(nullable = false)
var createdAt: LocalDateTime? = null,
@LastModifiedDate
@Column(nullable = false)
var modifiedAt: LocalDateTime? = null,
@ManyToOne
@JoinColumn(name = "application_form_id", nullable = false)
var applicationForm: ApplicationForm? = null,
@ManyToOne
@JoinColumn(name = "form_element_id", nullable = false)
var formElement: FormElement? = null,

View File

@@ -11,40 +11,50 @@ import java.util.UUID
@RestController
class CommentController(
val commentService: CommentService, val commentMapper: CommentMapper, val pagedCommentMapper: PagedCommentMapper
val commentService: CommentService,
val commentMapper: CommentMapper,
val pagedCommentMapper: PagedCommentMapper,
) : CommentApi {
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')")
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
)
override fun createComment(
applicationFormId: UUID,
formElementId: UUID,
createCommentDto: CreateCommentDto
): ResponseEntity<CommentDto> {
return ResponseEntity.ok(
createCommentDto: CreateCommentDto,
): ResponseEntity<CommentDto> =
ResponseEntity.ok(
commentMapper.toCommentDto(
commentService.createComment(applicationFormId, formElementId, createCommentDto)
)
commentService.createComment(applicationFormId, formElementId, createCommentDto),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')")
override fun getCommentsByApplicationFormId(applicationFormId: UUID): ResponseEntity<PagedCommentDto> {
return ResponseEntity.ok(
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
)
override fun getCommentsByApplicationFormId(applicationFormId: UUID): ResponseEntity<PagedCommentDto> =
ResponseEntity.ok(
pagedCommentMapper.toPagedCommentDto(
commentService.getComments(applicationFormId)
)
commentService.getComments(applicationFormId),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')")
override fun updateComment(id: UUID, commentDto: CommentDto): ResponseEntity<CommentDto> {
return ResponseEntity.ok(
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
)
override fun updateComment(
id: UUID,
commentDto: CommentDto,
): ResponseEntity<CommentDto> =
ResponseEntity.ok(
commentMapper.toCommentDto(
commentService.updateComment(commentDto)
)
commentService.updateComment(commentDto),
),
)
}
@PreAuthorize("hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')")
@PreAuthorize(
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
)
override fun deleteComment(id: UUID): ResponseEntity<Unit> {
commentService.deleteCommentByID(id)
return ResponseEntity.noContent().build()

View File

@@ -17,26 +17,33 @@ class CommentMapper(
private val userMapper: UserMapper,
private val userService: UserService,
private val applicationFormRepository: ApplicationFormRepository,
private val formElementRepository: FormElementRepository
private val formElementRepository: FormElementRepository,
) {
fun toCommentDto(comment: Comment): CommentDto {
return CommentDto(
fun toCommentDto(comment: Comment): CommentDto =
CommentDto(
id = comment.id ?: throw IllegalStateException("Comment ID must not be null!"),
message = comment.message,
createdAt = comment.createdAt ?: LocalDateTime.now(),
modifiedAt = comment.modifiedAt ?: LocalDateTime.now(),
createdBy = userMapper.toUserDto(comment.createdBy),
applicationFormId = comment.applicationForm?.id
?: throw IllegalStateException("ApplicationForm ID must not be null!"),
formElementId = comment.formElement?.id ?: throw IllegalStateException("FormElement ID must not be null!"),
applicationFormId =
comment.applicationForm?.id
?: throw IllegalStateException("ApplicationForm ID must not be null!"),
formElementId =
comment.formElement?.id ?: throw IllegalStateException(
"FormElement ID must not be null!",
),
)
}
fun toComment(commentDto: CommentDto): Comment {
val applicationForm = applicationFormRepository.findById(commentDto.applicationFormId)
.orElseThrow { ApplicationFormNotFoundException(commentDto.applicationFormId) }
val formElement = formElementRepository.findById(commentDto.formElementId)
.orElseThrow { FormElementNotFoundException(commentDto.formElementId) }
val applicationForm =
applicationFormRepository
.findById(commentDto.applicationFormId)
.orElseThrow { ApplicationFormNotFoundException(commentDto.applicationFormId) }
val formElement =
formElementRepository
.findById(commentDto.formElementId)
.orElseThrow { FormElementNotFoundException(commentDto.formElementId) }
return Comment(
id = commentDto.id,
@@ -45,15 +52,23 @@ class CommentMapper(
modifiedAt = commentDto.modifiedAt,
createdBy = userMapper.toUser(commentDto.createdBy),
applicationForm = applicationForm,
formElement = formElement
formElement = formElement,
)
}
fun toComment(applicationFormId: UUID, formElementId: UUID, commentDto: CreateCommentDto): Comment {
val applicationForm = applicationFormRepository.findById(applicationFormId)
.orElseThrow { FormElementNotFoundException(applicationFormId) }
val formElement = formElementRepository.findById(formElementId)
.orElseThrow { FormElementNotFoundException(formElementId) }
fun toComment(
applicationFormId: UUID,
formElementId: UUID,
commentDto: CreateCommentDto,
): Comment {
val applicationForm =
applicationFormRepository
.findById(applicationFormId)
.orElseThrow { FormElementNotFoundException(applicationFormId) }
val formElement =
formElementRepository
.findById(formElementId)
.orElseThrow { FormElementNotFoundException(formElementId) }
val currentUser = userService.getCurrentUser()
@@ -61,7 +76,7 @@ class CommentMapper(
message = commentDto.message,
createdBy = currentUser,
applicationForm = applicationForm,
formElement = formElement
formElement = formElement,
)
}
}

View File

@@ -9,5 +9,8 @@ import java.util.UUID
@Repository
interface CommentRepository : JpaRepository<Comment, UUID> {
fun findAllByApplicationForm(applicationForm: ApplicationForm, pageable: Pageable): Page<Comment>
fun findAllByApplicationForm(
applicationForm: ApplicationForm,
pageable: Pageable,
): Page<Comment>
}

View File

@@ -5,8 +5,6 @@ import com.betriebsratkanzlei.legalconsenthub.error.CommentNotCreatedException
import com.betriebsratkanzlei.legalconsenthub.error.CommentNotDeletedException
import com.betriebsratkanzlei.legalconsenthub.error.CommentNotFoundException
import com.betriebsratkanzlei.legalconsenthub.error.CommentNotUpdatedException
import com.betriebsratkanzlei.legalconsenthub.error.FormElementNotFoundException
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementRepository
import com.betriebsratkanzlei.legalconsenthub_api.model.CommentDto
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateCommentDto
import org.springframework.data.domain.Page
@@ -18,10 +16,13 @@ import java.util.UUID
class CommentService(
private val commentRepository: CommentRepository,
private val applicationFormRepository: ApplicationFormRepository,
private val commentMapper: CommentMapper
private val commentMapper: CommentMapper,
) {
fun createComment(applicationFormId: UUID, formElementId: UUID, createCommentDto: CreateCommentDto): Comment {
fun createComment(
applicationFormId: UUID,
formElementId: UUID,
createCommentDto: CreateCommentDto,
): Comment {
val comment = commentMapper.toComment(applicationFormId, formElementId, createCommentDto)
val savedComment: Comment
try {
@@ -33,9 +34,7 @@ class CommentService(
return savedComment
}
fun getCommentById(id: UUID): Comment {
return commentRepository.findById(id).orElseThrow { CommentNotFoundException(id) }
}
fun getCommentById(id: UUID): Comment = commentRepository.findById(id).orElseThrow { CommentNotFoundException(id) }
fun getComments(applicationFormId: UUID): Page<Comment> {
val applicationForm =

View File

@@ -5,9 +5,11 @@ import org.springframework.data.domain.Page
import org.springframework.stereotype.Component
@Component
class PagedCommentMapper(private val commentMapper: CommentMapper) {
fun toPagedCommentDto(pagedComment: Page<Comment>): PagedCommentDto {
return PagedCommentDto(
class PagedCommentMapper(
private val commentMapper: CommentMapper,
) {
fun toPagedCommentDto(pagedComment: Page<Comment>): PagedCommentDto =
PagedCommentDto(
content = pagedComment.content.map { commentMapper.toCommentDto(it) },
number = pagedComment.number,
propertySize = pagedComment.size,
@@ -18,5 +20,4 @@ class PagedCommentMapper(private val commentMapper: CommentMapper) {
empty = pagedComment.isEmpty,
first = pagedComment.isFirst,
)
}
}

View File

@@ -13,11 +13,10 @@ import org.springframework.security.web.SecurityFilterChain
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig {
@Bean
fun configure(
http: HttpSecurity,
customJwtAuthenticationConverter: CustomJwtAuthenticationConverter
customJwtAuthenticationConverter: CustomJwtAuthenticationConverter,
): SecurityFilterChain {
http {
csrf { disable() }

View File

@@ -8,17 +8,16 @@ import org.springframework.boot.testcontainers.service.connection.ServiceConnect
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.utility.DockerImageName
@Configuration
@Profile("testcontainers")
class TestContainersConfig {
@Bean
@ServiceConnection
fun postgresContainer(): PostgreSQLContainer<*> {
return PostgreSQLContainer(DockerImageName.parse("postgres:17-alpine"))
fun postgresContainer(): PostgreSQLContainer<*> =
PostgreSQLContainer(DockerImageName.parse("postgres:17-alpine"))
.withDatabaseName("legalconsenthub")
.withUsername("legalconsenthub")
.withPassword("legalconsenthub")
@@ -30,12 +29,10 @@ class TestContainersConfig {
this.withPortBindings(
PortBinding(
Ports.Binding.bindPort(5432),
ExposedPort(5432)
)
ExposedPort(5432),
),
)
}
},
)
}
.withReuse(true)
}
}.withReuse(true)
}

View File

@@ -7,5 +7,7 @@ class ApplicationFormInvalidStateException(
val applicationFormId: UUID,
val currentState: ApplicationFormStatus,
val expectedState: ApplicationFormStatus,
val operation: String
) : RuntimeException("Cannot $operation application form with ID $applicationFormId. Current state: $currentState, expected state: $expectedState")
val operation: String,
) : RuntimeException(
"Cannot $operation application form with ID $applicationFormId. Current state: $currentState, expected state: $expectedState",
)

View File

@@ -1,3 +1,5 @@
package com.betriebsratkanzlei.legalconsenthub.error
class ApplicationFormNotCreatedException(e: Exception): RuntimeException("Couldn't create application form", e)
class ApplicationFormNotCreatedException(
e: Exception,
) : RuntimeException("Couldn't create application form", e)

View File

@@ -1,3 +1,5 @@
package com.betriebsratkanzlei.legalconsenthub.error
class ApplicationFormNotDeletedException(e: Exception): RuntimeException("Couldn't delete application form", e)
class ApplicationFormNotDeletedException(
e: Exception,
) : RuntimeException("Couldn't delete application form", e)

View File

@@ -2,4 +2,6 @@ package com.betriebsratkanzlei.legalconsenthub.error
import java.util.UUID
class ApplicationFormNotFoundException(id: UUID): RuntimeException("Couldn't find application form with ID: $id")
class ApplicationFormNotFoundException(
id: UUID,
) : RuntimeException("Couldn't find application form with ID: $id")

View File

@@ -2,4 +2,7 @@ package com.betriebsratkanzlei.legalconsenthub.error
import java.util.UUID
class ApplicationFormNotUpdatedException(e: Exception, id: UUID): RuntimeException("Couldn't update application form with ID: $id", e)
class ApplicationFormNotUpdatedException(
e: Exception,
id: UUID,
) : RuntimeException("Couldn't update application form with ID: $id", e)

View File

@@ -1,3 +1,5 @@
package com.betriebsratkanzlei.legalconsenthub.error
class CommentNotCreatedException(e: Exception): RuntimeException("Couldn't create comment", e)
class CommentNotCreatedException(
e: Exception,
) : RuntimeException("Couldn't create comment", e)

View File

@@ -1,3 +1,5 @@
package com.betriebsratkanzlei.legalconsenthub.error
class CommentNotDeletedException(e: Exception): RuntimeException("Couldn't delete comment", e)
class CommentNotDeletedException(
e: Exception,
) : RuntimeException("Couldn't delete comment", e)

View File

@@ -2,4 +2,6 @@ package com.betriebsratkanzlei.legalconsenthub.error
import java.util.UUID
class CommentNotFoundException(id: UUID): RuntimeException("Couldn't find comment with ID: $id")
class CommentNotFoundException(
id: UUID,
) : RuntimeException("Couldn't find comment with ID: $id")

View File

@@ -2,4 +2,7 @@ package com.betriebsratkanzlei.legalconsenthub.error
import java.util.UUID
class CommentNotUpdatedException(e: Exception, id: UUID): RuntimeException("Couldn't update comment with ID: $id", e)
class CommentNotUpdatedException(
e: Exception,
id: UUID,
) : RuntimeException("Couldn't update comment with ID: $id", e)

View File

@@ -20,14 +20,16 @@ class ExceptionHandler {
@ResponseStatus(HttpStatus.NOT_FOUND)
fun handleNotFoundError(e: Exception): ResponseEntity<ProblemDetails> {
logger.warn(e.message, e)
return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_PROBLEM_JSON)
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
.body(
ProblemDetails(
title = "Not Found",
status = HttpStatus.NOT_FOUND.value(),
type = URI.create("about:blank"),
detail = e.message ?: "Something went wrong"
)
detail = e.message ?: "Something went wrong",
),
)
}
@@ -36,14 +38,16 @@ class ExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleInvalidStateError(e: ApplicationFormInvalidStateException): ResponseEntity<ProblemDetails> {
logger.warn(e.message, e)
return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_PROBLEM_JSON)
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
.body(
ProblemDetails(
title = "Invalid State",
status = HttpStatus.BAD_REQUEST.value(),
type = URI.create("about:blank"),
detail = e.message ?: "Operation not allowed in current state"
)
detail = e.message ?: "Operation not allowed in current state",
),
)
}
@@ -52,14 +56,16 @@ class ExceptionHandler {
@ResponseStatus(HttpStatus.CONFLICT)
fun handleUserAlreadyExistsError(e: UserAlreadyExistsException): ResponseEntity<ProblemDetails> {
logger.warn(e.message, e)
return ResponseEntity.status(HttpStatus.CONFLICT).contentType(MediaType.APPLICATION_PROBLEM_JSON)
return ResponseEntity
.status(HttpStatus.CONFLICT)
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
.body(
ProblemDetails(
title = "Conflict",
status = HttpStatus.CONFLICT.value(),
type = URI.create("about:blank"),
detail = e.message ?: "Resource already exists"
)
detail = e.message ?: "Resource already exists",
),
)
}
@@ -72,14 +78,16 @@ class ExceptionHandler {
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
fun handleInternalServerError(e: Exception): ResponseEntity<ProblemDetails> {
logger.warn(e.message, e)
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_PROBLEM_JSON)
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
.body(
ProblemDetails(
title = "Internal Server Error",
status = HttpStatus.INTERNAL_SERVER_ERROR.value(),
type = URI.create("about:blank"),
detail = e.message ?: "Something went wrong"
)
detail = e.message ?: "Something went wrong",
),
)
}
}

View File

@@ -2,4 +2,6 @@ package com.betriebsratkanzlei.legalconsenthub.error
import java.util.UUID
class FormElementNotFoundException(id: UUID): RuntimeException("Couldn't find form element with ID: $id")
class FormElementNotFoundException(
id: UUID,
) : RuntimeException("Couldn't find form element with ID: $id")

View File

@@ -2,4 +2,6 @@ package com.betriebsratkanzlei.legalconsenthub.error
import java.util.UUID
class FormElementSectionNotFoundException(id: UUID): RuntimeException("Couldn't find form element section with ID: $id")
class FormElementSectionNotFoundException(
id: UUID,
) : RuntimeException("Couldn't find form element section with ID: $id")

View File

@@ -1,3 +1,5 @@
package com.betriebsratkanzlei.legalconsenthub.error
class UserAlreadyExistsException(id: String): RuntimeException("User with ID $id already exists")
class UserAlreadyExistsException(
id: String,
) : RuntimeException("User with ID $id already exists")

View File

@@ -1,3 +1,5 @@
package com.betriebsratkanzlei.legalconsenthub.error
class UserNotFoundException(id: String): RuntimeException("Couldn't find user with ID: $id")
class UserNotFoundException(
id: String,
) : RuntimeException("Couldn't find user with ID: $id")

View File

@@ -1,37 +1,29 @@
package com.betriebsratkanzlei.legalconsenthub.form_element;
package com.betriebsratkanzlei.legalconsenthub.form_element
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
import com.betriebsratkanzlei.legalconsenthub_api.model.FormElementType
import jakarta.persistence.CollectionTable
import jakarta.persistence.Column
import jakarta.persistence.ElementCollection
import jakarta.persistence.Embeddable
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import java.util.UUID;
import java.util.UUID
@Entity
class FormElement(
@Id
@GeneratedValue
var id: UUID? = null,
var title: String? = null,
var description: String? = null,
@ElementCollection
@CollectionTable(name = "form_element_options", joinColumns = [JoinColumn(name = "form_element_id")])
var options: MutableList<FormOption> = mutableListOf(),
@Column(nullable = false)
var type: FormElementType,
@ManyToOne
@JoinColumn(name = "form_element_section_id", nullable = false)
var formElementSection: FormElementSection? = null
var formElementSection: FormElementSection? = null,
)

View File

@@ -8,23 +8,25 @@ import org.springframework.stereotype.Component
@Component
class FormElementMapper(
private val formOptionMapper: FormOptionMapper,
private val formElementSectionRepository: FormElementSectionRepository
private val formElementSectionRepository: FormElementSectionRepository,
) {
fun toFormElementDto(formElement: FormElement): FormElementDto {
return FormElementDto(
fun toFormElementDto(formElement: FormElement): FormElementDto =
FormElementDto(
id = formElement.id ?: throw IllegalStateException("ApplicationForm ID must not be null!"),
title = formElement.title,
description = formElement.description,
options = formElement.options.map { formOptionMapper.toFormOptionDto(it) },
type = formElement.type,
formElementSectionId = formElement.formElementSection?.id
?: throw IllegalStateException("FormElementSection ID must not be null!")
formElementSectionId =
formElement.formElementSection?.id
?: throw IllegalStateException("FormElementSection ID must not be null!"),
)
}
fun toFormElement(formElement: FormElementDto): FormElement {
val formElementSection = formElementSectionRepository.findById(formElement.formElementSectionId)
.orElseThrow { FormElementSectionNotFoundException(formElement.formElementSectionId) }
val formElementSection =
formElementSectionRepository
.findById(formElement.formElementSectionId)
.orElseThrow { FormElementSectionNotFoundException(formElement.formElementSectionId) }
return FormElement(
id = formElement.id,
@@ -32,18 +34,20 @@ class FormElementMapper(
description = formElement.description,
options = formElement.options.map { formOptionMapper.toFormOption(it) }.toMutableList(),
type = formElement.type,
formElementSection = formElementSection
formElementSection = formElementSection,
)
}
fun toFormElement(formElement: CreateFormElementDto, formElementSection: FormElementSection): FormElement {
return FormElement(
fun toFormElement(
formElement: CreateFormElementDto,
formElementSection: FormElementSection,
): FormElement =
FormElement(
id = null,
title = formElement.title,
description = formElement.description,
options = formElement.options.map { formOptionMapper.toFormOption(it) }.toMutableList(),
type = formElement.type,
formElementSection = formElementSection
formElementSection = formElementSection,
)
}
}

View File

@@ -1,8 +1,5 @@
package com.betriebsratkanzlei.legalconsenthub.form_element
import com.betriebsratkanzlei.legalconsenthub.comment.Comment
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.util.UUID

View File

@@ -1,35 +1,27 @@
package com.betriebsratkanzlei.legalconsenthub.form_element;
package com.betriebsratkanzlei.legalconsenthub.form_element
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
import jakarta.persistence.CascadeType
import jakarta.persistence.CollectionTable
import jakarta.persistence.Column
import jakarta.persistence.ElementCollection
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.OneToMany
import java.util.UUID;
import java.util.UUID
@Entity
class FormElementSection(
@Id
@GeneratedValue
var id: UUID? = null,
@Column(nullable = false)
var title: String,
var shortTitle: String? = null,
var description: String? = null,
@OneToMany(mappedBy = "formElementSection", cascade = [CascadeType.ALL], orphanRemoval = true)
var formElements: MutableList<FormElement> = mutableListOf(),
@ManyToOne
@JoinColumn(name = "application_form_id", nullable = false)
var applicationForm: ApplicationForm? = null,

View File

@@ -10,23 +10,25 @@ import org.springframework.stereotype.Component
@Component
class FormElementSectionMapper(
private val formElementMapper: FormElementMapper,
private val applicationFormRepository: ApplicationFormRepository
private val applicationFormRepository: ApplicationFormRepository,
) {
fun toFormElementSectionDto(formElementSection: FormElementSection): FormElementSectionDto {
return FormElementSectionDto(
fun toFormElementSectionDto(formElementSection: FormElementSection): FormElementSectionDto =
FormElementSectionDto(
id = formElementSection.id ?: throw IllegalStateException("FormElementSection ID must not be null!"),
title = formElementSection.title,
description = formElementSection.description,
shortTitle = formElementSection.shortTitle,
formElements = formElementSection.formElements.map { formElementMapper.toFormElementDto(it) },
applicationFormId = formElementSection.applicationForm?.id
?: throw IllegalStateException("ApplicationForm ID must not be null!")
applicationFormId =
formElementSection.applicationForm?.id
?: throw IllegalStateException("ApplicationForm ID must not be null!"),
)
}
fun toFormElementSection(formElementSection: FormElementSectionDto): FormElementSection {
val applicationForm = applicationFormRepository.findById(formElementSection.applicationFormId)
.orElseThrow { ApplicationFormNotFoundException(formElementSection.applicationFormId) }
val applicationForm =
applicationFormRepository
.findById(formElementSection.applicationFormId)
.orElseThrow { ApplicationFormNotFoundException(formElementSection.applicationFormId) }
return FormElementSection(
id = formElementSection.id,
@@ -34,20 +36,25 @@ class FormElementSectionMapper(
description = formElementSection.description,
shortTitle = formElementSection.shortTitle,
formElements = formElementSection.formElements.map { formElementMapper.toFormElement(it) }.toMutableList(),
applicationForm = applicationForm
applicationForm = applicationForm,
)
}
fun toFormElementSection(createFormElementSection: CreateFormElementSectionDto, applicationForm: ApplicationForm): FormElementSection {
val formElementSection = FormElementSection(
title = createFormElementSection.title,
description = createFormElementSection.description,
shortTitle = createFormElementSection.shortTitle,
applicationForm = applicationForm
)
formElementSection.formElements = createFormElementSection.formElements
.map { formElementMapper.toFormElement(it, formElementSection) }
.toMutableList()
fun toFormElementSection(
createFormElementSection: CreateFormElementSectionDto,
applicationForm: ApplicationForm,
): FormElementSection {
val formElementSection =
FormElementSection(
title = createFormElementSection.title,
description = createFormElementSection.description,
shortTitle = createFormElementSection.shortTitle,
applicationForm = applicationForm,
)
formElementSection.formElements =
createFormElementSection.formElements
.map { formElementMapper.toFormElement(it, formElementSection) }
.toMutableList()
return formElementSection
}
}

View File

@@ -1,7 +1,7 @@
package com.betriebsratkanzlei.legalconsenthub.form_element;
package com.betriebsratkanzlei.legalconsenthub.form_element
import com.betriebsratkanzlei.legalconsenthub_api.model.EmployeeDataCategory
import com.betriebsratkanzlei.legalconsenthub_api.model.ProcessingPurpose;
import com.betriebsratkanzlei.legalconsenthub_api.model.ProcessingPurpose
import jakarta.persistence.Column
import jakarta.persistence.Embeddable
@@ -9,13 +9,10 @@ import jakarta.persistence.Embeddable
class FormOption(
@Column(nullable = false, name = "option_value")
var value: String,
@Column(nullable = false)
var label: String,
@Column(nullable = false)
var processingPurpose: ProcessingPurpose,
@Column(nullable = false)
var employeeDataCategory: EmployeeDataCategory
var employeeDataCategory: EmployeeDataCategory,
)

View File

@@ -5,21 +5,19 @@ import org.springframework.stereotype.Component
@Component
class FormOptionMapper {
fun toFormOptionDto(formOption: FormOption): FormOptionDto {
return FormOptionDto(
fun toFormOptionDto(formOption: FormOption): FormOptionDto =
FormOptionDto(
value = formOption.value,
label = formOption.label,
processingPurpose = formOption.processingPurpose,
employeeDataCategory = formOption.employeeDataCategory
employeeDataCategory = formOption.employeeDataCategory,
)
}
fun toFormOption(formOptionDto: FormOptionDto): FormOption {
return FormOption(
fun toFormOption(formOptionDto: FormOptionDto): FormOption =
FormOption(
value = formOptionDto.value,
label = formOptionDto.label,
processingPurpose = formOptionDto.processingPurpose,
employeeDataCategory = formOptionDto.employeeDataCategory
employeeDataCategory = formOptionDto.employeeDataCategory,
)
}
}

View File

@@ -3,14 +3,14 @@ package com.betriebsratkanzlei.legalconsenthub.notification
import com.betriebsratkanzlei.legalconsenthub.user.User
import com.betriebsratkanzlei.legalconsenthub_api.model.NotificationType
import jakarta.persistence.Column
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.Enumerated
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.LocalDateTime
@@ -22,34 +22,25 @@ class Notification(
@Id
@GeneratedValue
var id: UUID? = null,
@Column(nullable = false)
var title: String = "",
@Column(nullable = false, columnDefinition = "TEXT")
var message: String = "",
@Column(nullable = false)
var clickTarget: String = "",
@Column(nullable = false)
var isRead: Boolean = false,
@ManyToOne
@JoinColumn(name = "recipient_id", nullable = true)
var recipient: User?,
@Column(nullable = false)
var role: String = "",
@Enumerated(EnumType.STRING)
@Column(nullable = false)
var type: NotificationType = NotificationType.INFO,
@Column(nullable = false)
var organizationId: String = "",
@CreatedDate
@Column(nullable = false)
var createdAt: LocalDateTime? = null
var createdAt: LocalDateTime? = null,
)

View File

@@ -7,11 +7,10 @@ import org.springframework.stereotype.Component
@Component
class NotificationMapper(
private val userMapper: UserMapper
private val userMapper: UserMapper,
) {
fun toNotificationDto(notification: Notification): NotificationDto {
return NotificationDto(
fun toNotificationDto(notification: Notification): NotificationDto =
NotificationDto(
id = notification.id!!,
title = notification.title,
message = notification.message,
@@ -21,12 +20,11 @@ class NotificationMapper(
role = notification.role,
type = notification.type,
organizationId = notification.organizationId,
createdAt = notification.createdAt!!
createdAt = notification.createdAt!!,
)
}
fun toNotification(createNotificationDto: CreateNotificationDto): Notification {
return Notification(
fun toNotification(createNotificationDto: CreateNotificationDto): Notification =
Notification(
title = createNotificationDto.title,
message = createNotificationDto.message,
clickTarget = createNotificationDto.clickTarget,
@@ -34,7 +32,6 @@ class NotificationMapper(
recipient = createNotificationDto.recipient?.let { userMapper.toUser(it) },
role = createNotificationDto.role,
type = createNotificationDto.type,
organizationId = createNotificationDto.organizationId
organizationId = createNotificationDto.organizationId,
)
}
}

View File

@@ -6,11 +6,10 @@ import org.springframework.stereotype.Component
@Component
class PagedNotificationMapper(
private val notificationMapper: NotificationMapper
private val notificationMapper: NotificationMapper,
) {
fun toPagedNotificationDto(page: Page<Notification>): PagedNotificationDto {
return PagedNotificationDto(
fun toPagedNotificationDto(page: Page<Notification>): PagedNotificationDto =
PagedNotificationDto(
content = page.content.map { notificationMapper.toNotificationDto(it) },
number = page.number,
propertySize = page.size,
@@ -19,7 +18,6 @@ class PagedNotificationMapper(
totalPages = page.totalPages,
first = page.isFirst,
last = page.isLast,
empty = page.isEmpty
empty = page.isEmpty,
)
}
}

View File

@@ -1,17 +1,17 @@
package com.betriebsratkanzlei.legalconsenthub.security
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
class CustomJwtAuthentication(
jwt: Jwt,
private val principal: CustomJwtTokenPrincipal,
authorities: Collection<GrantedAuthority>
authorities: Collection<GrantedAuthority>,
) : JwtAuthenticationToken(
jwt, authorities, principal.id
) {
override fun getPrincipal(): CustomJwtTokenPrincipal {
return principal
}
jwt,
authorities,
principal.id,
) {
override fun getPrincipal(): CustomJwtTokenPrincipal = principal
}

View File

@@ -17,9 +17,10 @@ class CustomJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationTo
val legalconsenthubResource = resourceAccess?.get("legalconsenthub") as? Map<*, *>
val roles = (legalconsenthubResource?.get("roles") as? List<*>)?.mapNotNull { it as? String } ?: emptyList()
val authorities: Collection<GrantedAuthority> = roles.map { role ->
SimpleGrantedAuthority("ROLE_$role")
}
val authorities: Collection<GrantedAuthority> =
roles.map { role ->
SimpleGrantedAuthority("ROLE_$role")
}
val principal = CustomJwtTokenPrincipal(userId, username, roles)

View File

@@ -14,11 +14,13 @@ import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
@Component
class JwtUserSyncFilter(val userService: UserService) : OncePerRequestFilter() {
class JwtUserSyncFilter(
val userService: UserService,
) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
filterChain: FilterChain,
) {
val logger = LoggerFactory.getLogger(JwtUserSyncFilter::class.java)
val auth: Authentication? = SecurityContextHolder.getContext().authentication
@@ -32,13 +34,14 @@ class JwtUserSyncFilter(val userService: UserService) : OncePerRequestFilter() {
// Extract organization information from JWT
val organizationClaim = jwt.getClaimAsMap("organization")
val organizationId = organizationClaim?.values?.firstOrNull()?.let { orgData ->
if (orgData is Map<*, *>) {
orgData["id"] as? String
} else {
null
val organizationId =
organizationClaim?.values?.firstOrNull()?.let { orgData ->
if (orgData is Map<*, *>) {
orgData["id"] as? String
} else {
null
}
}
}
val user = UserDto(keycloakId, name, organizationId)

View File

@@ -1,7 +1,10 @@
package com.betriebsratkanzlei.legalconsenthub.user
import com.betriebsratkanzlei.legalconsenthub_api.model.UserStatus
import jakarta.persistence.*
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.Id
import jakarta.persistence.Table
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
@@ -14,18 +17,14 @@ class User(
@Id
@Column(nullable = false)
var keycloakId: String,
@Column(nullable = false)
var name: String,
@Column(nullable = true)
var organizationId: String? = null,
@CreatedDate
@Column(nullable = false)
var createdAt: LocalDateTime? = null,
@LastModifiedDate
@Column(nullable = false)
var modifiedAt: LocalDateTime? = null
var modifiedAt: LocalDateTime? = null,
)

View File

@@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RestController
@RestController
class UserController(
private val userService: UserService,
private val userMapper: UserMapper
private val userMapper: UserMapper,
) : UserApi {
override fun getUserById(id: String): ResponseEntity<UserDto> {
val user = userService.getUserById(id)

View File

@@ -5,20 +5,20 @@ import org.springframework.stereotype.Component
@Component
class UserMapper {
fun toUserDto(user: User): UserDto {
return UserDto(
fun toUserDto(user: User): UserDto =
UserDto(
keycloakId = user.keycloakId,
name = user.name,
organizationId = user.organizationId
organizationId = user.organizationId,
)
}
fun toUser(userDto: UserDto): User {
val user = User(
keycloakId = userDto.keycloakId,
name = userDto.name,
organizationId = userDto.organizationId
)
val user =
User(
keycloakId = userDto.keycloakId,
name = userDto.name,
organizationId = userDto.organizationId,
)
return user
}

View File

@@ -11,14 +11,14 @@ import org.springframework.stereotype.Service
@Service
class UserService(
private val userRepository: UserRepository,
private val userMapper: UserMapper
private val userMapper: UserMapper,
) {
fun getCurrentUser(): User {
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
val userId = principal.id ?: throw IllegalStateException("User ID not found")
return userRepository.findById(userId)
return userRepository
.findById(userId)
.orElseThrow { UserNotFoundException(userId) }
}
@@ -42,24 +42,27 @@ class UserService(
throw UserAlreadyExistsException(userDto.keycloakId)
}
val user = User(
keycloakId = userDto.keycloakId,
name = userDto.name,
organizationId = userDto.organizationId
)
val user =
User(
keycloakId = userDto.keycloakId,
name = userDto.name,
organizationId = userDto.organizationId,
)
return userRepository.save(user)
}
fun getUserById(userId: String): User {
return userRepository.findById(userId)
fun getUserById(userId: String): User =
userRepository
.findById(userId)
.orElseThrow { UserNotFoundException(userId) }
}
@Transactional
fun updateUser(userDto: UserDto): User {
val user = userRepository.findById(userDto.keycloakId)
.orElseThrow { UserNotFoundException(userDto.keycloakId) }
val user =
userRepository
.findById(userDto.keycloakId)
.orElseThrow { UserNotFoundException(userDto.keycloakId) }
user.name = userDto.name
// Only update organization if it's not already set

View File

@@ -5,9 +5,7 @@ import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class LegalconsenthubApplicationTests {
@Test
fun contextLoads() {
}
@Test
fun contextLoads() {
}
}