Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<UrlStatistics> = emptyList()
val urlStatistics: List<UrlStatistics> = emptyList(),
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null,
)

data class UrlStatistics(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {

Expand All @@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -20,30 +20,39 @@ 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,
originalUrl = originalUrl,
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")
}
}
}