From a52bc2f267e69c18229faa185f15e0aee2f51402 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:23:21 +0200 Subject: [PATCH 1/5] add notification for inappropriate behaviour/bad words --- .../NotificationEventContextResolver.kt | 22 +++++++ .../TeacherNotificationEventListener.kt | 57 +++++++++++++++++++ .../domain/NotificationRenderCode.kt | 1 + .../notification/domain/NotificationType.kt | 1 + .../events/InappropriateBehaviourEvent.kt | 13 +++++ ...18__create_teacher_notifications_table.sql | 3 +- 6 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/edu/ntnu/idi/idatt/backend/shared/events/InappropriateBehaviourEvent.kt diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationEventContextResolver.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationEventContextResolver.kt index ad6af95b..2578e20e 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationEventContextResolver.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationEventContextResolver.kt @@ -22,6 +22,11 @@ data class StopNotificationContext( val stopTitle: String, ) +data class PupilNotificationContext( + val classroomTitle: String, + val pupilDisplayName: String, +) + /** * Resolves domain context needed to build teacher notification render arguments. */ @@ -75,6 +80,23 @@ class NotificationEventContextResolver( ) } + fun resolvePupilContext( + classroomId: Long, + pupilUserId: Long, + ): PupilNotificationContext { + val classroom = classroomRepository.findById(classroomId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Classroom not found") } + + val pupilProfile = pupilProfileRepository.findById(pupilUserId).orElse(null) + val pupil = userRepository.findById(pupilUserId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil not found") } + + return PupilNotificationContext( + classroomTitle = classroom.title, + pupilDisplayName = pupilProfile?.displayName ?: pupil.username ?: "Elev", + ) + } + /** * Serializes a small ordered key-value payload to JSON for frontend rendering arguments. * diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/TeacherNotificationEventListener.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/TeacherNotificationEventListener.kt index a09addf3..024e9654 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/TeacherNotificationEventListener.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/TeacherNotificationEventListener.kt @@ -6,6 +6,7 @@ import edu.ntnu.idi.idatt.backend.shared.events.MapStopCompletedEvent import edu.ntnu.idi.idatt.backend.shared.events.PupilGameCompletedEvent import edu.ntnu.idi.idatt.backend.shared.events.PupilRecoveredFromStuckEvent import edu.ntnu.idi.idatt.backend.shared.events.PupilStuckThresholdReachedEvent +import edu.ntnu.idi.idatt.backend.shared.events.InappropriateBehaviourEvent import org.springframework.stereotype.Component import org.springframework.transaction.event.TransactionPhase import org.springframework.transaction.event.TransactionalEventListener @@ -146,6 +147,48 @@ class TeacherNotificationEventListener( ) } + /** + * Materializes an inappropriate behaviour notification after a pupil attempts to write banned words. + * + * @param event committed inappropriate behaviour business event + */ + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + fun onInappropriateBehaviour(event: InappropriateBehaviourEvent) { + val context = + notificationEventContextResolver.resolvePupilContext( + classroomId = event.classroomId, + pupilUserId = event.pupilUserId, + ) + val threadKey = inappropriateBehaviourThreadKey( + event.classroomId, + event.pupilUserId, + ) + + notificationDeliveryService.deliverToClassroomTeachers( + NotificationDraft( + classroomId = event.classroomId, + pupilUserId = event.pupilUserId, + type = NotificationType.INAPPROPRIATE_BEHAVIOUR, + threadKey = threadKey, + renderCode = NotificationRenderCode.INAPPROPRIATE_BEHAVIOR, + renderArgsJson = + notificationEventContextResolver.json( + "classroomTitle" to context.classroomTitle, + "pupilDisplayName" to context.pupilDisplayName, + "input" to event.input, + "context" to event.context, + ), + payloadJson = + notificationEventContextResolver.json( + "classroomId" to event.classroomId, + "pupilUserId" to event.pupilUserId, + "input" to event.input, + "context" to event.context, + ), + ), + ) + } + companion object { /** * Stable thread key for stop-completion notifications for one pupil/stop/classroom tuple. @@ -186,5 +229,19 @@ class TeacherNotificationEventListener( pupilUserId: Long, stopId: Long, ): String = "stuck:$classroomId:$pupilUserId:$stopId" + + + /** + * Stable thread key for inappropriate behavior notifications for one pupil/classroom tuple. + * + * @param classroomId classroom that scopes the notification thread + * @param pupilUserId pupil that has behaved inappropriately + * @return deterministic notification thread key + */ + fun inappropriateBehaviourThreadKey( + classroomId: Long, + pupilUserId: Long, + ): String = "inappropriate:$classroomId:$pupilUserId" } + } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/domain/NotificationRenderCode.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/domain/NotificationRenderCode.kt index 6ad487fb..7a561d27 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/domain/NotificationRenderCode.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/domain/NotificationRenderCode.kt @@ -10,4 +10,5 @@ enum class NotificationRenderCode( GAME_COMPLETED("notifications.game-completed"), PUPIL_STUCK("notifications.pupil-stuck"), WEEKLY_MYSTERY_SUBMITTED("notifications.weekly-mystery-submitted"), + INAPPROPRIATE_BEHAVIOR("notifications.inappropriate-behavior"), } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/domain/NotificationType.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/domain/NotificationType.kt index 6628c231..92f49cdb 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/domain/NotificationType.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/domain/NotificationType.kt @@ -8,4 +8,5 @@ enum class NotificationType { GAME_COMPLETED, PUPIL_STUCK, WEEKLY_MYSTERY_SUBMITTED, + INAPPROPRIATE_BEHAVIOUR, } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/shared/events/InappropriateBehaviourEvent.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/shared/events/InappropriateBehaviourEvent.kt new file mode 100644 index 00000000..aa0ac5ce --- /dev/null +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/shared/events/InappropriateBehaviourEvent.kt @@ -0,0 +1,13 @@ +package edu.ntnu.idi.idatt.backend.shared.events + +import java.time.Instant + +data class InappropriateBehaviourEvent( + val pupilUserId: Long, + val classroomId: Long, + val input: String, + val context: String, + override val occurredAt: Instant = Instant.now(), + +) : DomainEvent + diff --git a/src/main/resources/db/migration/V18__create_teacher_notifications_table.sql b/src/main/resources/db/migration/V18__create_teacher_notifications_table.sql index 9c8a9a5f..84f6bf8e 100644 --- a/src/main/resources/db/migration/V18__create_teacher_notifications_table.sql +++ b/src/main/resources/db/migration/V18__create_teacher_notifications_table.sql @@ -7,7 +7,8 @@ CREATE TABLE teacher_notifications type ENUM('MAP_STOP_COMPLETED', 'GAME_COMPLETED', 'PUPIL_STUCK', - 'WEEKLY_MYSTERY_SUBMITTED' + 'WEEKLY_MYSTERY_SUBMITTED', + 'INAPPROPRIATE_BEHAVIOUR', ) NOT NULL, thread_key VARCHAR(255), active_thread_key VARCHAR(255), From c87c0381296022417e5773b8855cd9b27d5db537 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:45:10 +0200 Subject: [PATCH 2/5] fix --- .../db/migration/V18__create_teacher_notifications_table.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/db/migration/V18__create_teacher_notifications_table.sql b/src/main/resources/db/migration/V18__create_teacher_notifications_table.sql index 84f6bf8e..93b14b1d 100644 --- a/src/main/resources/db/migration/V18__create_teacher_notifications_table.sql +++ b/src/main/resources/db/migration/V18__create_teacher_notifications_table.sql @@ -8,7 +8,7 @@ CREATE TABLE teacher_notifications 'GAME_COMPLETED', 'PUPIL_STUCK', 'WEEKLY_MYSTERY_SUBMITTED', - 'INAPPROPRIATE_BEHAVIOUR', + 'INAPPROPRIATE_BEHAVIOUR' ) NOT NULL, thread_key VARCHAR(255), active_thread_key VARCHAR(255), From baf5d59e92b82c69d6b4ffbabf8a7130cb03a67a Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:36:26 +0200 Subject: [PATCH 3/5] trigger domain event publishing on bad words --- .../application/ClassroomModerationService.kt | 36 +++++++++++++++++++ .../mystery/application/MysteryService.kt | 9 ++++- .../notebook/application/NotebookService.kt | 12 +++++-- .../application/CurrentUserProfileService.kt | 12 +++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt index 95bb1f66..100dea6a 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt @@ -4,6 +4,8 @@ import edu.ntnu.idi.idatt.backend.iam.application.ClassroomUserResolver import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.moderation.domain.ClassroomBannedWord import edu.ntnu.idi.idatt.backend.moderation.infrastructure.ClassroomBannedWordRepository +import edu.ntnu.idi.idatt.backend.shared.events.DomainEventPublisher +import edu.ntnu.idi.idatt.backend.shared.events.InappropriateBehaviourEvent import org.springframework.http.HttpStatus import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -25,6 +27,7 @@ class ClassroomModerationService( private val currentUserResolver: CurrentUserResolver, private val classroomUserResolver: ClassroomUserResolver, private val classroomBannedWordRepository: ClassroomBannedWordRepository, + private val domainEventPublisher: DomainEventPublisher, ) { /** * Returns all banned words configured for one classroom without applying an auth check. @@ -146,22 +149,55 @@ class ClassroomModerationService( fun assertNoBannedWords( classroomId: Long, vararg texts: String, + pupilUserId: Long? = null, + context: String = "UNKNOWN" ) { val configuredWords = classroomBannedWordRepository.findAllByClassroomIdOrderByWordAsc(classroomId) if (configuredWords.isEmpty()) { return } + val combined = texts.joinToString(" ") + val blocked = configuredWords.firstOrNull { bannedWord -> texts.any { text -> containsConfiguredWord(text, bannedWord.word) } } if (blocked != null) { + if (pupilUserId != null) { + domainEventPublisher.publish( + InappropriateBehaviourEvent( + pupilUserId = pupilUserId, + classroomId = classroomId, + input = combined, + context = context + ) + ) + } throw ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, "Text contains banned words") } } + fun findBannedWordViolations( + classroomId: Long, + vararg texts: String, + ): List { + val configuredWords = classroomBannedWordRepository.findAllByClassroomIdOrderByWordAsc(classroomId) + + if (configuredWords.isEmpty()) return emptyList() + + val normalizedTexts = texts.filter { it.isNotBlank() } + + return configuredWords + .mapNotNull { banned -> + val matched = normalizedTexts.any { text -> + containsConfiguredWord(text, banned.word) + } + if (matched) banned.word else null + } + } + /** * Normalizes one teacher-supplied banned word and rejects blank values. */ diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/mystery/application/MysteryService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/mystery/application/MysteryService.kt index 14508890..7f71d17a 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/mystery/application/MysteryService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/mystery/application/MysteryService.kt @@ -8,6 +8,7 @@ import edu.ntnu.idi.idatt.backend.mystery.domain.WeeklyMystery import edu.ntnu.idi.idatt.backend.mystery.infrastructure.MysterySubmissionRepository import edu.ntnu.idi.idatt.backend.mystery.infrastructure.WeeklyMysteryRepository import edu.ntnu.idi.idatt.backend.shared.events.DomainEventPublisher +import edu.ntnu.idi.idatt.backend.shared.events.InappropriateBehaviourEvent import edu.ntnu.idi.idatt.backend.shared.events.WeeklyMysteryApprovedEvent import edu.ntnu.idi.idatt.backend.shared.events.WeeklyMysterySubmittedEvent import org.springframework.http.HttpStatus @@ -47,7 +48,13 @@ class MysteryService( */ @Transactional fun submitExample(command: SubmitMysteryCommand): MysterySubmission { - classroomModerationService.assertNoBannedWords(command.classroomId, command.description) + + classroomModerationService.assertNoBannedWords( + command.classroomId, + command.description, + pupilUserId = command.pupilUserId, + context = "MYSTERY_SUBMISSION", + ) val pupil = userRepository.findById(command.pupilUserId).orElseThrow { diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notebook/application/NotebookService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notebook/application/NotebookService.kt index ebecdb89..34041456 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notebook/application/NotebookService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notebook/application/NotebookService.kt @@ -105,7 +105,13 @@ class NotebookService( ) } - classroomModerationService.assertNoBannedWords(command.classroomId, command.title, command.content) + classroomModerationService.assertNoBannedWords( + classroomId = command.classroomId, + command.title, + command.content, + pupilUserId = pupil.id, + context = "NOTEBOOK_ENTRY_CREATE" + ) val stop = mapStopsRepository @@ -140,9 +146,11 @@ class NotebookService( val notebookEntry = verifyOwnership(pupil.id, command.notebookEntryId) classroomModerationService.assertNoBannedWords( - requireNotNull(notebookEntry.classroom.id) { "Notebook classroom is missing an id" }, + classroomId = requireNotNull(notebookEntry.classroom.id), command.title, command.content, + pupilUserId = pupil.id, + context = "NOTEBOOK_ENTRY_UPDATE" ) notebookEntry.title = command.title diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index b14618d8..1194d4a8 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -4,6 +4,7 @@ import edu.ntnu.idi.idatt.backend.game.gameProgress.application.PupilGameProgres import edu.ntnu.idi.idatt.backend.game.gameProgress.domain.PupilGameProgress import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.iam.domain.UserRole +import edu.ntnu.idi.idatt.backend.moderation.application.ClassroomModerationService import edu.ntnu.idi.idatt.backend.users.profile.domain.PupilProfile import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.TeacherProfileRepository @@ -26,6 +27,7 @@ class CurrentUserProfileService( private val pupilProfileRepository: PupilProfileRepository, private val pupilGameProgressService: PupilGameProgressService, private val teacherProfileRepository: TeacherProfileRepository, + private val classroomModerationService: ClassroomModerationService, ) { /** * Returns the current authenticated user's profile. @@ -101,6 +103,16 @@ class CurrentUserProfileService( val userId = requireNotNull(user.id) { "Authenticated user is missing an id" } val pupilProfile = findPupilProfile(userId) val progress = getCurrentClassroomProgress(userId, pupilProfile.currentClassroom?.id) + val classroomId = pupilProfile.currentClassroom?.id + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "No active classroom") + + classroomModerationService.assertNoBannedWords( + classroomId, + command.displayName, + pupilUserId = userId, + context = "DISPLAY_NAME_UPDATE" + ) + pupilProfile.displayName = command.displayName.trim() val savedProfile = pupilProfileRepository.save(pupilProfile) From 5f161e356cccf033fb244c20571ba74203e1530a Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:55:47 +0200 Subject: [PATCH 4/5] check output --- .../moderation/application/ClassroomModerationService.kt | 1 + .../notification/application/NotificationDeliveryService.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt index 100dea6a..8952fcc6 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt @@ -175,6 +175,7 @@ class ClassroomModerationService( ) ) } + println("BLOCKED WORDS DETECTED: $blocked") throw ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, "Text contains banned words") } } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationDeliveryService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationDeliveryService.kt index a3f28787..74325dc6 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationDeliveryService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationDeliveryService.kt @@ -27,6 +27,7 @@ class NotificationDeliveryService( */ @Transactional(propagation = Propagation.REQUIRES_NEW) fun deliverToClassroomTeachers(draft: NotificationDraft): List { + println("DELIVERING NOTIFICATION") val recipients = recipientResolver.resolveTeacherRecipientIds(draft.classroomId) val notifications = mutableListOf() @@ -67,6 +68,7 @@ class NotificationDeliveryService( } } + println("NOTIFICATIONS: $notifications") return notifications } From 2d4a588eacc40e7b0d04be3d17a8f7ce66373179 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:33:06 +0200 Subject: [PATCH 5/5] spotless --- .../application/ClassroomModerationService.kt | 13 +++++++------ .../backend/mystery/application/MysteryService.kt | 2 -- .../notebook/application/NotebookService.kt | 4 ++-- .../NotificationEventContextResolver.kt | 12 ++++++++---- .../TeacherNotificationEventListener.kt | 15 +++++++-------- .../shared/events/InappropriateBehaviourEvent.kt | 2 -- .../application/CurrentUserProfileService.kt | 7 ++++--- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt index 8952fcc6..257c3a11 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/moderation/application/ClassroomModerationService.kt @@ -150,7 +150,7 @@ class ClassroomModerationService( classroomId: Long, vararg texts: String, pupilUserId: Long? = null, - context: String = "UNKNOWN" + context: String = "UNKNOWN", ) { val configuredWords = classroomBannedWordRepository.findAllByClassroomIdOrderByWordAsc(classroomId) if (configuredWords.isEmpty()) { @@ -171,8 +171,8 @@ class ClassroomModerationService( pupilUserId = pupilUserId, classroomId = classroomId, input = combined, - context = context - ) + context = context, + ), ) } println("BLOCKED WORDS DETECTED: $blocked") @@ -192,9 +192,10 @@ class ClassroomModerationService( return configuredWords .mapNotNull { banned -> - val matched = normalizedTexts.any { text -> - containsConfiguredWord(text, banned.word) - } + val matched = + normalizedTexts.any { text -> + containsConfiguredWord(text, banned.word) + } if (matched) banned.word else null } } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/mystery/application/MysteryService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/mystery/application/MysteryService.kt index 7f71d17a..bd87c9c9 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/mystery/application/MysteryService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/mystery/application/MysteryService.kt @@ -8,7 +8,6 @@ import edu.ntnu.idi.idatt.backend.mystery.domain.WeeklyMystery import edu.ntnu.idi.idatt.backend.mystery.infrastructure.MysterySubmissionRepository import edu.ntnu.idi.idatt.backend.mystery.infrastructure.WeeklyMysteryRepository import edu.ntnu.idi.idatt.backend.shared.events.DomainEventPublisher -import edu.ntnu.idi.idatt.backend.shared.events.InappropriateBehaviourEvent import edu.ntnu.idi.idatt.backend.shared.events.WeeklyMysteryApprovedEvent import edu.ntnu.idi.idatt.backend.shared.events.WeeklyMysterySubmittedEvent import org.springframework.http.HttpStatus @@ -48,7 +47,6 @@ class MysteryService( */ @Transactional fun submitExample(command: SubmitMysteryCommand): MysterySubmission { - classroomModerationService.assertNoBannedWords( command.classroomId, command.description, diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notebook/application/NotebookService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notebook/application/NotebookService.kt index 34041456..6ef8fd57 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notebook/application/NotebookService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notebook/application/NotebookService.kt @@ -110,7 +110,7 @@ class NotebookService( command.title, command.content, pupilUserId = pupil.id, - context = "NOTEBOOK_ENTRY_CREATE" + context = "NOTEBOOK_ENTRY_CREATE", ) val stop = @@ -150,7 +150,7 @@ class NotebookService( command.title, command.content, pupilUserId = pupil.id, - context = "NOTEBOOK_ENTRY_UPDATE" + context = "NOTEBOOK_ENTRY_UPDATE", ) notebookEntry.title = command.title diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationEventContextResolver.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationEventContextResolver.kt index 2578e20e..44cea24e 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationEventContextResolver.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/NotificationEventContextResolver.kt @@ -84,12 +84,16 @@ class NotificationEventContextResolver( classroomId: Long, pupilUserId: Long, ): PupilNotificationContext { - val classroom = classroomRepository.findById(classroomId) - .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Classroom not found") } + val classroom = + classroomRepository + .findById(classroomId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Classroom not found") } val pupilProfile = pupilProfileRepository.findById(pupilUserId).orElse(null) - val pupil = userRepository.findById(pupilUserId) - .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil not found") } + val pupil = + userRepository + .findById(pupilUserId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil not found") } return PupilNotificationContext( classroomTitle = classroom.title, diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/TeacherNotificationEventListener.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/TeacherNotificationEventListener.kt index 024e9654..cb7a15d1 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/TeacherNotificationEventListener.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/notification/application/TeacherNotificationEventListener.kt @@ -2,11 +2,11 @@ package edu.ntnu.idi.idatt.backend.notification.application import edu.ntnu.idi.idatt.backend.notification.domain.NotificationRenderCode import edu.ntnu.idi.idatt.backend.notification.domain.NotificationType +import edu.ntnu.idi.idatt.backend.shared.events.InappropriateBehaviourEvent import edu.ntnu.idi.idatt.backend.shared.events.MapStopCompletedEvent import edu.ntnu.idi.idatt.backend.shared.events.PupilGameCompletedEvent import edu.ntnu.idi.idatt.backend.shared.events.PupilRecoveredFromStuckEvent import edu.ntnu.idi.idatt.backend.shared.events.PupilStuckThresholdReachedEvent -import edu.ntnu.idi.idatt.backend.shared.events.InappropriateBehaviourEvent import org.springframework.stereotype.Component import org.springframework.transaction.event.TransactionPhase import org.springframework.transaction.event.TransactionalEventListener @@ -158,11 +158,12 @@ class TeacherNotificationEventListener( notificationEventContextResolver.resolvePupilContext( classroomId = event.classroomId, pupilUserId = event.pupilUserId, - ) - val threadKey = inappropriateBehaviourThreadKey( - event.classroomId, - event.pupilUserId, - ) + ) + val threadKey = + inappropriateBehaviourThreadKey( + event.classroomId, + event.pupilUserId, + ) notificationDeliveryService.deliverToClassroomTeachers( NotificationDraft( @@ -230,7 +231,6 @@ class TeacherNotificationEventListener( stopId: Long, ): String = "stuck:$classroomId:$pupilUserId:$stopId" - /** * Stable thread key for inappropriate behavior notifications for one pupil/classroom tuple. * @@ -243,5 +243,4 @@ class TeacherNotificationEventListener( pupilUserId: Long, ): String = "inappropriate:$classroomId:$pupilUserId" } - } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/shared/events/InappropriateBehaviourEvent.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/shared/events/InappropriateBehaviourEvent.kt index aa0ac5ce..477145b7 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/shared/events/InappropriateBehaviourEvent.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/shared/events/InappropriateBehaviourEvent.kt @@ -8,6 +8,4 @@ data class InappropriateBehaviourEvent( val input: String, val context: String, override val occurredAt: Instant = Instant.now(), - ) : DomainEvent - diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index 1194d4a8..f61c2f87 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -103,14 +103,15 @@ class CurrentUserProfileService( val userId = requireNotNull(user.id) { "Authenticated user is missing an id" } val pupilProfile = findPupilProfile(userId) val progress = getCurrentClassroomProgress(userId, pupilProfile.currentClassroom?.id) - val classroomId = pupilProfile.currentClassroom?.id - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "No active classroom") + val classroomId = + pupilProfile.currentClassroom?.id + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "No active classroom") classroomModerationService.assertNoBannedWords( classroomId, command.displayName, pupilUserId = userId, - context = "DISPLAY_NAME_UPDATE" + context = "DISPLAY_NAME_UPDATE", ) pupilProfile.displayName = command.displayName.trim()