From 7c2aac3d692c77be197ab449a2b5414b44a55e63 Mon Sep 17 00:00:00 2001
From: Vladislav Frolov <50615459+Cheshiriks@users.noreply.github.com>
Date: Wed, 25 Oct 2023 15:49:45 +0300
Subject: [PATCH] Added notifications for approving new users (#2803)
* Added notifications for approving new users
---
db/v-3/core/db.changelog.xml | 1 +
db/v-3/core/notification.xml | 27 +++++++++++
.../save/backend/event/UserListener.kt | 46 +++++++++++++++++++
.../repository/NotificationRepository.kt | 11 +++++
.../save/backend/repository/UserRepository.kt | 6 +++
.../backend/service/NotificationService.kt | 29 ++++++++++++
.../backend/service/UserDetailsService.kt | 9 ++++
.../save/entities/NotificationDto.kt | 14 ++++++
.../saveourtool/save/entities/Notification.kt | 27 +++++++++++
9 files changed, 170 insertions(+)
create mode 100644 db/v-3/core/notification.xml
create mode 100644 save-backend/src/main/kotlin/com/saveourtool/save/backend/event/UserListener.kt
create mode 100644 save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/NotificationRepository.kt
create mode 100644 save-backend/src/main/kotlin/com/saveourtool/save/backend/service/NotificationService.kt
create mode 100644 save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/NotificationDto.kt
create mode 100644 save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/entities/Notification.kt
diff --git a/db/v-3/core/db.changelog.xml b/db/v-3/core/db.changelog.xml
index c25f331ec7..87363a1804 100644
--- a/db/v-3/core/db.changelog.xml
+++ b/db/v-3/core/db.changelog.xml
@@ -6,5 +6,6 @@
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
+
\ No newline at end of file
diff --git a/db/v-3/core/notification.xml b/db/v-3/core/notification.xml
new file mode 100644
index 0000000000..16d840e5e5
--- /dev/null
+++ b/db/v-3/core/notification.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/event/UserListener.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/event/UserListener.kt
new file mode 100644
index 0000000000..b866aebcd2
--- /dev/null
+++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/event/UserListener.kt
@@ -0,0 +1,46 @@
+package com.saveourtool.save.backend.event
+
+import com.saveourtool.save.backend.service.NotificationService
+import com.saveourtool.save.backend.service.UserDetailsService
+import com.saveourtool.save.domain.Role
+import com.saveourtool.save.entities.Notification
+import com.saveourtool.save.entities.User
+import com.saveourtool.save.info.UserStatus
+import org.springframework.context.event.EventListener
+import org.springframework.stereotype.Component
+
+/**
+ * A user listener for sending notifications.
+ */
+@Component
+class UserListener(
+ private val userDetailsService: UserDetailsService,
+ private val notificationService: NotificationService,
+) {
+ /**
+ * @param user new user
+ */
+ @EventListener
+ fun createUser(user: User) {
+ if (user.status == UserStatus.NOT_APPROVED) {
+ val recipients = userDetailsService.findByRole(Role.SUPER_ADMIN.asSpringSecurityRole())
+ val notifications = recipients.map {
+ Notification(
+ message = messageNewUser(user),
+ user = it,
+ )
+ }
+ notificationService.saveAll(notifications)
+ }
+ }
+
+ companion object {
+ /**
+ * @param user
+ * @return message
+ */
+ fun messageNewUser(user: User) = """
+ New user: ${user.name} is waiting for approve of his account.
+ """.trimIndent()
+ }
+}
diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/NotificationRepository.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/NotificationRepository.kt
new file mode 100644
index 0000000000..26f91d257d
--- /dev/null
+++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/NotificationRepository.kt
@@ -0,0 +1,11 @@
+package com.saveourtool.save.backend.repository
+
+import com.saveourtool.save.entities.Notification
+import com.saveourtool.save.spring.repository.BaseEntityRepository
+import org.springframework.stereotype.Repository
+
+/**
+ * Repository to access data about user notification
+ */
+@Repository
+interface NotificationRepository : BaseEntityRepository
diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/UserRepository.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/UserRepository.kt
index 044bdba0be..bbb57e877e 100644
--- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/UserRepository.kt
+++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/UserRepository.kt
@@ -18,6 +18,12 @@ interface UserRepository : BaseEntityRepository, ValidateRepository {
*/
fun findByName(username: String): User?
+ /**
+ * @param role
+ * @return users with status
+ */
+ fun findByRole(role: String): List
+
/**
* @param status
* @return users with status
diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/NotificationService.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/NotificationService.kt
new file mode 100644
index 0000000000..0b93bfe970
--- /dev/null
+++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/NotificationService.kt
@@ -0,0 +1,29 @@
+package com.saveourtool.save.backend.service
+
+import com.saveourtool.save.backend.repository.NotificationRepository
+import com.saveourtool.save.entities.Notification
+import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
+
+/**
+ * Service for notifications
+ */
+@Service
+@Transactional(readOnly = true)
+class NotificationService(
+ private val notificationRepository: NotificationRepository,
+) {
+ /**
+ * @param notification
+ * @return saved notification
+ */
+ @Transactional
+ fun save(notification: Notification): Notification = notificationRepository.save(notification)
+
+ /**
+ * @param notifications
+ * @return saved notifications
+ */
+ @Transactional
+ fun saveAll(notifications: List): List = notificationRepository.saveAll(notifications)
+}
diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/UserDetailsService.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/UserDetailsService.kt
index 7356e29834..e2d4b5bb1b 100644
--- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/UserDetailsService.kt
+++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/UserDetailsService.kt
@@ -14,6 +14,7 @@ import com.saveourtool.save.entities.User
import com.saveourtool.save.info.UserStatus
import com.saveourtool.save.utils.*
import org.slf4j.Logger
+import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.repository.findByIdOrNull
import org.springframework.security.core.Authentication
@@ -34,6 +35,7 @@ class UserDetailsService(
private val lnkUserOrganizationRepository: LnkUserOrganizationRepository,
private val lnkUserProjectRepository: LnkUserProjectRepository,
private val avatarStorage: AvatarStorage,
+ private val applicationEventPublisher: ApplicationEventPublisher,
) {
/**
* @param user user for update
@@ -47,6 +49,12 @@ class UserDetailsService(
*/
fun findByName(username: String) = userRepository.findByName(username)
+ /**
+ * @param role
+ * @return spring's UserDetails retrieved from save's user found by provided values
+ */
+ fun findByRole(role: String) = userRepository.findByRole(role)
+
/**
* @param name
* @return found [User] or exception
@@ -121,6 +129,7 @@ class UserDetailsService(
*/
@Transactional
fun saveUser(newUser: User, oldName: String?, oldUserStatus: UserStatus): UserSaveStatus {
+ applicationEventPublisher.publishEvent(newUser)
val isNameFreeAndNotTaken = userRepository.validateName(newUser.name) != 0L
// if we are registering new user (updating just name and status to NOT_APPROVED):
return if (oldUserStatus == UserStatus.CREATED && newUser.status == UserStatus.NOT_APPROVED) {
diff --git a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/NotificationDto.kt b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/NotificationDto.kt
new file mode 100644
index 0000000000..d71c890f07
--- /dev/null
+++ b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/NotificationDto.kt
@@ -0,0 +1,14 @@
+package com.saveourtool.save.entities
+
+import kotlinx.datetime.LocalDateTime
+import kotlinx.serialization.Serializable
+
+/**
+ * @property message
+ * @property createDate
+ */
+@Serializable
+class NotificationDto(
+ val message: String,
+ val createDate: LocalDateTime?,
+)
diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/entities/Notification.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/entities/Notification.kt
new file mode 100644
index 0000000000..ed07cd3331
--- /dev/null
+++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/entities/Notification.kt
@@ -0,0 +1,27 @@
+package com.saveourtool.save.entities
+
+import com.saveourtool.save.spring.entity.BaseEntityWithDateAndDto
+
+import javax.persistence.Entity
+import javax.persistence.JoinColumn
+import javax.persistence.ManyToOne
+
+import kotlinx.datetime.toKotlinLocalDateTime
+
+/**
+ * @property message
+ * @property user
+ */
+@Entity
+class Notification(
+ var message: String,
+
+ @ManyToOne
+ @JoinColumn(name = "user_id")
+ var user: User,
+) : BaseEntityWithDateAndDto() {
+ override fun toDto() = NotificationDto(
+ message = message,
+ createDate = createDate?.toKotlinLocalDateTime(),
+ )
+}