feat(fullstack): Set user roles per orga, scope notification to orga and role, add orga and role to JWT
This commit is contained in:
@@ -96,44 +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 notification for admin users
|
||||
notificationService.createNotificationForUser(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = null,
|
||||
targetGroup = "admin",
|
||||
type = NotificationType.INFO
|
||||
)
|
||||
// Create separate notification for each role that should be notified
|
||||
val rolesToNotify = listOf("admin", "works_council_member", "employer", "employee")
|
||||
|
||||
// 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
|
||||
)
|
||||
rolesToNotify.forEach { role ->
|
||||
notificationService.createNotification(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = null,
|
||||
role = role,
|
||||
organizationId = applicationForm.organizationId,
|
||||
type = NotificationType.INFO
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.config
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtAuthenticationConverter
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.annotation.Order
|
||||
@@ -20,14 +21,12 @@ class SecurityConfig {
|
||||
@Order(1)
|
||||
fun publicApiSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
securityMatcher("/swagger-ui/**", "/v3/**", "/actuator/**", "/users")
|
||||
securityMatcher("/swagger-ui/**", "/v3/**", "/actuator/**")
|
||||
csrf { disable() }
|
||||
authorizeHttpRequests {
|
||||
authorize("/swagger-ui/**", permitAll)
|
||||
authorize("/v3/**", permitAll)
|
||||
authorize("/actuator/**", permitAll)
|
||||
// For user registration
|
||||
authorize(HttpMethod.POST, "/users", permitAll)
|
||||
authorize(anyRequest, denyAll)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +42,8 @@ class SecurityConfig {
|
||||
http {
|
||||
csrf { disable() }
|
||||
authorizeHttpRequests {
|
||||
// Allow user registration without authentication
|
||||
authorize(HttpMethod.POST, "/users", permitAll)
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
|
||||
@@ -40,12 +40,15 @@ class Notification(
|
||||
var recipient: User?,
|
||||
|
||||
@Column(nullable = false)
|
||||
var targetGroup: String = "",
|
||||
var role: String = "",
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
var type: NotificationType = NotificationType.INFO,
|
||||
|
||||
@Column(nullable = false)
|
||||
var organizationId: String = "",
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false)
|
||||
var createdAt: LocalDateTime? = null
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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
|
||||
@@ -15,30 +14,20 @@ import java.util.UUID
|
||||
class NotificationController(
|
||||
private val notificationService: NotificationService,
|
||||
private val notificationMapper: NotificationMapper,
|
||||
private val pagedNotificationMapper: PagedNotificationMapper,
|
||||
private val userService: UserService
|
||||
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 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
|
||||
)
|
||||
}
|
||||
val notifications = notificationService.getNotifications(
|
||||
recipientId = recipientId,
|
||||
organizationId = organizationId,
|
||||
page = page,
|
||||
size = size
|
||||
)
|
||||
|
||||
return ResponseEntity.ok(pagedNotificationMapper.toPagedNotificationDto(notifications))
|
||||
}
|
||||
@@ -46,29 +35,25 @@ class NotificationController(
|
||||
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 user = userService.getUserById(recipientId)
|
||||
|
||||
val notifications = if (user.role != null) {
|
||||
notificationService.getUnreadNotificationsForUserAndGroup(recipientId, user.role!!.value)
|
||||
} else {
|
||||
notificationService.getUnreadNotificationsForUser(recipientId)
|
||||
}
|
||||
val notifications = notificationService.getUnreadNotifications(
|
||||
recipientId = recipientId,
|
||||
organizationId = organizationId
|
||||
)
|
||||
|
||||
return ResponseEntity.ok(notifications.map { notificationMapper.toNotificationDto(it) })
|
||||
}
|
||||
|
||||
override fun getUnreadNotificationCount(): ResponseEntity<Long> {
|
||||
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 user = userService.getUserById(recipientId)
|
||||
|
||||
val count = if (user.role != null) {
|
||||
notificationService.getUnreadNotificationCountForUserAndGroup(recipientId, user.role!!.value)
|
||||
} else {
|
||||
notificationService.getUnreadNotificationCount(recipientId)
|
||||
}
|
||||
val count = notificationService.getUnreadNotificationCount(
|
||||
recipientId = recipientId,
|
||||
organizationId = organizationId
|
||||
)
|
||||
|
||||
return ResponseEntity.ok(count)
|
||||
}
|
||||
@@ -76,22 +61,18 @@ class NotificationController(
|
||||
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")
|
||||
|
||||
val user = userService.getUserById(recipientId)
|
||||
|
||||
if (user.role != null) {
|
||||
notificationService.markAllAsReadForUserAndGroup(recipientId, user.role!!.value)
|
||||
} else {
|
||||
notificationService.markAllAsRead(recipientId)
|
||||
}
|
||||
notificationService.markAllAsRead(
|
||||
recipientId = recipientId,
|
||||
organizationId = organizationId
|
||||
)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
override fun markNotificationAsRead(id: UUID): ResponseEntity<NotificationDto> {
|
||||
val notification = notificationService.markAsRead(id)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
|
||||
val notification = notificationService.markNotificationAsRead(id)
|
||||
return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,9 @@ class NotificationMapper(
|
||||
clickTarget = notification.clickTarget,
|
||||
isRead = notification.isRead,
|
||||
recipient = notification.recipient?.let { userMapper.toUserDto(it) },
|
||||
targetGroup = notification.targetGroup,
|
||||
role = notification.role,
|
||||
type = notification.type,
|
||||
organizationId = notification.organizationId,
|
||||
createdAt = notification.createdAt!!
|
||||
)
|
||||
}
|
||||
@@ -31,8 +32,9 @@ class NotificationMapper(
|
||||
clickTarget = createNotificationDto.clickTarget,
|
||||
isRead = false,
|
||||
recipient = createNotificationDto.recipient?.let { userMapper.toUser(it) },
|
||||
targetGroup = createNotificationDto.targetGroup,
|
||||
type = createNotificationDto.type
|
||||
role = createNotificationDto.role,
|
||||
type = createNotificationDto.type,
|
||||
organizationId = createNotificationDto.organizationId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,35 +13,64 @@ import java.util.UUID
|
||||
interface NotificationRepository : JpaRepository<Notification, UUID> {
|
||||
|
||||
fun findByRecipientIdOrderByCreatedAtDesc(recipientId: String?, pageable: Pageable): Page<Notification>
|
||||
|
||||
fun findByRecipientIdAndIsReadFalseOrderByCreatedAtDesc(recipientId: String?): List<Notification>
|
||||
fun countByRecipientIdAndIsReadFalse(recipientId: String?): Long
|
||||
|
||||
fun findByRecipientIsNullOrderByCreatedAtDesc(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 = ''))
|
||||
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>
|
||||
|
||||
fun findByRecipientIsNullAndIsReadFalseOrderByCreatedAtDesc(): List<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 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 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
|
||||
|
||||
@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) 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)
|
||||
|
||||
@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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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
|
||||
@@ -12,7 +14,9 @@ import java.util.UUID
|
||||
@Service
|
||||
class NotificationService(
|
||||
private val notificationRepository: NotificationRepository,
|
||||
private val notificationMapper: NotificationMapper
|
||||
private val notificationMapper: NotificationMapper,
|
||||
private val userRepository: UserRepository,
|
||||
private val userRoleConverter: UserRoleConverter
|
||||
) {
|
||||
|
||||
fun createNotification(createNotificationDto: CreateNotificationDto): Notification {
|
||||
@@ -20,12 +24,13 @@ class NotificationService(
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
fun createNotificationForUser(
|
||||
fun createNotification(
|
||||
title: String,
|
||||
message: String,
|
||||
clickTarget: String,
|
||||
recipient: User?,
|
||||
targetGroup: String,
|
||||
role: String,
|
||||
organizationId: String,
|
||||
type: NotificationType = NotificationType.INFO
|
||||
): Notification {
|
||||
val notification = Notification(
|
||||
@@ -33,55 +38,90 @@ class NotificationService(
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipient = recipient,
|
||||
targetGroup = targetGroup,
|
||||
type = type
|
||||
role = role,
|
||||
type = type,
|
||||
organizationId = organizationId
|
||||
)
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
fun getNotificationsForUser(recipientId: String, page: Int = 0, size: Int = 20): Page<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 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)
|
||||
return if (userRoles.isNotEmpty()) {
|
||||
notificationRepository.findUserNotificationsByOrgRole(recipientId, listOf(organizationId), orgRolePairs, pageable)
|
||||
} else {
|
||||
null
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.config
|
||||
package com.betriebsratkanzlei.legalconsenthub.security
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtAuthentication
|
||||
import com.betriebsratkanzlei.legalconsenthub.security.CustomJwtTokenPrincipal
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
@@ -15,7 +13,10 @@ class CustomJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationTo
|
||||
|
||||
val userId = jwt.getClaimAsString("id")
|
||||
val username = jwt.getClaimAsString("name")
|
||||
val principal = CustomJwtTokenPrincipal(userId, username)
|
||||
val organizationId = jwt.getClaimAsString("organizationId")
|
||||
val roles = jwt.getClaimAsStringList("roles") ?: emptyList()
|
||||
|
||||
val principal = CustomJwtTokenPrincipal(userId, username, organizationId, roles)
|
||||
|
||||
return CustomJwtAuthentication(jwt, principal, authorities)
|
||||
}
|
||||
@@ -2,5 +2,7 @@ package com.betriebsratkanzlei.legalconsenthub.security
|
||||
|
||||
data class CustomJwtTokenPrincipal(
|
||||
val id: String? = null,
|
||||
val name: String? = null
|
||||
val name: String? = null,
|
||||
val organizationId: String? = null,
|
||||
val roles: List<String> = emptyList()
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.user
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserRole
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.UserStatus
|
||||
import jakarta.persistence.*
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
@@ -22,10 +21,10 @@ class User(
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
var status: UserStatus = UserStatus.ACTIVE,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = true)
|
||||
var role: UserRole? = null,
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "user_organization_roles", joinColumns = [JoinColumn(name = "user_id")])
|
||||
var organizationRoles: MutableSet<UserOrganizationRole> = mutableSetOf(),
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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
|
||||
@@ -22,8 +24,24 @@ class UserController(
|
||||
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,22 +4,31 @@ import com.betriebsratkanzlei.legalconsenthub_api.model.UserDto
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class UserMapper() {
|
||||
class UserMapper(
|
||||
private val roleConverter: UserRoleConverter
|
||||
) {
|
||||
fun toUserDto(user: User): UserDto {
|
||||
val organizationRolesDto = roleConverter.convertToMap(user.organizationRoles)
|
||||
|
||||
return UserDto(
|
||||
id = user.id,
|
||||
name = user.name,
|
||||
status = user.status,
|
||||
role = user.role
|
||||
organizationRoles = organizationRolesDto
|
||||
)
|
||||
}
|
||||
|
||||
fun toUser(userDto: UserDto): User {
|
||||
return User(
|
||||
val user = User(
|
||||
id = userDto.id,
|
||||
name = userDto.name,
|
||||
status = userDto.status,
|
||||
role = userDto.role
|
||||
status = userDto.status
|
||||
)
|
||||
|
||||
userDto.organizationRoles.forEach { (orgId, roles) ->
|
||||
roleConverter.setRolesForOrganization(user.organizationRoles, orgId, roles)
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
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() }
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,16 @@ 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
|
||||
|
||||
@Service
|
||||
class UserService(
|
||||
private val userRepository: UserRepository
|
||||
private val userRepository: UserRepository,
|
||||
private val roleConverter: UserRoleConverter
|
||||
) {
|
||||
|
||||
fun getCurrentUser(): User {
|
||||
@@ -29,9 +32,13 @@ class UserService(
|
||||
val user = User(
|
||||
id = createUserDto.id,
|
||||
name = createUserDto.name,
|
||||
status = createUserDto.status ?: UserStatus.ACTIVE,
|
||||
role = createUserDto.role
|
||||
status = createUserDto.status
|
||||
)
|
||||
|
||||
createUserDto.organizationRoles?.forEach { (orgId, roles) ->
|
||||
roleConverter.setRolesForOrganization(user.organizationRoles, orgId, roles)
|
||||
}
|
||||
|
||||
return userRepository.save(user)
|
||||
}
|
||||
|
||||
@@ -40,6 +47,44 @@ class UserService(
|
||||
.orElseThrow { UserNotFoundException(userId) }
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateUser(userId: String, userDto: UserDto): User {
|
||||
val user = userRepository.findById(userId)
|
||||
.orElseThrow { UserNotFoundException(userId) }
|
||||
|
||||
user.name = userDto.name
|
||||
user.status = userDto.status
|
||||
|
||||
user.organizationRoles.clear()
|
||||
userDto.organizationRoles.forEach { (orgId, roles) ->
|
||||
roleConverter.setRolesForOrganization(user.organizationRoles, orgId, roles)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user