From 3b9f0e566ccdeef976d7af771a7f4b36633c3fd7 Mon Sep 17 00:00:00 2001 From: xds Date: Mon, 3 Mar 2025 10:38:07 +0300 Subject: [PATCH] wishlists + statics + some fixes --- .../luminic/budgerapp/BudgerAppApplication.kt | 2 +- .../budgerapp/configs/BearerTokenFilter.kt | 9 +- .../budgerapp/configs/SecurityConfig.kt | 3 +- .../budgerapp/configs/StorageConfig.kt | 18 +++ .../budgerapp/controllers/ImageController.kt | 64 +++++++++ .../controllers/WishListController.kt | 90 ++++++++++++ .../controllers/WishlistExternalController.kt | 35 +++++ .../budgerapp/mappers/TransactionsMapper.kt | 2 - .../budgerapp/mappers/WishListMapper.kt | 46 ++++++ .../luminic/budgerapp/models/WishList.kt | 39 ++++++ .../luminic/budgerapp/repos/WishListRepo.kt | 13 ++ .../budgerapp/services/FinancialService.kt | 30 +++- .../budgerapp/services/StaticService.kt | 29 ++++ .../budgerapp/services/WishListService.kt | 132 ++++++++++++++++++ .../services/WishlistExternalService.kt | 62 ++++++++ src/main/resources/application.properties | 6 + 16 files changed, 566 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/space/luminic/budgerapp/configs/StorageConfig.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/controllers/ImageController.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/controllers/WishListController.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/controllers/WishlistExternalController.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/mappers/WishListMapper.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/models/WishList.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/repos/WishListRepo.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/services/StaticService.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/services/WishListService.kt create mode 100644 src/main/kotlin/space/luminic/budgerapp/services/WishlistExternalService.kt diff --git a/src/main/kotlin/space/luminic/budgerapp/BudgerAppApplication.kt b/src/main/kotlin/space/luminic/budgerapp/BudgerAppApplication.kt index 2b8d7c1..1c0cfbf 100644 --- a/src/main/kotlin/space/luminic/budgerapp/BudgerAppApplication.kt +++ b/src/main/kotlin/space/luminic/budgerapp/BudgerAppApplication.kt @@ -9,7 +9,7 @@ import org.springframework.scheduling.annotation.EnableAsync import org.springframework.scheduling.annotation.EnableScheduling import java.util.TimeZone -@SpringBootApplication +@SpringBootApplication(scanBasePackages = ["space.luminic.budgerapp"]) @EnableCaching @EnableAsync @EnableScheduling diff --git a/src/main/kotlin/space/luminic/budgerapp/configs/BearerTokenFilter.kt b/src/main/kotlin/space/luminic/budgerapp/configs/BearerTokenFilter.kt index 196b0fa..4524690 100644 --- a/src/main/kotlin/space/luminic/budgerapp/configs/BearerTokenFilter.kt +++ b/src/main/kotlin/space/luminic/budgerapp/configs/BearerTokenFilter.kt @@ -1,7 +1,6 @@ package space.luminic.budgerapp.configs import kotlinx.coroutines.reactor.mono -import org.slf4j.LoggerFactory import org.springframework.http.HttpHeaders import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.authority.SimpleGrantedAuthority @@ -16,8 +15,7 @@ import space.luminic.budgerapp.services.AuthService @Component class BearerTokenFilter(private val authService: AuthService) : SecurityContextServerWebExchangeWebFilter() { - private val logger = LoggerFactory.getLogger(BearerTokenFilter::class.java) - +// private val logger = LoggerFactory.getLogger(BearerTokenFilter::class.java) override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { @@ -27,7 +25,10 @@ class BearerTokenFilter(private val authService: AuthService) : SecurityContextS "/api/auth/login", "/api/auth/register", "/api/auth/tgLogin" - ) || exchange.request.path.value().startsWith("/api/actuator") + ) || exchange.request.path.value().startsWith("/api/actuator") || exchange.request.path.value() + .startsWith("/api/static/") + || exchange.request.path.value() + .startsWith("/api/wishlistexternal/") ) { return chain.filter(exchange) } diff --git a/src/main/kotlin/space/luminic/budgerapp/configs/SecurityConfig.kt b/src/main/kotlin/space/luminic/budgerapp/configs/SecurityConfig.kt index d7b0f55..d4dd76f 100644 --- a/src/main/kotlin/space/luminic/budgerapp/configs/SecurityConfig.kt +++ b/src/main/kotlin/space/luminic/budgerapp/configs/SecurityConfig.kt @@ -26,7 +26,8 @@ class SecurityConfig( .logout { it.disable() } .authorizeExchange { it.pathMatchers(HttpMethod.POST, "/auth/login", "/auth/register", "/auth/tgLogin").permitAll() - it.pathMatchers("/actuator/**").permitAll() + it.pathMatchers("/actuator/**", "/static/**").permitAll() + it.pathMatchers("/wishlistexternal/**").permitAll() it.anyExchange().authenticated() } .addFilterAt( diff --git a/src/main/kotlin/space/luminic/budgerapp/configs/StorageConfig.kt b/src/main/kotlin/space/luminic/budgerapp/configs/StorageConfig.kt new file mode 100644 index 0000000..bfc63b1 --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/configs/StorageConfig.kt @@ -0,0 +1,18 @@ +package space.luminic.budgerapp.configs + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Configuration +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + + +@Configuration +class StorageConfig(@Value("\${storage.location}") location: String) { + + val rootLocation: Path = Paths.get(location) + + init { + Files.createDirectories(rootLocation) // Создаем папку, если её нет + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/budgerapp/controllers/ImageController.kt b/src/main/kotlin/space/luminic/budgerapp/controllers/ImageController.kt new file mode 100644 index 0000000..ab94cec --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/controllers/ImageController.kt @@ -0,0 +1,64 @@ +package space.luminic.budgerapp.controllers + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.springframework.core.io.PathResource +import org.springframework.core.io.Resource +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.http.codec.multipart.FilePart +import org.springframework.web.bind.annotation.* +import space.luminic.budgerapp.configs.StorageConfig +import space.luminic.budgerapp.models.NotFoundException +import space.luminic.budgerapp.services.StaticService +import java.nio.file.Files +import java.nio.file.Paths + +@RestController +@RequestMapping("/static/{spaceId}/wishlists/{wishListItemId}") +class ImageController(private val staticService: StaticService, private val storageConfig: StorageConfig) { + + + @GetMapping("/{resourceId}") + suspend fun downloadFile( + @PathVariable spaceId: String, + @PathVariable wishListItemId: String, + @PathVariable resourceId: String + ): ResponseEntity { + return withContext(Dispatchers.IO) { +// val filePath = staticService.generatePathToFile(spaceId, wishListItemId, resourceId) + val filePath = + Paths.get(storageConfig.rootLocation.toString(), spaceId, "wishlists", wishListItemId, resourceId) + if (!Files.exists(filePath) || !Files.isReadable(filePath)) { + throw NotFoundException("File $filePath not found") + } + + val resource = PathResource(filePath) + val contentType = Files.probeContentType(filePath) ?: "application/octet-stream" + + ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .body(resource) + } + } + + @PostMapping + suspend fun addImage( + @PathVariable spaceId: String, + @PathVariable wishListItemId: String, + @RequestPart("file") file: FilePart, + @RequestHeader("Content-Length") contentLength: Long, + ): ResponseEntity { + val maxSize = 5L * 1024 * 1024 // 5MB + + + println(file.headers().contentType) + if (contentLength > maxSize) { + return ResponseEntity + .status(HttpStatus.PAYLOAD_TOO_LARGE) + .body("Размер файла превышает 5MB") + } + return ResponseEntity.ok(staticService.saveFile(spaceId, wishListItemId, file)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/budgerapp/controllers/WishListController.kt b/src/main/kotlin/space/luminic/budgerapp/controllers/WishListController.kt new file mode 100644 index 0000000..693ccca --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/controllers/WishListController.kt @@ -0,0 +1,90 @@ +package space.luminic.budgerapp.controllers + +import org.springframework.web.bind.annotation.* +import space.luminic.budgerapp.models.WishList +import space.luminic.budgerapp.models.WishListItem +import space.luminic.budgerapp.services.SpaceService +import space.luminic.budgerapp.services.WishListService + + +@RestController +@RequestMapping("/spaces/{spaceId}/wishlists") +class WishListController( + private val wishListService: WishListService, + private val spaceService: SpaceService +) { + + @GetMapping + suspend fun findWishList(@PathVariable spaceId: String): List { + spaceService.isValidRequest(spaceId) + return wishListService.findWishLists(spaceId) + } + + @GetMapping("/{wishListId}") + suspend fun getWishList(@PathVariable spaceId: String, @PathVariable wishListId: String): WishList { + spaceService.isValidRequest(spaceId) + return wishListService.getList(wishListId) + } + + @PostMapping + suspend fun createWishList(@PathVariable spaceId: String, @RequestBody wishList: WishList): WishList { + val space = spaceService.isValidRequest(spaceId) + return wishListService.createWishList(space, wishList) + } + + @PatchMapping("/{wishListId}") + suspend fun updateWishList( + @PathVariable spaceId: String, + @PathVariable wishListId: String, + @RequestBody wishList: WishList + ): WishList { + spaceService.isValidRequest(spaceId) + return wishListService.updateWishListInfo(wishList) + } + + @PatchMapping("/{wishListId}/items/{itemId}") + suspend fun updateWishListItem( + @PathVariable spaceId: String, + @PathVariable wishListId: String, + @PathVariable itemId: String, + @RequestBody wishListItem: WishListItem + ): WishList { + spaceService.isValidRequest(spaceId) + return wishListService.updateWishListItemInfo(wishListId, wishListItem) + } + + @PostMapping("/{wishListId}/items") + suspend fun addItemToWishList( + @PathVariable spaceId: String, + @PathVariable wishListId: String, + @RequestBody wishListItem: WishListItem + ): WishList { + spaceService.isValidRequest(spaceId) + return wishListService.addItemToWishList(wishListId, wishListItem) + } + + @DeleteMapping("/{wishListId}/items/{wishListItemId}") + suspend fun removeItemFromWishList( + @PathVariable spaceId: String, + @PathVariable wishListId: String, + @PathVariable wishListItemId: String + ): WishList { + spaceService.isValidRequest(spaceId) + return wishListService.removeItemFromWishList(wishListId, wishListItemId) + } + + @DeleteMapping("/{wishListId}") + suspend fun deleteWishList(@PathVariable spaceId: String, @PathVariable wishListId: String) { + spaceService.isValidRequest(spaceId) + wishListService.deleteWishList(wishListId) + } + + @PostMapping("/{wishListId}/{wishlistItemId}/reserve/_cancel") + suspend fun cancelReserve( + @PathVariable spaceId: String, @PathVariable wishListId: String, + @PathVariable wishlistItemId: String + ) : WishList { + spaceService.isValidRequest(spaceId) + return wishListService.cancelReserve(wishListId, wishlistItemId) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/budgerapp/controllers/WishlistExternalController.kt b/src/main/kotlin/space/luminic/budgerapp/controllers/WishlistExternalController.kt new file mode 100644 index 0000000..79a2f73 --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/controllers/WishlistExternalController.kt @@ -0,0 +1,35 @@ +package space.luminic.budgerapp.controllers + +import org.springframework.web.bind.annotation.* +import space.luminic.budgerapp.models.Reserve +import space.luminic.budgerapp.models.WishList +import space.luminic.budgerapp.models.WishListItem +import space.luminic.budgerapp.services.WishlistExternalService + +@RestController +@RequestMapping("/wishlistexternal/{wishListId}") +class WishlistExternalController(private val wishlistExternalService: WishlistExternalService) { + + @GetMapping + suspend fun getWishListInfo(@PathVariable wishListId: String): WishList { + return wishlistExternalService.getWishListInfo(wishListId) + } + + @PostMapping("/{wishlistItemId}/reserve/_create") + suspend fun reserveItem( + @PathVariable wishListId: String, + @PathVariable wishlistItemId: String, + @RequestBody reservedBy: Reserve + ): WishListItem { + return wishlistExternalService.reserveWishlistItem(wishListId, wishlistItemId, reservedBy) + } + + @PostMapping("/{wishlistItemId}/reserve/_cancel") + suspend fun cancelReserve( + @PathVariable wishListId: String, + @PathVariable wishlistItemId: String, + @RequestBody reservedBy: Reserve + ): WishListItem { + return wishlistExternalService.cancelReserve(wishListId, wishlistItemId, reservedBy) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/budgerapp/mappers/TransactionsMapper.kt b/src/main/kotlin/space/luminic/budgerapp/mappers/TransactionsMapper.kt index 2961dc5..d1997f7 100644 --- a/src/main/kotlin/space/luminic/budgerapp/mappers/TransactionsMapper.kt +++ b/src/main/kotlin/space/luminic/budgerapp/mappers/TransactionsMapper.kt @@ -8,8 +8,6 @@ import java.time.ZoneId @Component class TransactionsMapper : FromDocumentMapper { - - override fun fromDocument(document: Document): Transaction { val categoryDocument = document.get("categoryDetails", Document::class.java) val categoryTypeDocument = categoryDocument["type"] as Document diff --git a/src/main/kotlin/space/luminic/budgerapp/mappers/WishListMapper.kt b/src/main/kotlin/space/luminic/budgerapp/mappers/WishListMapper.kt new file mode 100644 index 0000000..4be62b9 --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/mappers/WishListMapper.kt @@ -0,0 +1,46 @@ +package space.luminic.budgerapp.mappers + +import org.bson.Document +import org.bson.types.ObjectId +import org.springframework.stereotype.Component +import space.luminic.budgerapp.models.* +import java.time.ZoneId + +@Component +class WishListMapper : FromDocumentMapper { + override fun fromDocument(document: Document): WishList { + val spaceDoc = document.get("spaceDetails", Document::class.java) + val ownerDoc = document.get("ownerDetails", Document::class.java) + val itemsDocList = document.getList("itemsDetails", Document::class.java).orEmpty() + return WishList( + id = document.get("_id", ObjectId::class.java).toString(), + name = document.get("name", String::class.java), + description = document.get("description", String::class.java), + space = Space(id = spaceDoc.getObjectId("_id").toString()), + isPrivate = document.getBoolean("isPrivate"), + owner = User( + ownerDoc.getObjectId("_id").toString(), + firstName = ownerDoc.getString("firstName").toString() + ), + items = itemsDocList.map { + val reserveDoc = it.get("reservedBy", Document::class.java) + WishListItem( + id = it.getObjectId("_id").toString(), + name = it.getString("name"), + description = it.getString("description"), + price = it.getDouble("price"), + link = it.getString("link"), + images = it.getList("images", String::class.java), + reservedBy = if (reserveDoc != null) Reserve( + reserveDoc.getString("aid"), + reserveDoc.getString("name") + ) else null, + updatedAt = it.getDate("updatedAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(), + createdAt = it.getDate("createdAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() + ) + }.toMutableList(), + updatedAt = document.getDate("updatedAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(), + createdAt = document.getDate("createdAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(), + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/budgerapp/models/WishList.kt b/src/main/kotlin/space/luminic/budgerapp/models/WishList.kt new file mode 100644 index 0000000..3cf7bf0 --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/models/WishList.kt @@ -0,0 +1,39 @@ +package space.luminic.budgerapp.models + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.DBRef +import org.springframework.data.mongodb.core.mapping.Document +import java.time.LocalDateTime + +@Document(collection = "wishlists") +data class WishList( + @Id val id: String? = null, + @DBRef var space: Space? = null, + var name: String, + var description: String, + var isPrivate: Boolean, + @DBRef var owner: User? = null, + @DBRef var items: MutableList = mutableListOf(), + var updatedAt: LocalDateTime = LocalDateTime.now(), + val createdAt: LocalDateTime = LocalDateTime.now() +) + + +@Document(collection = "wishlistItems") +data class WishListItem( + @Id val id: String? = null, + var name: String, + var description: String, + var price: Double, + var link: String, + var images: MutableList = mutableListOf(), + var reservedBy: Reserve? = null, + var updatedAt: LocalDateTime = LocalDateTime.now(), + var createdAt: LocalDateTime = LocalDateTime.now() +) + +data class Reserve( + val aid: String, + val name: String? = null, + + ) diff --git a/src/main/kotlin/space/luminic/budgerapp/repos/WishListRepo.kt b/src/main/kotlin/space/luminic/budgerapp/repos/WishListRepo.kt new file mode 100644 index 0000000..a6fab5b --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/repos/WishListRepo.kt @@ -0,0 +1,13 @@ +package space.luminic.budgerapp.repos + +import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import org.springframework.stereotype.Repository +import space.luminic.budgerapp.models.WishList +import space.luminic.budgerapp.models.WishListItem + +@Repository +interface WishListRepo : ReactiveMongoRepository { +} + +@Repository +interface WishListItemRepo : ReactiveMongoRepository {} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/budgerapp/services/FinancialService.kt b/src/main/kotlin/space/luminic/budgerapp/services/FinancialService.kt index c9222fc..9e17599 100644 --- a/src/main/kotlin/space/luminic/budgerapp/services/FinancialService.kt +++ b/src/main/kotlin/space/luminic/budgerapp/services/FinancialService.kt @@ -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) } } diff --git a/src/main/kotlin/space/luminic/budgerapp/services/StaticService.kt b/src/main/kotlin/space/luminic/budgerapp/services/StaticService.kt new file mode 100644 index 0000000..c2aa722 --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/services/StaticService.kt @@ -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() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/budgerapp/services/WishListService.kt b/src/main/kotlin/space/luminic/budgerapp/services/WishListService.kt new file mode 100644 index 0000000..fbcfff4 --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/services/WishListService.kt @@ -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 { + val aggregationOperation = mutableListOf() + 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 { + 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 + } + +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/budgerapp/services/WishlistExternalService.kt b/src/main/kotlin/space/luminic/budgerapp/services/WishlistExternalService.kt new file mode 100644 index 0000000..1c01941 --- /dev/null +++ b/src/main/kotlin/space/luminic/budgerapp/services/WishlistExternalService.kt @@ -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("Вы не можете отменить не свою бронь") + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d87391a..7fceb7f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,6 +20,12 @@ logging.level.org.mongodb.driver.protocol.command = INFO server.compression.enabled=true server.compression.mime-types=application/json +# ???????????? ?????? ????? (?? ????????? 1 ??) +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB + +storage.location: static + # Expose prometheus, health, and info endpoints #management.endpoints.web.exposure.include=prometheus,health,info