major: Migration from better-auth to keycloak
This commit is contained in:
@@ -339,34 +339,6 @@ 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
|
||||
@@ -394,35 +366,6 @@ paths:
|
||||
$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"
|
||||
put:
|
||||
summary: Update a user
|
||||
operationId: updateUser
|
||||
description: Updates a user. If no request body is provided, the user data will be synchronized from JWT token claims.
|
||||
tags:
|
||||
- user
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UserDto"
|
||||
responses:
|
||||
"200":
|
||||
description: User successfully updated
|
||||
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"
|
||||
"404":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/NotFound"
|
||||
"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"
|
||||
delete:
|
||||
summary: Delete a user
|
||||
operationId: deleteUser
|
||||
@@ -1075,45 +1018,17 @@ components:
|
||||
UserDto:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- keycloakId
|
||||
- name
|
||||
- status
|
||||
- organizationRoles
|
||||
- organizationId
|
||||
properties:
|
||||
id:
|
||||
keycloakId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
$ref: "#/components/schemas/UserStatus"
|
||||
organizationRoles:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/UserRole"
|
||||
description: "Map of organization IDs to arrays of user roles in those organizations"
|
||||
|
||||
CreateUserDto:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- status
|
||||
properties:
|
||||
id:
|
||||
organizationId:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
status:
|
||||
$ref: "#/components/schemas/UserStatus"
|
||||
organizationRoles:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/UserRole"
|
||||
description: "Map of organization IDs to arrays of user roles in those organizations"
|
||||
nullable: true
|
||||
|
||||
UserStatus:
|
||||
type: string
|
||||
|
||||
@@ -49,13 +49,11 @@ dependencies {
|
||||
implementation "com.openhtmltopdf:openhtmltopdf-slf4j:$openHtmlVersion"
|
||||
implementation "com.openhtmltopdf:openhtmltopdf-svg-support:$openHtmlVersion"
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
implementation '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'
|
||||
}
|
||||
|
||||
|
||||
45
legalconsenthub-backend/docker-compose.yaml
Normal file
45
legalconsenthub-backend/docker-compose.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
networks:
|
||||
net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres_data_testbed:
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:latest
|
||||
container_name: postgres-testbed
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 5532:5432
|
||||
networks:
|
||||
- net
|
||||
volumes:
|
||||
- postgres_data_testbed:/var/lib/postgresql/data
|
||||
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:26.4.0
|
||||
container_name: keycloak-testbed
|
||||
command: start-dev
|
||||
environment:
|
||||
KC_DB: postgres
|
||||
KC_DB_URL_HOST: db
|
||||
KC_DB_USERNAME: ${POSTGRES_USER}
|
||||
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
KC_DB_DATABASE: ${POSTGRES_DB}
|
||||
KC_DB_SCHEMA: public
|
||||
KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN}
|
||||
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 7080:8080
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- net
|
||||
@@ -5,7 +5,7 @@ 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.notification.NotificationService
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto
|
||||
@@ -19,7 +19,7 @@ import java.util.UUID
|
||||
class ApplicationFormService(
|
||||
private val applicationFormRepository: ApplicationFormRepository,
|
||||
private val applicationFormMapper: ApplicationFormMapper,
|
||||
private val notificationService: NotificationService
|
||||
// private val notificationService: NotificationService
|
||||
) {
|
||||
|
||||
fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm {
|
||||
@@ -96,19 +96,19 @@ class ApplicationFormService(
|
||||
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
|
||||
val rolesToNotify = listOf("admin", "works_council_member", "employer", "employee")
|
||||
|
||||
rolesToNotify.forEach { role ->
|
||||
notificationService.createNotification(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = null,
|
||||
role = role,
|
||||
organizationId = applicationForm.organizationId,
|
||||
type = NotificationType.INFO
|
||||
)
|
||||
}
|
||||
// // Create separate notification for each role that should be notified
|
||||
// val rolesToNotify = listOf("admin", "works_council_member", "employer", "employee")
|
||||
//
|
||||
// rolesToNotify.forEach { role ->
|
||||
// notificationService.createNotification(
|
||||
// title = title,
|
||||
// message = message,
|
||||
// clickTarget = clickTarget,
|
||||
// recipient = null,
|
||||
// role = role,
|
||||
// organizationId = applicationForm.organizationId,
|
||||
// type = NotificationType.INFO
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,32 +18,16 @@ import org.springframework.http.HttpMethod
|
||||
class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
fun publicApiSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
securityMatcher("/swagger-ui/**", "/v3/**", "/actuator/**")
|
||||
csrf { disable() }
|
||||
authorizeHttpRequests {
|
||||
authorize("/swagger-ui/**", permitAll)
|
||||
authorize("/v3/**", permitAll)
|
||||
authorize("/actuator/**", permitAll)
|
||||
authorize(anyRequest, denyAll)
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
fun protectedApiSecurityFilterChain(
|
||||
fun configure(
|
||||
http: HttpSecurity,
|
||||
customJwtAuthenticationConverter: CustomJwtAuthenticationConverter
|
||||
): SecurityFilterChain {
|
||||
http {
|
||||
csrf { disable() }
|
||||
authorizeHttpRequests {
|
||||
// Allow user registration without authentication
|
||||
authorize(HttpMethod.POST, "/users", permitAll)
|
||||
authorize("/swagger-ui/**", permitAll)
|
||||
authorize("/v3/**", permitAll)
|
||||
authorize("/actuator/**", permitAll)
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
@@ -52,10 +36,4 @@ class SecurityConfig {
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun jwtDecoder(): JwtDecoder {
|
||||
return NimbusJwtDecoder.withJwkSetUri("http://192.168.178.114:3001/api/auth/jwks")
|
||||
.jwsAlgorithm(SignatureAlgorithm.ES512).build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +1,83 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtTokenPrincipal
|
||||
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
|
||||
) : 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 organizationId = principal.organizationId ?: throw IllegalStateException("Organization ID not found")
|
||||
|
||||
val notifications = notificationService.getNotifications(
|
||||
recipientId = recipientId,
|
||||
organizationId = organizationId,
|
||||
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 organizationId = principal.organizationId ?: throw IllegalStateException("Organization ID not found")
|
||||
|
||||
val notifications = notificationService.getUnreadNotifications(
|
||||
recipientId = recipientId,
|
||||
organizationId = organizationId
|
||||
)
|
||||
|
||||
return ResponseEntity.ok(notifications.map { notificationMapper.toNotificationDto(it) })
|
||||
}
|
||||
|
||||
override fun getUnreadNotificationCount(): ResponseEntity<kotlin.Long> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
val organizationId = principal.organizationId ?: throw IllegalStateException("Organization ID not found")
|
||||
|
||||
val count = notificationService.getUnreadNotificationCount(
|
||||
recipientId = recipientId,
|
||||
organizationId = organizationId
|
||||
)
|
||||
|
||||
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 organizationId = principal.organizationId ?: throw IllegalStateException("Organization ID not found")
|
||||
|
||||
notificationService.markAllAsRead(
|
||||
recipientId = recipientId,
|
||||
organizationId = organizationId
|
||||
)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
override fun markNotificationAsRead(id: UUID): ResponseEntity<NotificationDto> {
|
||||
val notification = notificationService.markNotificationAsRead(id)
|
||||
return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
}
|
||||
|
||||
override fun createNotification(createNotificationDto: CreateNotificationDto): ResponseEntity<NotificationDto> {
|
||||
val notification = notificationService.createNotification(createNotificationDto)
|
||||
return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
}
|
||||
}
|
||||
// package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
//
|
||||
// import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtTokenPrincipal
|
||||
// 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
|
||||
// ) : 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 organizationId = principal.organizationId ?: throw IllegalStateException("Organization ID not found")
|
||||
//
|
||||
// val notifications = notificationService.getNotifications(
|
||||
// recipientId = recipientId,
|
||||
// organizationId = organizationId,
|
||||
// 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 organizationId = principal.organizationId ?: throw IllegalStateException("Organization ID not found")
|
||||
//
|
||||
// val notifications = notificationService.getUnreadNotifications(
|
||||
// recipientId = recipientId,
|
||||
// organizationId = organizationId
|
||||
// )
|
||||
//
|
||||
// return ResponseEntity.ok(notifications.map { notificationMapper.toNotificationDto(it) })
|
||||
// }
|
||||
//
|
||||
// override fun getUnreadNotificationCount(): ResponseEntity<kotlin.Long> {
|
||||
// val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
// val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
// val organizationId = principal.organizationId ?: throw IllegalStateException("Organization ID not found")
|
||||
//
|
||||
// val count = notificationService.getUnreadNotificationCount(
|
||||
// recipientId = recipientId,
|
||||
// organizationId = organizationId
|
||||
// )
|
||||
//
|
||||
// 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 organizationId = principal.organizationId ?: throw IllegalStateException("Organization ID not found")
|
||||
//
|
||||
// notificationService.markAllAsRead(
|
||||
// recipientId = recipientId,
|
||||
// organizationId = organizationId
|
||||
// )
|
||||
//
|
||||
// return ResponseEntity.noContent().build()
|
||||
// }
|
||||
//
|
||||
// override fun markNotificationAsRead(id: UUID): ResponseEntity<NotificationDto> {
|
||||
// val notification = notificationService.markNotificationAsRead(id)
|
||||
// return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
// }
|
||||
//
|
||||
// override fun createNotification(createNotificationDto: CreateNotificationDto): ResponseEntity<NotificationDto> {
|
||||
// val notification = notificationService.createNotification(createNotificationDto)
|
||||
// return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,76 +1,84 @@
|
||||
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 countByRecipientIdAndIsReadFalse(recipientId: String?): Long
|
||||
|
||||
@Query("""
|
||||
SELECT n FROM Notification n WHERE
|
||||
(n.recipient.id = :recipientId) OR
|
||||
(n.recipient IS NULL AND CONCAT(n.organizationId, ':', n.role) IN :orgRolePairs) OR
|
||||
(n.recipient IS NULL AND n.organizationId IN :organizationIds AND (n.role IS NULL OR n.role = ''))
|
||||
ORDER BY n.createdAt DESC
|
||||
""")
|
||||
fun findUserNotificationsByOrgRole(
|
||||
@Param("recipientId") recipientId: String,
|
||||
@Param("organizationIds") organizationIds: List<String>,
|
||||
@Param("orgRolePairs") orgRolePairs: List<String>,
|
||||
pageable: Pageable
|
||||
): Page<Notification>
|
||||
|
||||
@Query("""
|
||||
SELECT n FROM Notification n WHERE
|
||||
((n.recipient.id = :recipientId) OR
|
||||
(n.recipient IS NULL AND CONCAT(n.organizationId, ':', n.role) IN :orgRolePairs) OR
|
||||
(n.recipient IS NULL AND n.organizationId IN :organizationIds AND (n.role IS NULL OR n.role = '')))
|
||||
AND n.isRead = false
|
||||
ORDER BY n.createdAt DESC
|
||||
""")
|
||||
fun findUnreadUserNotificationsByOrgRole(
|
||||
@Param("recipientId") recipientId: String,
|
||||
@Param("organizationIds") organizationIds: List<String>,
|
||||
@Param("orgRolePairs") orgRolePairs: List<String>
|
||||
): List<Notification>
|
||||
|
||||
@Query("""
|
||||
SELECT COUNT(n) FROM Notification n WHERE
|
||||
((n.recipient.id = :recipientId) OR
|
||||
(n.recipient IS NULL AND CONCAT(n.organizationId, ':', n.role) IN :orgRolePairs) OR
|
||||
(n.recipient IS NULL AND n.organizationId IN :organizationIds AND (n.role IS NULL OR n.role = '')))
|
||||
AND n.isRead = false
|
||||
""")
|
||||
fun countUnreadUserNotifications(
|
||||
@Param("recipientId") recipientId: String,
|
||||
@Param("organizationIds") organizationIds: List<String>,
|
||||
@Param("orgRolePairs") orgRolePairs: List<String>
|
||||
): Long
|
||||
|
||||
@Modifying
|
||||
@Query("""
|
||||
UPDATE Notification n SET n.isRead = true WHERE
|
||||
(n.recipient.id = :recipientId) OR
|
||||
(n.recipient IS NULL AND CONCAT(n.organizationId, ':', n.role) IN :orgRolePairs) OR
|
||||
(n.recipient IS NULL AND n.organizationId IN :organizationIds AND (n.role IS NULL OR n.role = ''))
|
||||
""")
|
||||
fun markAllUserNotificationsAsRead(
|
||||
@Param("recipientId") recipientId: String,
|
||||
@Param("organizationIds") organizationIds: List<String>,
|
||||
@Param("orgRolePairs") orgRolePairs: List<String>
|
||||
)
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Notification n SET n.isRead = true WHERE n.recipient.id = :recipientId")
|
||||
fun markAllAsReadByRecipientId(@Param("recipientId") recipientId: String)
|
||||
}
|
||||
// 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 countByRecipientIdAndIsReadFalse(recipientId: String?): Long
|
||||
//
|
||||
// @Query(
|
||||
// """
|
||||
// SELECT n FROM Notification n WHERE
|
||||
// (n.recipient.keycloakId = :recipientId) OR
|
||||
// (n.recipient IS NULL AND CONCAT(n.organizationId, ':', n.role) IN :orgRolePairs) OR
|
||||
// (n.recipient IS NULL AND n.organizationId IN :organizationIds AND (n.role IS NULL OR n.role = ''))
|
||||
// ORDER BY n.createdAt DESC
|
||||
// """
|
||||
// )
|
||||
// fun findUserNotificationsByOrgRole(
|
||||
// @Param("recipientId") recipientId: String,
|
||||
// @Param("organizationIds") organizationIds: List<String>,
|
||||
// @Param("orgRolePairs") orgRolePairs: List<String>,
|
||||
// pageable: Pageable
|
||||
// ): Page<Notification>
|
||||
//
|
||||
// @Query(
|
||||
// """
|
||||
// SELECT n FROM Notification n WHERE
|
||||
// ((n.recipient.keycloakId = :recipientId) OR
|
||||
// (n.recipient IS NULL AND CONCAT(n.organizationId, ':', n.role) IN :orgRolePairs) OR
|
||||
// (n.recipient IS NULL AND n.organizationId IN :organizationIds AND (n.role IS NULL OR n.role = '')))
|
||||
// AND n.isRead = false
|
||||
// ORDER BY n.createdAt DESC
|
||||
// """
|
||||
// )
|
||||
// fun findUnreadUserNotificationsByOrgRole(
|
||||
// @Param("recipientId") recipientId: String,
|
||||
// @Param("organizationIds") organizationIds: List<String>,
|
||||
// @Param("orgRolePairs") orgRolePairs: List<String>
|
||||
// ): List<Notification>
|
||||
//
|
||||
// @Query(
|
||||
// """
|
||||
// SELECT COUNT(n) FROM Notification n WHERE
|
||||
// ((n.recipient.keycloakId = :recipientId) OR
|
||||
// (n.recipient IS NULL AND CONCAT(n.organizationId, ':', n.role) IN :orgRolePairs) OR
|
||||
// (n.recipient IS NULL AND n.organizationId IN :organizationIds AND (n.role IS NULL OR n.role = '')))
|
||||
// AND n.isRead = false
|
||||
// """
|
||||
// )
|
||||
// fun countUnreadUserNotifications(
|
||||
// @Param("recipientId") recipientId: String,
|
||||
// @Param("organizationIds") organizationIds: List<String>,
|
||||
// @Param("orgRolePairs") orgRolePairs: List<String>
|
||||
// ): Long
|
||||
//
|
||||
// @Modifying
|
||||
// @Query(
|
||||
// """
|
||||
// UPDATE Notification n SET n.isRead = true WHERE
|
||||
// (n.recipient.keycloakId = :recipientId) OR
|
||||
// (n.recipient IS NULL AND CONCAT(n.organizationId, ':', n.role) IN :orgRolePairs) OR
|
||||
// (n.recipient IS NULL AND n.organizationId IN :organizationIds AND (n.role IS NULL OR n.role = ''))
|
||||
// """
|
||||
// )
|
||||
// fun markAllUserNotificationsAsRead(
|
||||
// @Param("recipientId") recipientId: String,
|
||||
// @Param("organizationIds") organizationIds: List<String>,
|
||||
// @Param("orgRolePairs") orgRolePairs: List<String>
|
||||
// )
|
||||
//
|
||||
// @Modifying
|
||||
// @Query("UPDATE Notification n SET n.isRead = true WHERE n.recipient.keycloakId = :recipientId")
|
||||
// fun markAllAsReadByRecipientId(@Param("recipientId") recipientId: String)
|
||||
// }
|
||||
|
||||
@@ -1,127 +1,127 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.User
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserRepository
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserRoleConverter
|
||||
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,
|
||||
private val userRepository: UserRepository,
|
||||
private val userRoleConverter: UserRoleConverter
|
||||
) {
|
||||
|
||||
fun createNotification(createNotificationDto: CreateNotificationDto): Notification {
|
||||
val notification = notificationMapper.toNotification(createNotificationDto)
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
fun createNotification(
|
||||
title: String,
|
||||
message: String,
|
||||
clickTarget: String,
|
||||
recipient: User?,
|
||||
role: String,
|
||||
organizationId: String,
|
||||
type: NotificationType = NotificationType.INFO
|
||||
): Notification {
|
||||
val notification = Notification(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = recipient,
|
||||
role = role,
|
||||
type = type,
|
||||
organizationId = organizationId
|
||||
)
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
fun getNotifications(
|
||||
recipientId: String,
|
||||
organizationId: String,
|
||||
page: Int = 0,
|
||||
size: Int = 20
|
||||
): Page<Notification> {
|
||||
val user = userRepository.findById(recipientId)
|
||||
.orElseThrow { IllegalArgumentException("User not found with id: $recipientId") }
|
||||
|
||||
val userRoles = userRoleConverter.getRolesForOrganization(user.organizationRoles, organizationId)
|
||||
val orgRolePairs = userRoles.map { role -> "$organizationId:${role.value}" }
|
||||
|
||||
val pageable = PageRequest.of(page, size)
|
||||
return if (userRoles.isNotEmpty()) {
|
||||
notificationRepository.findUserNotificationsByOrgRole(recipientId, listOf(organizationId), orgRolePairs, pageable)
|
||||
} else {
|
||||
notificationRepository.findByRecipientIdOrderByCreatedAtDesc(recipientId, pageable)
|
||||
}
|
||||
}
|
||||
|
||||
fun getUnreadNotifications(
|
||||
recipientId: String,
|
||||
organizationId: String
|
||||
): List<Notification> {
|
||||
val user = userRepository.findById(recipientId)
|
||||
.orElseThrow { IllegalArgumentException("User not found with id: $recipientId") }
|
||||
|
||||
val userRoles = userRoleConverter.getRolesForOrganization(user.organizationRoles, organizationId)
|
||||
val orgRolePairs = userRoles.map { role -> "$organizationId:${role.value}" }
|
||||
|
||||
return if (userRoles.isNotEmpty()) {
|
||||
notificationRepository.findUnreadUserNotificationsByOrgRole(recipientId, listOf(organizationId), orgRolePairs)
|
||||
} else {
|
||||
notificationRepository.findByRecipientIdAndIsReadFalseOrderByCreatedAtDesc(recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
fun getUnreadNotificationCount(
|
||||
recipientId: String,
|
||||
organizationId: String
|
||||
): Long {
|
||||
val user = userRepository.findById(recipientId)
|
||||
.orElseThrow { IllegalArgumentException("User not found with id: $recipientId") }
|
||||
|
||||
val userRoles = userRoleConverter.getRolesForOrganization(user.organizationRoles, organizationId)
|
||||
val orgRolePairs = userRoles.map { role -> "$organizationId:${role.value}" }
|
||||
|
||||
return if (userRoles.isNotEmpty()) {
|
||||
notificationRepository.countUnreadUserNotifications(recipientId, listOf(organizationId), orgRolePairs)
|
||||
} else {
|
||||
notificationRepository.countByRecipientIdAndIsReadFalse(recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun markAllAsRead(
|
||||
recipientId: String,
|
||||
organizationId: String
|
||||
) {
|
||||
val user = userRepository.findById(recipientId)
|
||||
.orElseThrow { IllegalArgumentException("User not found with id: $recipientId") }
|
||||
|
||||
val userRoles = userRoleConverter.getRolesForOrganization(user.organizationRoles, organizationId)
|
||||
val orgRolePairs = userRoles.map { role -> "$organizationId:${role.value}" }
|
||||
|
||||
if (userRoles.isNotEmpty()) {
|
||||
notificationRepository.markAllUserNotificationsAsRead(recipientId, listOf(organizationId), orgRolePairs)
|
||||
} else {
|
||||
notificationRepository.markAllAsReadByRecipientId(recipientId)
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun markNotificationAsRead(notificationId: UUID): Notification {
|
||||
val notification = notificationRepository.findById(notificationId)
|
||||
.orElseThrow { IllegalArgumentException("Notification not found with id: $notificationId") }
|
||||
notification.isRead = true
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
}
|
||||
// package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
//
|
||||
// import com.betriebsratkanzlei.legalconsenthub.user.User
|
||||
// import com.betriebsratkanzlei.legalconsenthub.user.UserRepository
|
||||
// import com.betriebsratkanzlei.legalconsenthub.user.UserRoleConverter
|
||||
// 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,
|
||||
// private val userRepository: UserRepository,
|
||||
// private val userRoleConverter: UserRoleConverter
|
||||
// ) {
|
||||
//
|
||||
// fun createNotification(createNotificationDto: CreateNotificationDto): Notification {
|
||||
// val notification = notificationMapper.toNotification(createNotificationDto)
|
||||
// return notificationRepository.save(notification)
|
||||
// }
|
||||
//
|
||||
// fun createNotification(
|
||||
// title: String,
|
||||
// message: String,
|
||||
// clickTarget: String,
|
||||
// recipient: User?,
|
||||
// role: String,
|
||||
// organizationId: String,
|
||||
// type: NotificationType = NotificationType.INFO
|
||||
// ): Notification {
|
||||
// val notification = Notification(
|
||||
// title = title,
|
||||
// message = message,
|
||||
// clickTarget = clickTarget,
|
||||
// recipient = recipient,
|
||||
// role = role,
|
||||
// type = type,
|
||||
// organizationId = organizationId
|
||||
// )
|
||||
// return notificationRepository.save(notification)
|
||||
// }
|
||||
//
|
||||
// fun getNotifications(
|
||||
// recipientId: String,
|
||||
// organizationId: String,
|
||||
// page: Int = 0,
|
||||
// size: Int = 20
|
||||
// ): Page<Notification> {
|
||||
// val user = userRepository.findById(recipientId)
|
||||
// .orElseThrow { IllegalArgumentException("User not found with id: $recipientId") }
|
||||
//
|
||||
// val userRoles = userRoleConverter.getRolesForOrganization(user.organizationRoles, organizationId)
|
||||
// val orgRolePairs = userRoles.map { role -> "$organizationId:${role.value}" }
|
||||
//
|
||||
// val pageable = PageRequest.of(page, size)
|
||||
// return if (userRoles.isNotEmpty()) {
|
||||
// notificationRepository.findUserNotificationsByOrgRole(recipientId, listOf(organizationId), orgRolePairs, pageable)
|
||||
// } else {
|
||||
// notificationRepository.findByRecipientIdOrderByCreatedAtDesc(recipientId, pageable)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun getUnreadNotifications(
|
||||
// recipientId: String,
|
||||
// organizationId: String
|
||||
// ): List<Notification> {
|
||||
// val user = userRepository.findById(recipientId)
|
||||
// .orElseThrow { IllegalArgumentException("User not found with id: $recipientId") }
|
||||
//
|
||||
// val userRoles = userRoleConverter.getRolesForOrganization(user.organizationRoles, organizationId)
|
||||
// val orgRolePairs = userRoles.map { role -> "$organizationId:${role.value}" }
|
||||
//
|
||||
// return if (userRoles.isNotEmpty()) {
|
||||
// notificationRepository.findUnreadUserNotificationsByOrgRole(recipientId, listOf(organizationId), orgRolePairs)
|
||||
// } else {
|
||||
// notificationRepository.findByRecipientIdAndIsReadFalseOrderByCreatedAtDesc(recipientId)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun getUnreadNotificationCount(
|
||||
// recipientId: String,
|
||||
// organizationId: String
|
||||
// ): Long {
|
||||
// val user = userRepository.findById(recipientId)
|
||||
// .orElseThrow { IllegalArgumentException("User not found with id: $recipientId") }
|
||||
//
|
||||
// val userRoles = userRoleConverter.getRolesForOrganization(user.organizationRoles, organizationId)
|
||||
// val orgRolePairs = userRoles.map { role -> "$organizationId:${role.value}" }
|
||||
//
|
||||
// return if (userRoles.isNotEmpty()) {
|
||||
// notificationRepository.countUnreadUserNotifications(recipientId, listOf(organizationId), orgRolePairs)
|
||||
// } else {
|
||||
// notificationRepository.countByRecipientIdAndIsReadFalse(recipientId)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Transactional
|
||||
// fun markAllAsRead(
|
||||
// recipientId: String,
|
||||
// organizationId: String
|
||||
// ) {
|
||||
// val user = userRepository.findById(recipientId)
|
||||
// .orElseThrow { IllegalArgumentException("User not found with id: $recipientId") }
|
||||
//
|
||||
// val userRoles = userRoleConverter.getRolesForOrganization(user.organizationRoles, organizationId)
|
||||
// val orgRolePairs = userRoles.map { role -> "$organizationId:${role.value}" }
|
||||
//
|
||||
// if (userRoles.isNotEmpty()) {
|
||||
// notificationRepository.markAllUserNotificationsAsRead(recipientId, listOf(organizationId), orgRolePairs)
|
||||
// } else {
|
||||
// notificationRepository.markAllAsReadByRecipientId(recipientId)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Transactional
|
||||
// fun markNotificationAsRead(notificationId: UUID): Notification {
|
||||
// val notification = notificationRepository.findById(notificationId)
|
||||
// .orElseThrow { IllegalArgumentException("Notification not found with id: $notificationId") }
|
||||
// notification.isRead = true
|
||||
// return notificationRepository.save(notification)
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -11,12 +11,12 @@ class CustomJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationTo
|
||||
override fun convert(jwt: Jwt): AbstractAuthenticationToken {
|
||||
val authorities: Collection<GrantedAuthority> = emptyList()
|
||||
|
||||
val userId = jwt.getClaimAsString("id")
|
||||
val userId = jwt.subject
|
||||
val username = jwt.getClaimAsString("name")
|
||||
val organizationId = jwt.getClaimAsString("organizationId")
|
||||
val roles = jwt.getClaimAsStringList("roles") ?: emptyList()
|
||||
val realmAccess = jwt.getClaimAsMap("realm_access")
|
||||
val roles = (realmAccess?.get("roles") as? List<*>)?.mapNotNull { it as? String } ?: emptyList()
|
||||
|
||||
val principal = CustomJwtTokenPrincipal(userId, username, organizationId, roles)
|
||||
val principal = CustomJwtTokenPrincipal(userId, username, roles)
|
||||
|
||||
return CustomJwtAuthentication(jwt, principal, authorities)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@ package com.betriebsratkanzlei.legalconsenthub.security
|
||||
data class CustomJwtTokenPrincipal(
|
||||
val id: String? = null,
|
||||
val name: String? = null,
|
||||
val roles: List<String> = emptyList(),
|
||||
val organizationId: String? = null,
|
||||
val roles: List<String> = emptyList()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.security
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserService
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserDto
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.oauth2.jwt.Jwt
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
|
||||
@Component
|
||||
class JwtUserSyncFilter(val userService: UserService) : OncePerRequestFilter() {
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
filterChain: FilterChain
|
||||
) {
|
||||
val logger = LoggerFactory.getLogger(JwtUserSyncFilter::class.java)
|
||||
val auth: Authentication? = SecurityContextHolder.getContext().authentication
|
||||
|
||||
println("JwtUserSyncFilter invoked for request: ${request.requestURI}")
|
||||
try {
|
||||
if (auth is JwtAuthenticationToken && auth.isAuthenticated) {
|
||||
val jwt: Jwt = auth.token
|
||||
val keycloakId = jwt.subject
|
||||
val name = jwt.getClaimAsString("name")
|
||||
|
||||
// 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 user = UserDto(keycloakId, name, organizationId)
|
||||
|
||||
if (keycloakId != null) {
|
||||
userService.createUpdateUserFromJwt(user)
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
logger.warn("Failed to sync user from JWT: {}", ex.message, ex)
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
@@ -13,18 +13,13 @@ import java.time.LocalDateTime
|
||||
class User(
|
||||
@Id
|
||||
@Column(nullable = false)
|
||||
var id: String,
|
||||
var keycloakId: String,
|
||||
|
||||
@Column(nullable = false)
|
||||
var name: String,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
var status: UserStatus = UserStatus.ACTIVE,
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "user_organization_roles", joinColumns = [JoinColumn(name = "user_id")])
|
||||
var organizationRoles: MutableSet<UserOrganizationRole> = mutableSetOf(),
|
||||
@Column(nullable = true)
|
||||
var organizationId: String? = null,
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.user
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtTokenPrincipal
|
||||
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.security.core.context.SecurityContextHolder
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@@ -13,33 +10,11 @@ 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 updateUser(id: String, userDto: UserDto?): ResponseEntity<UserDto> {
|
||||
val user = if (userDto != null) {
|
||||
// Update with provided data
|
||||
userService.updateUser(id, userDto)
|
||||
} else {
|
||||
// Update from JWT data
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val userId = principal.id ?: throw IllegalArgumentException("User ID missing from JWT")
|
||||
val organizationId = principal.organizationId
|
||||
val roles = principal.roles
|
||||
|
||||
userService.updateUserFromJwt(userId, organizationId, roles)
|
||||
}
|
||||
return ResponseEntity.ok(userMapper.toUserDto(user))
|
||||
}
|
||||
|
||||
override fun deleteUser(id: String): ResponseEntity<Unit> {
|
||||
userService.deleteUser(id)
|
||||
return ResponseEntity.noContent().build()
|
||||
|
||||
@@ -4,31 +4,22 @@ import com.betriebsratkanzlei.legalconsenthub_api.model.UserDto
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class UserMapper(
|
||||
private val roleConverter: UserRoleConverter
|
||||
) {
|
||||
class UserMapper {
|
||||
fun toUserDto(user: User): UserDto {
|
||||
val organizationRolesDto = roleConverter.convertToMap(user.organizationRoles)
|
||||
|
||||
return UserDto(
|
||||
id = user.id,
|
||||
keycloakId = user.keycloakId,
|
||||
name = user.name,
|
||||
status = user.status,
|
||||
organizationRoles = organizationRolesDto
|
||||
organizationId = user.organizationId
|
||||
)
|
||||
}
|
||||
|
||||
fun toUser(userDto: UserDto): User {
|
||||
val user = User(
|
||||
id = userDto.id,
|
||||
keycloakId = userDto.keycloakId,
|
||||
name = userDto.name,
|
||||
status = userDto.status
|
||||
organizationId = userDto.organizationId
|
||||
)
|
||||
|
||||
userDto.organizationRoles.forEach { (orgId, roles) ->
|
||||
roleConverter.setRolesForOrganization(user.organizationRoles, orgId, roles)
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.user
|
||||
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Embeddable
|
||||
|
||||
@Embeddable
|
||||
data class UserOrganizationRole(
|
||||
@Column(name = "organization_id", nullable = false)
|
||||
val organizationId: String,
|
||||
|
||||
@Column(name = "role", nullable = false)
|
||||
val role: String
|
||||
)
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.user
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserRole
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
object UserRoleConverter {
|
||||
|
||||
fun getRolesForOrganization(organizationRoles: Set<UserOrganizationRole>, organizationId: String): List<UserRole> {
|
||||
return organizationRoles
|
||||
.filter { it.organizationId == organizationId }
|
||||
.mapNotNull { orgRole ->
|
||||
try {
|
||||
UserRole.valueOf(orgRole.role)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setRolesForOrganization(organizationRoles: MutableSet<UserOrganizationRole>, organizationId: String, roles: List<UserRole>) {
|
||||
organizationRoles.removeIf { it.organizationId == organizationId }
|
||||
roles.forEach { role ->
|
||||
organizationRoles.add(UserOrganizationRole(organizationId, role.name))
|
||||
}
|
||||
}
|
||||
|
||||
fun convertToMap(organizationRoles: Set<UserOrganizationRole>): Map<String, List<UserRole>> {
|
||||
return organizationRoles
|
||||
.groupBy { it.organizationId }
|
||||
.mapValues { (_, roles) ->
|
||||
roles.mapNotNull { orgRole ->
|
||||
try {
|
||||
UserRole.valueOf(orgRole.role)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
.filterValues { it.isNotEmpty() }
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,7 @@ 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.UserDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserStatus
|
||||
import jakarta.transaction.Transactional
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -13,7 +11,7 @@ import org.springframework.stereotype.Service
|
||||
@Service
|
||||
class UserService(
|
||||
private val userRepository: UserRepository,
|
||||
private val roleConverter: UserRoleConverter
|
||||
private val userMapper: UserMapper
|
||||
) {
|
||||
|
||||
fun getCurrentUser(): User {
|
||||
@@ -24,21 +22,32 @@ class UserService(
|
||||
.orElseThrow { UserNotFoundException(userId) }
|
||||
}
|
||||
|
||||
fun createUser(createUserDto: CreateUserDto): User {
|
||||
if (userRepository.existsById(createUserDto.id)) {
|
||||
throw UserAlreadyExistsException(createUserDto.id)
|
||||
@Transactional
|
||||
fun createUpdateUserFromJwt(userDto: UserDto): User {
|
||||
val existingUser = userRepository.findById(userDto.keycloakId)
|
||||
|
||||
if (existingUser.isEmpty) {
|
||||
return createUser(userDto)
|
||||
} else {
|
||||
val user = existingUser.get()
|
||||
if (user.organizationId == null && userDto.organizationId != null) {
|
||||
user.organizationId = userDto.organizationId
|
||||
}
|
||||
return updateUser(userMapper.toUserDto(user))
|
||||
}
|
||||
}
|
||||
|
||||
fun createUser(userDto: UserDto): User {
|
||||
if (userRepository.existsById(userDto.keycloakId)) {
|
||||
throw UserAlreadyExistsException(userDto.keycloakId)
|
||||
}
|
||||
|
||||
val user = User(
|
||||
id = createUserDto.id,
|
||||
name = createUserDto.name,
|
||||
status = createUserDto.status
|
||||
keycloakId = userDto.keycloakId,
|
||||
name = userDto.name,
|
||||
organizationId = userDto.organizationId
|
||||
)
|
||||
|
||||
createUserDto.organizationRoles?.forEach { (orgId, roles) ->
|
||||
roleConverter.setRolesForOrganization(user.organizationRoles, orgId, roles)
|
||||
}
|
||||
|
||||
return userRepository.save(user)
|
||||
}
|
||||
|
||||
@@ -48,43 +57,19 @@ class UserService(
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateUser(userId: String, userDto: UserDto): User {
|
||||
val user = userRepository.findById(userId)
|
||||
.orElseThrow { UserNotFoundException(userId) }
|
||||
fun updateUser(userDto: UserDto): User {
|
||||
val user = userRepository.findById(userDto.keycloakId)
|
||||
.orElseThrow { UserNotFoundException(userDto.keycloakId) }
|
||||
|
||||
user.name = userDto.name
|
||||
user.status = userDto.status
|
||||
|
||||
user.organizationRoles.clear()
|
||||
userDto.organizationRoles.forEach { (orgId, roles) ->
|
||||
roleConverter.setRolesForOrganization(user.organizationRoles, orgId, roles)
|
||||
// Only update organization if it's not already set
|
||||
if (user.organizationId == null && userDto.organizationId != null) {
|
||||
user.organizationId = userDto.organizationId
|
||||
}
|
||||
|
||||
return userRepository.save(user)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateUserFromJwt(userId: String, jwtOrganizationId: String?, jwtRoles: List<String>?): User {
|
||||
val existingUser = userRepository.findById(userId)
|
||||
.orElseThrow { UserNotFoundException(userId) }
|
||||
|
||||
if (jwtOrganizationId != null && !jwtRoles.isNullOrEmpty()) {
|
||||
existingUser.organizationRoles.removeIf { it.organizationId == jwtOrganizationId }
|
||||
|
||||
jwtRoles.forEach { role ->
|
||||
val normalizedRole = role.lowercase().replace("_", "_")
|
||||
existingUser.organizationRoles.add(
|
||||
UserOrganizationRole(
|
||||
organizationId = jwtOrganizationId,
|
||||
role = normalizedRole
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return userRepository.save(existingUser)
|
||||
}
|
||||
|
||||
fun deleteUser(userId: String) {
|
||||
userRepository.deleteById(userId)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,13 @@ spring:
|
||||
init:
|
||||
platform: h2
|
||||
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
issuer-uri: http://localhost:7080/realms/legalconsenthub
|
||||
jwk-set-uri: http://localhost:7080/realms/legalconsenthub/protocol/openid-connect/certs
|
||||
|
||||
logging:
|
||||
level:
|
||||
org:
|
||||
|
||||
Reference in New Issue
Block a user