feat(fullstack): Add notifications, user is now an entity, add testcontainers, rework custom permissions, get user from JWT in endpoints
This commit is contained in:
@@ -339,6 +339,34 @@ paths:
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable"
|
||||
|
||||
####### Users #######
|
||||
/users:
|
||||
post:
|
||||
summary: Create a new user
|
||||
operationId: createUser
|
||||
tags:
|
||||
- user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateUserDto"
|
||||
responses:
|
||||
"201":
|
||||
description: User successfully created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UserDto"
|
||||
"400":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
"503":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable"
|
||||
|
||||
/users/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
@@ -346,7 +374,6 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
summary: Get a specific user
|
||||
operationId: getUserById
|
||||
@@ -602,6 +629,142 @@ paths:
|
||||
"503":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable"
|
||||
|
||||
####### Notifications #######
|
||||
/notifications:
|
||||
get:
|
||||
summary: Get notifications for the current user
|
||||
operationId: getNotifications
|
||||
tags:
|
||||
- notification
|
||||
parameters:
|
||||
- in: query
|
||||
name: page
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
description: Page number
|
||||
- in: query
|
||||
name: size
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
description: Page size
|
||||
responses:
|
||||
"200":
|
||||
description: Paged list of notifications
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PagedNotificationDto"
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
post:
|
||||
summary: Create a new notification
|
||||
operationId: createNotification
|
||||
tags:
|
||||
- notification
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateNotificationDto"
|
||||
responses:
|
||||
"201":
|
||||
description: Successfully created notification
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NotificationDto"
|
||||
"400":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
|
||||
/notifications/unread:
|
||||
get:
|
||||
summary: Get unread notifications for the current user
|
||||
operationId: getUnreadNotifications
|
||||
tags:
|
||||
- notification
|
||||
responses:
|
||||
"200":
|
||||
description: List of unread notifications
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/NotificationDto"
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
|
||||
/notifications/unread/count:
|
||||
get:
|
||||
summary: Get count of unread notifications for the current user
|
||||
operationId: getUnreadNotificationCount
|
||||
tags:
|
||||
- notification
|
||||
responses:
|
||||
"200":
|
||||
description: Count of unread notifications
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
|
||||
/notifications/mark-all-read:
|
||||
put:
|
||||
summary: Mark all notifications as read for the current user
|
||||
operationId: markAllNotificationsAsRead
|
||||
tags:
|
||||
- notification
|
||||
responses:
|
||||
"204":
|
||||
description: All notifications marked as read
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
|
||||
/notifications/{id}/mark-read:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
put:
|
||||
summary: Mark a specific notification as read
|
||||
operationId: markNotificationAsRead
|
||||
tags:
|
||||
- notification
|
||||
responses:
|
||||
"200":
|
||||
description: Notification marked as read
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NotificationDto"
|
||||
"404":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/NotFound"
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
|
||||
####### Files #######
|
||||
/files:
|
||||
get:
|
||||
@@ -751,7 +914,6 @@ components:
|
||||
type: string
|
||||
status:
|
||||
$ref: "#/components/schemas/ApplicationFormStatus"
|
||||
default: DRAFT
|
||||
|
||||
PagedApplicationFormDto:
|
||||
type: object
|
||||
@@ -886,11 +1048,51 @@ components:
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- status
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
$ref: "#/components/schemas/UserStatus"
|
||||
role:
|
||||
$ref: "#/components/schemas/UserRole"
|
||||
|
||||
CreateUserDto:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- status
|
||||
- role
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
$ref: "#/components/schemas/UserStatus"
|
||||
role:
|
||||
$ref: "#/components/schemas/UserRole"
|
||||
|
||||
UserStatus:
|
||||
type: string
|
||||
enum:
|
||||
- INVITED
|
||||
- ACTIVE
|
||||
- BLOCKED
|
||||
- SUSPENDED_SUBSCRIPTION
|
||||
|
||||
UserRole:
|
||||
type: string
|
||||
description: "User's role in the organization"
|
||||
enum:
|
||||
- owner
|
||||
- admin
|
||||
- employer
|
||||
- works_council_member
|
||||
- employee
|
||||
|
||||
####### CommentDto #######
|
||||
CommentDto:
|
||||
@@ -968,6 +1170,85 @@ components:
|
||||
name:
|
||||
type: string
|
||||
|
||||
####### Notification #######
|
||||
NotificationDto:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- title
|
||||
- message
|
||||
- clickTarget
|
||||
- isRead
|
||||
- targetGroup
|
||||
- type
|
||||
- createdAt
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
clickTarget:
|
||||
type: string
|
||||
isRead:
|
||||
type: boolean
|
||||
recipient:
|
||||
nullable: true
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/UserDto"
|
||||
targetGroup:
|
||||
type: string
|
||||
type:
|
||||
$ref: "#/components/schemas/NotificationType"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
CreateNotificationDto:
|
||||
type: object
|
||||
required:
|
||||
- title
|
||||
- message
|
||||
- clickTarget
|
||||
- targetGroup
|
||||
- type
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
clickTarget:
|
||||
type: string
|
||||
recipient:
|
||||
nullable: true
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/UserDto"
|
||||
targetGroup:
|
||||
type: string
|
||||
type:
|
||||
$ref: "#/components/schemas/NotificationType"
|
||||
|
||||
PagedNotificationDto:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/Page"
|
||||
required:
|
||||
- content
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/NotificationDto"
|
||||
|
||||
NotificationType:
|
||||
type: string
|
||||
enum:
|
||||
- INFO
|
||||
- WARNING
|
||||
- ERROR
|
||||
|
||||
####### FileDto #######
|
||||
FileDto:
|
||||
type: object
|
||||
@@ -1004,24 +1285,6 @@ components:
|
||||
type: string
|
||||
format: binary
|
||||
|
||||
####### Notification #######
|
||||
NotificationDto:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- notificationType
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
notificationType:
|
||||
$ref: "#/components/schemas/NotificationType"
|
||||
|
||||
NotificationType:
|
||||
type: string
|
||||
enum:
|
||||
- EMAIL
|
||||
|
||||
####### Miscellaneous #######
|
||||
ProcessingPurpose:
|
||||
type: string
|
||||
|
||||
@@ -24,6 +24,12 @@ 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'
|
||||
@@ -42,8 +48,13 @@ dependencies {
|
||||
implementation "com.openhtmltopdf:openhtmltopdf-slf4j:$openHtmlVersion"
|
||||
implementation "com.openhtmltopdf:openhtmltopdf-svg-support:$openHtmlVersion"
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
implementation 'org.springframework.boot:spring-boot-testcontainers'
|
||||
implementation 'org.testcontainers:postgresql'
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
// implementation 'eu.europa.ec.joinup.sd-dss:dss-validation'
|
||||
// implementation 'eu.europa.ec.joinup.sd-dss:dss-pades'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ 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.AttributeOverride
|
||||
import jakarta.persistence.AttributeOverrides
|
||||
import jakarta.persistence.JoinColumn
|
||||
import jakarta.persistence.ManyToOne
|
||||
import jakarta.persistence.CascadeType
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
@@ -14,7 +14,7 @@ import jakarta.persistence.EnumType
|
||||
import jakarta.persistence.GeneratedValue
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.OneToMany
|
||||
import jakarta.persistence.Embedded
|
||||
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener
|
||||
@@ -43,18 +43,12 @@ class ApplicationForm(
|
||||
@Column(nullable = false)
|
||||
var status: ApplicationFormStatus = ApplicationFormStatus.DRAFT,
|
||||
|
||||
@Embedded
|
||||
@AttributeOverrides(
|
||||
AttributeOverride(name = "id", column = Column(name = "created_by_id", nullable = false)),
|
||||
AttributeOverride(name = "name", column = Column(name = "created_by_name", nullable = false))
|
||||
)
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "created_by_id", nullable = false)
|
||||
var createdBy: User,
|
||||
|
||||
@Embedded
|
||||
@AttributeOverrides(
|
||||
AttributeOverride(name = "id", column = Column(name = "last_modified_by_id", nullable = false)),
|
||||
AttributeOverride(name = "name", column = Column(name = "last_modified_by_name", nullable = false))
|
||||
)
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "last_modified_by_id", nullable = false)
|
||||
var lastModifiedBy: User,
|
||||
|
||||
@CreatedDate
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.application_form
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementSectionMapper
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtTokenPrincipal
|
||||
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
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Component
|
||||
class ApplicationFormMapper(private val formElementSectionMapper: FormElementSectionMapper, private val userMapper: UserMapper) {
|
||||
class ApplicationFormMapper(
|
||||
private val formElementSectionMapper: FormElementSectionMapper,
|
||||
private val userMapper: UserMapper,
|
||||
private val userService: UserService
|
||||
) {
|
||||
fun toApplicationFormDto(applicationForm: ApplicationForm): ApplicationFormDto {
|
||||
return ApplicationFormDto(
|
||||
id = applicationForm.id ?: throw IllegalStateException("ApplicationForm ID must not be null!"),
|
||||
@@ -43,18 +46,15 @@ class ApplicationFormMapper(private val formElementSectionMapper: FormElementSec
|
||||
}
|
||||
|
||||
fun toApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm {
|
||||
// TODO: Move this in upper layer
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val createdBy = User(principal.name ?: "UNKNOWN USER", principal.id ?: "")
|
||||
val lastModifiedBy = User(principal.name ?: "UNKNOWN USER", principal.id ?: "")
|
||||
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 = createdBy,
|
||||
lastModifiedBy = lastModifiedBy,
|
||||
createdBy = currentUser,
|
||||
lastModifiedBy = currentUser,
|
||||
)
|
||||
applicationForm.formElementSections = createApplicationFormDto.formElementSections
|
||||
.map { formElementSectionMapper.toFormElementSection(it, applicationForm) }
|
||||
|
||||
@@ -5,9 +5,11 @@ 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
|
||||
@@ -16,7 +18,8 @@ import java.util.UUID
|
||||
@Service
|
||||
class ApplicationFormService(
|
||||
private val applicationFormRepository: ApplicationFormRepository,
|
||||
private val applicationFormMapper: ApplicationFormMapper
|
||||
private val applicationFormMapper: ApplicationFormMapper,
|
||||
private val notificationService: NotificationService
|
||||
) {
|
||||
|
||||
fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm {
|
||||
@@ -76,10 +79,61 @@ class ApplicationFormService(
|
||||
|
||||
applicationForm.status = ApplicationFormStatus.SUBMITTED
|
||||
|
||||
return try {
|
||||
val savedApplicationForm = try {
|
||||
applicationFormRepository.save(applicationForm)
|
||||
} catch (e: Exception) {
|
||||
throw ApplicationFormNotUpdatedException(e, id)
|
||||
}
|
||||
|
||||
// Create notifications for relevant users
|
||||
createSubmissionNotifications(savedApplicationForm)
|
||||
|
||||
return savedApplicationForm
|
||||
}
|
||||
|
||||
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 clickTarget = "/application-forms/${applicationForm.id}/0"
|
||||
|
||||
// Create notification for admin users
|
||||
notificationService.createNotificationForUser(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = null,
|
||||
targetGroup = "admin",
|
||||
type = NotificationType.INFO
|
||||
)
|
||||
|
||||
// Create notification for works council members
|
||||
notificationService.createNotificationForUser(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = null,
|
||||
targetGroup = "works_council_member",
|
||||
type = NotificationType.INFO
|
||||
)
|
||||
|
||||
// Create notification for employer
|
||||
notificationService.createNotificationForUser(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = null,
|
||||
targetGroup = "employer",
|
||||
type = NotificationType.INFO
|
||||
)
|
||||
|
||||
// Create notification for employee
|
||||
notificationService.createNotificationForUser(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = null,
|
||||
targetGroup = "employee",
|
||||
type = NotificationType.INFO
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,7 @@ package com.betriebsratkanzlei.legalconsenthub.comment;
|
||||
import com.betriebsratkanzlei.legalconsenthub.application_form.ApplicationForm
|
||||
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElement
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.User
|
||||
import jakarta.persistence.AttributeOverride
|
||||
import jakarta.persistence.AttributeOverrides
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Embedded
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.EntityListeners
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
@@ -30,11 +27,8 @@ class Comment(
|
||||
@Column(nullable = false)
|
||||
var message: String = "",
|
||||
|
||||
@Embedded
|
||||
@AttributeOverrides(
|
||||
AttributeOverride(name = "id", column = Column(name = "created_by_id", nullable = false)),
|
||||
AttributeOverride(name = "name", column = Column(name = "created_by_name", nullable = false))
|
||||
)
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "created_by_id", nullable = false)
|
||||
var createdBy: User,
|
||||
|
||||
@CreatedDate
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundExcep
|
||||
import com.betriebsratkanzlei.legalconsenthub.error.FormElementNotFoundException
|
||||
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementRepository
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserMapper
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserService
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CommentDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateCommentDto
|
||||
import org.springframework.stereotype.Component
|
||||
@@ -14,6 +15,7 @@ import java.util.UUID
|
||||
@Component
|
||||
class CommentMapper(
|
||||
private val userMapper: UserMapper,
|
||||
private val userService: UserService,
|
||||
private val applicationFormRepository: ApplicationFormRepository,
|
||||
private val formElementRepository: FormElementRepository
|
||||
) {
|
||||
@@ -53,9 +55,11 @@ class CommentMapper(
|
||||
val formElement = formElementRepository.findById(formElementId)
|
||||
.orElseThrow { FormElementNotFoundException(formElementId) }
|
||||
|
||||
val currentUser = userService.getCurrentUser()
|
||||
|
||||
return Comment(
|
||||
message = commentDto.message,
|
||||
createdBy = userMapper.toUser(commentDto.createdBy),
|
||||
createdBy = currentUser,
|
||||
applicationForm = applicationForm,
|
||||
formElement = formElement
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.http.HttpMethod
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@@ -24,6 +25,8 @@ class SecurityConfig {
|
||||
authorizeHttpRequests {
|
||||
authorize("/swagger-ui/**", permitAll)
|
||||
authorize("/v3/**", permitAll)
|
||||
// For user registration
|
||||
authorize(HttpMethod.POST, "/users", permitAll)
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.config
|
||||
|
||||
import com.github.dockerjava.api.model.ExposedPort
|
||||
import com.github.dockerjava.api.model.HostConfig
|
||||
import com.github.dockerjava.api.model.PortBinding
|
||||
import com.github.dockerjava.api.model.Ports
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
|
||||
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.utility.DockerImageName
|
||||
|
||||
@Configuration
|
||||
@Profile("testcontainers")
|
||||
class TestContainersConfig {
|
||||
|
||||
@Bean
|
||||
@ServiceConnection
|
||||
fun postgresContainer(): PostgreSQLContainer<*> {
|
||||
return PostgreSQLContainer(DockerImageName.parse("postgres:17-alpine"))
|
||||
.withDatabaseName("legalconsenthub")
|
||||
.withUsername("legalconsenthub")
|
||||
.withPassword("legalconsenthub")
|
||||
.withExposedPorts(5432)
|
||||
.withCreateContainerCmdModifier { cmd ->
|
||||
cmd.withHostConfig(
|
||||
HostConfig().apply {
|
||||
this.withPortBindings(
|
||||
PortBinding(
|
||||
Ports.Binding.bindPort(5432),
|
||||
ExposedPort(5432)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
.withReuse(true)
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class ExceptionHandler {
|
||||
var logger = LoggerFactory.getLogger(ExceptionHandler::class.java)
|
||||
|
||||
@ResponseBody
|
||||
@ExceptionHandler(ApplicationFormNotFoundException::class)
|
||||
@ExceptionHandler(ApplicationFormNotFoundException::class, UserNotFoundException::class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
fun handleNotFoundError(e: Exception): ResponseEntity<ProblemDetails> {
|
||||
logger.warn(e.message, e)
|
||||
@@ -47,6 +47,22 @@ class ExceptionHandler {
|
||||
)
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@ExceptionHandler(UserAlreadyExistsException::class)
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
fun handleUserAlreadyExistsError(e: UserAlreadyExistsException): ResponseEntity<ProblemDetails> {
|
||||
logger.warn(e.message, e)
|
||||
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"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@ExceptionHandler(
|
||||
ApplicationFormNotCreatedException::class,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.error
|
||||
|
||||
class UserAlreadyExistsException(id: String): RuntimeException("User with ID $id already exists")
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.error
|
||||
|
||||
class UserNotFoundException(id: String): RuntimeException("Couldn't find user with ID: $id")
|
||||
@@ -0,0 +1,52 @@
|
||||
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.GeneratedValue
|
||||
import jakarta.persistence.Id
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener
|
||||
import java.time.LocalDateTime
|
||||
import java.util.UUID
|
||||
|
||||
@Entity
|
||||
@EntityListeners(AuditingEntityListener::class)
|
||||
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 targetGroup: String = "",
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
var type: NotificationType = NotificationType.INFO,
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false)
|
||||
var createdAt: LocalDateTime? = null
|
||||
)
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtTokenPrincipal
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserService
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.api.NotificationApi
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateNotificationDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.NotificationDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.PagedNotificationDto
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.util.UUID
|
||||
|
||||
@RestController
|
||||
class NotificationController(
|
||||
private val notificationService: NotificationService,
|
||||
private val notificationMapper: NotificationMapper,
|
||||
private val pagedNotificationMapper: PagedNotificationMapper,
|
||||
private val userService: UserService
|
||||
) : NotificationApi {
|
||||
|
||||
override fun getNotifications(page: Int, size: Int): ResponseEntity<PagedNotificationDto> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
|
||||
val user = userService.getUserById(recipientId)
|
||||
|
||||
val notifications = if (user.role != null) {
|
||||
notificationService.getNotificationsForUserAndGroup(
|
||||
recipientId = recipientId,
|
||||
userRole = user.role!!.value,
|
||||
page = page,
|
||||
size = size
|
||||
)
|
||||
} else {
|
||||
notificationService.getNotificationsForUser(
|
||||
recipientId = recipientId,
|
||||
page = page,
|
||||
size = size
|
||||
)
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(pagedNotificationMapper.toPagedNotificationDto(notifications))
|
||||
}
|
||||
|
||||
override fun getUnreadNotifications(): ResponseEntity<List<NotificationDto>> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
|
||||
val user = userService.getUserById(recipientId)
|
||||
|
||||
val notifications = if (user.role != null) {
|
||||
notificationService.getUnreadNotificationsForUserAndGroup(recipientId, user.role!!.value)
|
||||
} else {
|
||||
notificationService.getUnreadNotificationsForUser(recipientId)
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(notifications.map { notificationMapper.toNotificationDto(it) })
|
||||
}
|
||||
|
||||
override fun getUnreadNotificationCount(): ResponseEntity<Long> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
|
||||
val user = userService.getUserById(recipientId)
|
||||
|
||||
val count = if (user.role != null) {
|
||||
notificationService.getUnreadNotificationCountForUserAndGroup(recipientId, user.role!!.value)
|
||||
} else {
|
||||
notificationService.getUnreadNotificationCount(recipientId)
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(count)
|
||||
}
|
||||
|
||||
override fun markAllNotificationsAsRead(): ResponseEntity<Unit> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
|
||||
val user = userService.getUserById(recipientId)
|
||||
|
||||
if (user.role != null) {
|
||||
notificationService.markAllAsReadForUserAndGroup(recipientId, user.role!!.value)
|
||||
} else {
|
||||
notificationService.markAllAsRead(recipientId)
|
||||
}
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
override fun markNotificationAsRead(id: UUID): ResponseEntity<NotificationDto> {
|
||||
val notification = notificationService.markAsRead(id)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
|
||||
return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
}
|
||||
|
||||
override fun createNotification(createNotificationDto: CreateNotificationDto): ResponseEntity<NotificationDto> {
|
||||
val notification = notificationService.createNotification(createNotificationDto)
|
||||
return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserMapper
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateNotificationDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.NotificationDto
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class NotificationMapper(
|
||||
private val userMapper: UserMapper
|
||||
) {
|
||||
|
||||
fun toNotificationDto(notification: Notification): NotificationDto {
|
||||
return NotificationDto(
|
||||
id = notification.id!!,
|
||||
title = notification.title,
|
||||
message = notification.message,
|
||||
clickTarget = notification.clickTarget,
|
||||
isRead = notification.isRead,
|
||||
recipient = notification.recipient?.let { userMapper.toUserDto(it) },
|
||||
targetGroup = notification.targetGroup,
|
||||
type = notification.type,
|
||||
createdAt = notification.createdAt!!
|
||||
)
|
||||
}
|
||||
|
||||
fun toNotification(createNotificationDto: CreateNotificationDto): Notification {
|
||||
return Notification(
|
||||
title = createNotificationDto.title,
|
||||
message = createNotificationDto.message,
|
||||
clickTarget = createNotificationDto.clickTarget,
|
||||
isRead = false,
|
||||
recipient = createNotificationDto.recipient?.let { userMapper.toUser(it) },
|
||||
targetGroup = createNotificationDto.targetGroup,
|
||||
type = createNotificationDto.type
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.util.UUID
|
||||
|
||||
@Repository
|
||||
interface NotificationRepository : JpaRepository<Notification, UUID> {
|
||||
|
||||
fun findByRecipientIdOrderByCreatedAtDesc(recipientId: String?, pageable: Pageable): Page<Notification>
|
||||
|
||||
fun findByRecipientIdAndIsReadFalseOrderByCreatedAtDesc(recipientId: String?): List<Notification>
|
||||
|
||||
fun findByRecipientIsNullOrderByCreatedAtDesc(pageable: Pageable): Page<Notification>
|
||||
|
||||
fun findByRecipientIsNullAndIsReadFalseOrderByCreatedAtDesc(): List<Notification>
|
||||
|
||||
@Query("SELECT n FROM Notification n WHERE (n.recipient.id = :recipientId) OR (n.recipient IS NULL AND n.targetGroup = :targetGroup) OR (n.recipient IS NULL AND n.targetGroup = 'all') ORDER BY n.createdAt DESC")
|
||||
fun findByRecipientIdOrTargetGroupOrderByCreatedAtDesc(@Param("recipientId") recipientId: String, @Param("targetGroup") targetGroup: String, pageable: Pageable): Page<Notification>
|
||||
|
||||
@Query("SELECT n FROM Notification n WHERE ((n.recipient.id = :recipientId) OR (n.recipient IS NULL AND n.targetGroup = :targetGroup) OR (n.recipient IS NULL AND n.targetGroup = 'all')) AND n.isRead = false ORDER BY n.createdAt DESC")
|
||||
fun findByRecipientIdOrTargetGroupAndIsReadFalseOrderByCreatedAtDesc(@Param("recipientId") recipientId: String, @Param("targetGroup") targetGroup: String): List<Notification>
|
||||
|
||||
@Query("SELECT COUNT(n) FROM Notification n WHERE ((n.recipient.id = :recipientId) OR (n.recipient IS NULL AND n.targetGroup = :targetGroup) OR (n.recipient IS NULL AND n.targetGroup = 'all')) AND n.isRead = false")
|
||||
fun countByRecipientIdOrTargetGroupAndIsReadFalse(@Param("recipientId") recipientId: String, @Param("targetGroup") targetGroup: String): Long
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Notification n SET n.isRead = true WHERE n.recipient.id = :recipientId")
|
||||
fun markAllAsReadByRecipientId(@Param("recipientId") recipientId: String)
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Notification n SET n.isRead = true WHERE n.recipient IS NULL")
|
||||
fun markAllAsReadForNullRecipients()
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Notification n SET n.isRead = true WHERE (n.recipient.id = :recipientId) OR (n.recipient IS NULL AND n.targetGroup = :targetGroup) OR (n.recipient IS NULL AND n.targetGroup = 'all')")
|
||||
fun markAllAsReadByRecipientIdOrTargetGroup(@Param("recipientId") recipientId: String, @Param("targetGroup") targetGroup: String)
|
||||
|
||||
fun countByRecipientIdAndIsReadFalse(recipientId: String?): Long
|
||||
|
||||
fun countByRecipientIsNullAndIsReadFalse(): Long
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.User
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateNotificationDto
|
||||
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 org.springframework.transaction.annotation.Transactional
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
class NotificationService(
|
||||
private val notificationRepository: NotificationRepository,
|
||||
private val notificationMapper: NotificationMapper
|
||||
) {
|
||||
|
||||
fun createNotification(createNotificationDto: CreateNotificationDto): Notification {
|
||||
val notification = notificationMapper.toNotification(createNotificationDto)
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
fun createNotificationForUser(
|
||||
title: String,
|
||||
message: String,
|
||||
clickTarget: String,
|
||||
recipient: User?,
|
||||
targetGroup: String,
|
||||
type: NotificationType = NotificationType.INFO
|
||||
): Notification {
|
||||
val notification = Notification(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = recipient,
|
||||
targetGroup = targetGroup,
|
||||
type = type
|
||||
)
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
fun getNotificationsForUser(recipientId: String, page: Int = 0, size: Int = 20): Page<Notification> {
|
||||
val pageable = PageRequest.of(page, size)
|
||||
return notificationRepository.findByRecipientIdOrderByCreatedAtDesc(recipientId, pageable)
|
||||
}
|
||||
|
||||
fun getNotificationsForUserAndGroup(recipientId: String, userRole: String, page: Int = 0, size: Int = 20): Page<Notification> {
|
||||
val pageable = PageRequest.of(page, size)
|
||||
return notificationRepository.findByRecipientIdOrTargetGroupOrderByCreatedAtDesc(recipientId, userRole, pageable)
|
||||
}
|
||||
|
||||
fun getUnreadNotificationsForUser(recipientId: String): List<Notification> {
|
||||
return notificationRepository.findByRecipientIdAndIsReadFalseOrderByCreatedAtDesc(recipientId)
|
||||
}
|
||||
|
||||
fun getUnreadNotificationsForUserAndGroup(recipientId: String, userRole: String): List<Notification> {
|
||||
return notificationRepository.findByRecipientIdOrTargetGroupAndIsReadFalseOrderByCreatedAtDesc(recipientId, userRole)
|
||||
}
|
||||
|
||||
fun getUnreadNotificationCount(recipientId: String): Long {
|
||||
return notificationRepository.countByRecipientIdAndIsReadFalse(recipientId)
|
||||
}
|
||||
|
||||
fun getUnreadNotificationCountForUserAndGroup(recipientId: String, userRole: String): Long {
|
||||
return notificationRepository.countByRecipientIdOrTargetGroupAndIsReadFalse(recipientId, userRole)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun markAllAsRead(recipientId: String) {
|
||||
notificationRepository.markAllAsReadByRecipientId(recipientId)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun markAllAsReadForUserAndGroup(recipientId: String, userRole: String) {
|
||||
notificationRepository.markAllAsReadByRecipientIdOrTargetGroup(recipientId, userRole)
|
||||
}
|
||||
|
||||
fun markAsRead(notificationId: UUID): Notification? {
|
||||
val notification = notificationRepository.findById(notificationId).orElse(null)
|
||||
return if (notification != null) {
|
||||
notification.isRead = true
|
||||
notificationRepository.save(notification)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.PagedNotificationDto
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class PagedNotificationMapper(
|
||||
private val notificationMapper: NotificationMapper
|
||||
) {
|
||||
|
||||
fun toPagedNotificationDto(page: Page<Notification>): PagedNotificationDto {
|
||||
return PagedNotificationDto(
|
||||
content = page.content.map { notificationMapper.toNotificationDto(it) },
|
||||
number = page.number,
|
||||
propertySize = page.size,
|
||||
numberOfElements = page.numberOfElements,
|
||||
totalElements = page.totalElements,
|
||||
totalPages = page.totalPages,
|
||||
first = page.isFirst,
|
||||
last = page.isLast,
|
||||
empty = page.isEmpty
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,37 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.user
|
||||
|
||||
import jakarta.persistence.Embeddable
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserRole
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserStatus
|
||||
import jakarta.persistence.*
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Embeddable
|
||||
@Entity
|
||||
@EntityListeners(AuditingEntityListener::class)
|
||||
@Table(name = "app_user")
|
||||
class User(
|
||||
@Id
|
||||
@Column(nullable = false)
|
||||
var id: String,
|
||||
|
||||
@Column(nullable = false)
|
||||
var name: String,
|
||||
var id: String
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
var status: UserStatus = UserStatus.ACTIVE,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = true)
|
||||
var role: UserRole? = null,
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false)
|
||||
var createdAt: LocalDateTime? = null,
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(nullable = false)
|
||||
var modifiedAt: LocalDateTime? = null
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.user
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.api.UserApi
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateUserDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserDto
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
class UserController(
|
||||
private val userService: UserService,
|
||||
private val userMapper: UserMapper
|
||||
) : UserApi {
|
||||
|
||||
override fun createUser(createUserDto: CreateUserDto): ResponseEntity<UserDto> {
|
||||
val user = userService.createUser(createUserDto)
|
||||
return ResponseEntity.status(201).body(userMapper.toUserDto(user))
|
||||
}
|
||||
|
||||
override fun getUserById(id: String): ResponseEntity<UserDto> {
|
||||
val user = userService.getUserById(id)
|
||||
return ResponseEntity.ok(userMapper.toUserDto(user))
|
||||
}
|
||||
|
||||
override fun deleteUser(id: String): ResponseEntity<Unit> {
|
||||
userService.deleteUser(id)
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ class UserMapper() {
|
||||
return UserDto(
|
||||
id = user.id,
|
||||
name = user.name,
|
||||
status = user.status,
|
||||
role = user.role
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +18,8 @@ class UserMapper() {
|
||||
return User(
|
||||
id = userDto.id,
|
||||
name = userDto.name,
|
||||
status = userDto.status,
|
||||
role = userDto.role
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.user
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface UserRepository : JpaRepository<User, String>
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.user
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.error.UserAlreadyExistsException
|
||||
import com.betriebsratkanzlei.legalconsenthub.error.UserNotFoundException
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtTokenPrincipal
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateUserDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserStatus
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class UserService(
|
||||
private val userRepository: UserRepository
|
||||
) {
|
||||
|
||||
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)
|
||||
.orElseThrow { UserNotFoundException(userId) }
|
||||
}
|
||||
|
||||
fun createUser(createUserDto: CreateUserDto): User {
|
||||
if (userRepository.existsById(createUserDto.id)) {
|
||||
throw UserAlreadyExistsException(createUserDto.id)
|
||||
}
|
||||
|
||||
val user = User(
|
||||
id = createUserDto.id,
|
||||
name = createUserDto.name,
|
||||
status = createUserDto.status ?: UserStatus.ACTIVE,
|
||||
role = createUserDto.role
|
||||
)
|
||||
return userRepository.save(user)
|
||||
}
|
||||
|
||||
fun getUserById(userId: String): User {
|
||||
return userRepository.findById(userId)
|
||||
.orElseThrow { UserNotFoundException(userId) }
|
||||
}
|
||||
|
||||
fun deleteUser(userId: String) {
|
||||
userRepository.deleteById(userId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
spring:
|
||||
application:
|
||||
name: legalconsenthub
|
||||
|
||||
jpa:
|
||||
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||
hibernate:
|
||||
ddl-auto: create
|
||||
show-sql: true
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
jdbc:
|
||||
batch_size: 100
|
||||
order_inserts: true
|
||||
enable_lazy_load_no_trans: true
|
||||
|
||||
liquibase:
|
||||
enabled: true
|
||||
drop-first: false
|
||||
change-log: classpath:/db/changelog/db.changelog-master.yaml
|
||||
default-schema: public
|
||||
|
||||
sql:
|
||||
init:
|
||||
platform: postgresql
|
||||
|
||||
logging:
|
||||
level:
|
||||
org:
|
||||
springframework:
|
||||
security: TRACE
|
||||
oauth2: TRACE
|
||||
org.testcontainers: INFO
|
||||
com.github.dockerjava: WARN
|
||||
@@ -1,16 +1,25 @@
|
||||
create table app_user
|
||||
(
|
||||
created_at timestamp(6) not null,
|
||||
modified_at timestamp(6) not null,
|
||||
id varchar(255) not null,
|
||||
name varchar(255) not null,
|
||||
role varchar(255),
|
||||
status varchar(255) not null check (status in ('INVITED', 'ACTIVE', 'BLOCKED', 'SUSPENDED_SUBSCRIPTION')),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table application_form
|
||||
(
|
||||
is_template boolean not null,
|
||||
created_at timestamp(6) not null,
|
||||
modified_at timestamp(6) not null,
|
||||
id uuid not null,
|
||||
created_by_id varchar(255) not null,
|
||||
created_by_name varchar(255) not null,
|
||||
last_modified_by_id varchar(255) not null,
|
||||
last_modified_by_name varchar(255) not null,
|
||||
name varchar(255) not null,
|
||||
organization_id varchar(255),
|
||||
status enum ('APPROVED','DRAFT','REJECTED','SIGNED','SUBMITTED') not null,
|
||||
is_template boolean not null,
|
||||
created_at timestamp(6) not null,
|
||||
modified_at timestamp(6) not null,
|
||||
id uuid not null,
|
||||
created_by_id varchar(255) not null,
|
||||
last_modified_by_id varchar(255) not null,
|
||||
name varchar(255) not null,
|
||||
organization_id varchar(255),
|
||||
status varchar(255) not null check (status in ('DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED', 'SIGNED')),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
@@ -22,15 +31,14 @@ create table comment
|
||||
form_element_id uuid not null,
|
||||
id uuid not null,
|
||||
created_by_id varchar(255) not null,
|
||||
created_by_name varchar(255) not null,
|
||||
message varchar(255) not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table form_element_options
|
||||
(
|
||||
employee_data_category tinyint not null check (employee_data_category between 0 and 3),
|
||||
processing_purpose tinyint not null check (processing_purpose between 0 and 3),
|
||||
employee_data_category smallint not null check (employee_data_category between 0 and 3),
|
||||
processing_purpose smallint not null check (processing_purpose between 0 and 3),
|
||||
form_element_id uuid not null,
|
||||
label varchar(255) not null,
|
||||
option_value varchar(255) not null
|
||||
@@ -38,9 +46,9 @@ create table form_element_options
|
||||
|
||||
create table form_element
|
||||
(
|
||||
type tinyint not null check (type between 0 and 4),
|
||||
form_element_section_id uuid not null,
|
||||
id uuid not null,
|
||||
type smallint not null check (type between 0 and 4),
|
||||
form_element_section_id uuid not null,
|
||||
id uuid not null,
|
||||
description varchar(255),
|
||||
title varchar(255),
|
||||
primary key (id)
|
||||
@@ -56,11 +64,40 @@ create table form_element_section
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table notification
|
||||
(
|
||||
is_read boolean not null,
|
||||
created_at timestamp(6) not null,
|
||||
id uuid not null,
|
||||
click_target varchar(255) not null,
|
||||
message TEXT not null,
|
||||
recipient_id varchar(255),
|
||||
target_group varchar(255) not null,
|
||||
title varchar(255) not null,
|
||||
type varchar(255) not null check (type in ('INFO', 'WARNING', 'ERROR')),
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
alter table if exists application_form
|
||||
add constraint FKhtad5onoy2jknhtyfmx6cvvey
|
||||
foreign key (created_by_id)
|
||||
references app_user;
|
||||
|
||||
alter table if exists application_form
|
||||
add constraint FK5yewx8bespw0uiivxioeh7q0d
|
||||
foreign key (last_modified_by_id)
|
||||
references app_user;
|
||||
|
||||
alter table if exists comment
|
||||
add constraint FKlavy9axrt26sepreg5lqtuoap
|
||||
foreign key (application_form_id)
|
||||
references application_form;
|
||||
|
||||
alter table if exists comment
|
||||
add constraint FKbbjqikfmgeacfsnaasxxqoygh
|
||||
foreign key (created_by_id)
|
||||
references app_user;
|
||||
|
||||
alter table if exists comment
|
||||
add constraint FKfg84w0i76tw9os13950272c6f
|
||||
foreign key (form_element_id)
|
||||
@@ -80,3 +117,8 @@ alter table if exists form_element_section
|
||||
add constraint FKtn0lreovauwf2v29doo70o3qs
|
||||
foreign key (application_form_id)
|
||||
references application_form;
|
||||
|
||||
alter table if exists notification
|
||||
add constraint FKeg1j4hnp0y4lbm0y35hgr4e8r
|
||||
foreign key (recipient_id)
|
||||
references app_user;
|
||||
|
||||
Reference in New Issue
Block a user