This commit is contained in:
Vladimir Voronin
2025-01-07 13:15:08 +03:00
parent 2506e6081f
commit 8b440ad9e8
45 changed files with 51 additions and 1048 deletions

View File

@@ -1,20 +0,0 @@
package space.luminic.budgerapp.configs
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
import java.util.concurrent.Executor
//
//@Configuration
//class AsyncConfig {
// @Bean(name = ["taskExecutor"])
// fun taskExecutor(): Executor {
// val executor = ThreadPoolTaskExecutor()
// executor.corePoolSize = 5
// executor.maxPoolSize = 10
// executor.setQueueCapacity(25)
// executor.setThreadNamePrefix("Async-")
// executor.initialize()
// return executor
// }
//}

View File

@@ -1,18 +0,0 @@
package space.luminic.budgerapp.configs
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.config.CorsRegistry
import org.springframework.web.reactive.config.WebFluxConfigurer
@Configuration
class CorsConfig : WebFluxConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**") // Разрешить все пути
.allowedOrigins("http://localhost:5173") // Разрешить домен localhost:5173
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") // Разрешить методы
.allowedHeaders("*") // Разрешить все заголовки
.allowCredentials(true) // Разрешить передачу cookies
}
}

View File

@@ -1,44 +0,0 @@
//package space.luminic.budgerapp.configs
//import org.springframework.http.HttpHeaders
//import org.springframework.http.HttpStatus
//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
//import org.springframework.security.core.context.SecurityContextHolder
//import org.springframework.stereotype.Component
//import org.springframework.web.server.ServerWebExchange
//import org.springframework.web.server.WebFilter
//import org.springframework.web.server.WebFilterChain
//import reactor.core.publisher.Mono
//import space.luminic.budgerapp.services.AuthService
//import space.luminic.budgerapp.services.TokenService
//import space.luminic.budgerapp.utils.JWTUtil
//
//@Component
//class JWTAuthFilter(
// private val jwtUtil: JWTUtil,
// private val authService: AuthService
//) : WebFilter {
//
// override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
// val authHeader = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)
//
// if (authHeader != null && authHeader.startsWith("Bearer ")) {
// val token = authHeader.substring(7)
// return Mono.just(token)
// .filter { authService.isTokenValid(it) }
// .flatMap { validToken ->
// val username = jwtUtil.validateToken(validToken)
// if (username != null) {
// val auth = UsernamePasswordAuthenticationToken(username, null, emptyList())
// SecurityContextHolder.getContext().authentication = auth
// }
// chain.filter(exchange)
// }
// .onErrorResume {
// exchange.response.statusCode = HttpStatus.UNAUTHORIZED
// exchange.response.setComplete()
// }
// }
//
// return chain.filter(exchange)
// }
//}

View File

@@ -1,33 +0,0 @@
//package space.luminic.budgerapp.configs
//
//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
//import org.springframework.security.authentication.ReactiveAuthenticationManager
//import org.springframework.security.core.Authentication
//import org.springframework.security.core.userdetails.ReactiveUserDetailsService
//import org.springframework.security.crypto.password.PasswordEncoder
//import org.springframework.stereotype.Component
//import reactor.core.publisher.Mono
//import space.luminic.budgerapp.services.CustomReactiveUserDetailsService
//import space.luminic.budgerapp.services.TokenService
//import space.luminic.budgerapp.utils.JWTUtil
//
//@Component
//class JWTReactiveAuthenticationManager(
// private val passwordEncoder: PasswordEncoder,
// private val userDetailsService: CustomReactiveUserDetailsService
//) : ReactiveAuthenticationManager {
//
//
// override fun authenticate(authentication: Authentication): Mono<Authentication> {
// val username = authentication.principal as String
// val password = authentication.credentials as String
//
// return userDetailsService.findByUsername(username)
// .filter { userDetails -> password == passwordEncoder.encode(userDetails.password) } // Пример проверки пароля
// .map { userDetails ->
// UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities)
// }
// }
//
//
//}

View File

