From 328312fe3fcbc652f91296c982f714f3fbcca150 Mon Sep 17 00:00:00 2001 From: Reyzis2021 Date: Mon, 2 Dec 2024 11:58:32 +0400 Subject: [PATCH] Add additional logic to statistic api --- .../urlshortener/auth/entity/UserDetails.kt | 2 +- .../shorten/service/UrlDeleter.kt | 2 +- .../shorten/service/UrlShortener.kt | 12 ++-- .../statistics/entity/Statistics.kt | 7 ++- .../statistics/service/StatisticsProvider.kt | 2 +- .../DeleteOperationStatisticsUpdater.kt | 39 ++++++++---- .../RedirectOperationStatisticsUpdater.kt | 59 +++++++++++-------- .../ShortenOperationStatisticsUpdater.kt | 33 +++++++---- 8 files changed, 99 insertions(+), 57 deletions(-) diff --git a/src/main/kotlin/com/zufar/urlshortener/auth/entity/UserDetails.kt b/src/main/kotlin/com/zufar/urlshortener/auth/entity/UserDetails.kt index 356c264..0429d72 100644 --- a/src/main/kotlin/com/zufar/urlshortener/auth/entity/UserDetails.kt +++ b/src/main/kotlin/com/zufar/urlshortener/auth/entity/UserDetails.kt @@ -8,7 +8,7 @@ import java.time.LocalDateTime data class UserDetails( @Id - val id: String, + val id: String? = null, val firstName: String, val lastName: String, val password: String, diff --git a/src/main/kotlin/com/zufar/urlshortener/shorten/service/UrlDeleter.kt b/src/main/kotlin/com/zufar/urlshortener/shorten/service/UrlDeleter.kt index 3bf3b33..5de5c8c 100644 --- a/src/main/kotlin/com/zufar/urlshortener/shorten/service/UrlDeleter.kt +++ b/src/main/kotlin/com/zufar/urlshortener/shorten/service/UrlDeleter.kt @@ -28,7 +28,7 @@ class UrlDeleter( log.info("Deleted URL mapping for urlHash='{}' successfully", urlHash) val userDetails: UserDetails = userDetailsProvider.getUserEntity() - val userId = userDetails.id + val userId = userDetails.id ?: "anonymous" statisticsUpdater.update(userId, urlHash) log.info("Statistics was updated for 'DeleteUrl' operation with url={}", urlHash) diff --git a/src/main/kotlin/com/zufar/urlshortener/shorten/service/UrlShortener.kt b/src/main/kotlin/com/zufar/urlshortener/shorten/service/UrlShortener.kt index 5f06a8a..ed2be2f 100644 --- a/src/main/kotlin/com/zufar/urlshortener/shorten/service/UrlShortener.kt +++ b/src/main/kotlin/com/zufar/urlshortener/shorten/service/UrlShortener.kt @@ -28,11 +28,11 @@ class UrlShortener( val originalUrl = shortenUrlRequest.originalUrl var urlHash = StringEncoder.encode(originalUrl) - var urlMapping = urlRepository.findByUrlHash(urlHash) + val urlMappingOptional = urlRepository.findByUrlHash(urlHash) - if (!urlMapping.isEmpty) { + if (urlMappingOptional.isPresent) { log.info("ShortUrl found for the urlHash='{}' in database", urlHash) - return urlMapping.get().shortUrl + return urlMappingOptional.get().shortUrl } log.info("No existing shortUrl found for the urlHash='{}'. Creating a new one.", urlHash) @@ -52,11 +52,11 @@ class UrlShortener( val newShortUrl = "$baseUrl/url/$urlHash" log.info("Generated new shortURL='{}' for originalURL='{}'", newShortUrl, originalUrl) - urlMapping = urlMappingEntityCreator.create(shortenUrlRequest, httpServletRequest, urlHash, newShortUrl) - urlRepository.save(urlMapping) + val createdUrl = urlMappingEntityCreator.create(shortenUrlRequest, httpServletRequest, urlHash, newShortUrl) + urlRepository.save(createdUrl) log.info("Saved URL mapping for urlHash='{}' in MongoDB", urlHash) - val userId = urlMapping.userId ?: "anonymous" + val userId = createdUrl.userId ?: "anonymous" statisticsUpdater.updateStatistics(userId, urlHash, newShortUrl, originalUrl) log.info("Statistics was updated for 'ShortenUrl' operation with url={}", urlHash) diff --git a/src/main/kotlin/com/zufar/urlshortener/statistics/entity/Statistics.kt b/src/main/kotlin/com/zufar/urlshortener/statistics/entity/Statistics.kt index a3304e0..6da988e 100644 --- a/src/main/kotlin/com/zufar/urlshortener/statistics/entity/Statistics.kt +++ b/src/main/kotlin/com/zufar/urlshortener/statistics/entity/Statistics.kt @@ -2,16 +2,19 @@ package com.zufar.urlshortener.statistics.entity import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document +import java.time.LocalDateTime @Document(collection = "statistics") data class Statistics( @Id - val id: String, + val id: String? = null, val userId: String, val totalShortLinksCount: Long = 0, val totalVisitsCount: Long = 0, - val urlStatistics: List = emptyList() + val urlStatistics: List = emptyList(), + val createdAt: LocalDateTime? = null, + val updatedAt: LocalDateTime? = null, ) data class UrlStatistics( diff --git a/src/main/kotlin/com/zufar/urlshortener/statistics/service/StatisticsProvider.kt b/src/main/kotlin/com/zufar/urlshortener/statistics/service/StatisticsProvider.kt index 956f66c..908ef43 100644 --- a/src/main/kotlin/com/zufar/urlshortener/statistics/service/StatisticsProvider.kt +++ b/src/main/kotlin/com/zufar/urlshortener/statistics/service/StatisticsProvider.kt @@ -15,7 +15,7 @@ class StatisticsProvider( fun get(): UserStatisticsDto { val user = userDetailsProvider.getUserEntity() - val userId = user.id + val userId = user.id ?: "anonymous" val statistics = statisticsRepository.findByUserId(userId) return if (statistics != null) { diff --git a/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/DeleteOperationStatisticsUpdater.kt b/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/DeleteOperationStatisticsUpdater.kt index 4fe4696..9106da7 100644 --- a/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/DeleteOperationStatisticsUpdater.kt +++ b/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/DeleteOperationStatisticsUpdater.kt @@ -3,35 +3,54 @@ package com.zufar.urlshortener.statistics.service.updater import com.zufar.urlshortener.shorten.repository.UrlRepository import com.zufar.urlshortener.statistics.entity.Statistics import com.zufar.urlshortener.statistics.repository.StatisticsRepository +import org.springframework.data.mongodb.core.MongoOperations +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query import org.springframework.stereotype.Service +import java.time.LocalDateTime @Service class DeleteOperationStatisticsUpdater( private val statisticsRepository: StatisticsRepository, - private val urlRepository: UrlRepository + private val urlRepository: UrlRepository, + private val mongoOperations: MongoOperations ) { fun update(userId: String, urlHash: String) { val statistics = statisticsRepository.findByUserId(userId) + val now = LocalDateTime.now() if (statistics == null) { - val totalShortLinksCount = urlRepository.countByUserId(userId) - 1 // Subtracting the deleted URL + val totalShortLinksCount = urlRepository.countByUserId(userId) - 1 val newStatistics = Statistics( userId = userId, totalShortLinksCount = totalShortLinksCount, totalVisitsCount = 0, - urlStatistics = mutableListOf() + urlStatistics = mutableListOf(), + createdAt = now, + updatedAt = now ) statisticsRepository.save(newStatistics) return } - // Update existing statistics - statistics.totalShortLinksCount -= 1 + val urlStat = statistics.urlStatistics.find { it.urlHash == urlHash } - if (urlStat != null) { - statistics.totalVisitsCount -= urlStat.totalVisitsCount - statistics.urlStatistics.remove(urlStat) - } - statisticsRepository.save(statistics) + val totalVisitsToDecrement = urlStat?.totalVisitsCount ?: 0 + + val updatedStatistics = statistics.copy( + totalShortLinksCount = statistics.totalShortLinksCount - 1, + totalVisitsCount = statistics.totalVisitsCount - totalVisitsToDecrement, + updatedAt = now, + urlStatistics = statistics.urlStatistics.filter { it.urlHash != urlHash } + ) + + val query = Query(Criteria.where("userId").`is`(userId)) + val update = org.springframework.data.mongodb.core.query.Update() + .set("totalShortLinksCount", updatedStatistics.totalShortLinksCount) + .set("totalVisitsCount", updatedStatistics.totalVisitsCount) + .set("modifiedDateTime", now) + .pull("urlStatistics", Query.query(Criteria.where("urlHash").`is`(urlHash))) + + mongoOperations.updateFirst(query, update, Statistics::class.java) } } diff --git a/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/RedirectOperationStatisticsUpdater.kt b/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/RedirectOperationStatisticsUpdater.kt index 16deecf..27cee85 100644 --- a/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/RedirectOperationStatisticsUpdater.kt +++ b/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/RedirectOperationStatisticsUpdater.kt @@ -3,12 +3,16 @@ package com.zufar.urlshortener.statistics.service.updater import com.zufar.urlshortener.shorten.repository.UrlRepository import com.zufar.urlshortener.statistics.entity.Statistics import com.zufar.urlshortener.statistics.entity.UrlStatistics -import com.zufar.urlshortener.statistics.repository.StatisticsRepository +import org.springframework.data.mongodb.core.MongoOperations +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.Update import org.springframework.stereotype.Service +import java.time.LocalDateTime @Service class RedirectOperationStatisticsUpdater( - private val statisticsRepository: StatisticsRepository, + private val mongoOperations: MongoOperations, private val urlRepository: UrlRepository ) { @@ -18,37 +22,44 @@ class RedirectOperationStatisticsUpdater( shortenedUrl: String, originalUrl: String ) { - val statistics = statisticsRepository.findByUserId(userId) + val now = LocalDateTime.now() - if (statistics == null) { - // Statistics data is absent, create new record - val totalShortLinksCount = urlRepository.countByUserId(userId) - val newStatistics = Statistics( + val query = Query(Criteria.where("userId").`is`(userId)) + + val update = Update() + .inc("totalVisitsCount", 1) + .set("modifiedDateTime", now) + + val updateResult = mongoOperations.upsert(query, update, Statistics::class.java) + + if (updateResult.upsertedId != null) { + val initialStatistics = Statistics( userId = userId, - totalShortLinksCount = totalShortLinksCount, + totalShortLinksCount = urlRepository.countByUserId(userId), totalVisitsCount = 1, - urlStatistics = mutableListOf( - UrlStatistics(urlHash, shortenedUrl, originalUrl, totalVisitsCount = 1) + createdAt = now, + updatedAt = now, + urlStatistics = listOf( + UrlStatistics(urlHash = urlHash, shortenedUrl = shortenedUrl, originalUrl = originalUrl, totalVisitsCount = 1) ) ) - statisticsRepository.save(newStatistics) + mongoOperations.save(initialStatistics) } else { - // Update existing statistics - statistics.totalVisitsCount += 1 - val urlStat = statistics.urlStatistics.find { it.urlHash == urlHash } + val urlStat = mongoOperations.findOne(query, Statistics::class.java)?.urlStatistics?.find { + it.urlHash == urlHash + } if (urlStat != null) { - urlStat.totalVisitsCount += 1 + val urlUpdate = Update().inc("urlStatistics.$[elem].totalVisitsCount", 1) + .filterArray(Criteria.where("elem.urlHash").`is`(urlHash)) + .set("modifiedDateTime", now) + + mongoOperations.updateFirst(query, urlUpdate, Statistics::class.java) } else { - statistics.urlStatistics.add( - UrlStatistics( - urlHash = urlHash, - shortenedUrl = shortenedUrl, - originalUrl = originalUrl, - totalVisitsCount = 1 - ) - ) + val addUrlStat = Update().push("urlStatistics", UrlStatistics(urlHash = urlHash, shortenedUrl = shortenedUrl, originalUrl = originalUrl, totalVisitsCount = 1)) + .set("modifiedDateTime", now) + + mongoOperations.updateFirst(query, addUrlStat, Statistics::class.java) } - statisticsRepository.save(statistics) } } } diff --git a/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/ShortenOperationStatisticsUpdater.kt b/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/ShortenOperationStatisticsUpdater.kt index 7650357..d3d2a8c 100644 --- a/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/ShortenOperationStatisticsUpdater.kt +++ b/src/main/kotlin/com/zufar/urlshortener/statistics/service/updater/ShortenOperationStatisticsUpdater.kt @@ -1,17 +1,17 @@ package com.zufar.urlshortener.statistics.service.updater -import com.zufar.urlshortener.shorten.repository.UrlRepository import com.zufar.urlshortener.statistics.entity.UrlStatistics import org.springframework.data.mongodb.core.MongoOperations +import org.springframework.data.mongodb.core.aggregation.Aggregation import org.springframework.data.mongodb.core.query.Criteria import org.springframework.data.mongodb.core.query.Query import org.springframework.data.mongodb.core.query.Update import org.springframework.stereotype.Service +import java.time.Instant @Service class ShortenOperationStatisticsUpdater( - private val mongoOperations: MongoOperations, - private val urlRepository: UrlRepository + private val mongoOperations: MongoOperations ) { fun updateStatistics( @@ -20,7 +20,10 @@ class ShortenOperationStatisticsUpdater( shortenedUrl: String, originalUrl: String ) { + val currentTime = Instant.now() + val query = Query(Criteria.where("userId").`is`(userId)) + val urlStatistics = UrlStatistics( urlHash = urlHash, shortenedUrl = shortenedUrl, @@ -28,22 +31,28 @@ class ShortenOperationStatisticsUpdater( totalVisitsCount = 0 ) - // Use upsert to atomically insert or update the document val update = Update() .inc("totalShortLinksCount", 1) .push("urlStatistics", urlStatistics) + .set("modifiedDateTime", currentTime) val updateResult = mongoOperations.upsert(query, update, "statistics") - // If the document didn't exist and was inserted, we need to ensure totalShortLinksCount is accurate if (updateResult.upsertedId != null) { - // Document was inserted; recalculate totalShortLinksCount - val totalShortLinksCount = urlRepository.countByUserId(userId) - mongoOperations.updateFirst( - query, - Update().set("totalShortLinksCount", totalShortLinksCount), - "statistics" - ) + val totalShortLinksCount = mongoOperations.aggregate( + Aggregation.newAggregation( + Aggregation.match(Criteria.where("userId").`is`(userId)), + Aggregation.project("totalShortLinksCount") + ), + "statistics", Long::class.java + ).first() + + val finalUpdate = Update() + .set("totalShortLinksCount", totalShortLinksCount) + .set("creationDateTime", currentTime) + .set("modifiedDateTime", currentTime) + + mongoOperations.updateFirst(query, finalUpdate, "statistics") } } }