From 416d818a1c28b63d2cb9d8810ee7b110bff6872b Mon Sep 17 00:00:00 2001 From: xds Date: Sun, 23 Feb 2025 12:24:34 +0300 Subject: [PATCH] some --- .../budgerapp/controllers/AuthController.kt | 2 +- .../budgerapp/controllers/SpaceController.kt | 10 +-- .../budgerapp/mappers/CategoryMapper.kt | 4 +- .../space/luminic/budgerapp/models/User.kt | 8 +- .../luminic/budgerapp/services/AuthService.kt | 4 +- .../budgerapp/services/CategoryService.kt | 54 +++++++----- .../budgerapp/services/FinancialService.kt | 57 ++++--------- .../budgerapp/services/SpaceService.kt | 85 ++++++++++++------- src/main/resources/persprof.json | 19 +++-- 9 files changed, 129 insertions(+), 114 deletions(-) diff --git a/src/main/kotlin/space/luminic/budgerapp/controllers/AuthController.kt b/src/main/kotlin/space/luminic/budgerapp/controllers/AuthController.kt index d3c04f0..5589c85 100644 --- a/src/main/kotlin/space/luminic/budgerapp/controllers/AuthController.kt +++ b/src/main/kotlin/space/luminic/budgerapp/controllers/AuthController.kt @@ -37,7 +37,7 @@ class AuthController( fun getMe(@RequestHeader("Authorization") token: String): Mono { return authService.isTokenValid(token.removePrefix("Bearer ")) .flatMap { username -> - userService.getByUserNameWoPass(username.username) + userService.getByUserNameWoPass(username.username!!) } } } diff --git a/src/main/kotlin/space/luminic/budgerapp/controllers/SpaceController.kt b/src/main/kotlin/space/luminic/budgerapp/controllers/SpaceController.kt index 865e8f0..29d5cea 100644 --- a/src/main/kotlin/space/luminic/budgerapp/controllers/SpaceController.kt +++ b/src/main/kotlin/space/luminic/budgerapp/controllers/SpaceController.kt @@ -5,10 +5,8 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.client.HttpClientErrorException -import reactor.core.publisher.Flux import reactor.core.publisher.Mono import space.luminic.budgerapp.controllers.BudgetController.LimitValue -import space.luminic.budgerapp.controllers.dtos.BudgetCreationDTO import space.luminic.budgerapp.models.* import space.luminic.budgerapp.services.CategoryService import space.luminic.budgerapp.services.FinancialService @@ -103,10 +101,10 @@ class SpaceController( @PostMapping("/{spaceId}/budgets") fun createBudget( @PathVariable spaceId: String, - @RequestBody budgetCreationDTO: BudgetCreationDTO, + @RequestBody budget: Budget ): Mono { return spaceService.isValidRequest(spaceId).flatMap { - financialService.createBudget(it, budgetCreationDTO.budget, budgetCreationDTO.createRecurrent) + financialService.createBudget(it, budget) } } @@ -304,7 +302,7 @@ class SpaceController( } } - @PostMapping("/{spaceId}/recurrent") + @PostMapping("/{spaceId}/recurrents") fun createRecurrent(@PathVariable spaceId: String, @RequestBody recurrent: Recurrent): Mono { return spaceService.isValidRequest(spaceId).flatMap { recurrentService.createRecurrent(it, recurrent) @@ -312,7 +310,7 @@ class SpaceController( } - @PutMapping("/{spaceId}/recurrent/{id}") + @PutMapping("/{spaceId}/recurrents/{id}") fun editRecurrent( @PathVariable spaceId: String, @PathVariable id: String, diff --git a/src/main/kotlin/space/luminic/budgerapp/mappers/CategoryMapper.kt b/src/main/kotlin/space/luminic/budgerapp/mappers/CategoryMapper.kt index 023bea4..6ae8be9 100644 --- a/src/main/kotlin/space/luminic/budgerapp/mappers/CategoryMapper.kt +++ b/src/main/kotlin/space/luminic/budgerapp/mappers/CategoryMapper.kt @@ -14,7 +14,7 @@ class CategoryMapper : FromDocumentMapper { override fun fromDocument(document: Document): Category { val categoryTypeDocument = document["type"] as Document val spaceDocument = document.get("spaceDetails", Document::class.java) ?: Document() - val spaceId = spaceDocument.get("_id", String::class.java)?:null + val spaceId = spaceDocument.get("_id", ObjectId::class.java)?:null val tags = document.getList("tags", Document::class.java) ?: emptyList() val categoryTags = tags.map { tag -> @@ -22,7 +22,7 @@ class CategoryMapper : FromDocumentMapper { }.toMutableSet() return Category( id = (document["_id"] as ObjectId).toString(), - space = Space(id = spaceId), + space = Space(id = spaceId.toString()), type = CategoryType( categoryTypeDocument["code"] as String, categoryTypeDocument["name"] as String diff --git a/src/main/kotlin/space/luminic/budgerapp/models/User.kt b/src/main/kotlin/space/luminic/budgerapp/models/User.kt index 6cf7a52..6bbd932 100644 --- a/src/main/kotlin/space/luminic/budgerapp/models/User.kt +++ b/src/main/kotlin/space/luminic/budgerapp/models/User.kt @@ -1,19 +1,17 @@ package space.luminic.budgerapp.models import com.fasterxml.jackson.annotation.JsonIgnore -import jakarta.validation.constraints.NotBlank import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document import java.time.LocalDate import java.time.LocalDateTime -import java.util.Date @Document("users") -data class User ( +data class User( @Id var id: String? = null, - @field:NotBlank val username: String, - var firstName: String, + val username: String? = null, + var firstName: String? = null, var tgId: String? = null, var tgUserName: String? = null, @JsonIgnore // Скрывает пароль при сериализации diff --git a/src/main/kotlin/space/luminic/budgerapp/services/AuthService.kt b/src/main/kotlin/space/luminic/budgerapp/services/AuthService.kt index 1ec26a5..fdbfe47 100644 --- a/src/main/kotlin/space/luminic/budgerapp/services/AuthService.kt +++ b/src/main/kotlin/space/luminic/budgerapp/services/AuthService.kt @@ -27,7 +27,7 @@ class AuthService( return userRepository.findByUsername(username) .flatMap { user -> if (passwordEncoder.matches(password, user.password)) { - val token = jwtUtil.generateToken(user.username) + val token = jwtUtil.generateToken(user.username!!) val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10) tokenService.saveToken( token = token, @@ -48,7 +48,7 @@ class AuthService( .switchIfEmpty(Mono.error(AuthException("Invalid credentials"))) .flatMap { user -> println("here") - val token = jwtUtil.generateToken(user.username) + val token = jwtUtil.generateToken(user.username!!) val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10) tokenService.saveToken( token = token, diff --git a/src/main/kotlin/space/luminic/budgerapp/services/CategoryService.kt b/src/main/kotlin/space/luminic/budgerapp/services/CategoryService.kt index 8877a32..a71e728 100644 --- a/src/main/kotlin/space/luminic/budgerapp/services/CategoryService.kt +++ b/src/main/kotlin/space/luminic/budgerapp/services/CategoryService.kt @@ -17,6 +17,7 @@ import reactor.core.publisher.Flux import reactor.core.publisher.Mono import space.luminic.budgerapp.mappers.CategoryMapper import space.luminic.budgerapp.models.* +import space.luminic.budgerapp.repos.BudgetRepo import space.luminic.budgerapp.repos.CategoryRepo @Service @@ -24,16 +25,14 @@ class CategoryService( private val categoryRepo: CategoryRepo, private val financialService: FinancialService, private val mongoTemplate: ReactiveMongoTemplate, - private val eventPublisher: ApplicationEventPublisher, private val categoryMapper: CategoryMapper, + private val budgetRepo: BudgetRepo, ) { private val logger = LoggerFactory.getLogger(javaClass) - fun getCategory(id: String): Mono { - return categoryRepo.findById(id) - } + fun findCategory( space: Space? = null, @@ -115,7 +114,7 @@ class CategoryService( @Cacheable("categoryTypes") fun getCategoryTypes(): List { - var types = mutableListOf() + val types = mutableListOf() types.add(CategoryType("EXPENSE", "Траты")) types.add(CategoryType("INCOME", "Поступления")) return types @@ -135,30 +134,41 @@ class CategoryService( } fun deleteCategory(space: Space, categoryId: String): Mono { - return findCategory(space, id = categoryId).switchIfEmpty( + return findCategory(space, categoryId).switchIfEmpty( Mono.error(IllegalArgumentException("Category with id: $categoryId not found")) - ).flatMap { + ).flatMap { categoryToDelete -> financialService.getTransactions(space.id!!, categoryId = categoryId) .flatMapMany { transactions -> - findCategory(space, name = "Другое").switchIfEmpty( - categoryRepo.save( - Category( - space = space, - type = CategoryType("EXPENSE", "Траты"), - name = "Другое", - description = "Категория для других трат", - icon = "🚮" + findCategory(space, name = "Другое") + .switchIfEmpty( + categoryRepo.save( + Category( + space = space, + type = CategoryType("EXPENSE", "Траты"), + name = "Другое", + description = "Категория для других трат", + icon = "🚮" + ) ) ) - ).flatMapMany { category -> - Flux.fromIterable(transactions).flatMap { transaction -> - transaction.category = category // Присваиваем конкретный объект категории - financialService.editTransaction(transaction) // Сохраняем изменения + .flatMapMany { otherCategory -> + Flux.fromIterable(transactions).flatMap { transaction -> + transaction.category = otherCategory + financialService.editTransaction(transaction) + } } - } } - .then(categoryRepo.deleteById(categoryId)) // Удаляем старую категорию - .thenReturn(categoryId) // Возвращаем удалённую категорию + .then( + financialService.findProjectedBudgets(ObjectId(space.id)) + .flatMapMany { budgets -> + Flux.fromIterable(budgets).flatMap { budget -> + budget.categories.removeIf { it.category.id == categoryId } + budgetRepo.save(budget) + } + }.collectList() + ) + .then(categoryRepo.deleteById(categoryId)) // Удаление категории + .thenReturn(categoryId) } } diff --git a/src/main/kotlin/space/luminic/budgerapp/services/FinancialService.kt b/src/main/kotlin/space/luminic/budgerapp/services/FinancialService.kt index db1184d..194a295 100644 --- a/src/main/kotlin/space/luminic/budgerapp/services/FinancialService.kt +++ b/src/main/kotlin/space/luminic/budgerapp/services/FinancialService.kt @@ -1,6 +1,5 @@ package space.luminic.budgerapp.services -import com.mongodb.DBRef import org.bson.BsonNull import org.bson.Document import org.bson.types.ObjectId @@ -12,9 +11,7 @@ import org.springframework.data.domain.Sort.Direction 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.aggregation.SortOperation import org.springframework.data.mongodb.core.query.Criteria -import org.springframework.data.mongodb.core.query.Query import org.springframework.security.core.context.ReactiveSecurityContextHolder import org.springframework.stereotype.Service import reactor.core.publisher.Flux @@ -36,7 +33,6 @@ class FinancialService( val budgetRepo: BudgetRepo, val warnRepo: WarnRepo, val transactionsRepo: TransactionRepo, - val recurrentService: RecurrentService, val userService: UserService, val reactiveMongoTemplate: ReactiveMongoTemplate, private val categoryRepo: CategoryRepo, @@ -211,7 +207,6 @@ class FinancialService( val unwindSpace = unwind("spaceDetails") val matchStage = match(Criteria.where("spaceDetails._id").`is`(spaceId)) // matchCriteria.add(Criteria.where("spaceDetails._id").`is`(ObjectId(spaceId))) - val projectStage = project("_id", "name", "dateFrom", "dateTo") // Оставляем только нужные поля val sort = sortRequested?.let { sort(it) } ?: sort( Sort.by(Direction.DESC, "date").and(Sort.by(Direction.DESC, "createdAt")) ) @@ -229,11 +224,9 @@ class FinancialService( spaceId: String, budgetId: String? = null, dateFrom: LocalDate? = null, dateTo: LocalDate? = null ): Mono { val lookupCategories = lookup("categories", "categories.category.\$id", "_id", "categoriesDetails") - val unwindCategories = unwind("categoriesDetails") val lookupIncomeCategories = lookup("categories", "incomeCategories.category.\$id", "_id", "incomeCategoriesDetails") - val unwindIncomeCategories = unwind("incomeCategoriesDetails") val lookupSpace = lookup("spaces", "space.\$id", "_id", "spaceDetails") val unwindSpace = unwind("spaceDetails") val matchCriteria = mutableListOf() @@ -354,7 +347,7 @@ class FinancialService( } - fun createBudget(space: Space, budget: Budget, createRecurrent: Boolean): Mono { + fun createBudget(space: Space, budget: Budget): Mono { return Mono.zip(getBudgetByDate(budget.dateFrom, space.id!!).map { Optional.ofNullable(it) } .switchIfEmpty(Mono.just(Optional.empty())), getBudgetByDate(budget.dateTo, space.id!!).map { Optional.ofNullable(it) } @@ -367,35 +360,22 @@ class FinancialService( return@flatMap Mono.error(IllegalArgumentException("Бюджет с теми же датами найден")) } - // Получаем Space по spaceId - - - // Присваиваем Space бюджету budget.space = space - // Если createRecurrent=true, создаем рекуррентные транзакции - val recurrentsCreation = if (createRecurrent) { - recurrentService.createRecurrentsForBudget(space, budget) - } else { - Mono.empty() - } - - // Создаем бюджет после возможного создания рекуррентных транзакций - recurrentsCreation.then( - getCategoryTransactionPipeline( - space.id!!, - budget.dateFrom, - budget.dateTo - ).flatMap { categories -> - budget.categories = categories - budgetRepo.save(budget) - }.publishOn(Schedulers.boundedElastic()).doOnNext { savedBudget -> - // Выполнение updateBudgetWarns в фоне - updateBudgetWarns(budget = savedBudget).doOnError { error -> - // Логируем ошибку, если произошла - logger.error("Error during updateBudgetWarns: ${error.message}") - }.subscribe() - }).then( + getCategoryTransactionPipeline( + space.id!!, + budget.dateFrom, + budget.dateTo + ).flatMap { categories -> + budget.categories = categories + budgetRepo.save(budget) + }.publishOn(Schedulers.boundedElastic()).doOnNext { savedBudget -> + // Выполнение updateBudgetWarns в фоне + updateBudgetWarns(budget = savedBudget).doOnError { error -> + // Логируем ошибку, если произошла + logger.error("Error during updateBudgetWarns: ${error.message}") + }.subscribe() + }.then( getCategoryTransactionPipeline( space.id!!, budget.dateFrom, @@ -636,7 +616,7 @@ class FinancialService( dateTo?.let { matchCriteria.add(Criteria.where("date").lt(it)) } transactionType?.let { matchCriteria.add(Criteria.where("type.code").`is`(it)) } isDone?.let { matchCriteria.add(Criteria.where("isDone").`is`(it)) } - categoryId?.let { matchCriteria.add(Criteria.where("categoryDetails._id").`is`(it)) } + categoryId?.let { matchCriteria.add(Criteria.where("categoryDetails._id").`is`(ObjectId(it))) } categoryType?.let { matchCriteria.add( Criteria.where("categoryDetails.type.code").`is`(it) @@ -1144,7 +1124,6 @@ class FinancialService( private fun documentToTransactionMapper(document: Document): Transaction { val transactionType = document["type"] as Document - var user: User? val userDocument = document["userDetailed"] as Document user = User( @@ -1345,7 +1324,7 @@ class FinancialService( fun getCategoryTransactionPipeline( - spaceId: String, dateFrom: LocalDate, dateTo: LocalDate, catType: String? = "EXPENSE" + spaceId: String, dateFrom: LocalDate, dateTo: LocalDate, categoryType: String? = "EXPENSE" ): Mono> { val pipeline = listOf( Document( @@ -1358,7 +1337,7 @@ class FinancialService( "\$match", Document( Document("spaceDetailed._id", ObjectId(spaceId)) ) - ), Document("\$match", Document("type.code", catType)), Document( + ), Document("\$match", Document("type.code", categoryType)), Document( "\$lookup", Document("from", "transactions").append( "let", Document("categoryId", "\$_id") ).append( diff --git a/src/main/kotlin/space/luminic/budgerapp/services/SpaceService.kt b/src/main/kotlin/space/luminic/budgerapp/services/SpaceService.kt index a826701..dca941b 100644 --- a/src/main/kotlin/space/luminic/budgerapp/services/SpaceService.kt +++ b/src/main/kotlin/space/luminic/budgerapp/services/SpaceService.kt @@ -10,10 +10,7 @@ import org.springframework.security.core.context.ReactiveSecurityContextHolder import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Mono -import space.luminic.budgerapp.models.Category -import space.luminic.budgerapp.models.Space -import space.luminic.budgerapp.models.SpaceInvite -import space.luminic.budgerapp.models.Tag +import space.luminic.budgerapp.models.* import space.luminic.budgerapp.repos.* import java.time.LocalDateTime import java.util.UUID @@ -23,7 +20,6 @@ class SpaceService( private val spaceRepo: SpaceRepo, private val userService: UserService, private val budgetRepo: BudgetRepo, - private val userRepo: UserRepo, private val reactiveMongoTemplate: ReactiveMongoTemplate, private val categoryRepo: CategoryRepo, private val recurrentRepo: RecurrentRepo, @@ -44,7 +40,7 @@ class SpaceService( .switchIfEmpty(Mono.error(IllegalArgumentException("User not found for username: $username"))) .flatMap { user -> // Получаем пространство по ID - spaceRepo.findById(spaceId) + getSpace(spaceId) .switchIfEmpty(Mono.error(IllegalArgumentException("Space not found for id: $spaceId"))) .flatMap { space -> // Проверяем доступ пользователя к пространству @@ -65,10 +61,51 @@ class SpaceService( val username = authentication.name userService.getByUsername(username) .switchIfEmpty(Mono.error(IllegalArgumentException("User not found for username: $username"))) - .flatMapMany { user -> - spaceRepo.findByArrayElement(ObjectId(user.id!!)) + .flatMap { user -> + val userId = ObjectId(user.id!!) + + // Поиск пространств пользователя + + + // Агрегация для загрузки владельца и пользователей + val lookupOwner = lookup("users", "owner.\$id", "_id", "ownerDetails") + val unwindOwner = unwind("ownerDetails") + + val lookupUsers = lookup("users", "users.\$id", "_id", "usersDetails") +// val unwindUsers = unwind("usersDetails") + + val matchStage = match(Criteria.where("usersDetails._id").`is`(userId)) + val aggregation = newAggregation(lookupOwner, unwindOwner, lookupUsers, matchStage) + + reactiveMongoTemplate.aggregate(aggregation, "spaces", Document::class.java) + .collectList() + .map { docs -> + docs.map { doc -> + val ownerDoc = doc.get("ownerDetails", Document::class.java) + val usersDocList = doc.getList("usersDetails", Document::class.java) + + Space( + id = doc.getObjectId("_id").toString(), + name = doc.getString("name"), + description = doc.getString("description"), + owner = User( + id = ownerDoc.getObjectId("_id").toString(), + username = ownerDoc.getString("username"), + firstName = ownerDoc.getString("firstName") + ), + users = usersDocList.map { userDoc -> + User( + id = userDoc.getObjectId("_id").toString(), + username = userDoc.getString("username"), + firstName = userDoc.getString("firstName") + ) + }.toMutableList() + ) + } + } + + } - .collectList() // Возвращаем Mono> } } @@ -94,12 +131,10 @@ class SpaceService( reactiveMongoTemplate.find(Query(), Category::class.java, "categories-etalon") .map { category -> - category.copy( - id = null, - space = savedSpace - ) // Создаем новую копию без id (чтобы создать новый документ) + category.copy(id = null, space = savedSpace) // Создаем новую копию } - .flatMap { categoryRepo.save(it) } + .collectList() // Собираем в список перед сохранением + .flatMap { categoryRepo.saveAll(it).collectList() } // Сохраняем и возвращаем список .then(Mono.just(savedSpace)) // После сохранения всех категорий, возвращаем пространство } } @@ -111,25 +146,17 @@ class SpaceService( return Mono.`when`( financialService.findProjectedBudgets(objectId) - .flatMapMany { Flux.fromIterable(it) } - .flatMap { budgetRepo.deleteById(it.id!!) } - .then(), + .flatMap { budgetRepo.deleteAll(it) }, financialService.getTransactions(objectId.toString()) - .flatMapMany { Flux.fromIterable(it) } - .flatMap { transactionRepo.deleteById(it.id!!) } - .then(), + .flatMap { transactionRepo.deleteAll(it) }, categoryService.getCategories(objectId.toString(), null, "name", "ASC") - .flatMapMany { Flux.fromIterable(it) } - .flatMap { categoryRepo.deleteById(it.id!!) } - .then(), + .flatMap { categoryRepo.deleteAll(it) }, recurrentService.getRecurrents(objectId.toString()) - .flatMapMany { Flux.fromIterable(it) } - .flatMap { recurrentRepo.deleteById(it.id!!) } - .then() - ).then(spaceRepo.deleteById(space.id!!)) // Исправлено: удаление по ID + .flatMap { recurrentRepo.deleteAll(it) } + ).then(spaceRepo.deleteById(space.id!!)) // Удаление Space после завершения всех операций } @@ -331,7 +358,7 @@ class SpaceService( return reactiveMongoTemplate.aggregate( aggregation, "tags", Document::class.java ) - .collectList() // Преобразуем Flux в Mono> + .collectList() .map { docs -> docs.map { doc -> Tag( @@ -347,7 +374,7 @@ class SpaceService( fun regenSpaceCategory(): Mono { return getSpace("67af3c0f652da946a7dd9931") .flatMap { space -> - categoryService.findCategory(id= "677bc767c7857460a491bd4f") + categoryService.findCategory(id = "677bc767c7857460a491bd4f") .flatMap { category -> // заменил map на flatMap category.space = space category.name = "Сбережения" diff --git a/src/main/resources/persprof.json b/src/main/resources/persprof.json index 425c40f..169ca0b 100644 --- a/src/main/resources/persprof.json +++ b/src/main/resources/persprof.json @@ -7,6 +7,9 @@ "as": "categoryDetails" } }, + { + "$unwind": "$categoryDetails" + }, { "$lookup": { "from": "spaces", @@ -15,6 +18,9 @@ "as": "spaceDetails" } }, + { + "$unwind": "$spaceDetails" + }, { "$lookup": { "from": "users", @@ -23,16 +29,19 @@ "as": "userDetails" } }, + { + "$unwind": "$userDetails" + }, { "$match": { "$and": [ { "spaceDetails._id": { - "$oid": "67af52e8b0aa7b0f7f74b491" + "$oid": "67af3c0f652da946a7dd9931" } }, { - "type.code": "INSTANT" + "categoryDetails._id": "67b83f3c1fc0575a3f0a3847" } ] } @@ -42,11 +51,5 @@ "date": -1, "createdAt": -1 } - }, - { - "$skip": 0 - }, - { - "$limit": 10 } ] \ No newline at end of file