feat(#2): Get notifications working again
This commit is contained in:
@@ -609,6 +609,12 @@ paths:
|
||||
tags:
|
||||
- notification
|
||||
parameters:
|
||||
- in: query
|
||||
name: organizationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Organization ID to get notifications for
|
||||
- in: query
|
||||
name: page
|
||||
schema:
|
||||
@@ -663,6 +669,13 @@ paths:
|
||||
operationId: getUnreadNotifications
|
||||
tags:
|
||||
- notification
|
||||
parameters:
|
||||
- in: query
|
||||
name: organizationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Organization ID to get unread notifications for
|
||||
responses:
|
||||
"200":
|
||||
description: List of unread notifications
|
||||
@@ -674,15 +687,30 @@ paths:
|
||||
$ref: "#/components/schemas/NotificationDto"
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"403":
|
||||
description: User is not authorized to access this organization
|
||||
"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
|
||||
summary: Get count of unread notifications for a user (public endpoint)
|
||||
operationId: getUnreadNotificationCount
|
||||
tags:
|
||||
- notification
|
||||
parameters:
|
||||
- in: query
|
||||
name: userId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Keycloak user ID to get notification count for
|
||||
- in: query
|
||||
name: organizationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Organization ID to get notification count for
|
||||
responses:
|
||||
"200":
|
||||
description: Count of unread notifications
|
||||
@@ -691,8 +719,6 @@ paths:
|
||||
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"
|
||||
|
||||
@@ -702,11 +728,43 @@ paths:
|
||||
operationId: markAllNotificationsAsRead
|
||||
tags:
|
||||
- notification
|
||||
parameters:
|
||||
- in: query
|
||||
name: organizationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Organization ID to mark notifications as read for
|
||||
responses:
|
||||
"204":
|
||||
description: All notifications marked as read
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"403":
|
||||
description: User is not authorized to access this organization
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
|
||||
/notifications/clear-all:
|
||||
delete:
|
||||
summary: Clear all notifications for the current user
|
||||
operationId: clearAllNotifications
|
||||
tags:
|
||||
- notification
|
||||
parameters:
|
||||
- in: query
|
||||
name: organizationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Organization ID to clear notifications for
|
||||
responses:
|
||||
"204":
|
||||
description: All notifications cleared
|
||||
"401":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized"
|
||||
"403":
|
||||
description: User is not authorized to access this organization
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
|
||||
@@ -723,6 +781,13 @@ paths:
|
||||
operationId: markNotificationAsRead
|
||||
tags:
|
||||
- notification
|
||||
parameters:
|
||||
- in: query
|
||||
name: organizationId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Organization ID for authorization
|
||||
responses:
|
||||
"200":
|
||||
description: Notification marked as read
|
||||
@@ -734,6 +799,8 @@ paths:
|
||||
$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"
|
||||
"403":
|
||||
description: User is not authorized to access this organization
|
||||
"500":
|
||||
$ref: "https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError"
|
||||
|
||||
@@ -1130,7 +1197,6 @@ components:
|
||||
- message
|
||||
- clickTarget
|
||||
- isRead
|
||||
- role
|
||||
- type
|
||||
- createdAt
|
||||
- organizationId
|
||||
@@ -1150,8 +1216,10 @@ components:
|
||||
nullable: true
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/UserDto"
|
||||
role:
|
||||
targetRoles:
|
||||
type: string
|
||||
description: Comma-separated list of target roles (only for role-based notifications)
|
||||
nullable: true
|
||||
type:
|
||||
$ref: "#/components/schemas/NotificationType"
|
||||
organizationId:
|
||||
@@ -1166,7 +1234,6 @@ components:
|
||||
- title
|
||||
- message
|
||||
- clickTarget
|
||||
- role
|
||||
- type
|
||||
- organizationId
|
||||
properties:
|
||||
@@ -1176,16 +1243,21 @@ components:
|
||||
type: string
|
||||
clickTarget:
|
||||
type: string
|
||||
recipient:
|
||||
nullable: true
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/UserDto"
|
||||
role:
|
||||
recipientId:
|
||||
type: string
|
||||
description: Keycloak ID of the recipient user. If not provided, notification will be role-based or organization-wide.
|
||||
nullable: true
|
||||
targetRoles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: List of roles to send notification to. If both recipientId and targetRoles are null, notification will be sent to all organization members.
|
||||
nullable: true
|
||||
type:
|
||||
$ref: "#/components/schemas/NotificationType"
|
||||
organizationId:
|
||||
type: string
|
||||
description: The organization ID for this notification
|
||||
|
||||
PagedNotificationDto:
|
||||
type: object
|
||||
|
||||
@@ -5,20 +5,22 @@ 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.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 java.util.UUID
|
||||
// import com.betriebsratkanzlei.legalconsenthub.notification.NotificationService
|
||||
|
||||
@Service
|
||||
class ApplicationFormService(
|
||||
private val applicationFormRepository: ApplicationFormRepository,
|
||||
private val applicationFormMapper: ApplicationFormMapper,
|
||||
// private val notificationService: NotificationService
|
||||
private val notificationService: NotificationService,
|
||||
) {
|
||||
fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm {
|
||||
val applicationForm = applicationFormMapper.toApplicationForm(createApplicationFormDto)
|
||||
@@ -85,32 +87,29 @@ class ApplicationFormService(
|
||||
throw ApplicationFormNotUpdatedException(e, id)
|
||||
}
|
||||
|
||||
// Create notifications for relevant users
|
||||
createSubmissionNotifications(savedApplicationForm)
|
||||
createNotificationForOrganization(savedApplicationForm)
|
||||
|
||||
return savedApplicationForm
|
||||
}
|
||||
|
||||
private fun createSubmissionNotifications(applicationForm: ApplicationForm) {
|
||||
private fun createNotificationForOrganization(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."
|
||||
"${applicationForm.createdBy.name} eingereicht."
|
||||
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
|
||||
// )
|
||||
// }
|
||||
val createNotificationDto =
|
||||
CreateNotificationDto(
|
||||
title = title,
|
||||
message = message,
|
||||
clickTarget = clickTarget,
|
||||
recipientId = null,
|
||||
targetRoles = null,
|
||||
type = NotificationType.INFO,
|
||||
organizationId = applicationForm.organizationId,
|
||||
)
|
||||
|
||||
notificationService.createNotificationForOrganization(createNotificationDto)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
@@ -14,16 +15,32 @@ import org.springframework.security.web.SecurityFilterChain
|
||||
@EnableMethodSecurity
|
||||
class SecurityConfig {
|
||||
@Bean
|
||||
fun configure(
|
||||
@Order(1)
|
||||
fun publicFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
securityMatcher(
|
||||
"/swagger-ui/**",
|
||||
"/v3/**",
|
||||
"/actuator/**",
|
||||
"/notifications/unread/count",
|
||||
)
|
||||
csrf { disable() }
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, permitAll)
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
fun protectedFilterChain(
|
||||
http: HttpSecurity,
|
||||
customJwtAuthenticationConverter: CustomJwtAuthenticationConverter,
|
||||
): SecurityFilterChain {
|
||||
http {
|
||||
csrf { disable() }
|
||||
authorizeHttpRequests {
|
||||
authorize("/swagger-ui/**", permitAll)
|
||||
authorize("/v3/**", permitAll)
|
||||
authorize("/actuator/**", permitAll)
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
|
||||
@@ -32,9 +32,9 @@ class Notification(
|
||||
var isRead: Boolean = false,
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "recipient_id", nullable = true)
|
||||
var recipient: User?,
|
||||
@Column(nullable = false)
|
||||
var role: String = "",
|
||||
var recipient: User? = null,
|
||||
@Column(nullable = true, columnDefinition = "TEXT")
|
||||
var targetRoles: String? = null,
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
var type: NotificationType = NotificationType.INFO,
|
||||
|
||||
@@ -1,83 +1,152 @@
|
||||
// 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.access.prepost.PreAuthorize
|
||||
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 {
|
||||
@PreAuthorize(
|
||||
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
|
||||
)
|
||||
override fun getNotifications(
|
||||
organizationId: String,
|
||||
page: Int,
|
||||
size: Int,
|
||||
): ResponseEntity<PagedNotificationDto> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
validateOrganizationAccess(principal, organizationId)
|
||||
val userRoles = principal.roles
|
||||
|
||||
val notifications =
|
||||
notificationService.getNotifications(
|
||||
recipientKeycloakId = recipientId,
|
||||
organizationId = organizationId,
|
||||
userRoles = userRoles,
|
||||
page = page,
|
||||
size = size,
|
||||
)
|
||||
|
||||
return ResponseEntity.ok(pagedNotificationMapper.toPagedNotificationDto(notifications))
|
||||
}
|
||||
|
||||
@PreAuthorize(
|
||||
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
|
||||
)
|
||||
override fun getUnreadNotifications(organizationId: String): ResponseEntity<List<NotificationDto>> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
validateOrganizationAccess(principal, organizationId)
|
||||
val userRoles = principal.roles
|
||||
|
||||
val notifications =
|
||||
notificationService.getUnreadNotifications(
|
||||
recipientKeycloakId = recipientId,
|
||||
organizationId = organizationId,
|
||||
userRoles = userRoles,
|
||||
)
|
||||
|
||||
return ResponseEntity.ok(notifications.map { notificationMapper.toNotificationDto(it) })
|
||||
}
|
||||
|
||||
override fun getUnreadNotificationCount(
|
||||
userId: String,
|
||||
organizationId: String,
|
||||
): ResponseEntity<kotlin.Long> {
|
||||
val count =
|
||||
notificationService.getUnreadNotificationCount(
|
||||
recipientKeycloakId = userId,
|
||||
organizationId = organizationId,
|
||||
userRoles = emptyList(),
|
||||
)
|
||||
|
||||
return ResponseEntity.ok(count)
|
||||
}
|
||||
|
||||
@PreAuthorize(
|
||||
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
|
||||
)
|
||||
override fun markAllNotificationsAsRead(organizationId: String): ResponseEntity<Unit> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
validateOrganizationAccess(principal, organizationId)
|
||||
|
||||
notificationService.markAllAsRead(
|
||||
recipientKeycloakId = recipientId,
|
||||
organizationId = organizationId,
|
||||
)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@PreAuthorize(
|
||||
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
|
||||
)
|
||||
override fun markNotificationAsRead(
|
||||
id: UUID,
|
||||
organizationId: String,
|
||||
): ResponseEntity<NotificationDto> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
validateOrganizationAccess(principal, organizationId)
|
||||
|
||||
val notification = notificationService.markNotificationAsRead(id)
|
||||
return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
}
|
||||
|
||||
@PreAuthorize(
|
||||
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL')",
|
||||
)
|
||||
override fun createNotification(createNotificationDto: CreateNotificationDto): ResponseEntity<NotificationDto> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
validateOrganizationAccess(principal, createNotificationDto.organizationId)
|
||||
|
||||
val notification =
|
||||
when {
|
||||
createNotificationDto.recipientId != null ->
|
||||
notificationService.createNotificationForUser(createNotificationDto)
|
||||
!createNotificationDto.targetRoles.isNullOrEmpty() ->
|
||||
notificationService.createNotificationForRoles(createNotificationDto)
|
||||
else ->
|
||||
notificationService.createNotificationForOrganization(createNotificationDto)
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(notificationMapper.toNotificationDto(notification))
|
||||
}
|
||||
|
||||
@PreAuthorize(
|
||||
"hasAnyRole('CHIEF_EXECUTIVE_OFFICER', 'BUSINESS_DEPARTMENT', 'IT_DEPARTMENT', 'HUMAN_RESOURCES', 'HEAD_OF_WORKS_COUNCIL', 'WORKS_COUNCIL', 'EMPLOYEE')",
|
||||
)
|
||||
override fun clearAllNotifications(organizationId: String): ResponseEntity<Unit> {
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as CustomJwtTokenPrincipal
|
||||
val recipientId = principal.id ?: throw IllegalStateException("User ID not found")
|
||||
validateOrganizationAccess(principal, organizationId)
|
||||
|
||||
notificationService.clearAllNotifications(
|
||||
recipientKeycloakId = recipientId,
|
||||
organizationId = organizationId,
|
||||
)
|
||||
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
private fun validateOrganizationAccess(
|
||||
principal: CustomJwtTokenPrincipal,
|
||||
organizationId: String,
|
||||
) {
|
||||
if (organizationId !in principal.organizationIds) {
|
||||
throw SecurityException("User is not authorized to access organization: $organizationId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.betriebsratkanzlei.legalconsenthub.notification
|
||||
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.User
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserMapper
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateNotificationDto
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.NotificationDto
|
||||
@@ -17,21 +18,29 @@ class NotificationMapper(
|
||||
clickTarget = notification.clickTarget,
|
||||
isRead = notification.isRead,
|
||||
recipient = notification.recipient?.let { userMapper.toUserDto(it) },
|
||||
role = notification.role,
|
||||
type = notification.type,
|
||||
organizationId = notification.organizationId,
|
||||
createdAt = notification.createdAt!!,
|
||||
targetRoles = notification.targetRoles,
|
||||
)
|
||||
|
||||
fun toNotification(createNotificationDto: CreateNotificationDto): Notification =
|
||||
Notification(
|
||||
fun toNotification(
|
||||
createNotificationDto: CreateNotificationDto,
|
||||
recipient: User? = null,
|
||||
targetRoles: String? = null,
|
||||
): Notification {
|
||||
if (recipient != null && targetRoles != null) {
|
||||
throw IllegalArgumentException("Only one of recipient or targetRoles can be provided")
|
||||
}
|
||||
|
||||
return Notification(
|
||||
title = createNotificationDto.title,
|
||||
message = createNotificationDto.message,
|
||||
clickTarget = createNotificationDto.clickTarget,
|
||||
isRead = false,
|
||||
recipient = createNotificationDto.recipient?.let { userMapper.toUser(it) },
|
||||
role = createNotificationDto.role,
|
||||
recipient = recipient,
|
||||
targetRoles = targetRoles,
|
||||
type = createNotificationDto.type,
|
||||
organizationId = createNotificationDto.organizationId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,80 @@
|
||||
// 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)
|
||||
// }
|
||||
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> {
|
||||
@Query(
|
||||
"""
|
||||
SELECT n FROM Notification n
|
||||
WHERE n.organizationId = :organizationId
|
||||
AND (n.recipient.keycloakId = :keycloakId OR n.recipient IS NULL)
|
||||
ORDER BY n.createdAt DESC
|
||||
""",
|
||||
)
|
||||
fun findByRecipientOrWithoutRecipientAndOrganizationId(
|
||||
@Param("keycloakId") keycloakId: String,
|
||||
@Param("organizationId") organizationId: String,
|
||||
pageable: Pageable,
|
||||
): Page<Notification>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT n FROM Notification n
|
||||
WHERE n.organizationId = :organizationId
|
||||
AND (n.recipient.keycloakId = :keycloakId OR n.recipient IS NULL)
|
||||
AND n.isRead = false
|
||||
ORDER BY n.createdAt DESC
|
||||
""",
|
||||
)
|
||||
fun findUnreadByRecipientOrWithoutRecipientAndOrganizationId(
|
||||
@Param("keycloakId") keycloakId: String,
|
||||
@Param("organizationId") organizationId: String,
|
||||
): List<Notification>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT COUNT(n) FROM Notification n
|
||||
WHERE n.organizationId = :organizationId
|
||||
AND (n.recipient.keycloakId = :keycloakId OR n.recipient IS NULL)
|
||||
AND n.isRead = false
|
||||
""",
|
||||
)
|
||||
fun countUnreadByRecipientOrWithoutRecipientAndOrganizationId(
|
||||
@Param("keycloakId") keycloakId: String,
|
||||
@Param("organizationId") organizationId: String,
|
||||
): Long
|
||||
|
||||
@Modifying
|
||||
@Query(
|
||||
"""
|
||||
UPDATE Notification n SET n.isRead = true
|
||||
WHERE n.organizationId = :organizationId
|
||||
AND (n.recipient.keycloakId = :keycloakId OR n.recipient IS NULL)
|
||||
""",
|
||||
)
|
||||
fun markAllAsReadByRecipientAndOrganization(
|
||||
@Param("keycloakId") keycloakId: String,
|
||||
@Param("organizationId") organizationId: String,
|
||||
)
|
||||
|
||||
@Modifying
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM Notification n
|
||||
WHERE n.organizationId = :organizationId
|
||||
AND (n.recipient.keycloakId = :keycloakId OR n.recipient IS NULL)
|
||||
""",
|
||||
)
|
||||
fun deleteAllByRecipientAndOrganization(
|
||||
@Param("keycloakId") keycloakId: String,
|
||||
@Param("organizationId") organizationId: String,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,127 +1,163 @@
|
||||
// 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.security.CustomJwtTokenPrincipal
|
||||
import com.betriebsratkanzlei.legalconsenthub.user.UserRepository
|
||||
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateNotificationDto
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.PageImpl
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
class NotificationService(
|
||||
private val notificationRepository: NotificationRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val notificationMapper: NotificationMapper,
|
||||
) {
|
||||
fun createNotificationForUser(createNotificationDto: CreateNotificationDto): Notification {
|
||||
val recipientKeycloakId =
|
||||
createNotificationDto.recipientId
|
||||
?: throw IllegalArgumentException("recipientId must be provided for user notifications")
|
||||
|
||||
val recipient =
|
||||
userRepository
|
||||
.findById(recipientKeycloakId)
|
||||
.orElseThrow { IllegalArgumentException("User not found with id: $recipientKeycloakId") }
|
||||
|
||||
val notification =
|
||||
notificationMapper.toNotification(
|
||||
createNotificationDto = createNotificationDto,
|
||||
recipient = recipient,
|
||||
)
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createNotificationForRoles(createNotificationDto: CreateNotificationDto): Notification {
|
||||
val targetRoles =
|
||||
createNotificationDto.targetRoles
|
||||
?: throw IllegalArgumentException("targetRoles must be provided for role-based notifications")
|
||||
|
||||
val notification =
|
||||
notificationMapper.toNotification(
|
||||
createNotificationDto = createNotificationDto,
|
||||
targetRoles = targetRoles.joinToString(","),
|
||||
)
|
||||
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createNotificationForOrganization(createNotificationDto: CreateNotificationDto): Notification {
|
||||
val notification =
|
||||
notificationMapper.toNotification(
|
||||
createNotificationDto = createNotificationDto,
|
||||
)
|
||||
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
fun getNotifications(
|
||||
recipientKeycloakId: String,
|
||||
organizationId: String,
|
||||
userRoles: List<String>,
|
||||
page: Int = 0,
|
||||
size: Int = 20,
|
||||
): Page<Notification> {
|
||||
val pageable = PageRequest.of(page, size)
|
||||
val allNotifications =
|
||||
notificationRepository.findByRecipientOrWithoutRecipientAndOrganizationId(
|
||||
recipientKeycloakId,
|
||||
organizationId,
|
||||
pageable,
|
||||
)
|
||||
|
||||
val filteredContent =
|
||||
allNotifications.content.filter { notification ->
|
||||
when {
|
||||
// Direct recipient notification
|
||||
notification.recipient != null -> true
|
||||
// Organization-wide notification (no recipient, no roles)
|
||||
notification.targetRoles.isNullOrEmpty() -> true
|
||||
// Role-based notification
|
||||
else -> {
|
||||
val targetRolesList = notification.targetRoles!!.split(",")
|
||||
userRoles.any { it in targetRolesList }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PageImpl(filteredContent, pageable, allNotifications.totalElements)
|
||||
}
|
||||
|
||||
fun getUnreadNotifications(
|
||||
recipientKeycloakId: String,
|
||||
organizationId: String,
|
||||
userRoles: List<String>,
|
||||
): List<Notification> {
|
||||
val allNotifications =
|
||||
notificationRepository.findUnreadByRecipientOrWithoutRecipientAndOrganizationId(
|
||||
recipientKeycloakId,
|
||||
organizationId,
|
||||
)
|
||||
|
||||
return allNotifications.filter { notification ->
|
||||
when {
|
||||
// Direct recipient notification
|
||||
notification.recipient != null -> true
|
||||
// Organization-wide notification (no recipient, no roles)
|
||||
notification.targetRoles.isNullOrEmpty() -> true
|
||||
// Role-based notification
|
||||
else -> {
|
||||
val targetRolesList = notification.targetRoles!!.split(",")
|
||||
userRoles.any { it in targetRolesList }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getUnreadNotificationCount(
|
||||
recipientKeycloakId: String,
|
||||
organizationId: String,
|
||||
userRoles: List<String>,
|
||||
): Long = getUnreadNotifications(recipientKeycloakId, organizationId, userRoles).size.toLong()
|
||||
|
||||
@Transactional
|
||||
fun markAllAsRead(
|
||||
recipientKeycloakId: String,
|
||||
organizationId: String,
|
||||
) {
|
||||
notificationRepository.markAllAsReadByRecipientAndOrganization(recipientKeycloakId, organizationId)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun markNotificationAsRead(notificationId: UUID): Notification {
|
||||
val notification =
|
||||
notificationRepository
|
||||
.findById(notificationId)
|
||||
.orElseThrow { IllegalArgumentException("Notification not found with id: $notificationId") }
|
||||
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal as? CustomJwtTokenPrincipal
|
||||
val currentUserKeycloakId =
|
||||
principal?.id
|
||||
?: throw IllegalStateException("User ID not found in security context")
|
||||
|
||||
if (notification.recipient != null && notification.recipient?.keycloakId != currentUserKeycloakId) {
|
||||
throw IllegalArgumentException("Cannot mark notification as read for another user")
|
||||
}
|
||||
|
||||
notification.isRead = true
|
||||
return notificationRepository.save(notification)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun clearAllNotifications(
|
||||
recipientKeycloakId: String,
|
||||
organizationId: String,
|
||||
) {
|
||||
notificationRepository.deleteAllByRecipientAndOrganization(recipientKeycloakId, organizationId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,23 @@ class CustomJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationTo
|
||||
val legalconsenthubResource = resourceAccess?.get("legalconsenthub") as? Map<*, *>
|
||||
val roles = (legalconsenthubResource?.get("roles") as? List<*>)?.mapNotNull { it as? String } ?: emptyList()
|
||||
|
||||
val organizationIds = extractOrganizationIds(jwt)
|
||||
|
||||
val authorities: Collection<GrantedAuthority> =
|
||||
roles.map { role ->
|
||||
SimpleGrantedAuthority("ROLE_$role")
|
||||
}
|
||||
|
||||
val principal = CustomJwtTokenPrincipal(userId, username, roles)
|
||||
val principal = CustomJwtTokenPrincipal(userId, username, roles, organizationIds)
|
||||
|
||||
return CustomJwtAuthentication(jwt, principal, authorities)
|
||||
}
|
||||
|
||||
private fun extractOrganizationIds(jwt: Jwt): List<String> {
|
||||
val organizationClaim = jwt.getClaimAsMap("organization") ?: return emptyList()
|
||||
|
||||
return organizationClaim.values.mapNotNull { meta ->
|
||||
(meta as? Map<*, *>)?.get("id") as? String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ data class CustomJwtTokenPrincipal(
|
||||
val id: String? = null,
|
||||
val name: String? = null,
|
||||
val roles: List<String> = emptyList(),
|
||||
val organizationId: String? = null,
|
||||
val organizationIds: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
@@ -18,8 +18,8 @@ const color = computed(() => (colorMode.value === 'dark' ? '#111827' : 'white'))
|
||||
useHead({
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ userName: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ key: 'theme-color', userName: 'theme-color', content: color }
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
link: [{ rel: 'icon', href: '/favicon.ico' }],
|
||||
htmlAttrs: {
|
||||
@@ -39,8 +39,4 @@ useSeoMeta({
|
||||
twitterImage: 'https://dashboard-template.nuxt.dev/social-card.png',
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
|
||||
// onBeforeMount(() => {
|
||||
// $fetch('/api/auth/refresh')
|
||||
// })
|
||||
</script>
|
||||
|
||||
@@ -53,9 +53,6 @@
|
||||
>
|
||||
{{ notification.type }}
|
||||
</UBadge>
|
||||
<UBadge color="neutral" variant="subtle" size="xs">
|
||||
{{ notification.role }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
@@ -80,13 +77,13 @@ const isOpen = computed({
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
// const { notifications, fetchNotifications, handleNotificationClick } = useNotification()
|
||||
//
|
||||
// watch(isOpen, async (newValue) => {
|
||||
// if (newValue) {
|
||||
// await fetchNotifications()
|
||||
// }
|
||||
// })
|
||||
const { notifications, fetchNotifications, handleNotificationClick } = useNotification()
|
||||
|
||||
watch(isOpen, async (newValue) => {
|
||||
if (newValue) {
|
||||
await fetchNotifications()
|
||||
}
|
||||
})
|
||||
|
||||
function onNotificationClick(notification: NotificationDto) {
|
||||
handleNotificationClick(notification)
|
||||
|
||||
@@ -9,15 +9,24 @@ export const useNotification = () => {
|
||||
markNotificationAsRead
|
||||
} = useNotificationApi()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const organizationId = computed(() => userStore.selectedOrganization?.id)
|
||||
const { user } = useUserSession()
|
||||
const userId = computed(() => user.value?.keycloakId)
|
||||
|
||||
const notifications = ref<NotificationDto[]>([])
|
||||
const unreadNotifications = ref<NotificationDto[]>([])
|
||||
const unreadCount = ref<number>(0)
|
||||
const isLoading = ref(false)
|
||||
|
||||
const fetchNotifications = async (page: number = 0, size: number = 20) => {
|
||||
if (!organizationId.value) {
|
||||
console.warn('No organization selected')
|
||||
return
|
||||
}
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await getNotifications(page, size)
|
||||
const response = await getNotifications(organizationId.value, page, size)
|
||||
notifications.value = response.content || []
|
||||
return response
|
||||
} catch (error) {
|
||||
@@ -29,8 +38,12 @@ export const useNotification = () => {
|
||||
}
|
||||
|
||||
const fetchUnreadNotifications = async () => {
|
||||
if (!organizationId.value) {
|
||||
console.warn('No organization selected')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await getUnreadNotifications()
|
||||
const response = await getUnreadNotifications(organizationId.value)
|
||||
unreadNotifications.value = response || []
|
||||
return response
|
||||
} catch (error) {
|
||||
@@ -40,8 +53,12 @@ export const useNotification = () => {
|
||||
}
|
||||
|
||||
const fetchUnreadCount = async () => {
|
||||
if (!userId.value || !organizationId.value) {
|
||||
console.warn('No user or organization selected')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const count = await getUnreadNotificationCount()
|
||||
const count = await getUnreadNotificationCount(userId.value, organizationId.value)
|
||||
unreadCount.value = count || 0
|
||||
return count
|
||||
} catch (error) {
|
||||
@@ -51,8 +68,12 @@ export const useNotification = () => {
|
||||
}
|
||||
|
||||
const markAllAsRead = async () => {
|
||||
if (!organizationId.value) {
|
||||
console.warn('No organization selected')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await markAllNotificationsAsRead()
|
||||
await markAllNotificationsAsRead(organizationId.value)
|
||||
unreadCount.value = 0
|
||||
unreadNotifications.value = []
|
||||
notifications.value = notifications.value.map((n) => ({ ...n, isRead: true }))
|
||||
@@ -63,8 +84,12 @@ export const useNotification = () => {
|
||||
}
|
||||
|
||||
const markAsRead = async (notificationId: string) => {
|
||||
if (!organizationId.value) {
|
||||
console.warn('No organization selected')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await markNotificationAsRead(notificationId)
|
||||
await markNotificationAsRead(notificationId, organizationId.value)
|
||||
const index = notifications.value.findIndex((n) => n.id === notificationId)
|
||||
if (index !== -1) {
|
||||
notifications.value[index].isRead = true
|
||||
|
||||
@@ -28,24 +28,28 @@ export function useNotificationApi() {
|
||||
return notificationApiClient.createNotification({ createNotificationDto })
|
||||
}
|
||||
|
||||
async function getNotifications(page?: number, size?: number): Promise<PagedNotificationDto> {
|
||||
return notificationApiClient.getNotifications({ page, size })
|
||||
async function getNotifications(organizationId: string, page?: number, size?: number): Promise<PagedNotificationDto> {
|
||||
return notificationApiClient.getNotifications({ organizationId, page, size })
|
||||
}
|
||||
|
||||
async function getUnreadNotifications(): Promise<NotificationDto[]> {
|
||||
return notificationApiClient.getUnreadNotifications()
|
||||
async function getUnreadNotifications(organizationId: string): Promise<NotificationDto[]> {
|
||||
return notificationApiClient.getUnreadNotifications({ organizationId })
|
||||
}
|
||||
|
||||
async function getUnreadNotificationCount(): Promise<number> {
|
||||
return notificationApiClient.getUnreadNotificationCount()
|
||||
async function getUnreadNotificationCount(userId: string, organizationId: string): Promise<number> {
|
||||
return notificationApiClient.getUnreadNotificationCount({ userId, organizationId })
|
||||
}
|
||||
|
||||
async function markAllNotificationsAsRead(): Promise<void> {
|
||||
return notificationApiClient.markAllNotificationsAsRead()
|
||||
async function markAllNotificationsAsRead(organizationId: string): Promise<void> {
|
||||
return notificationApiClient.markAllNotificationsAsRead({ organizationId })
|
||||
}
|
||||
|
||||
async function markNotificationAsRead(id: string): Promise<NotificationDto> {
|
||||
return notificationApiClient.markNotificationAsRead({ id })
|
||||
async function markNotificationAsRead(id: string, organizationId: string): Promise<NotificationDto> {
|
||||
return notificationApiClient.markNotificationAsRead({ id, organizationId })
|
||||
}
|
||||
|
||||
async function clearAllNotifications(organizationId: string): Promise<void> {
|
||||
return notificationApiClient.clearAllNotifications({ organizationId })
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -54,6 +58,7 @@ export function useNotificationApi() {
|
||||
getUnreadNotifications,
|
||||
getUnreadNotificationCount,
|
||||
markAllNotificationsAsRead,
|
||||
markNotificationAsRead
|
||||
markNotificationAsRead,
|
||||
clearAllNotifications
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ const open = ref(false)
|
||||
const isNotificationsSlideoverOpen = ref(false)
|
||||
const { unreadCount, fetchUnreadCount, startPeriodicRefresh } = useNotification()
|
||||
|
||||
// onMounted(async () => {
|
||||
// await fetchUnreadCount()
|
||||
// startPeriodicRefresh()
|
||||
// })
|
||||
onMounted(async () => {
|
||||
await fetchUnreadCount()
|
||||
startPeriodicRefresh()
|
||||
})
|
||||
|
||||
provide('notificationState', {
|
||||
isNotificationsSlideoverOpen,
|
||||
|
||||
@@ -12,9 +12,6 @@ export default defineEventHandler(async (event: H3Event) => {
|
||||
const session = await getUserSession(event)
|
||||
const accessToken = session?.jwt?.accessToken
|
||||
|
||||
console.log('🔍 PROXY: proxying request, found access token:', accessToken)
|
||||
console.log('🔍 PROXY: Expiration:', new Date(jwtDecode(accessToken).exp! * 1000).toISOString())
|
||||
|
||||
if (!accessToken) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
@@ -22,7 +19,8 @@ export default defineEventHandler(async (event: H3Event) => {
|
||||
})
|
||||
}
|
||||
|
||||
console.log('🔀 proxying request to', target)
|
||||
console.log('🔀 [PROXY] Expiration:', new Date(jwtDecode(accessToken).exp! * 1000).toISOString())
|
||||
console.log('🔀 [PROXY] Proxying request to:', target)
|
||||
|
||||
return proxyRequest(event, target, {
|
||||
headers: {
|
||||
|
||||
Reference in New Issue
Block a user