diff --git a/src/main/kotlin/com/epam/brn/auth/filter/FirebaseTokenAuthenticationFilter.kt b/src/main/kotlin/com/epam/brn/auth/filter/FirebaseTokenAuthenticationFilter.kt index bca34d009..704627dfc 100644 --- a/src/main/kotlin/com/epam/brn/auth/filter/FirebaseTokenAuthenticationFilter.kt +++ b/src/main/kotlin/com/epam/brn/auth/filter/FirebaseTokenAuthenticationFilter.kt @@ -37,9 +37,16 @@ class FirebaseTokenAuthenticationFilter( filterChain: FilterChain ) { verifyToken(request) + markVisit() filterChain.doFilter(request, response) } + private fun markVisit() { + if (SecurityContextHolder.getContext().authentication != null) { + userAccountService.markVisitForCurrentUser() + } + } + private fun verifyToken(request: HttpServletRequest) { val token: String? = tokenHelperUtils.getBearerToken(request) try { diff --git a/src/main/kotlin/com/epam/brn/model/UserAccount.kt b/src/main/kotlin/com/epam/brn/model/UserAccount.kt index 91d32910f..0182e6df6 100644 --- a/src/main/kotlin/com/epam/brn/model/UserAccount.kt +++ b/src/main/kotlin/com/epam/brn/model/UserAccount.kt @@ -53,6 +53,7 @@ data class UserAccount( @OneToMany(mappedBy = "userAccount", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) var headphones: MutableSet = hashSetOf() ) { + var lastVisit: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC) var password: String? = null @Column(name = "is_firebase_error") diff --git a/src/main/kotlin/com/epam/brn/service/UserAccountService.kt b/src/main/kotlin/com/epam/brn/service/UserAccountService.kt index 06a68e0e0..4a4ca2627 100644 --- a/src/main/kotlin/com/epam/brn/service/UserAccountService.kt +++ b/src/main/kotlin/com/epam/brn/service/UserAccountService.kt @@ -34,4 +34,6 @@ interface UserAccountService { fun updateDoctorForPatient(userId: Long, doctorId: Long): UserAccount fun removeDoctorFromPatient(userId: Long): UserAccount fun getPatientsForDoctor(doctorId: Long): List + + fun markVisitForCurrentUser() } diff --git a/src/main/kotlin/com/epam/brn/service/impl/UserAccountServiceImpl.kt b/src/main/kotlin/com/epam/brn/service/impl/UserAccountServiceImpl.kt index d36584627..ef31b12b1 100644 --- a/src/main/kotlin/com/epam/brn/service/impl/UserAccountServiceImpl.kt +++ b/src/main/kotlin/com/epam/brn/service/impl/UserAccountServiceImpl.kt @@ -11,6 +11,7 @@ import com.epam.brn.model.UserAccount import com.epam.brn.repo.UserAccountRepository import com.epam.brn.service.HeadphonesService import com.epam.brn.service.RoleService +import com.epam.brn.service.TimeService import com.epam.brn.service.UserAccountService import com.google.firebase.auth.UserRecord import org.springframework.data.domain.Pageable @@ -24,7 +25,8 @@ import java.security.Principal class UserAccountServiceImpl( private val userAccountRepository: UserAccountRepository, private val roleService: RoleService, - private val headphonesService: HeadphonesService + private val headphonesService: HeadphonesService, + private val timeService: TimeService ) : UserAccountService { override fun findUserByEmail(email: String): UserAccountDto = @@ -73,6 +75,13 @@ class UserAccountServiceImpl( .orElseThrow { EntityNotFoundException("No user was found for email=$email") } } + override fun markVisitForCurrentUser() { + getCurrentUser().let { + it.lastVisit = timeService.now() + userAccountRepository.save(it) + } + } + override fun getCurrentUserDto(): UserAccountDto = getCurrentUser().toDto() diff --git a/src/main/resources/db/migration/V220230119_2406.sql b/src/main/resources/db/migration/V220230119_2406.sql new file mode 100644 index 000000000..2c7dcf55f --- /dev/null +++ b/src/main/resources/db/migration/V220230119_2406.sql @@ -0,0 +1 @@ +alter table user_account add column if not exists last_visit timestamp; diff --git a/src/test/kotlin/com/epam/brn/auth/filter/FirebaseTokenAuthenticationFilterTest.kt b/src/test/kotlin/com/epam/brn/auth/filter/FirebaseTokenAuthenticationFilterTest.kt index fc5bfcf62..65e507d63 100644 --- a/src/test/kotlin/com/epam/brn/auth/filter/FirebaseTokenAuthenticationFilterTest.kt +++ b/src/test/kotlin/com/epam/brn/auth/filter/FirebaseTokenAuthenticationFilterTest.kt @@ -18,6 +18,7 @@ import io.mockk.every import io.mockk.impl.annotations.InjectMockKs import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension +import io.mockk.justRun import io.mockk.mockk import io.mockk.verify import org.amshove.kluent.shouldBeInstanceOf @@ -85,6 +86,8 @@ internal class FirebaseTokenAuthenticationFilterTest { every { firebaseAuth.verifyIdToken(token, true) } returns firebaseTokenMock every { firebaseTokenMock.email } returns email every { brainUpUserDetailsService.loadUserByUsername(email) } returns customUserDetailsMock + justRun { userAccountService.markVisitForCurrentUser() } + // WHEN firebaseTokenAuthenticationFilter.doFilter(request, response, filterChain) // THEN @@ -97,6 +100,7 @@ internal class FirebaseTokenAuthenticationFilterTest { verify(exactly = 1) { tokenHelperUtils.getBearerToken(request) } verify(exactly = 1) { firebaseAuth.verifyIdToken(token, true) } verify(exactly = 1) { brainUpUserDetailsService.loadUserByUsername(email) } + verify(exactly = 1) { userAccountService.markVisitForCurrentUser() } verify(exactly = 0) { firebaseUserService.getUserByUuid(any()) } verify(exactly = 0) { userAccountService.createUser(any()) } } @@ -124,6 +128,7 @@ internal class FirebaseTokenAuthenticationFilterTest { gender = null, name = fullName ) + justRun { userAccountService.markVisitForCurrentUser() } // WHEN firebaseTokenAuthenticationFilter.doFilter(requestMock, responseMock, filterChain) @@ -139,6 +144,7 @@ internal class FirebaseTokenAuthenticationFilterTest { verify(exactly = 2) { brainUpUserDetailsService.loadUserByUsername(email) } verify(exactly = 1) { firebaseUserService.getUserByUuid(uuid) } verify(exactly = 1) { userAccountService.createUser(any()) } + verify(exactly = 1) { userAccountService.markVisitForCurrentUser() } } @Test @@ -163,6 +169,7 @@ internal class FirebaseTokenAuthenticationFilterTest { verify(exactly = 0) { brainUpUserDetailsService.loadUserByUsername(any()) } verify(exactly = 0) { firebaseUserService.getUserByUuid(any()) } verify(exactly = 0) { userAccountService.createUser(any()) } + verify(exactly = 0) { userAccountService.markVisitForCurrentUser() } } @Test @@ -187,6 +194,7 @@ internal class FirebaseTokenAuthenticationFilterTest { verify(exactly = 0) { brainUpUserDetailsService.loadUserByUsername(any()) } verify(exactly = 0) { firebaseUserService.getUserByUuid(any()) } verify(exactly = 0) { userAccountService.createUser(any()) } + verify(exactly = 0) { userAccountService.markVisitForCurrentUser() } } @Test @@ -216,6 +224,7 @@ internal class FirebaseTokenAuthenticationFilterTest { verify(exactly = 1) { firebaseAuth.verifyIdToken(tokenMock, true) } verify(exactly = 1) { brainUpUserDetailsService.loadUserByUsername(email) } verify(exactly = 1) { firebaseUserService.getUserByUuid(uuid) } + verify(exactly = 0) { userAccountService.markVisitForCurrentUser() } verify(exactly = 0) { userAccountService.createUser(any()) } } diff --git a/src/test/kotlin/com/epam/brn/service/UserAccountServiceTest.kt b/src/test/kotlin/com/epam/brn/service/UserAccountServiceTest.kt index 02182fc16..2bfaefe35 100644 --- a/src/test/kotlin/com/epam/brn/service/UserAccountServiceTest.kt +++ b/src/test/kotlin/com/epam/brn/service/UserAccountServiceTest.kt @@ -33,6 +33,7 @@ import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContext import org.springframework.security.core.context.SecurityContextHolder import java.time.LocalDateTime +import java.time.ZoneOffset import java.util.Optional import kotlin.test.assertFailsWith import kotlin.test.assertNotNull @@ -78,6 +79,9 @@ internal class UserAccountServiceTest { @MockK lateinit var pageable: Pageable + @MockK + lateinit var timeService: TimeService + @Nested @DisplayName("Tests for getting users") inner class GetUserAccounts { @@ -295,6 +299,42 @@ internal class UserAccountServiceTest { assertThat(userForSave.fullName).isEqualTo("newName") assertThat(userForSave.id).isEqualTo(userAccount.id) } + + @Test + fun `should mark visit current session user`() { + // GIVEN + val email = "test@test.ru" + val userAccount = UserAccount( + id = 1L, + fullName = "testUserFirstName", + email = email, + gender = BrnGender.MALE.toString(), + bornYear = 2000, + changed = LocalDateTime.now().minusMinutes(5), + avatar = null + ) + val userAccountUpdated = userAccount.copy() + val now = LocalDateTime.now(ZoneOffset.UTC) + userAccountUpdated.lastVisit = now + val userArgumentCaptor = slot() + + SecurityContextHolder.setContext(securityContext) + every { securityContext.authentication } returns authentication + every { authentication.name } returns email + every { userAccountRepository.findUserAccountByEmail(email) } returns Optional.of(userAccount) + every { userAccountRepository.save(ofType(UserAccount::class)) } returns userAccountUpdated + every { userAccountRepository.save(capture(userArgumentCaptor)) } returns userAccount + every { timeService.now() } returns now + // WHEN + userAccountService.markVisitForCurrentUser() + // THEN + verify { userAccountRepository.findUserAccountByEmail(email) } + verify { userAccountRepository.save(userArgumentCaptor.captured) } + val userForSave = userArgumentCaptor.captured + assertThat(userForSave.lastVisit).isEqualTo(now) + assertThat(userForSave.id).isEqualTo(userAccount.id) + assertThat(userForSave.fullName).isEqualTo(userAccount.fullName) + } } @Nested