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