wishlists + statics + some fixes

This commit is contained in:
xds
2025-03-03 10:38:07 +03:00
parent db0ada5ee8
commit 3b9f0e566c
16 changed files with 566 additions and 14 deletions

View File

@@ -23,7 +23,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.aggregation.Aggregation.*
import org.springframework.data.mongodb.core.aggregation.DateOperators.DateToString
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.context.ReactiveSecurityContextHolder
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@@ -169,16 +169,34 @@ class FinancialService(
}
}
private fun findBudgetCategory(transaction: Transaction, budget: Budget): BudgetCategory {
private suspend fun findBudgetCategory(transaction: Transaction, budget: Budget): BudgetCategory {
return if (transaction.category.type.code == "EXPENSE") {
budget.categories.firstOrNull { it.category.id == transaction.category.id }
?: throw RuntimeException("Budget category not found for expense")
?: addCategoryToBudget(transaction.category, budget)
} else {
budget.incomeCategories.firstOrNull { it.category.id == transaction.category.id }
?: throw RuntimeException("Budget category not found for income")
?:addCategoryToBudget(transaction.category, budget)
}
}
private suspend fun addCategoryToBudget(category: Category, budget: Budget): BudgetCategory {
val sums = getBudgetSumsByCategory(category.id!!, budget)
val categoryBudget = BudgetCategory(
currentSpent = sums.getDouble("instantAmount"),
currentPlanned = sums.getDouble("plannedAmount"),
currentLimit = sums.getDouble("plannedAmount"),
category = category
)
if (category.type.code == "EXPENSE") {
budget.categories.add(categoryBudget)
} else {
budget.incomeCategories.add(categoryBudget)
}
budgetRepo.save(budget).awaitSingle()
return categoryBudget
}
private suspend fun updateBudgetCategory(
transaction: Transaction,
budget: Budget,
@@ -826,7 +844,7 @@ class FinancialService(
suspend fun createTransaction(space: Space, transaction: Transaction): Transaction {
val securityContextHolder = SecurityContextHolder.getContext()
val securityContextHolder = ReactiveSecurityContextHolder.getContext().awaitSingle()
val user = userService.getByUserNameWoPass(securityContextHolder.authentication.name)
if (space.users.none { it.id.toString() == user.id }) {
throw IllegalArgumentException("User does not have access to this Space")
@@ -903,7 +921,7 @@ class FinancialService(
suspend fun deleteTransaction(transaction: Transaction) = coroutineScope {
transactionsRepo.deleteById(transaction.id!!).awaitSingle()
transactionsRepo.deleteById(transaction.id!!).awaitFirstOrNull()
launch { updateBudgetOnDelete(transaction) }
}

View File

@@ -0,0 +1,29 @@
package space.luminic.budgerapp.services
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kotlinx.coroutines.withContext
import org.springframework.http.codec.multipart.FilePart
import org.springframework.stereotype.Service
import space.luminic.budgerapp.configs.StorageConfig
import java.nio.file.Files
import java.nio.file.Paths
import java.util.*
@Service
class StaticService(private val storageConfig: StorageConfig) {
suspend fun saveFile(spaceId: String, wishListItemId: String, filePart: FilePart): String {
val folder = Paths.get(storageConfig.rootLocation.toString(), spaceId, "wishlists", wishListItemId)
withContext(Dispatchers.IO) {
Files.createDirectories(folder)
}
val filename = UUID.randomUUID().toString().split("-")[0] + "." + filePart.filename().split(".").last()
val filePath =
folder.resolve(filename)
filePart.transferTo(filePath).awaitSingleOrNull()
return filePath.toString()
}
}

View File

@@ -0,0 +1,132 @@
package space.luminic.budgerapp.services
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import org.bson.Document
import org.bson.types.ObjectId
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.aggregation.Aggregation.*
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.security.core.context.ReactiveSecurityContextHolder
import org.springframework.stereotype.Service
import space.luminic.budgerapp.mappers.WishListMapper
import space.luminic.budgerapp.models.NotFoundException
import space.luminic.budgerapp.models.Space
import space.luminic.budgerapp.models.WishList
import space.luminic.budgerapp.models.WishListItem
import space.luminic.budgerapp.repos.WishListItemRepo
import space.luminic.budgerapp.repos.WishListRepo
import java.time.LocalDateTime
@Service
class WishListService(
private val reactiveMongoTemplate: ReactiveMongoTemplate,
private val wishListMapper: WishListMapper,
private val wishListRepo: WishListRepo,
private val wishListItemRepo: WishListItemRepo,
private val userService: UserService
) {
private fun getLookupsAndUnwinds(): List<AggregationOperation> {
val aggregationOperation = mutableListOf<AggregationOperation>()
aggregationOperation.add(lookup("spaces", "space.\$id", "_id", "spaceDetails"))
aggregationOperation.add(unwind("spaceDetails"))
aggregationOperation.add(lookup("users", "owner.\$id", "_id", "ownerDetails"))
aggregationOperation.add(unwind("ownerDetails"))
aggregationOperation.add(lookup("wishlistItems", "items.\$id", "_id", "itemsDetails"))
return aggregationOperation
}
suspend fun findWishLists(spaceId: String): List<WishList> {
val user = userService.getByUserNameWoPass(
ReactiveSecurityContextHolder.getContext().awaitSingle().authentication.name
)
val match = match(
Criteria.where("spaceDetails._id").`is`(ObjectId(spaceId))
.andOperator(
Criteria.where("ownerDetails._id").`is`(ObjectId(user.id))
.orOperator(Criteria.where("isPrivate").`is`(false))
)
)
val aggregation = newAggregation(*(getLookupsAndUnwinds().toTypedArray()), match)
return reactiveMongoTemplate.aggregate(aggregation, "wishlists", Document::class.java)
.asFlow().map {
wishListMapper.fromDocument(it)
}.toList()
}
suspend fun getList(listId: String): WishList {
val user = userService.getByUserNameWoPass(
ReactiveSecurityContextHolder.getContext().awaitSingle().authentication.name
)
val match = match(
Criteria.where("_id").`is`(ObjectId(listId))
.andOperator(Criteria.where("ownerDetails._id").`is`(ObjectId(user.id)))
)
val aggregation = newAggregation(*(getLookupsAndUnwinds().toTypedArray()), match)
// val aggregation = newAggregation(lookupSpace, unwindSpace, lookupOwner, unwindOwner, lookupItems, match)
return reactiveMongoTemplate.aggregate(aggregation, "wishlists", Document::class.java)
.next().map {
wishListMapper.fromDocument(it)
}.awaitSingleOrNull() ?: throw NotFoundException("WishList with id $listId does not exist")
}
suspend fun createWishList(space: Space, wishList: WishList): WishList {
val user = userService.getByUserNameWoPass(
ReactiveSecurityContextHolder.getContext().awaitSingle().authentication.name
)
wishList.owner = user
wishList.space = space
return wishListRepo.save(wishList).awaitSingle()
}
suspend fun updateWishListInfo(wishList: WishList): WishList {
val oldStateOfWishList = getList(wishList.id!!)
val newStateOfWishList = oldStateOfWishList.copy(
name = wishList.name,
description = wishList.description,
isPrivate = wishList.isPrivate,
updatedAt = LocalDateTime.now()
)
return wishListRepo.save(newStateOfWishList).awaitSingle()
}
suspend fun updateWishListItemInfo(wishListId: String, wishListItem: WishListItem): WishList {
wishListItemRepo.save(wishListItem).awaitSingle()
return getList(wishListId)
}
suspend fun addItemToWishList(wishListId: String, item: WishListItem): WishList {
val wishList = getList(wishListId)
val savedItem = wishListItemRepo.save(item).awaitSingle()
wishList.items.add(savedItem)
return wishListRepo.save(wishList).awaitSingle()
}
suspend fun removeItemFromWishList(wishListId: String, itemId: String): WishList {
val wishList = getList(wishListId)
return if (wishList.items.removeIf { it.id == itemId }) wishListRepo.save(wishList).awaitSingle() else wishList
}
suspend fun deleteWishList(wishListId: String) {
wishListRepo.deleteById(wishListId).awaitSingleOrNull()
}
suspend fun cancelReserve(wishListId: String, wishlistItemId: String): WishList {
val wishList = getList(wishListId)
val wishlistItem = wishList.items.first { it.id == wishlistItemId }
return if (wishlistItem.reservedBy != null) {
wishlistItem.reservedBy = null
wishListItemRepo.save(wishlistItem).awaitSingle()
wishList
} else wishList
}
}

View File

@@ -0,0 +1,62 @@
package space.luminic.budgerapp.services
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import org.bson.Document
import org.bson.types.ObjectId
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.aggregation.Aggregation.*
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.stereotype.Service
import space.luminic.budgerapp.mappers.WishListMapper
import space.luminic.budgerapp.models.NotFoundException
import space.luminic.budgerapp.models.Reserve
import space.luminic.budgerapp.models.WishList
import space.luminic.budgerapp.models.WishListItem
import space.luminic.budgerapp.repos.WishListItemRepo
import java.time.LocalDateTime
@Service
class WishlistExternalService(
private val reactiveMongoTemplate: ReactiveMongoTemplate,
private val wishListMapper: WishListMapper,
private val wishListItemRepo: WishListItemRepo
) {
suspend fun getWishListInfo(wishListId: String): WishList {
val lookupSpace = lookup("spaces", "space.\$id", "_id", "spaceDetails")
val unwindSpace = unwind("spaceDetails")
val lookupOwner = lookup("users", "owner.\$id", "_id", "ownerDetails")
val unwindOwner = unwind("ownerDetails")
val lookupItems = lookup("wishlistItems", "items.\$id", "_id", "itemsDetails")
val match = match(
Criteria.where("_id").`is`(ObjectId(wishListId))
)
val aggregation = newAggregation(lookupSpace, unwindSpace, lookupOwner, unwindOwner, lookupItems, match)
return reactiveMongoTemplate.aggregate(aggregation, "wishlists", Document::class.java)
.next().map {
wishListMapper.fromDocument(it)
}.awaitSingleOrNull() ?: throw NotFoundException("WishList with id $wishListId does not exist")
}
suspend fun reserveWishlistItem(wishListId: String, wishlistItemId: String, reservedBy: Reserve): WishListItem {
val wishlist = getWishListInfo(wishListId)
val wishlistItem = wishlist.items.first { wishlistItem -> wishlistItem.id == wishlistItemId }
return if (wishlistItem.reservedBy == null) {
wishlistItem.reservedBy = reservedBy
wishlistItem.updatedAt = LocalDateTime.now()
wishListItemRepo.save(wishlistItem).awaitSingle()
} else throw IllegalArgumentException("Wishlist item already reserved")
}
suspend fun cancelReserve(wishListId: String, wishlistItemId: String, reservedBy: Reserve): WishListItem {
val wishlist = getWishListInfo(wishListId)
val wishlistItem = wishlist.items.first { wishlistItem -> wishlistItem.id == wishlistItemId }
return if (wishlistItem.reservedBy?.aid == reservedBy.aid) {
wishlistItem.reservedBy = null
wishListItemRepo.save(wishlistItem).awaitSingle()
} else if (wishlistItem.reservedBy == null) {
wishlistItem
} else throw IllegalArgumentException("Вы не можете отменить не свою бронь")
}
}