@@ -1,5 +1,6 @@
package space.luminic.budgerapp.configs
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
@@ -8,20 +9,15 @@ import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.server.SecurityWebFilterChain
import space.luminic.budgerapp.controllers.CustomAuthenticationEntryPoint
import space.luminic.budgerapp.services.AuthService
@Configuration
class SecurityConfig(
private val authService: AuthService
) {
@Bean
fun securityWebFilterChain(
http: ServerHttpSecurity,
bearerTokenFilter: BearerTokenFilter,
customAuthenticationEntryPoint: CustomAuthenticationEntryPoint
bearerTokenFilter: BearerTokenFilter
): SecurityWebFilterChain {
return http
.csrf { it.disable() }

View File

@@ -1,22 +1,17 @@
package space.luminic.budgerapp.controllers
import org.springframework.http.ResponseEntity
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Mono
//import space.luminic.budgerapp.configs.JWTReactiveAuthenticationManager
import space.luminic.budgerapp.models.User
import space.luminic.budgerapp.services.AuthService
import space.luminic.budgerapp.services.UserService
import space.luminic.budgerapp.utils.JWTUtil
@RestController
@RequestMapping("/auth")
class AuthController(
// private val authenticationManager: JWTReactiveAuthenticationManager,
private val jwtUtil: JWTUtil,
private val userService: UserService,
private val authService: AuthService
) {
@@ -34,11 +29,6 @@ class AuthController(
.flatMap { username ->
userService.getByUserNameWoPass(username.username)
}
// return mapOf(
// "username" to authentication.name,
// "details" to authentication.details,
// "roles" to authentication.authorities.map { it.authority }
// )
}
}

View File

@@ -1,14 +1,12 @@
package space.luminic.budgerapp.controllers
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@@ -18,11 +16,7 @@ import space.luminic.budgerapp.models.Budget
import space.luminic.budgerapp.models.BudgetDTO
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.models.Warn
import space.luminic.budgerapp.services.BudgetService
import space.luminic.budgerapp.services.CacheInspector
import java.math.BigDecimal
import java.sql.Date
import java.time.LocalDate
@@ -41,7 +35,7 @@ class BudgetController(
@GetMapping("/{id}")
fun getBudget(@PathVariable id: String): Mono<BudgetDTO> {
// logger.info(cacheInspector.getCacheContent("budgets").toString())
return budgetService.getBudget(id)
}
@@ -88,11 +82,7 @@ class BudgetController(
}
}
@GetMapping("/recalc-categories")
fun recalcCategories(): Mono<Void> {
return budgetService.recalcBudgetCategory()
}
@GetMapping("/{id}/warns")
fun budgetWarns(@PathVariable id: String, @RequestParam hidden: Boolean? = null): Mono<List<Warn>> {
@@ -101,7 +91,7 @@ class BudgetController(
@PostMapping("/{id}/warns/{warnId}/hide")
fun setWarnHide(@PathVariable id: String, @PathVariable warnId: String): Mono<Warn> {
return budgetService.hideWarn(id, warnId)
return budgetService.hideWarn( warnId)
}

View File

@@ -27,12 +27,8 @@ class CategoriesController(
private val logger = LoggerFactory.getLogger(javaClass)
@GetMapping()
fun getCategories(): ResponseEntity<Any> {
return try {
ResponseEntity.ok(categoryService.getCategories())
} catch (e: Exception) {
ResponseEntity(HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR), HttpStatus.INTERNAL_SERVER_ERROR)
}
fun getCategories(): Mono<List<Category>> {
return categoryService.getCategories()
}

View File

@@ -1,34 +0,0 @@
package space.luminic.budgerapp.controllers
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import java.util.*
@Component
class CustomAuthenticationEntryPoint : ServerAuthenticationEntryPoint {
override fun commence(
exchange: ServerWebExchange,
ex: AuthenticationException
): Mono<Void> {
val response = exchange.response
response.statusCode = HttpStatus.UNAUTHORIZED
response.headers.contentType = MediaType.APPLICATION_JSON
val body = mapOf(
"timestamp" to Date(),
"status" to HttpStatus.UNAUTHORIZED.value(),
"error" to "Unauthorized",
"message" to ex.message,
"path" to exchange.request.path.value()
)
val buffer = response.bufferFactory().wrap(ObjectMapper().writeValueAsBytes(body))
return response.writeWith(Mono.just(buffer))
}
}

View File

@@ -3,11 +3,8 @@ package space.luminic.budgerapp.controllers
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.security.core.AuthenticationException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import space.luminic.budgerapp.configs.AuthException
@@ -77,7 +74,7 @@ class GlobalExceptionHandler {
exchange: ServerWebExchange
): Mono<ResponseEntity<Map<String, Any?>>?> {
e.printStackTrace()
val errorBody = mapOf("error" to e.message.orEmpty())
return Mono.just(
ResponseEntity(
constructErrorBody(
@@ -96,14 +93,8 @@ class GlobalExceptionHandler {
exchange: ServerWebExchange
): Mono<out ResponseEntity<out Map<String, Any?>>?> {
e.printStackTrace()
// val errorBody = mapOf("error" to "An unexpected error occurred")
val errorResponse = mapOf(
"timestamp" to System.currentTimeMillis(),
"status" to HttpStatus.INTERNAL_SERVER_ERROR.value(),
"error" to "Internal Server Error",
"message" to e.message,
"path" to exchange.request.path.value()
)
return Mono.just(
ResponseEntity(
constructErrorBody(

View File

@@ -5,11 +5,10 @@ import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.context.ApplicationContext
import org.springframework.core.annotation.Order
import org.springframework.http.MediaType
import org.springframework.http.codec.ServerCodecConfigurer
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.server.*
import org.springframework.web.reactive.result.view.ViewResolver
import org.springframework.http.codec.ServerCodecConfigurer
import reactor.core.publisher.Mono
@Component
@@ -45,7 +44,5 @@ class GlobalErrorWebExceptionHandler(
.body(BodyInserters.fromValue(errorAttributesMap))
}
private fun getHttpStatus(errorAttributes: Map<String, Any>): Int {
return errorAttributes["status"] as Int
}
}

View File

@@ -1,6 +1,5 @@
package space.luminic.budgerapp.controllers
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
@@ -47,8 +46,5 @@ class RecurrentController (
}
@GetMapping("/transfer")
fun transferRecurrents() : ResponseEntity<Any> {
return ResponseEntity.ok(recurrentService.transferRecurrents())
}
}

View File

@@ -1,19 +0,0 @@
package space.luminic.budgerapp.controllers
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/push")
class SubsController {
@GetMapping("/vapid")
fun getVapid(): ResponseEntity<String> {
return ResponseEntity.ok("BKmMyBUhpkcmzYWcYsjH_spqcy0zf_8eVtZo60f7949TgLztCmv3YD0E_vtV2dTfECQ4sdLdPK3ICDcyOkCqr84")
}
}

View File

@@ -2,6 +2,7 @@ package space.luminic.budgerapp.controllers
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
@@ -21,6 +22,11 @@ class SubscriptionController(
) {
@GetMapping("/vapid")
fun getVapid(): ResponseEntity<String> {
return ResponseEntity.ok("BKmMyBUhpkcmzYWcYsjH_spqcy0zf_8eVtZo60f7949TgLztCmv3YD0E_vtV2dTfECQ4sdLdPK3ICDcyOkCqr84")
}
@PostMapping("/subscribe")
fun subscribe(
@RequestBody subscription: SubscriptionDTO,

View File

@@ -1,12 +1,9 @@
package space.luminic.budgerapp.controllers
import org.springframework.data.mongodb.core.aggregation.AggregationResults
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
@@ -23,7 +20,7 @@ import space.luminic.budgerapp.services.TransactionService
@RestController
@RequestMapping("/transactions")
class TransactionController(private val transactionService: TransactionService, val budgetService: BudgetService) {
class TransactionController(private val transactionService: TransactionService) {
@GetMapping
@@ -91,17 +88,6 @@ class TransactionController(private val transactionService: TransactionService,
}
// @PatchMapping("/{id}/set-done")
// fun setTransactionDoneStatus(@PathVariable id: String, @RequestBody transaction: Transaction): ResponseEntity<Any> {
// try {
// transactionService.setTransactionDone(transaction)
// return ResponseEntity.ok(true)
// } catch (e: Exception) {
// e.printStackTrace()
// return ResponseEntity(e.message, HttpStatus.INTERNAL_SERVER_ERROR)
// }
// }
@GetMapping("/{id}/child")
fun getChildTransactions(@PathVariable id: String): ResponseEntity<Any> {
return ResponseEntity.ok(transactionService.getChildTransaction(id))
@@ -112,10 +98,7 @@ class TransactionController(private val transactionService: TransactionService,
return ResponseEntity.ok(transactionService.getAverageSpendingByCategory())
}
// @GetMapping("/_transfer")
// fun transfer(): ResponseEntity<Any> {
// return ResponseEntity.ok(transactionService.transferTransactions())
// }
@GetMapping("/types")
fun getTypes(): ResponseEntity<Any> {
@@ -126,10 +109,6 @@ class TransactionController(private val transactionService: TransactionService,
}
}
// @GetMapping("/test")
// fun getSome(): List<Map<*, *>?> {
// return transactionService.getPlannedForBudget(budgetService.getBudget("675ae567f232c35272f8a692")!!)
// }
}

View File

@@ -1,37 +0,0 @@
package space.luminic.budgerapp.controllers
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Budget
import space.luminic.budgerapp.models.Category
import space.luminic.budgerapp.models.Recurrent
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.services.TransferService
@RestController
@RequestMapping("/transfer")
class TransferController(private val transferService: TransferService) {
@GetMapping("/transactions")
fun transferTransactions(): Mono<List<Transaction>> {
return transferService.getTransactions()
}
@GetMapping("/categories")
fun transferCategories(): Mono<List<Category>> {
return transferService.getCategories()
}
@GetMapping("/recurrents")
fun recurrentTransactions(): Mono<List<Recurrent>> {
return transferService.recurrents()
}
@GetMapping("/budgets")
fun budgets(): Mono<List<Budget>> {
return transferService.transferBudgets()
}
}

View File

@@ -1,7 +1,6 @@
package space.luminic.budgerapp.controllers
import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
@@ -24,12 +23,8 @@ class UsersController(val userService: UserService) {
@GetMapping("/")
fun getUsers(): Mono<List<User>> {
// return ResponseEntity.ok("teset")
return userService.getUsers()
}
//
// @GetMapping("/regen")
// fun regenUsers(): ResponseEntity<Any> {
// return ResponseEntity.ok(userService.regenPass())
// }
}

View File

@@ -2,18 +2,12 @@ package space.luminic.budgerapp.repos
import org.springframework.data.domain.Sort
import org.springframework.data.mongodb.core.aggregation.SortOperation
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.Query
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.data.repository.PagingAndSortingRepository
import org.springframework.stereotype.Repository
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Budget
import java.time.LocalDate
import java.util.Date
import java.util.Optional
@Repository
interface BudgetRepo: ReactiveMongoRepository<Budget, String> {

View File

@@ -1,11 +1,9 @@
package space.luminic.budgerapp.repos
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Category
import java.util.Optional
@Repository
interface CategoryRepo : ReactiveMongoRepository<Category, String> {

View File

@@ -1,11 +0,0 @@
package space.luminic.budgerapp.repos
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Repository
import space.luminic.budgerapp.models.Category
@Repository
interface CategoryRepoOld: MongoRepository<Category, String> {
fun findByName(name: String): Category?
}

View File

@@ -1,7 +1,5 @@
package space.luminic.budgerapp.repos
import org.bson.types.ObjectId
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import space.luminic.budgerapp.models.Recurrent

View File

@@ -1,6 +0,0 @@
package space.luminic.budgerapp.repos
import org.springframework.data.mongodb.repository.MongoRepository
import space.luminic.budgerapp.models.Recurrent
interface RecurrentRepoOld : MongoRepository<Recurrent, String>

View File

@@ -1,7 +1,6 @@
package space.luminic.budgerapp.repos
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import space.luminic.budgerapp.models.Subscription

View File

@@ -1,17 +1,15 @@
package space.luminic.budgerapp.repos
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Token
import space.luminic.budgerapp.models.TokenStatus
import java.time.LocalDateTime
@Repository
interface TokenRepo: ReactiveMongoRepository<Token, String> {
fun findByToken(token: String): Mono<Token>
fun findByUsernameAndStatus(username: String, status: TokenStatus): Mono<List<Token>>
fun deleteByExpiresAtBefore(dateTime: LocalDateTime)
}

View File

@@ -1,20 +0,0 @@
package space.luminic.budgerapp.repos
import org.bson.types.ObjectId
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import space.luminic.budgerapp.models.Transaction
import java.util.Optional
@Repository
interface TransactionReactiveRepo: ReactiveMongoRepository<Transaction, String> {
// fun findByOldId(transactionId: Int): Optional<Transaction>
fun findByParentId(parentId: String): Optional<Transaction>
}

View File

@@ -1,12 +1,9 @@
package space.luminic.budgerapp.repos
import org.bson.types.ObjectId
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Transaction
import java.util.Optional
@Repository

View File

@@ -1,12 +1,10 @@
package space.luminic.budgerapp.repos
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.Query
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.User
import java.util.Optional
@Repository
interface UserRepo : ReactiveMongoRepository<User, String> {

View File

@@ -1,9 +0,0 @@
package space.luminic.budgerapp.repos
import org.springframework.data.mongodb.repository.MongoRepository
import space.luminic.budgerapp.models.User
interface UserRepoOld: MongoRepository<User, String> {
fun findByUsername(username: String): User?
}

View File

@@ -1,6 +1,5 @@
package space.luminic.budgerapp.repos
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import reactor.core.publisher.Flux
@@ -11,7 +10,6 @@ import space.luminic.budgerapp.models.Warn
interface WarnRepo : ReactiveMongoRepository<Warn, String> {
fun findAllByBudgetIdAndIsHide(budgetId: String, isHide: Boolean): Flux<Warn>
fun deleteAllByBudgetId(budgetId: String)
fun findWarnByContext(context: String): Mono<Warn>

View File

@@ -1,137 +0,0 @@
package space.luminic.budgerapp.repos.sqlrepo
import org.slf4j.LoggerFactory
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Repository
import space.luminic.budgerapp.controllers.dtos.BudgetCreationDTO
import space.luminic.budgerapp.models.Budget
import space.luminic.budgerapp.models.BudgetCategory
import space.luminic.budgerapp.models.Category
import space.luminic.budgerapp.models.CategoryType
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.models.TransactionType
import space.luminic.budgerapp.services.CategoryService
import space.luminic.budgerapp.services.UserService
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.time.ZoneOffset
@Repository
class BudgetRepoSQL(
val jdbcTemplate: JdbcTemplate,
val userService: UserService,
val categoryService: CategoryService,
val transactionsRepoSQl: TransactionsRepoSQl
) {
private val logger = LoggerFactory.getLogger(BudgetRepoSQL::class.java)
fun getBudgetsIds(): List<Int> {
return jdbcTemplate.queryForList("SELECT id FROM budger.budgets", Int::class.java)
}
fun getBudgets(): List<BudgetCreationDTO> {
val sql = "SELECT * FROM budger.budgets"
return jdbcTemplate.query(sql, RowMapper { rs, _ ->
BudgetCreationDTO(
Budget(
// id = rs.getString("id"),
name = rs.getString("name"),
dateFrom = rs.getDate("date_from").toLocalDate(),
dateTo = rs.getDate("date_to").toLocalDate(),
createdAt = LocalDateTime.of(rs.getDate("created_at").toLocalDate(), LocalTime.NOON),
), false
)
})
}
// fun getBudgetCategory(id: Int): List<BudgetCategory> {
// val sql = "SELECT c.*, \n" +
// " COALESCE(SUM(CASE WHEN t.transaction_type_code = 'INSTANT' THEN t.amount END), 0) AS total_instant_expenses,\n" +
// " COALESCE(SUM(CASE WHEN t.transaction_type_code = 'PLANNED' THEN t.amount END), 0) AS total_planned_expenses,\n" +
// " COALESCE(bcs.category_setting_value, 0) AS current_limit,\n" +
// " COALESCE((SELECT AVG(amount) FROM budger.transactions where category_id=c.id AND transaction_type_code='INSTANT'),0) as average\n" +
// "FROM\n" +
// " budger.budget_category_settings bcs\n" +
// "JOIN\n" +
// " budger.budgets b ON bcs.budget_id = b.id\n" +
// "LEFT JOIN\n" +
// " budger.transactions t ON bcs.category_id = t.category_id\n" +
// " AND t.date BETWEEN b.date_from AND b.date_to\n" +
// "JOIN\n" +
// " budger.categories c ON bcs.category_id = c.id\n" +
// "WHERE\n" +
// " b.id = ? -- Укажите ID бюджета, для которого нужно вывести данные\n" +
// "GROUP BY\n" +
// " c.id, bcs.category_setting_value\n" +
// "ORDER BY\n" +
// " c.id;"
// return jdbcTemplate.query(sql, RowMapper { rs, _ ->
// BudgetCategory(
// currentSpent = rs.getBigDecimal("total_instant_expenses"),
// currentLimit = rs.getBigDecimal("current_limit"),
// category = categoryService.getCategoryByName(rs.getString("name"))
// )
// }, id)
// }
fun getBudgetTransactions(id: Int, transactionType: String?, categoryType: String?): List<Transaction> {
val whereConditions = mutableListOf<String>()
val parameters = mutableListOf<Any>()
// Базовое условие
whereConditions.add("b.id = ?")
parameters.add(id)
// Условие для transactionType, если указано
if (transactionType != null) {
whereConditions.add("t.transaction_type_code = ?")
parameters.add(transactionType)
}
// Условие для categoryType, если указано
if (categoryType != null) {
whereConditions.add("c.type_code = ?")
parameters.add(categoryType)
}
// Генерация WHERE
val whereClause = if (whereConditions.isNotEmpty()) "WHERE ${whereConditions.joinToString(" AND ")}" else ""
// SQL запрос
val sql = """
SELECT
tt.code as tt_code,
tt.name as tt_name,
t.comment,
t.date,
t.amount,
t.is_done,
t.created_at,
u.username,
c.name as c_name,
t.id
FROM budger.transactions t
JOIN budger.transaction_types tt ON tt.code = t.transaction_type_code
JOIN budger.categories c ON c.id = t.category_id
JOIN budger.category_types ct ON ct.code = c.type_code
JOIN budger.budgets b ON t.date BETWEEN b.date_from AND b.date_to
LEFT JOIN budger.users u ON t.user_id = u.id
$whereClause
ORDER BY t.date DESC, c.name, t.id
""".trimIndent()
logger.info(parameters.toTypedArray().toString())
// Выполнение запроса
return jdbcTemplate.query(
sql, parameters.toTypedArray(),
transactionsRepoSQl.transactionRowMapper()
)
}
}

View File

@@ -1,40 +0,0 @@
package space.luminic.budgerapp.repos.sqlrepo
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Repository
import space.luminic.budgerapp.models.Category
import space.luminic.budgerapp.models.CategoryType
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.models.TransactionType
import java.time.LocalDate
import java.time.ZoneId
@Repository
class CategoriesRepoSQL(private val jdbcTemplate: JdbcTemplate) {
fun getCategories(): List<Category> {
val sql = "SELECT * FROM budger.categories"
return jdbcTemplate.query(sql, categoriesRowMapper())
}
fun categoriesRowMapper() = RowMapper { rs, _ ->
val category = Category(
type = if (rs.getString("type_code") == "EXPENSE") CategoryType("EXPENSE", "Траты") else CategoryType(
"INCOME",
"Поступления"
),
name = rs.getString("name"),
description = rs.getString("description"),
icon = rs.getString("icon")
)
return@RowMapper category
}
}

View File

@@ -1,39 +0,0 @@
package space.luminic.budgerapp.repos.sqlrepo
import org.slf4j.LoggerFactory
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Repository
import space.luminic.budgerapp.models.Recurrent
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.models.TransactionType
import space.luminic.budgerapp.repos.CategoryRepoOld
@Repository
class RecurrentRepoSQL(
private val jdbcTemplate: JdbcTemplate,
private val categoryRepoOld: CategoryRepoOld
) {
private val logger = LoggerFactory.getLogger(RecurrentRepoSQL::class.java)
fun getRecurrents(): List<Recurrent> {
return jdbcTemplate.query("SELECT r.*, c.name as c_name FROM budger.recurrent_payments r join budger.categories c on r.category_id = c.id ", recurrentRowMapper())
}
fun recurrentRowMapper() = RowMapper { rs, _ ->
val recurrent = Recurrent(
atDay = rs.getInt("at_day"),
category = categoryRepoOld.findByName(rs.getString("c_name"))!!,
name = rs.getString("name"),
description = rs.getString("description"),
amount = rs.getInt("amount")
)
logger.info(recurrent.toString())
return@RowMapper recurrent
}
}

View File

@@ -1,65 +0,0 @@
package space.luminic.budgerapp.repos.sqlrepo
import org.slf4j.LoggerFactory
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Repository
import space.luminic.budgerapp.models.Category
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.models.TransactionType
import space.luminic.budgerapp.models.User
import space.luminic.budgerapp.repos.CategoryRepoOld
import space.luminic.budgerapp.repos.UserRepoOld
import space.luminic.budgerapp.services.CategoryService
import space.luminic.budgerapp.services.UserService
import java.sql.SQLException
import java.time.LocalDate
import java.time.ZoneId
@Repository
class TransactionsRepoSQl(
private val jdbcTemplate: JdbcTemplate,
private val userRepoOld: UserRepoOld,
private val categoryRepoOld: CategoryRepoOld
) {
private val logger = LoggerFactory.getLogger(TransactionsRepoSQl::class.java)
fun getTransactions(): List<Transaction> {
return jdbcTemplate.query(
"SELECT tt.code as tt_code, tt.name as tt_name, u.username, c.name as c_name, t.comment, t.date, t.amount, t.is_done, t.created_at, t.id" +
" FROM budger.transactions t" +
" JOIN budger.transaction_types tt on t.transaction_type_code = tt.code " +
" JOIN budger.categories c on t.category_id = c.id" +
" LEFT JOIN budger.users u on t.user_id = u.id", transactionRowMapper()
)
}
fun transactionRowMapper() = RowMapper { rs, _ ->
val transaction = Transaction(
type = TransactionType(rs.getString("tt_code"), rs.getString("tt_name")),
user = rs.getString("username")
?.let { userRepoOld.findByUsername(it) }
?: userRepoOld.findByUsername("voroninv"),
category = categoryRepoOld.findByName(rs.getString("c_name"))!!,
comment = rs.getString("comment"),
date = rs.getDate("date").toLocalDate(),
amount = rs.getDouble("amount"),
isDone = rs.getBoolean("is_done"),
createdAt = rs.getTimestamp("created_at").toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
)
logger.info(transaction.toString())
return@RowMapper transaction
}
}

View File

@@ -1,9 +1,7 @@
package space.luminic.budgerapp.services
import org.springframework.cache.annotation.Cacheable
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import space.luminic.budgerapp.configs.AuthException
@@ -16,7 +14,6 @@ import space.luminic.budgerapp.utils.JWTUtil
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.Date
import java.util.UUID
@Service

View File

@@ -1,42 +1,37 @@
package space.luminic.budgerapp.services
import org.slf4j.LoggerFactory
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
import org.springframework.context.event.EventListener
import org.springframework.data.domain.Sort
import org.springframework.data.domain.Sort.Direction
import org.springframework.stereotype.Service
import space.luminic.budgerapp.models.Budget
import space.luminic.budgerapp.models.BudgetCategory
import space.luminic.budgerapp.models.Transaction
import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.aggregation.Aggregation.group
import org.springframework.data.mongodb.core.aggregation.Aggregation.lookup
import org.springframework.data.mongodb.core.aggregation.Aggregation.match
import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation
import org.springframework.data.mongodb.core.aggregation.Aggregation.project
import org.springframework.data.mongodb.core.aggregation.Aggregation.sort
import org.springframework.data.mongodb.core.aggregation.Aggregation.unwind
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Budget
import space.luminic.budgerapp.models.BudgetCategory
import space.luminic.budgerapp.models.BudgetDTO
import space.luminic.budgerapp.models.BudgetNotFoundException
import space.luminic.budgerapp.models.PushMessage
import space.luminic.budgerapp.models.SortSetting
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.models.TransactionEvent
import space.luminic.budgerapp.models.TransactionEventType
import space.luminic.budgerapp.models.Warn
import space.luminic.budgerapp.models.WarnSerenity
import space.luminic.budgerapp.repos.BudgetRepo
import space.luminic.budgerapp.repos.WarnRepo
import java.time.LocalDate
import java.util.Optional
import kotlin.collections.get
@@ -57,7 +52,7 @@ class BudgetService(
@CacheEvict(cacheNames = ["budgets"], allEntries = true)
fun handleTransactionEvent(event: TransactionEvent) {
logger.info("Got ${event.eventType} event on transaction ${event.newTransaction.id}")
if (event.newTransaction.category.type?.code == "EXPENSE") {
if (event.newTransaction.category.type.code == "EXPENSE") {
when (event.eventType) {
TransactionEventType.EDIT -> updateBudgetOnEdit(event)
TransactionEventType.CREATE -> updateBudgetOnCreate(event)
@@ -196,23 +191,6 @@ class BudgetService(
}
fun updateBudgetTransactions(budget: Budget): Budget {
// budget.plannedExpenses = getBudgetTransactions(
// budget = budget,
// transactionType = "PLANNED",
// categoryType = "EXPENSE",
// sortBy = SortSetting("date", Direction.ASC)
// )
// budget.plannedIncomes = getBudgetTransactions(
// budget = budget,
// transactionType = "PLANNED",
// categoryType = "INCOME",
// sortBy = SortSetting("date", Direction.ASC)
// )
return budget
}
@Cacheable("budgetsList")
fun getBudgets(sortSetting: SortSetting? = null): Mono<MutableList<Budget>> {
val sort = if (sortSetting != null) {
@@ -277,22 +255,6 @@ class BudgetService(
}
// fun transferBudgets() {
// val budgets = getBudgets()
// budgetRepo.saveAll<Budget>(budgets)
//
// }
//
// fun getBudgets(): List<Budget> {
// val budgetIds = budgetRepoSql.getBudgetsIds()
// var budgets = mutableListOf<Budget>()
// budgetIds.forEach { budgetId ->
// budgets.add(getBudgetSQL(budgetId)!!)
// }
// return budgets
// }
@CacheEvict(cacheNames = ["budgets", "budgetsList"], allEntries = true)
fun createBudget(budget: Budget, createRecurrent: Boolean): Mono<Budget> {
return Mono.zip(
@@ -323,6 +285,7 @@ class BudgetService(
budget.categories = categories
budgetRepo.save(budget)
}
.publishOn(reactor.core.scheduler.Schedulers.boundedElastic())
.doOnNext { savedBudget ->
// Выполнение updateBudgetWarns в фоне
updateBudgetWarns(budget = savedBudget)
@@ -374,27 +337,6 @@ class BudgetService(
}
}
fun getBudgetTransactions(
budget: Budget,
transactionType: String? = null,
isDone: Boolean? = null,
categoryType: String? = null,
sortBy: SortSetting? = null
): Mono<MutableList<Transaction>> {
val defaultSort = SortSetting("date", Sort.Direction.ASC) // Сортировка по умолчанию
return transactionService.getTransactions(
dateFrom = budget.dateFrom,
dateTo = budget.dateTo,
transactionType = transactionType,
isDone = isDone,
categoryType = categoryType,
sortSetting = sortBy ?: defaultSort
).onErrorResume { e ->
Mono.error(RuntimeException("Error fetching transactions: ${e.message}", e))
}
}
@CacheEvict(cacheNames = ["budgets", "budgetsList"], allEntries = true)
fun deleteBudget(budgetId: String): Mono<Void> {
@@ -436,38 +378,14 @@ class BudgetService(
}
fun recalcBudgetCategory(): Mono<Void> {
return budgetRepo.findAll(Sort.by(Direction.DESC, "dateFrom")) // Получаем все бюджеты
// .flatMapIterable { budgets -> budgets } // Преобразуем Flux<Budget> в Flux<Budget> (итерация по бюджетам)
// .flatMap { budget ->
// logger.warn("HERE $budget")
// Flux.fromIterable(budget.categories) // Преобразуем категории в поток
// .flatMap { category ->
// logger.warn("HERE $category")
// Mono.zip(
// transactionService.calcTransactionsSum(budget, category.category.id!!, "PLANNED"),
// transactionService.calcTransactionsSum(budget, category.category.id!!, "INSTANT", isDone = true),
// transactionService.calcTransactionsSum(budget, category.category.id!!, "PLANNED")
// ).map { (plannedSum, spentSum, limitSum) ->
// category.currentPlanned = plannedSum
// category.currentSpent = spentSum
// category.currentLimit = limitSum
// }
// }
// .then() // Завершаем поток категорий
// .flatMap { updateBudgetWarns(budgetId = budget.id!!) } // Обновляем предупреждения
// }
// .collectList() // Собираем все бюджеты
// .flatMap { budgets -> budgetRepo.saveAll(budgets).collectList() } // Сохраняем все бюджеты
.then() // Завершаем метод
}
fun getWarns(budgetId: String, isHide: Boolean? = null): Mono<List<Warn>> {
return warnRepo.findAllByBudgetIdAndIsHide(budgetId, isHide == true).collectList()
}
fun hideWarn(budgetId: String, warnId: String): Mono<Warn> {
fun hideWarn(warnId: String): Mono<Warn> {
return warnRepo.findById(warnId) // Ищем предупреждение
.flatMap { warn ->
warn.isHide = true // Обновляем поле

View File

@@ -5,9 +5,7 @@ import org.bson.Document
import org.slf4j.LoggerFactory
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@@ -22,8 +20,6 @@ import java.time.ZoneId
import java.time.ZoneOffset
import java.util.Date
import kotlin.jvm.optionals.getOrNull
@Service
class CategoryService(
private val categoryRepo: CategoryRepo,
@@ -33,10 +29,6 @@ class CategoryService(
private val logger = LoggerFactory.getLogger(javaClass)
fun getCategoryByName(name: String): Mono<Category> {
return categoryRepo.findByName(name)
}
@Cacheable("getAllCategories")
fun getCategories(): Mono<List<Category>> {

View File

@@ -1,16 +0,0 @@
package space.luminic.budgerapp.services
import org.springframework.security.core.userdetails.ReactiveUserDetailsService
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
@Service
class CustomReactiveUserDetailsService(
private val userDetailsService: UserDetailsService // Ваш синхронный сервис
) : ReactiveUserDetailsService {
override fun findByUsername(username: String): Mono<UserDetails> {
return Mono.fromCallable { userDetailsService.loadUserByUsername(username) }
}
}

View File

@@ -6,7 +6,6 @@ import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
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.Budget
import space.luminic.budgerapp.models.NotFoundException
@@ -15,19 +14,12 @@ import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.models.TransactionType
import space.luminic.budgerapp.repos.RecurrentRepo
import space.luminic.budgerapp.repos.TransactionRepo
import space.luminic.budgerapp.repos.sqlrepo.RecurrentRepoSQL
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.YearMonth
import kotlin.jvm.optionals.getOrNull
@Service
class RecurrentService(
private val recurrentRepo: RecurrentRepo,
private val recurrentRepoSQL: RecurrentRepoSQL,
private val transactionRepo: TransactionRepo,
private val userService: UserService,
) {
@@ -120,9 +112,6 @@ class RecurrentService(
}
fun transferRecurrents() {
recurrentRepo.saveAll(recurrentRepoSQL.getRecurrents()).then().subscribe()
}
}

View File

@@ -1,16 +1,14 @@
package space.luminic.budgerapp.services
import com.interaso.webpush.VapidKeys
import com.interaso.webpush.WebPushService
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.slf4j.LoggerFactory
import org.springframework.dao.DuplicateKeyException
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.PushMessage
import space.luminic.budgerapp.models.Subscription
import space.luminic.budgerapp.models.SubscriptionDTO
@@ -19,7 +17,6 @@ import space.luminic.budgerapp.repos.SubscriptionRepo
import space.luminic.budgerapp.services.VapidConstants.VAPID_PRIVATE_KEY
import space.luminic.budgerapp.services.VapidConstants.VAPID_PUBLIC_KEY
import space.luminic.budgerapp.services.VapidConstants.VAPID_SUBJECT
import java.security.GeneralSecurityException
object VapidConstants {
const val VAPID_PUBLIC_KEY =

View File

@@ -1,6 +1,5 @@
package space.luminic.budgerapp.services
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Token
@@ -12,29 +11,28 @@ import java.time.LocalDateTime
class TokenService(private val tokenRepository: TokenRepo) {
@CacheEvict("tokens", allEntries = true)
fun saveToken(token: String, username: String, expiresAt: LocalDateTime) {
fun saveToken(token: String, username: String, expiresAt: LocalDateTime): Mono<Token> {
val newToken = Token(
token = token,
username = username,
issuedAt = LocalDateTime.now(),
expiresAt = expiresAt
)
tokenRepository.save(newToken)
return tokenRepository.save(newToken)
}
@CacheEvict("tokens", allEntries = true)
fun revokeToken(token: String): Mono<Void> {
return tokenRepository.findByToken(token)
.switchIfEmpty(Mono.error(Exception("Token not found")))
.flatMap { existingToken ->
val updatedToken = existingToken.copy(status = TokenStatus.REVOKED)
tokenRepository.save(updatedToken).then()
}
.switchIfEmpty(Mono.error(Exception("Token not found")))
}
@CacheEvict("tokens", allEntries = true)
fun deleteExpiredTokens() {
tokenRepository.deleteByExpiresAtBefore(LocalDateTime.now())

View File

@@ -1,9 +1,6 @@
package space.luminic.budgerapp.services
import com.mongodb.client.model.Aggregates.addFields
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.bson.Document
import org.bson.types.ObjectId
import org.slf4j.LoggerFactory
@@ -12,74 +9,44 @@ import org.springframework.cache.annotation.Cacheable
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.domain.Sort
import org.springframework.data.domain.Sort.Direction
import org.springframework.data.mongodb.MongoExpression
import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.aggregation.AddFieldsOperation
import org.springframework.data.mongodb.core.aggregation.Aggregation
import org.springframework.data.mongodb.core.aggregation.Aggregation.ROOT
import org.springframework.data.mongodb.core.aggregation.Aggregation.group
import org.springframework.data.mongodb.core.aggregation.Aggregation.limit
import org.springframework.data.mongodb.core.aggregation.Aggregation.lookup
import org.springframework.data.mongodb.core.aggregation.Aggregation.match
import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation
import org.springframework.data.mongodb.core.aggregation.Aggregation.project
import org.springframework.data.mongodb.core.aggregation.Aggregation.skip
import org.springframework.data.mongodb.core.aggregation.Aggregation.sort
import org.springframework.data.mongodb.core.aggregation.Aggregation.unwind
import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields
import org.springframework.data.mongodb.core.aggregation.Aggregation.limit
import org.springframework.data.mongodb.core.aggregation.Aggregation.skip
import org.springframework.data.mongodb.core.aggregation.AggregationExpression
import org.springframework.data.mongodb.core.aggregation.AggregationResults
import org.springframework.data.mongodb.core.aggregation.ArrayOperators
import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.filter
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ifNull
import org.springframework.data.mongodb.core.aggregation.DateOperators
import org.springframework.data.mongodb.core.aggregation.DateOperators.DateToString
import org.springframework.data.mongodb.core.aggregation.LookupOperation
import org.springframework.data.mongodb.core.aggregation.MatchOperation
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.security.core.context.ReactiveSecurityContextHolder
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Budget
import space.luminic.budgerapp.models.Category
import space.luminic.budgerapp.models.CategoryType
import space.luminic.budgerapp.models.SortSetting
import space.luminic.budgerapp.models.SortTypes
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.models.TransactionEvent
import space.luminic.budgerapp.models.TransactionEventType
import space.luminic.budgerapp.models.TransactionType
import space.luminic.budgerapp.models.User
import space.luminic.budgerapp.repos.TransactionRepo
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters
import java.util.ArrayList
import java.util.Arrays
import java.util.Date
import kotlin.jvm.optionals.getOrNull
@Service
class TransactionService(
private val mongoTemplate: MongoTemplate,
private val reactiveMongoTemplate: ReactiveMongoTemplate,
val transactionsRepo: TransactionRepo,
val userService: UserService,
@@ -608,13 +575,6 @@ class TransactionService(
}
fun getCategoriesExplainReactive(pipeline: List<Document>): Mono<Document> {
val command = Document("aggregate", "transactions")
.append("pipeline", pipeline)
.append("explain", true)
return reactiveMongoTemplate.executeCommand(command)
}
private fun extractTransactions(aggregationResult: Document, key: String): Mono<List<Transaction>> {
val resultTransactions = aggregationResult[key] as? List<Document> ?: emptyList()
@@ -658,7 +618,7 @@ class TransactionService(
transactionType["code"] as String,
transactionType["name"] as String
),
user = user!!,
user = user,
category = category,
comment = document["comment"] as String,
date = (document["date"] as Date).toInstant().atZone(ZoneId.systemDefault()).toLocalDate(),
@@ -669,116 +629,4 @@ class TransactionService(
)
}
// fun getPlannedForBudget(budget: Budget, transactionType: String? = null): List<Map<String, Any>> {
// // 1) $lookup: "categories"
// val lookupCategories = Aggregation.lookup(
// "categories",
// "category.\$id",
// "_id",
// "categoryDetailed"
// )
//
// // 2) $lookup: "budgets" (pipeline + let)
// val matchBudgetsDoc = Document(
// "\$expr", Document(
// "\$and", listOf(
// Document("\$gte", listOf("\$\$transactionDate", "\$dateFrom")),
// Document("\$lt", listOf("\$\$transactionDate", "\$dateTo"))
// )
// )
// )
// val matchBudgetsOp = MatchOperation(matchBudgetsDoc)
//
// val lookupBudgets = LookupOperation.newLookup()
// .from("budgets")
// .letValueOf("transactionDate").bindTo("date")
// .pipeline(matchBudgetsOp)
// .`as`("budgetDetails")
//
// // 3) $unwind
// val unwindCategory = Aggregation.unwind("categoryDetailed")
// val unwindBudget = Aggregation.unwind("budgetDetails")
//
// // 4) $match: диапазон дат
// val matchDates = Aggregation.match(
// Criteria("date")
// .gte(budget.dateFrom)
// .lt(budget.dateTo)
// )
//
// // 5) $facet (разные ветки: plannedExpenses, plannedExpensesSum, ...)
// // plannedExpenses
// val plannedExpensesMatch = Aggregation.match(
// Criteria().andOperator(
// Criteria("type.code").`is`("PLANNED"),
// Criteria("categoryDetailed.type.code").`is`("EXPENSE")
// )
// )
// val plannedExpensesPipeline = listOf(plannedExpensesMatch)
//
// // plannedExpensesSum
// val plannedExpensesSumPipeline = listOf(
// plannedExpensesMatch,
// group(null).`as`("_id").sum("amount").`as`("sum"),
// project("sum").andExclude("_id")
// )
//
// // plannedIncome
// val plannedIncomeMatch = Aggregation.match(
// Criteria().andOperator(
// Criteria("type.code").`is`("PLANNED"),
// Criteria("categoryDetailed.type.code").`is`("INCOME")
// )
// )
// val plannedIncomePipeline = listOf(plannedIncomeMatch)
//
// // plannedIncomeSum
// val plannedIncomeSumPipeline = listOf(
// plannedIncomeMatch,
// group().`as`("_id").sum("amount").`as`("sum"),
// project("sum").andExclude("_id")
// )
//
// // instantTransactions
// val instantTransactionsMatch = Aggregation.match(
// Criteria("type.code").`is`("INSTANT")
// )
// val instantTransactionsProject = Aggregation.project(
// "_id", "type", "comment", "user", "amount", "date",
// "category", "isDone", "createdAt", "parentId"
// )
// val instantTransactionsPipeline = listOf(instantTransactionsMatch, instantTransactionsProject)
//
// val facetStage = Aggregation.facet(*plannedExpensesPipeline.toTypedArray()).`as`("plannedExpenses")
// .and(*plannedExpensesSumPipeline.toTypedArray()).`as`("plannedExpensesSum")
// .and(*plannedIncomePipeline.toTypedArray()).`as`("plannedIncome")
// .and(*plannedIncomeSumPipeline.toTypedArray()).`as`("plannedIncomeSum")
// .and(*instantTransactionsPipeline.toTypedArray()).`as`("instantTransactions")
//
// // 6) $set: вытаскиваем суммы из массивов
// val setStage = AddFieldsOperation.builder()
// .addField("plannedExpensesSum").withValue(
// ArrayOperators.ArrayElemAt.arrayOf("\$plannedExpensesSum.sum").elementAt(0)
// )
// .addField("plannedIncomeSum").withValue(
// ArrayOperators.ArrayElemAt.arrayOf("\$plannedIncomeSum.sum").elementAt(0)
// )
// .build()
//
// // Собираем все стадии
// val aggregation = Aggregation.newAggregation(
// lookupCategories,
// lookupBudgets,
// unwindCategory,
// unwindBudget,
// matchDates,
// facetStage,
// setStage
// )
//
// val results = mongoTemplate.aggregate(aggregation, "transactions", Map::class.java)
// return results.mappedResults
// }
}

View File

@@ -1,62 +0,0 @@
package space.luminic.budgerapp.services
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.Budget
import space.luminic.budgerapp.models.Category
import space.luminic.budgerapp.models.Recurrent
import space.luminic.budgerapp.models.Transaction
import space.luminic.budgerapp.repos.BudgetRepo
import space.luminic.budgerapp.repos.CategoryRepo
import space.luminic.budgerapp.repos.RecurrentRepo
import space.luminic.budgerapp.repos.TransactionRepo
import space.luminic.budgerapp.repos.sqlrepo.BudgetRepoSQL
import space.luminic.budgerapp.repos.sqlrepo.CategoriesRepoSQL
import space.luminic.budgerapp.repos.sqlrepo.RecurrentRepoSQL
import space.luminic.budgerapp.repos.sqlrepo.TransactionsRepoSQl
@Service
class TransferService(
private val transactionsRepoSQl: TransactionsRepoSQl,
private val categoriesRepoSQL: CategoriesRepoSQL,
private val recurrentRepoSQL: RecurrentRepoSQL,
private val budgetRepoSQL: BudgetRepoSQL,
private val categoryRepo: CategoryRepo,
private val transactionRepo: TransactionRepo,
private val recurrentRepo: RecurrentRepo,
private val budgetService: BudgetService
) {
fun getTransactions(): Mono<List<Transaction>> {
val transactions = transactionsRepoSQl.getTransactions()
return transactionRepo.saveAll(transactions).collectList()
}
fun getCategories(): Mono<List<Category>> {
val categories = categoriesRepoSQL.getCategories()
return Flux.fromIterable(categories)
.flatMap { category -> categoryRepo.save(category) }
.collectList() // Преобразуем Flux<Category> в Mono<List<Category>>
}
fun recurrents(): Mono<List<Recurrent>> {
val recurrents = recurrentRepoSQL.getRecurrents()
return Flux.fromIterable(recurrents).flatMap { recurrent ->
recurrentRepo.save(recurrent)
}.collectList()
}
fun transferBudgets(): Mono<List<Budget>> {
val budgets = budgetRepoSQL.getBudgets()
return Flux.fromIterable(budgets)
.flatMap { budget ->
budgetService.createBudget(budget.budget, budget.createRecurrent)
}.collectList()
}
}

View File

@@ -3,27 +3,17 @@ package space.luminic.budgerapp.services
import org.slf4j.LoggerFactory
import org.springframework.cache.annotation.Cacheable
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import space.luminic.budgerapp.models.NotFoundException
import space.luminic.budgerapp.models.User
import space.luminic.budgerapp.repos.UserRepo
import kotlin.jvm.optionals.getOrNull
@Service
class UserService(val userRepo: UserRepo, val passwordEncoder: PasswordEncoder) {
class UserService(val userRepo: UserRepo) {
val logger = LoggerFactory.getLogger(javaClass)
// fun regenPass(): List<User>? {
// var users = getUsers()!!.toMutableList()
// for (user in users) {
// user.password = passwordEncoder.encode(user.password)
// }
// userRepo.saveAll<User>(users)
// return users
// }
@Cacheable("users", key = "#username")
fun getByUsername(username: String): Mono<User> {
@@ -47,6 +37,7 @@ class UserService(val userRepo: UserRepo, val passwordEncoder: PasswordEncoder)
return userRepo.findByUsernameWOPassword(username)
}
@Cacheable("usersList")
fun getUsers(): Mono<List<User>> {
return userRepo.findAll()
.map { user -> user.apply { password = null } } // Убираем пароль

View File

@@ -1,19 +0,0 @@
package space.luminic.budgerapp.utils
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Date
//class DateSerializer : JsonDeserializer<LocalDateTime>() {
//
// private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") // Пример формата
//
// override fun deserialize(p: JsonParser, ctxt: DeserializationContext): LocalDateTime {
// val dateAsString = p.text
// var date = formatter.parse(dateAsString)
// return LocalDateTime.from(date, LocalDateTime.MIN ) // Преобразуем строку в LocalDateTime
// }
//}

View File

@@ -36,19 +36,5 @@ class JWTUtil(private val tokenService: TokenService) {
}
fun validateToken(token: String): String? {
return try {
val claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token)
claims.body.subject
} catch (e: io.jsonwebtoken.ExpiredJwtException) {
println("Token expired: ${e.message}")
null
} catch (e: io.jsonwebtoken.SignatureException) {
println("Invalid token signature: ${e.message}")
null
} catch (e: Exception) {
println("Token validation error: ${e.message}")
null
}
}
}