Compare commits
2 Commits
040da34ff7
...
0b54384258
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b54384258 | |||
| 7972ea0fdf |
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
# ---------- build stage ----------
|
||||
FROM gradle:jdk17-ubi AS build
|
||||
WORKDIR /app
|
||||
COPY gradlew gradlew
|
||||
COPY gradle gradle
|
||||
COPY build.gradle.kts settings.gradle.kts ./
|
||||
COPY src src
|
||||
RUN ./gradlew --no-daemon dependencies
|
||||
RUN ./gradlew --no-daemon clean bootJar
|
||||
|
||||
# ---------- run stage ----------
|
||||
FROM eclipse-temurin:17.0.16_8-jre AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# Create non-root user with a higher UID/GID to avoid conflicts
|
||||
RUN groupadd --system --gid 1001 app && \
|
||||
useradd --system --gid app --uid 1001 --shell /bin/bash --create-home app
|
||||
|
||||
# Создаём директорию и меняем владельца ДО переключения пользователя
|
||||
RUN mkdir -p /app/static && chown -R app:app /app
|
||||
|
||||
USER app
|
||||
|
||||
COPY --from=build /app/build/libs/*.jar /app/app.jar
|
||||
|
||||
# Настройки JVM (Java 17)
|
||||
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
|
||||
|
||||
EXPOSE 8080
|
||||
HEALTHCHECK --interval=20s --timeout=3s --retries=3 CMD wget -qO- http://localhost:8080/actuator/health || exit 1
|
||||
ENTRYPOINT ["java","-jar","/app/app.jar"]
|
||||
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.9.25"
|
||||
kotlin("plugin.spring") version "1.9.25"
|
||||
kotlin("plugin.jpa") version "1.9.25"
|
||||
id("org.springframework.boot") version "3.4.0"
|
||||
id("io.spring.dependency-management") version "1.1.6"
|
||||
kotlin("plugin.serialization") version "2.1.0"
|
||||
@@ -32,20 +33,21 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
// Spring
|
||||
// implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
|
||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||
implementation("org.springframework.boot:spring-boot-starter-cache")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
implementation ("org.springframework.boot:spring-boot-starter-actuator")
|
||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||
implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:2.8.13")
|
||||
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web") // MVC
|
||||
implementation("org.springframework.boot:spring-boot-starter-jdbc")
|
||||
implementation("org.postgresql:postgresql:42.7.8")
|
||||
|
||||
implementation("io.r2dbc:r2dbc-postgresql")
|
||||
// Аудит Spring Data JPA (@CreatedBy/@CreatedDate)
|
||||
implementation("org.springframework.data:spring-data-commons")
|
||||
// Миграции
|
||||
implementation("org.flywaydb:flyway-core")
|
||||
implementation("org.flywaydb:flyway-core:11.14.1")
|
||||
implementation("org.flywaydb:flyway-database-postgresql:11.14.1")
|
||||
// jackson для jsonb (если маппишь объекты в json)
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
|
||||
@@ -56,6 +58,7 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.7.3")
|
||||
implementation("org.jetbrains.kotlin.plugin.jpa:org.jetbrains.kotlin.plugin.jpa.gradle.plugin:1.9.25")
|
||||
|
||||
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
|
||||
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
|
||||
|
||||
@@ -4,25 +4,17 @@ import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.cache.annotation.EnableCaching
|
||||
import org.springframework.data.mongodb.config.EnableMongoAuditing
|
||||
import org.springframework.data.mongodb.config.EnableReactiveMongoAuditing
|
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories
|
||||
import org.springframework.scheduling.annotation.EnableAsync
|
||||
import org.springframework.scheduling.annotation.EnableScheduling
|
||||
import java.util.TimeZone
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
|
||||
|
||||
@SpringBootApplication(scanBasePackages = ["space.luminic.finance"])
|
||||
@EnableReactiveMongoAuditing(auditorAwareRef = "coroutineAuditorAware")
|
||||
@EnableCaching
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
//@EnableConfigurationProperties([TelegramBotProperties::class,)
|
||||
@EnableWebSecurity
|
||||
@ConfigurationPropertiesScan(basePackages = ["space.luminic.finance"])
|
||||
@EnableMongoRepositories(basePackages = ["space.luminic.finance.repos"])
|
||||
class Main
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Moscow"))
|
||||
runApplication<Main>(*args)
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package space.luminic.finance.api
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||
import jakarta.ws.rs.GET
|
||||
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.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import space.luminic.finance.dtos.AccountDTO
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.mappers.AccountMapper.toDto
|
||||
import space.luminic.finance.mappers.TransactionMapper.toDto
|
||||
import space.luminic.finance.models.Account
|
||||
import space.luminic.finance.services.AccountService
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/spaces/{spaceId}/accounts")
|
||||
@SecurityScheme(
|
||||
name = "bearerAuth",
|
||||
type = SecuritySchemeType.HTTP,
|
||||
bearerFormat = "JWT",
|
||||
scheme = "bearer"
|
||||
)
|
||||
class AccountController(
|
||||
private val accountService: AccountService
|
||||
) {
|
||||
|
||||
|
||||
@GetMapping
|
||||
suspend fun getAccounts(@PathVariable spaceId: String): List<AccountDTO> {
|
||||
return accountService.getAccounts(spaceId).map { it.toDto() }
|
||||
}
|
||||
|
||||
@GetMapping("/{accountId}")
|
||||
suspend fun getAccount(@PathVariable spaceId: String, @PathVariable accountId: String): AccountDTO {
|
||||
return accountService.getAccount(accountId, spaceId).toDto()
|
||||
}
|
||||
|
||||
@GetMapping("/{accountId}/transactions")
|
||||
suspend fun getAccountTransactions(
|
||||
@PathVariable spaceId: String,
|
||||
@PathVariable accountId: String
|
||||
): List<TransactionDTO> {
|
||||
return accountService.getAccountTransactions(spaceId, accountId).map { it.toDto() }
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
suspend fun createAccount(
|
||||
@PathVariable spaceId: String,
|
||||
@RequestBody accountDTO: AccountDTO.CreateAccountDTO
|
||||
): AccountDTO {
|
||||
return accountService.createAccount(spaceId, accountDTO).toDto()
|
||||
}
|
||||
|
||||
@PutMapping("/{accountId}")
|
||||
suspend fun updateAccount(
|
||||
@PathVariable spaceId: String,
|
||||
@PathVariable accountId: String,
|
||||
@RequestBody accountDTO: AccountDTO.UpdateAccountDTO
|
||||
): AccountDTO {
|
||||
return accountService.updateAccount(spaceId, accountDTO).toDto()
|
||||
}
|
||||
|
||||
@DeleteMapping("/{accountId}")
|
||||
suspend fun deleteAccount(@PathVariable spaceId: String, @PathVariable accountId: String) {
|
||||
accountService.deleteAccount(accountId, spaceId)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,23 +1,18 @@
|
||||
package space.luminic.finance.api
|
||||
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import space.luminic.finance.dtos.UserDTO.*
|
||||
import space.luminic.finance.dtos.UserDTO
|
||||
import space.luminic.finance.dtos.UserDTO.AuthUserDTO
|
||||
import space.luminic.finance.dtos.UserDTO.RegisterUserDTO
|
||||
import space.luminic.finance.mappers.UserMapper.toDto
|
||||
import space.luminic.finance.services.AuthService
|
||||
import space.luminic.finance.services.UserService
|
||||
import kotlin.jvm.javaClass
|
||||
import kotlin.to
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
class AuthController(
|
||||
private val userService: UserService,
|
||||
private val authService: AuthService
|
||||
) {
|
||||
|
||||
@@ -31,26 +26,28 @@ class AuthController(
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
suspend fun login(@RequestBody request: AuthUserDTO): Map<String, String> {
|
||||
val token = authService.login(request.username, request.password)
|
||||
fun login(@RequestBody request: AuthUserDTO): Map<String, String> {
|
||||
val token = authService.login(request.username.lowercase(), request.password)
|
||||
return mapOf("token" to token)
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
suspend fun register(@RequestBody request: RegisterUserDTO): UserDTO {
|
||||
fun register(@RequestBody request: RegisterUserDTO): UserDTO {
|
||||
return authService.register(request.username, request.password, request.firstName).toDto()
|
||||
}
|
||||
|
||||
@PostMapping("/tgLogin")
|
||||
suspend fun tgLogin(@RequestHeader("X-Tg-Id") tgId: String): Map<String, String> {
|
||||
fun tgLogin(@RequestHeader("X-Tg-Id") tgId: String): Map<String, String> {
|
||||
val token = authService.tgLogin(tgId)
|
||||
return mapOf("token" to token)
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/me")
|
||||
suspend fun getMe(): UserDTO {
|
||||
val securityContext = ReactiveSecurityContextHolder.getContext().awaitSingle()
|
||||
return userService.getByUsername(securityContext.authentication.name).toDto()
|
||||
fun getMe(): UserDTO {
|
||||
logger.info("Get Me")
|
||||
authService.getSecurityUser()
|
||||
|
||||
return authService.getSecurityUser().toDto()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package space.luminic.finance.api
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||
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.PutMapping
|
||||
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
|
||||
import space.luminic.finance.dtos.BudgetDTO
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.mappers.BudgetMapper.toDto
|
||||
import space.luminic.finance.mappers.BudgetMapper.toShortDto
|
||||
import space.luminic.finance.mappers.TransactionMapper.toDto
|
||||
import space.luminic.finance.models.Budget
|
||||
import space.luminic.finance.services.BudgetService
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/spaces/{spaceId}/budgets")
|
||||
@SecurityScheme(
|
||||
name = "bearerAuth",
|
||||
type = SecuritySchemeType.HTTP,
|
||||
bearerFormat = "JWT",
|
||||
scheme = "bearer"
|
||||
)
|
||||
class BudgetController(
|
||||
private val budgetService: BudgetService
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
suspend fun getBudgets(
|
||||
@PathVariable spaceId: String,
|
||||
@RequestParam(value = "sort", defaultValue = "dateFrom") sortBy: String,
|
||||
@RequestParam("direction", defaultValue = "DESC") sortDirection: String
|
||||
): List<BudgetDTO.BudgetShortInfoDTO> {
|
||||
return budgetService.getBudgets(spaceId, sortBy, sortDirection).map { it.toShortDto() }
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/{budgetId}")
|
||||
suspend fun getBudgetById(@PathVariable spaceId: String, @PathVariable budgetId: String): BudgetDTO {
|
||||
return budgetService.getBudget(spaceId, budgetId).toDto()
|
||||
}
|
||||
|
||||
@GetMapping("/{budgetId}/transactions")
|
||||
suspend fun getBudgetTransactions(
|
||||
@PathVariable spaceId: String,
|
||||
@PathVariable budgetId: String
|
||||
): BudgetDTO.BudgetTransactionsDTO {
|
||||
return budgetService.getBudgetTransactions(spaceId, budgetId).toDto()
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
suspend fun createBudget(
|
||||
@PathVariable spaceId: String,
|
||||
@RequestBody createBudgetDTO: BudgetDTO.CreateBudgetDTO
|
||||
): BudgetDTO {
|
||||
return budgetService.createBudget(spaceId, Budget.BudgetType.SPECIAL, createBudgetDTO).toDto()
|
||||
}
|
||||
|
||||
@PutMapping("/{budgetId}")
|
||||
suspend fun updateBudget(
|
||||
@PathVariable spaceId: String,
|
||||
@PathVariable budgetId: String,
|
||||
@RequestBody updateBudgetDTO: BudgetDTO.UpdateBudgetDTO
|
||||
): BudgetDTO {
|
||||
return budgetService.updateBudget(spaceId, updateBudgetDTO).toDto()
|
||||
}
|
||||
|
||||
@DeleteMapping("/{budgetId}")
|
||||
suspend fun deleteBudget(@PathVariable spaceId: String, @PathVariable budgetId: String) {
|
||||
budgetService.deleteBudget(spaceId, budgetId)
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,10 @@ package space.luminic.finance.api
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||
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.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import space.luminic.finance.dtos.CategoryDTO
|
||||
import space.luminic.finance.mappers.CategoryMapper.toDto
|
||||
import space.luminic.finance.models.Category
|
||||
import space.luminic.finance.services.CategoryService
|
||||
|
||||
@RestController
|
||||
@@ -23,43 +17,47 @@ import space.luminic.finance.services.CategoryService
|
||||
scheme = "bearer"
|
||||
)
|
||||
class CategoryController(
|
||||
private val categoryService: CategoryService,
|
||||
service: CategoryService
|
||||
private val categoryService: CategoryService
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
suspend fun getCategories(@PathVariable spaceId: String): List<CategoryDTO> {
|
||||
fun getCategories(@PathVariable spaceId: Int): List<CategoryDTO> {
|
||||
return categoryService.getCategories(spaceId).map { it.toDto() }
|
||||
}
|
||||
|
||||
@GetMapping("/{categoryId}")
|
||||
suspend fun getCategory(@PathVariable spaceId: String, @PathVariable categoryId: String): CategoryDTO {
|
||||
fun getCategory(@PathVariable spaceId: Int, @PathVariable categoryId: Int): CategoryDTO {
|
||||
return categoryService.getCategory(spaceId, categoryId).toDto()
|
||||
}
|
||||
|
||||
|
||||
@PostMapping
|
||||
suspend fun createCategory(
|
||||
@PathVariable spaceId: String,
|
||||
fun createCategory(
|
||||
@PathVariable spaceId: Int,
|
||||
@RequestBody categoryDTO: CategoryDTO.CreateCategoryDTO
|
||||
): CategoryDTO {
|
||||
return categoryService.createCategory(spaceId, categoryDTO).toDto()
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/_many")
|
||||
fun createManyCategory(@PathVariable spaceId: Int, @RequestBody categoryDTOs: List<Category.CategoryEtalon>): List<CategoryDTO> {
|
||||
return categoryService.createEtalonCategoriesForSpace(spaceId).map { it.toDto() }
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/{categoryId}")
|
||||
suspend fun updateCategory(
|
||||
@PathVariable spaceId: String,
|
||||
@PathVariable categoryId: String,
|
||||
fun updateCategory(
|
||||
@PathVariable spaceId: Int,
|
||||
@PathVariable categoryId: Int,
|
||||
@RequestBody categoryDTO: CategoryDTO.UpdateCategoryDTO
|
||||
): CategoryDTO {
|
||||
return categoryService.updateCategory(spaceId, categoryDTO).toDto()
|
||||
return categoryService.updateCategory(spaceId, categoryId, categoryDTO).toDto()
|
||||
}
|
||||
|
||||
|
||||
@DeleteMapping("/{categoryId}")
|
||||
suspend fun deleteCategory(@PathVariable spaceId: String, @PathVariable categoryId: String) {
|
||||
fun deleteCategory(@PathVariable spaceId: Int, @PathVariable categoryId: Int) {
|
||||
categoryService.deleteCategory(spaceId, categoryId)
|
||||
}
|
||||
}
|
||||
19
src/main/kotlin/space/luminic/finance/api/GoalController.kt
Normal file
19
src/main/kotlin/space/luminic/finance/api/GoalController.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
package space.luminic.finance.api
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import space.luminic.finance.dtos.GoalDTO
|
||||
import space.luminic.finance.mappers.GoalMapper.toDto
|
||||
import space.luminic.finance.services.GoalService
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/spaces/{spaceId}/goals")
|
||||
class GoalController(private val goalService: GoalService) {
|
||||
|
||||
@GetMapping
|
||||
fun findAll(@PathVariable spaceId: Int): List<GoalDTO> {
|
||||
return goalService.findAllBySpaceId(spaceId).map { it.toDto() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package space.luminic.finance.api
|
||||
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import space.luminic.finance.dtos.RecurrentOperationDTO
|
||||
import space.luminic.finance.mappers.RecurrentOperationMapper.toDTO
|
||||
import space.luminic.finance.services.RecurrentOperationService
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = ["/spaces/{spaceId}/recurrents"])
|
||||
class RecurrentOperationController(private val recurrentOperationService: RecurrentOperationService) {
|
||||
|
||||
|
||||
@GetMapping
|
||||
fun findAll(@PathVariable spaceId: Int): List<RecurrentOperationDTO> {
|
||||
return recurrentOperationService.findBySpaceId(spaceId).map { it.toDTO() }
|
||||
}
|
||||
|
||||
@GetMapping("/{operationId}")
|
||||
fun findById(@PathVariable spaceId: Int, @PathVariable operationId: Int): RecurrentOperationDTO {
|
||||
return recurrentOperationService.findBySpaceIdAndId(spaceId, operationId).toDTO()
|
||||
}
|
||||
|
||||
|
||||
@PostMapping
|
||||
fun createOperation(@PathVariable spaceId: Int, @RequestBody createOperation: RecurrentOperationDTO.CreateRecurrentOperationDTO): Map<String, Int> {
|
||||
return mapOf("id" to recurrentOperationService.create(spaceId, createOperation))
|
||||
}
|
||||
|
||||
@PutMapping("/{operationId}")
|
||||
fun updateOperation(@PathVariable spaceId: Int, @PathVariable operationId: Int, @RequestBody operation: RecurrentOperationDTO.UpdateRecurrentOperationDTO): Map<String, Int> {
|
||||
recurrentOperationService.update(spaceId, operationId, operation)
|
||||
return mapOf("id" to operationId)
|
||||
}
|
||||
|
||||
@DeleteMapping("/{operationId}")
|
||||
fun deleteOperation(@PathVariable spaceId: Int, @PathVariable operationId: Int) {
|
||||
recurrentOperationService.delete(spaceId, operationId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,14 +2,7 @@ package space.luminic.finance.api
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||
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.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import space.luminic.finance.dtos.CurrencyDTO
|
||||
import space.luminic.finance.mappers.CurrencyMapper.toDto
|
||||
import space.luminic.finance.services.CurrencyService
|
||||
@@ -27,27 +20,27 @@ class ReferenceController(
|
||||
) {
|
||||
|
||||
@GetMapping("/currencies")
|
||||
suspend fun getCurrencies(): List<CurrencyDTO> {
|
||||
fun getCurrencies(): List<CurrencyDTO> {
|
||||
return currencyService.getCurrencies().map { it.toDto() }
|
||||
}
|
||||
|
||||
@GetMapping("/currencies/{currencyCode}")
|
||||
suspend fun getCurrency(@PathVariable currencyCode: String): CurrencyDTO {
|
||||
fun getCurrency(@PathVariable currencyCode: String): CurrencyDTO {
|
||||
return currencyService.getCurrency(currencyCode).toDto()
|
||||
}
|
||||
|
||||
@PostMapping("/currencies")
|
||||
suspend fun createCurrency(@RequestBody currencyDTO: CurrencyDTO): CurrencyDTO {
|
||||
fun createCurrency(@RequestBody currencyDTO: CurrencyDTO): CurrencyDTO {
|
||||
return currencyService.createCurrency(currencyDTO).toDto()
|
||||
}
|
||||
|
||||
@PutMapping("/currencies/{currencyCode}")
|
||||
suspend fun updateCurrency(@PathVariable currencyCode: String, @RequestBody currencyDTO: CurrencyDTO): CurrencyDTO {
|
||||
fun updateCurrency(@PathVariable currencyCode: String, @RequestBody currencyDTO: CurrencyDTO): CurrencyDTO {
|
||||
return currencyService.updateCurrency(currencyDTO).toDto()
|
||||
}
|
||||
|
||||
@DeleteMapping("/currencies/{currencyCode}")
|
||||
suspend fun deleteCurrency(@PathVariable currencyCode: String) {
|
||||
fun deleteCurrency(@PathVariable currencyCode: String) {
|
||||
currencyService.deleteCurrency(currencyCode)
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,9 @@ package space.luminic.finance.api
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||
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.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import space.luminic.finance.dtos.SpaceDTO
|
||||
import space.luminic.finance.mappers.SpaceMapper.toDto
|
||||
import space.luminic.finance.models.Space
|
||||
import space.luminic.finance.services.SpaceService
|
||||
|
||||
@RestController
|
||||
@@ -29,27 +21,27 @@ class SpaceController(
|
||||
|
||||
|
||||
@GetMapping
|
||||
suspend fun getSpaces(): List<SpaceDTO> {
|
||||
fun getSpaces(): List<SpaceDTO> {
|
||||
return spaceService.getSpaces().map { it.toDto() }
|
||||
}
|
||||
|
||||
@GetMapping("/{spaceId}")
|
||||
suspend fun getSpace(@PathVariable spaceId: String): SpaceDTO {
|
||||
fun getSpace(@PathVariable spaceId: Int): SpaceDTO {
|
||||
return spaceService.getSpace(spaceId).toDto()
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
suspend fun createSpace(@RequestBody space: SpaceDTO.CreateSpaceDTO): SpaceDTO {
|
||||
return spaceService.createSpace(space).toDto()
|
||||
fun createSpace(@RequestBody space: SpaceDTO.CreateSpaceDTO): Map<String, Int> {
|
||||
return mapOf("id" to spaceService.createSpace(space) )
|
||||
}
|
||||
|
||||
@PutMapping("/{spaceId}")
|
||||
suspend fun updateSpace(@PathVariable spaceId: String, @RequestBody space: SpaceDTO.UpdateSpaceDTO): SpaceDTO {
|
||||
return spaceService.updateSpace(spaceId, space).toDto()
|
||||
fun updateSpace(@PathVariable spaceId: Int, @RequestBody space: SpaceDTO.UpdateSpaceDTO): Map<String, Int> {
|
||||
return mapOf("id" to spaceService.updateSpace(spaceId, space) )
|
||||
}
|
||||
|
||||
@DeleteMapping("/{spaceId}")
|
||||
suspend fun deleteSpace(@PathVariable spaceId: String) {
|
||||
fun deleteSpace(@PathVariable spaceId: Int) {
|
||||
spaceService.deleteSpace(spaceId)
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,7 @@ package space.luminic.finance.api
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||
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.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.mappers.TransactionMapper.toDto
|
||||
import space.luminic.finance.services.TransactionService
|
||||
@@ -25,34 +18,32 @@ import space.luminic.finance.services.TransactionService
|
||||
)
|
||||
class TransactionController (
|
||||
private val transactionService: TransactionService,
|
||||
service: TransactionService,
|
||||
transactionService1: TransactionService,
|
||||
){
|
||||
|
||||
|
||||
@GetMapping
|
||||
suspend fun getTransactions(@PathVariable spaceId: String) : List<TransactionDTO>{
|
||||
fun getTransactions(@PathVariable spaceId: Int) : List<TransactionDTO>{
|
||||
return transactionService.getTransactions(spaceId, TransactionService.TransactionsFilter(),"date", "DESC").map { it.toDto() }
|
||||
}
|
||||
|
||||
@GetMapping("/{transactionId}")
|
||||
suspend fun getTransaction(@PathVariable spaceId: String, @PathVariable transactionId: String): TransactionDTO {
|
||||
fun getTransaction(@PathVariable spaceId: Int, @PathVariable transactionId: Int): TransactionDTO {
|
||||
return transactionService.getTransaction(spaceId, transactionId).toDto()
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
suspend fun createTransaction(@PathVariable spaceId: String, @RequestBody transactionDTO: TransactionDTO.CreateTransactionDTO): TransactionDTO {
|
||||
return transactionService.createTransaction(spaceId, transactionDTO).toDto()
|
||||
fun createTransaction(@PathVariable spaceId: Int, @RequestBody transactionDTO: TransactionDTO.CreateTransactionDTO): Map<String, Int> {
|
||||
return mapOf("id" to transactionService.createTransaction(spaceId, transactionDTO))
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/{transactionId}")
|
||||
suspend fun updateTransaction(@PathVariable spaceId: String, @PathVariable transactionId: String, @RequestBody transactionDTO: TransactionDTO.UpdateTransactionDTO): TransactionDTO {
|
||||
return transactionService.updateTransaction(spaceId, transactionDTO).toDto()
|
||||
fun updateTransaction(@PathVariable spaceId: Int, @PathVariable transactionId: Int, @RequestBody transactionDTO: TransactionDTO.UpdateTransactionDTO): Map<String, Int> {
|
||||
return mapOf("id" to transactionService.updateTransaction(spaceId, transactionId, transactionDTO))
|
||||
}
|
||||
|
||||
@DeleteMapping("/{transactionId}")
|
||||
suspend fun deleteTransaction(@PathVariable spaceId: String, @PathVariable transactionId: String) {
|
||||
fun deleteTransaction(@PathVariable spaceId: Int, @PathVariable transactionId: Int) {
|
||||
transactionService.deleteTransaction(spaceId, transactionId)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,109 +1,112 @@
|
||||
package space.luminic.finance.api.exceptionHandlers
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
import space.luminic.finance.configs.AuthException
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
|
||||
@RestControllerAdvice
|
||||
class GlobalExceptionHandler {
|
||||
|
||||
data class ErrorResponse(
|
||||
val timestamp: Long = System.currentTimeMillis(),
|
||||
val status: Int,
|
||||
val error: String,
|
||||
val message: String?,
|
||||
val path: String,
|
||||
)
|
||||
|
||||
// Изменённый параметр request
|
||||
fun constructErrorBody(
|
||||
e: Exception,
|
||||
message: String,
|
||||
status: HttpStatus,
|
||||
request: ServerHttpRequest
|
||||
): Map<String, Any?> {
|
||||
val errorResponse = mapOf(
|
||||
"timestamp" to System.currentTimeMillis(),
|
||||
"status" to status.value(),
|
||||
"error" to message,
|
||||
"message" to e.message,
|
||||
"path" to request.path.value()
|
||||
request: HttpServletRequest // <--- Изменённый тип
|
||||
): ErrorResponse {
|
||||
return ErrorResponse(
|
||||
status = status.value(),
|
||||
error = message,
|
||||
message = e.message,
|
||||
path = request.requestURI // <--- Получаем путь через HttpServletRequest
|
||||
)
|
||||
return errorResponse
|
||||
}
|
||||
|
||||
@ExceptionHandler(AuthException::class)
|
||||
// Изменённый параметр exchange
|
||||
fun handleAuthenticationException(
|
||||
ex: AuthException,
|
||||
exchange: ServerWebExchange
|
||||
): Mono<ResponseEntity<Map<String, Any?>>?> {
|
||||
request: HttpServletRequest // <--- Изменённый тип
|
||||
): ResponseEntity<ErrorResponse>? {
|
||||
ex.printStackTrace()
|
||||
|
||||
return Mono.just(
|
||||
ResponseEntity(
|
||||
constructErrorBody(
|
||||
ex,
|
||||
ex.message.toString(),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
exchange.request
|
||||
), HttpStatus.UNAUTHORIZED
|
||||
)
|
||||
return ResponseEntity(
|
||||
constructErrorBody(
|
||||
ex,
|
||||
ex.message.toString(),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
request // <--- Передаём HttpServletRequest
|
||||
),
|
||||
HttpStatus.UNAUTHORIZED
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(NotFoundException::class)
|
||||
// Изменённый параметр exchange
|
||||
fun handleNotFoundException(
|
||||
e: NotFoundException,
|
||||
exchange: ServerWebExchange
|
||||
): Mono<ResponseEntity<Map<String, Any?>>?> {
|
||||
request: HttpServletRequest // <--- Изменённый тип
|
||||
): ResponseEntity<ErrorResponse>? {
|
||||
e.printStackTrace()
|
||||
|
||||
return Mono.just(
|
||||
ResponseEntity(
|
||||
constructErrorBody(
|
||||
e,
|
||||
e.message.toString(),
|
||||
HttpStatus.NOT_FOUND,
|
||||
exchange.request
|
||||
), HttpStatus.NOT_FOUND
|
||||
)
|
||||
return ResponseEntity(
|
||||
constructErrorBody(
|
||||
e,
|
||||
e.message.toString(),
|
||||
HttpStatus.NOT_FOUND,
|
||||
request // <--- Передаём HttpServletRequest
|
||||
),
|
||||
HttpStatus.NOT_FOUND
|
||||
)
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException::class)
|
||||
// Изменённый параметр exchange
|
||||
fun handleIllegalArgumentException(
|
||||
e: IllegalArgumentException,
|
||||
exchange: ServerWebExchange
|
||||
): Mono<ResponseEntity<Map<String, Any?>>?> {
|
||||
request: HttpServletRequest // <--- Изменённый тип
|
||||
): ResponseEntity<ErrorResponse>? {
|
||||
e.printStackTrace()
|
||||
|
||||
return Mono.just(
|
||||
ResponseEntity(
|
||||
constructErrorBody(
|
||||
e,
|
||||
e.message.toString(),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
exchange.request
|
||||
), HttpStatus.BAD_REQUEST
|
||||
)
|
||||
return ResponseEntity(
|
||||
constructErrorBody(
|
||||
e,
|
||||
e.message.toString(),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
request // <--- Передаём HttpServletRequest
|
||||
),
|
||||
HttpStatus.BAD_REQUEST
|
||||
)
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception::class)
|
||||
// Изменённый параметр exchange
|
||||
fun handleGenericException(
|
||||
e: Exception,
|
||||
exchange: ServerWebExchange
|
||||
): Mono<out ResponseEntity<out Map<String, Any?>>?> {
|
||||
request: HttpServletRequest // <--- Изменённый тип
|
||||
): ResponseEntity<ErrorResponse>? {
|
||||
e.printStackTrace()
|
||||
|
||||
|
||||
return Mono.just(
|
||||
ResponseEntity(
|
||||
constructErrorBody(
|
||||
e,
|
||||
e.message.toString(),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
exchange.request
|
||||
), HttpStatus.INTERNAL_SERVER_ERROR
|
||||
)
|
||||
return ResponseEntity(
|
||||
constructErrorBody(
|
||||
e,
|
||||
e.message.toString(),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
request // <--- Передаём HttpServletRequest
|
||||
),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,37 @@
|
||||
package space.luminic.finance.api.exceptionHandlers
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties
|
||||
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions
|
||||
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 reactor.core.publisher.Mono
|
||||
|
||||
@Component
|
||||
@Order(-2)
|
||||
class GlobalErrorWebExceptionHandler(
|
||||
errorAttributes: ErrorAttributes,
|
||||
applicationContext: ApplicationContext,
|
||||
serverCodecConfigurer: ServerCodecConfigurer
|
||||
) : AbstractErrorWebExceptionHandler(
|
||||
errorAttributes,
|
||||
WebProperties.Resources(),
|
||||
applicationContext
|
||||
) {
|
||||
|
||||
init {
|
||||
super.setMessageWriters(serverCodecConfigurer.writers)
|
||||
super.setMessageReaders(serverCodecConfigurer.readers)
|
||||
}
|
||||
|
||||
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
|
||||
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse)
|
||||
}
|
||||
|
||||
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
|
||||
val errorAttributesMap = getErrorAttributes(
|
||||
request,
|
||||
ErrorAttributeOptions.of(
|
||||
ErrorAttributeOptions.Include.MESSAGE
|
||||
)
|
||||
)
|
||||
return ServerResponse.status(401)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(BodyInserters.fromValue(errorAttributesMap))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
//
|
||||
//@Component
|
||||
//@Order(-2)
|
||||
//class GlobalErrorWebExceptionHandler(
|
||||
// errorAttributes: ErrorAttributes,
|
||||
// applicationContext: ApplicationContext,
|
||||
// serverCodecConfigurer: ServerCodecConfigurer
|
||||
//) : AbstractErrorWebExceptionHandler(
|
||||
// errorAttributes,
|
||||
// WebProperties.Resources(),
|
||||
// applicationContext
|
||||
//) {
|
||||
//
|
||||
// init {
|
||||
// super.setMessageWriters(serverCodecConfigurer.writers)
|
||||
// super.setMessageReaders(serverCodecConfigurer.readers)
|
||||
// }
|
||||
//
|
||||
// override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
|
||||
// return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse)
|
||||
// }
|
||||
//
|
||||
// private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
|
||||
// val errorAttributesMap = getErrorAttributes(
|
||||
// request,
|
||||
// ErrorAttributeOptions.of(
|
||||
// ErrorAttributeOptions.Include.MESSAGE
|
||||
// )
|
||||
// )
|
||||
// return ServerResponse.status(401)
|
||||
// .contentType(MediaType.APPLICATION_JSON)
|
||||
// .body(BodyInserters.fromValue(errorAttributesMap))
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -1,56 +1,80 @@
|
||||
package space.luminic.finance.configs
|
||||
|
||||
import kotlinx.coroutines.reactor.mono
|
||||
import jakarta.servlet.FilterChain
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder
|
||||
import org.springframework.security.core.context.SecurityContextImpl
|
||||
import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.WebFilterChain
|
||||
import reactor.core.publisher.Mono
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
import space.luminic.finance.services.AuthService
|
||||
|
||||
@Component
|
||||
class BearerTokenFilter(private val authService: AuthService) : SecurityContextServerWebExchangeWebFilter() {
|
||||
// private val logger = LoggerFactory.getLogger(BearerTokenFilter::class.java)
|
||||
class BearerTokenFilter(
|
||||
private val authService: AuthService
|
||||
) : OncePerRequestFilter() {
|
||||
|
||||
// Публичные пути — как в твоём WebFlux-фильтре
|
||||
private val publicMatchers = listOf(
|
||||
AntPathRequestMatcher("/auth/login", "POST"),
|
||||
AntPathRequestMatcher("/auth/register", "POST"),
|
||||
AntPathRequestMatcher("/auth/tgLogin", "POST"),
|
||||
AntPathRequestMatcher("/actuator/**"),
|
||||
AntPathRequestMatcher("/static/**"),
|
||||
AntPathRequestMatcher("/wishlistexternal/**"),
|
||||
AntPathRequestMatcher("/swagger-ui/**"),
|
||||
AntPathRequestMatcher("/v3/api-docs/**"),
|
||||
)
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
|
||||
val token = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)?.removePrefix("Bearer ")
|
||||
override fun shouldNotFilter(request: HttpServletRequest): Boolean {
|
||||
return publicMatchers.any { it.matches(request) }
|
||||
}
|
||||
|
||||
if (exchange.request.path.value() in listOf(
|
||||
"/api/auth/login",
|
||||
"/api/auth/register",
|
||||
"/api/auth/tgLogin"
|
||||
) || exchange.request.path.value().startsWith("/api/actuator") || exchange.request.path.value()
|
||||
.startsWith("/api/static/")
|
||||
|| exchange.request.path.value()
|
||||
.startsWith("/api/wishlistexternal/")
|
||||
|| exchange.request.path.value().startsWith("/api/swagger-ui") || exchange.request.path.value().startsWith("/api/v3/api-docs")
|
||||
) {
|
||||
return chain.filter(exchange)
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
chain: FilterChain
|
||||
) {
|
||||
val raw = request.getHeader(HttpHeaders.AUTHORIZATION)
|
||||
val token = raw?.takeIf { it.startsWith("Bearer ", ignoreCase = true) }?.substring(7)
|
||||
|
||||
if (token.isNullOrBlank()) {
|
||||
unauthorized(response, "Authorization token is missing")
|
||||
return
|
||||
}
|
||||
|
||||
return if (token != null) {
|
||||
mono {
|
||||
val userDetails = authService.isTokenValid(token) // suspend вызов
|
||||
val authorities = userDetails.roles.map { SimpleGrantedAuthority(it) }
|
||||
val securityContext = SecurityContextImpl(
|
||||
UsernamePasswordAuthenticationToken(userDetails.username, null, authorities)
|
||||
)
|
||||
securityContext
|
||||
}.flatMap { securityContext ->
|
||||
chain.filter(exchange)
|
||||
.contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)))
|
||||
}
|
||||
} else {
|
||||
Mono.error(AuthException("Authorization token is missing"))
|
||||
try {
|
||||
// AuthService.isTokenValid у тебя suspend — в Servlet-фильтре вызываем через runBlocking
|
||||
|
||||
val userDetails = authService.isTokenValid(token)
|
||||
|
||||
val authorities = userDetails.roles.map { SimpleGrantedAuthority(it) }
|
||||
val auth = UsernamePasswordAuthenticationToken(userDetails.id, null, authorities)
|
||||
|
||||
SecurityContextHolder.getContext().authentication = auth
|
||||
chain.doFilter(request, response)
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
unauthorized(response, ex.message ?: "Invalid token")
|
||||
} finally {
|
||||
// важно не «протекать» аутентификацией за пределы запроса
|
||||
SecurityContextHolder.clearContext()
|
||||
}
|
||||
}
|
||||
|
||||
private fun unauthorized(response: HttpServletResponse, message: String) {
|
||||
response.status = HttpServletResponse.SC_UNAUTHORIZED
|
||||
response.contentType = "application/json"
|
||||
response.writer.use { out ->
|
||||
out.write("""{"error":"unauthorized","message":${message.quoteJson()}}""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.quoteJson(): String =
|
||||
"\"" + this.replace("\\", "\\\\").replace("\"", "\\\"") + "\""
|
||||
}
|
||||
|
||||
|
||||
open class AuthException(msg: String) : RuntimeException(msg)
|
||||
@@ -1,9 +1,5 @@
|
||||
package space.luminic.finance.configs
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
|
||||
//class CommonConfig {
|
||||
// @Bean
|
||||
@@ -12,8 +8,8 @@ import org.springframework.context.annotation.Configuration
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
@ConfigurationProperties(prefix = "nlp")
|
||||
data class NLPConfig(
|
||||
val address: String,
|
||||
)
|
||||
//
|
||||
//@ConfigurationProperties(prefix = "nlp")
|
||||
//data class NLPConfig(
|
||||
// val address: String,
|
||||
//)
|
||||
31
src/main/kotlin/space/luminic/finance/configs/JpaConfig.kt
Normal file
31
src/main/kotlin/space/luminic/finance/configs/JpaConfig.kt
Normal file
@@ -0,0 +1,31 @@
|
||||
package space.luminic.finance.configs
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.data.domain.AuditorAware
|
||||
import org.springframework.stereotype.Component
|
||||
import space.luminic.finance.models.User
|
||||
import space.luminic.finance.services.AuthService
|
||||
import java.util.*
|
||||
|
||||
@Configuration
|
||||
class JpaConfig // Класс для включения аудита
|
||||
|
||||
@Component // Помечаем как Spring-компонент
|
||||
class AuditorAwareImpl(
|
||||
private val authService: AuthService,
|
||||
) : AuditorAware<User> { // Убедитесь, что тип возвращаемого ID совпадает (Int)
|
||||
|
||||
override fun getCurrentAuditor(): Optional<User> {
|
||||
return try {
|
||||
val currentUser = authService.getSecurityUser()
|
||||
// Проверяем, что пользователь не null перед доступом к id
|
||||
Optional.of(currentUser) // currentUser.id должен быть Int
|
||||
|
||||
} catch (ex: Exception) {
|
||||
// Логирование (по желанию)
|
||||
// logger.debug("Authentication not found or invalid, auditor is null", ex)
|
||||
// Если возникла ошибка при получении пользователя (например, не аутентифицирован), возвращаем Optional.empty()
|
||||
Optional.empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,62 @@
|
||||
package space.luminic.finance.configs
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.security.config.web.server.SecurityWebFiltersOrder
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.http.SessionCreationPolicy
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
import org.springframework.web.cors.CorsConfiguration
|
||||
import org.springframework.web.cors.CorsConfigurationSource
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class SecurityConfig(
|
||||
|
||||
private val bearerTokenFilter: BearerTokenFilter, // см. примечание ниже
|
||||
) {
|
||||
@Bean
|
||||
fun securityWebFilterChain(
|
||||
http: ServerHttpSecurity,
|
||||
bearerTokenFilter: BearerTokenFilter
|
||||
): SecurityWebFilterChain {
|
||||
return http
|
||||
.csrf { it.disable() }
|
||||
.cors { it.configurationSource(corsConfigurationSource()) }
|
||||
|
||||
.logout { it.disable() }
|
||||
.authorizeExchange {
|
||||
it.pathMatchers(HttpMethod.POST, "/auth/login", "/auth/register", "/auth/tgLogin").permitAll()
|
||||
it.pathMatchers("/actuator/**", "/static/**").permitAll()
|
||||
it.pathMatchers("/wishlistexternal/**").permitAll()
|
||||
it.pathMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**").permitAll()
|
||||
it.anyExchange().authenticated()
|
||||
@Bean
|
||||
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http
|
||||
.csrf {
|
||||
it.disable()
|
||||
}
|
||||
.addFilterAt(
|
||||
bearerTokenFilter,
|
||||
SecurityWebFiltersOrder.AUTHENTICATION
|
||||
) // BearerTokenFilter только для authenticated
|
||||
.build()
|
||||
}
|
||||
.cors { it.configurationSource(corsConfigurationSource()) }
|
||||
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
|
||||
.logout { it.disable() }
|
||||
|
||||
.authorizeHttpRequests {
|
||||
it.requestMatchers(HttpMethod.POST, "/auth/login", "/auth/register", "/auth/tgLogin").permitAll()
|
||||
it.requestMatchers("/actuator/**", "/static/**").permitAll()
|
||||
it.requestMatchers("/wishlistexternal/**").permitAll()
|
||||
it.requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**").permitAll()
|
||||
it.anyRequest().authenticated()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun passwordEncoder(): PasswordEncoder {
|
||||
return BCryptPasswordEncoder()
|
||||
// ваш JWT-фильтр до стандартной аутентификации
|
||||
.addFilterBefore(bearerTokenFilter, UsernamePasswordAuthenticationFilter::class.java)
|
||||
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun corsConfigurationSource(): org.springframework.web.cors.reactive.CorsConfigurationSource {
|
||||
val corsConfig = org.springframework.web.cors.CorsConfiguration()
|
||||
corsConfig.allowedOrigins =
|
||||
listOf("https://luminic.space", "http://localhost:5173") // Ваши разрешённые источники
|
||||
corsConfig.allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")
|
||||
corsConfig.allowedHeaders = listOf("*")
|
||||
corsConfig.allowCredentials = true
|
||||
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
||||
|
||||
val source = org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", corsConfig)
|
||||
@Bean
|
||||
fun corsConfigurationSource(): CorsConfigurationSource {
|
||||
val cors = CorsConfiguration().apply {
|
||||
allowedOrigins = listOf("https://luminic.space", "http://localhost:5173")
|
||||
allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")
|
||||
allowedHeaders = listOf("*")
|
||||
allowCredentials = true
|
||||
}
|
||||
val source = UrlBasedCorsConfigurationSource()
|
||||
source.registerCorsConfiguration("/**", cors)
|
||||
return source
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package space.luminic.finance.dtos
|
||||
|
||||
import space.luminic.finance.models.Account.AccountType
|
||||
import space.luminic.finance.models.Currency
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
|
||||
data class AccountDTO(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val type: AccountType = AccountType.COLLECTING,
|
||||
val currencyCode: String,
|
||||
val currency: CurrencyDTO? = null,
|
||||
var balance: BigDecimal = BigDecimal.ZERO,
|
||||
val goal: GoalDTO? = null,
|
||||
val createdBy: String? = null,
|
||||
val createdAt: Instant? = null,
|
||||
val updatedBy: String? = null,
|
||||
val updatedAt: Instant? = null,
|
||||
){
|
||||
data class CreateAccountDTO(
|
||||
val name: String,
|
||||
val type: AccountType,
|
||||
val currencyCode: String,
|
||||
val amount: BigDecimal,
|
||||
val goalId: String? = null,
|
||||
)
|
||||
|
||||
data class UpdateAccountDTO(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val type: AccountType,
|
||||
val currencyCode: String,
|
||||
val amount: BigDecimal,
|
||||
val goalId: String? = null,
|
||||
)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package space.luminic.finance.dtos
|
||||
|
||||
import space.luminic.finance.models.Budget
|
||||
import space.luminic.finance.models.Category
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.models.Transaction.TransactionKind
|
||||
import space.luminic.finance.models.Transaction.TransactionType
|
||||
import space.luminic.finance.models.User
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
data class BudgetDTO(
|
||||
val id: String?,
|
||||
val type: Budget.BudgetType,
|
||||
var name: String,
|
||||
var description: String? = null,
|
||||
var dateFrom: LocalDate,
|
||||
var dateTo: LocalDate,
|
||||
val isActive: Boolean,
|
||||
val createdBy: UserDTO?,
|
||||
val createdAt: Instant,
|
||||
var updatedBy: UserDTO?,
|
||||
var updatedAt: Instant,
|
||||
) {
|
||||
|
||||
data class BudgetShortInfoDTO(
|
||||
val id: String,
|
||||
val type: Budget.BudgetType,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val dateFrom: LocalDate,
|
||||
val dateTo: LocalDate,
|
||||
val createdBy: String
|
||||
)
|
||||
|
||||
data class BudgetCategoryDto(
|
||||
val category: CategoryDTO,
|
||||
val limit: BigDecimal,
|
||||
val totalPlannedAmount: BigDecimal,
|
||||
val totalSpendingAmount: BigDecimal
|
||||
)
|
||||
|
||||
data class CreateBudgetDTO(
|
||||
val name: String,
|
||||
val description: String? = null,
|
||||
val dateFrom: LocalDate,
|
||||
val dateTo: LocalDate
|
||||
)
|
||||
|
||||
data class UpdateBudgetDTO(
|
||||
val id: String,
|
||||
val name: String? = null,
|
||||
val description: String? = null,
|
||||
val dateFrom: LocalDate? = null,
|
||||
val dateTo: LocalDate? = null,
|
||||
)
|
||||
|
||||
data class BudgetTransactionsDTO(
|
||||
val categories: List<BudgetCategoryDto>,
|
||||
val transactions: List<TransactionDTO>,
|
||||
val plannedIncomeTransactions: List<TransactionDTO>,
|
||||
val plannedExpenseTransactions: List<TransactionDTO>,
|
||||
val instantIncomeTransactions: List<TransactionDTO>,
|
||||
val instantExpenseTransactions: List<TransactionDTO>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,26 +4,29 @@ import space.luminic.finance.models.Category.CategoryType
|
||||
import java.time.Instant
|
||||
|
||||
data class CategoryDTO(
|
||||
val id: String,
|
||||
val id: Int,
|
||||
val type: CategoryType,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val icon: String,
|
||||
val createdBy: UserDTO? = null,
|
||||
val createdAt: Instant,
|
||||
val createdAt: Instant? = null,
|
||||
val updatedBy: UserDTO? = null,
|
||||
val updatedAt: Instant,
|
||||
val updatedAt: Instant? = null,
|
||||
|
||||
) {
|
||||
data class CreateCategoryDTO(
|
||||
val name: String,
|
||||
|
||||
val type: CategoryType,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val icon: String,
|
||||
)
|
||||
|
||||
data class UpdateCategoryDTO(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val type: CategoryType,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val icon: String,
|
||||
)
|
||||
}
|
||||
@@ -1,20 +1,39 @@
|
||||
package space.luminic.finance.dtos
|
||||
|
||||
import space.luminic.finance.models.Goal
|
||||
import space.luminic.finance.models.Goal.GoalType
|
||||
import space.luminic.finance.models.Transaction
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
data class GoalDTO(
|
||||
val id: String,
|
||||
val id: Int,
|
||||
val type: GoalType,
|
||||
val name: String,
|
||||
val description: String? = null,
|
||||
val amount: BigDecimal,
|
||||
val date: LocalDate,
|
||||
val components: List<Goal.GoalComponent>,
|
||||
val transactions: List<Transaction>,
|
||||
val createdBy: UserDTO,
|
||||
val createdAt: Instant,
|
||||
val updatedBy: UserDTO,
|
||||
val updatedAt: Instant,
|
||||
val updatedBy: UserDTO? = null,
|
||||
val updatedAt: Instant? = null,
|
||||
) {
|
||||
data class CreateGoalDTO(
|
||||
val type: GoalType,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val amount: BigDecimal,
|
||||
val date: LocalDate
|
||||
)
|
||||
|
||||
data class UpdateGoalDTO(
|
||||
val type: GoalType,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val amount: BigDecimal,
|
||||
val date: LocalDate
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package space.luminic.finance.dtos
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
|
||||
data class RecurrentOperationDTO (
|
||||
val id: Int,
|
||||
val category: CategoryDTO,
|
||||
val name: String,
|
||||
val amount: BigDecimal,
|
||||
val date: Int,
|
||||
val createdBy: UserDTO? = null,
|
||||
val createdAt: Instant
|
||||
) {
|
||||
|
||||
data class CreateRecurrentOperationDTO (
|
||||
val categoryId: Int,
|
||||
val name: String,
|
||||
val amount: BigDecimal,
|
||||
val date: Int,
|
||||
)
|
||||
|
||||
data class UpdateRecurrentOperationDTO (
|
||||
val categoryId: Int,
|
||||
val name: String,
|
||||
val amount: BigDecimal,
|
||||
val date: Int,
|
||||
)
|
||||
}
|
||||
@@ -4,15 +4,29 @@ import space.luminic.finance.models.User
|
||||
import java.time.Instant
|
||||
|
||||
data class SpaceDTO(
|
||||
val id: String? = null,
|
||||
val id: Int? = null,
|
||||
val name: String,
|
||||
val owner: UserDTO,
|
||||
val participants: List<UserDTO> = emptyList(),
|
||||
val participants: Set<UserDTO> = emptySet(),
|
||||
val createdBy: UserDTO? = null,
|
||||
val createdAt: Instant,
|
||||
var updatedBy: UserDTO? = null,
|
||||
var updatedAt: Instant,
|
||||
) {
|
||||
|
||||
data class SpaceShortInfoDTO(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val isOwner: Boolean,
|
||||
val owner: User,
|
||||
val participant: User,
|
||||
val createdAt: Instant,
|
||||
val updatedAt: Instant? = null,
|
||||
val createdBy: User,
|
||||
val updatedBy: User? = null,
|
||||
)
|
||||
|
||||
|
||||
data class CreateSpaceDTO(
|
||||
val name: String,
|
||||
val createBasicCategories: Boolean = true,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package space.luminic.finance.dtos
|
||||
|
||||
data class SubscriptionDTO (
|
||||
val endpoint: String,
|
||||
val keys: Map<String, String>
|
||||
)
|
||||
@@ -1,52 +1,43 @@
|
||||
package space.luminic.finance.dtos
|
||||
|
||||
import space.luminic.finance.models.Account
|
||||
import space.luminic.finance.models.Category
|
||||
import space.luminic.finance.models.Transaction.TransactionKind
|
||||
import space.luminic.finance.models.Transaction.TransactionType
|
||||
import space.luminic.finance.models.User
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
|
||||
data class TransactionDTO(
|
||||
val id: String? = null,
|
||||
var parentId: String? = null,
|
||||
val id: Int? = null,
|
||||
var parentId: Int? = null,
|
||||
val type: TransactionType = TransactionType.EXPENSE,
|
||||
val kind: TransactionKind = TransactionKind.INSTANT,
|
||||
val categoryId: String,
|
||||
val category: CategoryDTO? = null,
|
||||
val category: CategoryDTO,
|
||||
val comment: String,
|
||||
val amount: BigDecimal,
|
||||
val fees: BigDecimal = BigDecimal.ZERO,
|
||||
val fromAccount: AccountDTO? = null,
|
||||
val toAccount: AccountDTO? = null,
|
||||
val date: Instant,
|
||||
val createdBy: String? = null,
|
||||
val updatedBy: String? = null,
|
||||
val date: LocalDate,
|
||||
val isDone: Boolean,
|
||||
val createdBy: UserDTO,
|
||||
val updatedBy: UserDTO? = null,
|
||||
) {
|
||||
data class CreateTransactionDTO(
|
||||
val type: TransactionType = TransactionType.EXPENSE,
|
||||
val kind: TransactionKind = TransactionKind.INSTANT,
|
||||
val categoryId: String,
|
||||
val categoryId: Int,
|
||||
val comment: String,
|
||||
val amount: BigDecimal,
|
||||
val fees: BigDecimal = BigDecimal.ZERO,
|
||||
val fromAccountId: String,
|
||||
val toAccountId: String? = null,
|
||||
val date: Instant
|
||||
val date: LocalDate,
|
||||
)
|
||||
|
||||
data class UpdateTransactionDTO(
|
||||
val id: String,
|
||||
val type: TransactionType = TransactionType.EXPENSE,
|
||||
val kind: TransactionKind = TransactionKind.INSTANT,
|
||||
val category: String,
|
||||
val categoryId: Int,
|
||||
val comment: String,
|
||||
val amount: BigDecimal,
|
||||
val fees: BigDecimal = BigDecimal.ZERO,
|
||||
val fromAccountId: String,
|
||||
val toAccountId: String? = null,
|
||||
val date: Instant
|
||||
val isDone: Boolean,
|
||||
val date: LocalDate
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package space.luminic.finance.dtos
|
||||
|
||||
import space.luminic.finance.models.User
|
||||
|
||||
data class UserDTO (
|
||||
var id: String,
|
||||
var id: Int,
|
||||
val username: String,
|
||||
var firstName: String,
|
||||
var tgId: String? = null,
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package space.luminic.finance.mappers
|
||||
|
||||
import space.luminic.finance.dtos.AccountDTO
|
||||
import space.luminic.finance.mappers.CurrencyMapper.toDto
|
||||
import space.luminic.finance.mappers.GoalMapper.toDto
|
||||
import space.luminic.finance.models.Account
|
||||
|
||||
object AccountMapper {
|
||||
|
||||
fun Account.toDto(): AccountDTO {
|
||||
return AccountDTO(
|
||||
id = this.id ?: throw IllegalStateException("Account ID must not be null"),
|
||||
name = this.name,
|
||||
type = this.type,
|
||||
currencyCode = this.currencyCode,
|
||||
currency = this.currency?.toDto(),
|
||||
balance = this.amount,
|
||||
goal = this.goal?.toDto(),
|
||||
createdBy = this.createdBy?.username,
|
||||
createdAt = this.createdAt,
|
||||
updatedBy = this.updatedBy?.username,
|
||||
updatedAt = this.updatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package space.luminic.finance.mappers
|
||||
|
||||
|
||||
import space.luminic.finance.dtos.BudgetDTO
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.mappers.CategoryMapper.toDto
|
||||
import space.luminic.finance.mappers.TransactionMapper.toDto
|
||||
import space.luminic.finance.mappers.UserMapper.toDto
|
||||
import space.luminic.finance.models.Budget
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.models.Transaction.TransactionKind
|
||||
import space.luminic.finance.models.Transaction.TransactionType
|
||||
import java.time.LocalDate
|
||||
|
||||
object BudgetMapper {
|
||||
|
||||
|
||||
fun Budget.toDto(): BudgetDTO {
|
||||
val isActive = this.dateTo.isBefore(LocalDate.now())
|
||||
|
||||
return BudgetDTO(
|
||||
id = this.id,
|
||||
type = this.type,
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
dateFrom = this.dateFrom,
|
||||
dateTo = this.dateTo,
|
||||
isActive = isActive,
|
||||
createdBy = this.createdBy?.toDto(),
|
||||
createdAt = this.createdAt?: throw IllegalArgumentException("created at is null"),
|
||||
updatedBy = this.updatedBy?.toDto(),
|
||||
updatedAt = this.updatedAt?: throw IllegalArgumentException("updated at is null"),
|
||||
)
|
||||
}
|
||||
|
||||
fun Budget.toShortDto(): BudgetDTO.BudgetShortInfoDTO = BudgetDTO.BudgetShortInfoDTO(
|
||||
id = this.id!!,
|
||||
type = this.type,
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
dateFrom = this.dateFrom,
|
||||
dateTo = this.dateTo,
|
||||
createdBy = this.createdBy?.username ?: throw IllegalArgumentException("created by is null"),
|
||||
)
|
||||
|
||||
fun List<Transaction>.toDto(): BudgetDTO.BudgetTransactionsDTO {
|
||||
|
||||
val planningSpending = this.filter {
|
||||
it.type == TransactionType.EXPENSE && it.kind == TransactionKind.PLANNING
|
||||
}
|
||||
|
||||
val planningIncomes = this.filter {
|
||||
it.type == TransactionType.INCOME && it.kind == TransactionKind.PLANNING
|
||||
}
|
||||
|
||||
val instantSpendingTransactions = this.filter {
|
||||
it.type == TransactionType.EXPENSE && it.kind == TransactionKind.INSTANT && it.parentId == null }
|
||||
|
||||
val instantIncomeTransactions = this.filter {
|
||||
it.type == TransactionType.INCOME && it.kind == TransactionKind.INSTANT && it.parentId == null
|
||||
}
|
||||
val totalPlannedIncome = planningIncomes.sumOf { it.amount }
|
||||
val totalPlannedSpending = planningSpending.sumOf { it.amount }
|
||||
|
||||
val categoriesWithPlannedAmounts = this.categories.map { cat ->
|
||||
val totalPlannedAmount = planningSpending
|
||||
.filter { it.id == cat.categoryId }
|
||||
.sumOf { it.amount }
|
||||
val totalInstantAmount = instantSpendingTransactions
|
||||
.filter { it.id == cat.categoryId }
|
||||
.sumOf { it.amount }
|
||||
BudgetDTO.BudgetCategoryDto(cat.category?.toDto() ?: throw java.lang.IllegalArgumentException("category is not provided"), cat.limit, totalPlannedAmount, totalInstantAmount)
|
||||
}
|
||||
return BudgetDTO.BudgetTransactionsDTO(
|
||||
categories = categoriesWithPlannedAmounts,
|
||||
transactions = this.map{it.toDto()},
|
||||
plannedIncomeTransactions = this.filter { it.kind == TransactionKind.PLANNING && it.type == TransactionType.INCOME }.map{it.toDto()},
|
||||
plannedExpenseTransactions = this.filter { it.kind == TransactionKind.PLANNING && it.type == TransactionType.EXPENSE }.map{it.toDto()},
|
||||
instantIncomeTransactions = this.filter { it.kind == TransactionKind.INSTANT && it.type == TransactionType.INCOME }.map{it.toDto()},
|
||||
instantExpenseTransactions = this.filter { it.kind == TransactionKind.INSTANT && it.type == TransactionType.EXPENSE }.map{it.toDto()},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -11,11 +11,12 @@ object CategoryMapper {
|
||||
id = this.id ?: throw IllegalArgumentException("category id is not set"),
|
||||
type = this.type,
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
icon = this.icon,
|
||||
createdBy = this.createdBy?.toDto(),
|
||||
createdAt = this.createdAt ?: throw IllegalArgumentException("created at is not set"),
|
||||
createdAt = this.createdAt,
|
||||
updatedBy = this.updatedBy?.toDto(),
|
||||
updatedAt = this.updatedAt ?: throw IllegalArgumentException("updated at is not set"),
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -10,12 +10,13 @@ object GoalMapper {
|
||||
id = this.id ?: throw IllegalArgumentException("Goal id is not provided"),
|
||||
type = this.type,
|
||||
name = this.name,
|
||||
amount = this.goalAmount,
|
||||
date = this.goalDate,
|
||||
amount = this.amount,
|
||||
date = this.untilDate,
|
||||
components = this.components,
|
||||
transactions = this.transactions,
|
||||
createdBy = (this.createdBy ?: throw IllegalArgumentException("created by not provided")).toDto(),
|
||||
createdAt = this.createdAt ?: throw IllegalArgumentException("created at not provided"),
|
||||
updatedBy = this.updatedBy?.toDto() ?: throw IllegalArgumentException("updated by not provided"),
|
||||
updatedAt = this.updatedAt ?: throw IllegalArgumentException("updatedAt not provided"),
|
||||
updatedBy = this.updatedBy?.toDto() ,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package space.luminic.finance.mappers
|
||||
|
||||
import space.luminic.finance.dtos.RecurrentOperationDTO
|
||||
import space.luminic.finance.mappers.CategoryMapper.toDto
|
||||
import space.luminic.finance.mappers.UserMapper.toDto
|
||||
import space.luminic.finance.models.RecurrentOperation
|
||||
|
||||
object RecurrentOperationMapper {
|
||||
|
||||
fun RecurrentOperation.toDTO(): RecurrentOperationDTO {
|
||||
return RecurrentOperationDTO(
|
||||
id = this.id ?: throw IllegalArgumentException("id is null"),
|
||||
category = this.category.toDto(),
|
||||
name = this.name,
|
||||
amount = this.amount,
|
||||
date = this.date,
|
||||
createdBy = this.createdBy?.toDto(),
|
||||
createdAt = this.createdAt ?: throw NullPointerException("created at"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ object SpaceMapper {
|
||||
fun Space.toDto() = SpaceDTO(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
owner = this.owner?.toDto() ?: throw IllegalArgumentException("Owner is not provided"),
|
||||
participants = this.participants?.map { it.toDto() } ?: emptyList(),
|
||||
owner = this.owner.toDto(),
|
||||
participants = this.participants.map { it.toDto() }.toSet(),
|
||||
createdBy = this.createdBy?.toDto(),
|
||||
createdAt = this.createdAt ?: throw IllegalArgumentException("createdAt is not provided"),
|
||||
updatedBy = this.updatedBy?.toDto(),
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package space.luminic.finance.mappers
|
||||
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.dtos.TransactionDTO.CreateTransactionDTO
|
||||
import space.luminic.finance.dtos.TransactionDTO.UpdateTransactionDTO
|
||||
import space.luminic.finance.mappers.AccountMapper.toDto
|
||||
import space.luminic.finance.mappers.CategoryMapper.toDto
|
||||
import space.luminic.finance.mappers.UserMapper.toDto
|
||||
import space.luminic.finance.models.Transaction
|
||||
@@ -11,25 +8,16 @@ import space.luminic.finance.models.Transaction
|
||||
object TransactionMapper {
|
||||
fun Transaction.toDto() = TransactionDTO(
|
||||
id = this.id,
|
||||
parentId = this.parentId,
|
||||
parentId = this.parent?.id,
|
||||
type = this.type,
|
||||
kind = this.kind,
|
||||
categoryId = this.categoryId,
|
||||
category = this.category?.toDto(),
|
||||
category = this.category.toDto(),
|
||||
comment = this.comment,
|
||||
amount = this.amount,
|
||||
fees = this.fees,
|
||||
fromAccount = this.fromAccount?.toDto(),
|
||||
toAccount = this.toAccount?.toDto(),
|
||||
date = this.date,
|
||||
createdBy = this.createdBy?.username,
|
||||
updatedBy = this.updatedBy?.username,
|
||||
isDone = this.isDone,
|
||||
createdBy = this.createdBy?.toDto() ?: throw IllegalStateException("created by null"),
|
||||
updatedBy = this.updatedBy?.toDto()
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ object UserMapper {
|
||||
tgUserName = this.tgUserName,
|
||||
roles = this.roles
|
||||
)
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.annotation.ReadOnlyProperty
|
||||
import org.springframework.data.relational.core.mapping.Column
|
||||
import org.springframework.data.relational.core.mapping.Table
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
|
||||
@Table(name = "accounts")
|
||||
data class Account (
|
||||
@Id val id: Int? = null,
|
||||
@Column("space_id")
|
||||
val spaceId: Int,
|
||||
val name: String,
|
||||
val type: AccountType = AccountType.COLLECTING,
|
||||
@Column("currency_code")
|
||||
val currencyCode: String,
|
||||
|
||||
var amount: BigDecimal,
|
||||
@Column("goal_id")
|
||||
var goalId: Int? = null,
|
||||
@Column("is_deleted")
|
||||
var isDeleted: Boolean = false,
|
||||
@Column("created_by")
|
||||
val createdById: String? = null,
|
||||
@CreatedDate
|
||||
@Column("created_at")
|
||||
val createdAt: Instant? = null,
|
||||
@Column("updated_by")
|
||||
val updatedById: String? = null,
|
||||
@LastModifiedDate
|
||||
@Column("updated_at")
|
||||
val updatedAt: Instant? = null,
|
||||
|
||||
) {
|
||||
@ReadOnlyProperty var goal: Goal? = null
|
||||
@ReadOnlyProperty var currency: Currency? = null
|
||||
@ReadOnlyProperty var transactions: List<Transaction>? = null
|
||||
@ReadOnlyProperty var createdBy: User? = null
|
||||
@ReadOnlyProperty var updatedBy: User? = null
|
||||
|
||||
|
||||
enum class AccountType(displayName: String) {
|
||||
SALARY("Зарплатный"),
|
||||
COLLECTING("Накопительный"),
|
||||
LOANS("Долговой"),
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.annotation.LastModifiedBy
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.annotation.ReadOnlyProperty
|
||||
import org.springframework.data.relational.core.mapping.Column
|
||||
import org.springframework.data.relational.core.mapping.Table
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
|
||||
@Table( "budgets")
|
||||
data class Budget(
|
||||
@Id var id: Int? = null,
|
||||
@Column("space_id")
|
||||
val spaceId: Int,
|
||||
val type: BudgetType = BudgetType.SPECIAL,
|
||||
var name: String,
|
||||
var description: String? = null,
|
||||
@Column("date_from")
|
||||
var dateFrom: LocalDate,
|
||||
@Column("date_to")
|
||||
var dateTo: LocalDate,
|
||||
val transactions: List<Transaction> = listOf(),
|
||||
val categories: List<BudgetCategory> = listOf(),
|
||||
@Column("is_deleted")
|
||||
var isDeleted: Boolean = false,
|
||||
@CreatedBy
|
||||
@Column("created_by")
|
||||
val createdById: Int? = null,
|
||||
@CreatedDate
|
||||
@Column("created_at")
|
||||
var createdAt: Instant? = null,
|
||||
@LastModifiedBy
|
||||
@Column("updated_by")
|
||||
var updatedById: Int? = null,
|
||||
@LastModifiedDate
|
||||
@Column("updated_at")
|
||||
var updatedAt: Instant? = null,
|
||||
) {
|
||||
|
||||
@ReadOnlyProperty var createdBy: User? = null
|
||||
@ReadOnlyProperty var updatedBy: User? = null
|
||||
data class BudgetCategory(
|
||||
val categoryId: String,
|
||||
val limit: BigDecimal
|
||||
) {
|
||||
@ReadOnlyProperty var category: Category? = null
|
||||
}
|
||||
|
||||
enum class BudgetType(val displayName: String) {
|
||||
MONTHLY("Месячный"),
|
||||
SPECIAL("Специальный")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,35 +2,24 @@ package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.annotation.LastModifiedBy
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.annotation.ReadOnlyProperty
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import org.springframework.data.annotation.Transient
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
@Document(collection = "categories")
|
||||
data class Category(
|
||||
@Id val id: String? = null,
|
||||
val spaceId: String,
|
||||
var id: Int? = null,
|
||||
val space: Space? = null,
|
||||
val type: CategoryType,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val icon: String,
|
||||
var isDeleted: Boolean = false,
|
||||
|
||||
@CreatedBy
|
||||
val createdById: String? = null,
|
||||
@CreatedDate
|
||||
val createdAt: Instant? = null,
|
||||
@LastModifiedBy
|
||||
val updatedById: String? = null,
|
||||
@LastModifiedDate
|
||||
val updatedAt: Instant? = null,
|
||||
@CreatedBy var createdBy: User? = null,
|
||||
@CreatedDate var createdAt: Instant? = null,
|
||||
@LastModifiedBy var updatedBy: User? = null,
|
||||
@LastModifiedDate var updatedAt: Instant? = null,
|
||||
) {
|
||||
@ReadOnlyProperty var createdBy: User? = null
|
||||
@ReadOnlyProperty var updatedBy: User? = null
|
||||
|
||||
enum class CategoryType(val displayName: String) {
|
||||
INCOME("Поступления"),
|
||||
@@ -38,9 +27,8 @@ data class Category(
|
||||
}
|
||||
|
||||
|
||||
@Document(collection = "categories_etalon")
|
||||
data class CategoryEtalon(
|
||||
@Id val id: String? = null,
|
||||
var id: Int? = null,
|
||||
val type: CategoryType,
|
||||
val name: String,
|
||||
val icon: String
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Document(collection = "currencies_ref")
|
||||
|
||||
data class Currency(
|
||||
@Id val code: String,
|
||||
val code: String,
|
||||
val name: String,
|
||||
val symbol: String
|
||||
)
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.annotation.ReadOnlyProperty
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
|
||||
@Document(collection = "currency_rates")
|
||||
|
||||
data class CurrencyRate(
|
||||
@Id val id: String? = null,
|
||||
val currencyCode: String,
|
||||
var id: Int? = null,
|
||||
val currency: Currency,
|
||||
val rate: BigDecimal,
|
||||
val date: LocalDate
|
||||
)
|
||||
{
|
||||
@ReadOnlyProperty var currency: Currency? = null
|
||||
}
|
||||
)
|
||||
@@ -2,35 +2,43 @@ package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.annotation.LastModifiedBy
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import org.springframework.data.annotation.Transient
|
||||
import space.luminic.finance.dtos.UserDTO
|
||||
import java.math.BigDecimal
|
||||
import java.text.Bidi
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
|
||||
@Document(collection = "goals")
|
||||
data class Goal(
|
||||
@Id val id: String? = null,
|
||||
val spaceId: String,
|
||||
var id: Int? = null,
|
||||
val space: Space? = null,
|
||||
val type: GoalType,
|
||||
val name: String,
|
||||
val description: String? = null,
|
||||
val goalAmount: BigDecimal,
|
||||
val goalDate: LocalDate,
|
||||
@CreatedBy val createdById: String,
|
||||
@Transient val createdBy: User? = null,
|
||||
@CreatedDate val createdAt: Instant? = null,
|
||||
@LastModifiedBy val updatedById: String,
|
||||
@Transient val updatedBy: User? = null,
|
||||
@LastModifiedDate val updatedAt: Instant? = null,
|
||||
val amount: BigDecimal,
|
||||
val components: List<GoalComponent> = emptyList(),
|
||||
val transactions: List<Transaction> = emptyList(),
|
||||
val untilDate: LocalDate,
|
||||
@CreatedBy var createdBy: User? = null,
|
||||
|
||||
@CreatedDate var createdAt: Instant? = null,
|
||||
@LastModifiedBy var updatedBy: User? = null,
|
||||
|
||||
@LastModifiedDate var updatedAt: Instant? = null,
|
||||
) {
|
||||
|
||||
var currentAmount: BigDecimal = {
|
||||
this.transactions.sumOf { it.amount }
|
||||
} as BigDecimal
|
||||
|
||||
|
||||
data class GoalComponent(
|
||||
val id: Int? = null,
|
||||
val name: String,
|
||||
val amount: BigDecimal,
|
||||
val isDone: Boolean = false,
|
||||
val date: LocalDate = LocalDate.now(),
|
||||
)
|
||||
|
||||
enum class GoalType(val displayName: String, val icon: String) {
|
||||
AUTO("Авто", "🏎️"),
|
||||
VACATION("Отпуск", "🏖️"),
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package space.luminic.finance.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
|
||||
data class RecurrentOperation(
|
||||
val id: Int? = null,
|
||||
val space: Space,
|
||||
val category: Category,
|
||||
val name: String,
|
||||
val amount: BigDecimal,
|
||||
val date: Int,
|
||||
val createdBy: User? = null,
|
||||
val createdAt: Instant? = null,
|
||||
val updatedBy: User? = null,
|
||||
val updatedAt: Instant? = null,
|
||||
)
|
||||
@@ -2,35 +2,21 @@ package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.annotation.LastModifiedBy
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.annotation.ReadOnlyProperty
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import org.springframework.data.annotation.Transient
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
@Document(collection = "spaces")
|
||||
data class Space (
|
||||
@Id val id: String? = null,
|
||||
data class Space (
|
||||
var id: Int? = null,
|
||||
val name: String,
|
||||
val ownerId: String,
|
||||
val participantsIds: List<String> = emptyList(),
|
||||
val owner: User,
|
||||
var participants: Set<User>,
|
||||
var isDeleted: Boolean = false,
|
||||
@CreatedBy val createdById: String? = null,
|
||||
@CreatedDate val createdAt: Instant? = null,
|
||||
@LastModifiedBy val updatedById: String? = null,
|
||||
@LastModifiedDate var updatedAt: Instant? = null,
|
||||
@CreatedBy var createdBy: User? = null,
|
||||
@CreatedDate var createdAt: Instant? = null,
|
||||
@LastModifiedBy var updatedBy: User? = null,
|
||||
@LastModifiedDate var updatedAt: Instant ? = null,
|
||||
) {
|
||||
@ReadOnlyProperty var owner: User? = null
|
||||
|
||||
@ReadOnlyProperty var participants: List<User>? = null
|
||||
|
||||
@ReadOnlyProperty var createdBy: User? = null
|
||||
|
||||
@ReadOnlyProperty var updatedBy: User? = null
|
||||
|
||||
|
||||
|
||||
override fun equals(other: Any?) = this === other || (other is Space && id != null && id == other.id)
|
||||
override fun hashCode() = id?.hashCode() ?: 0
|
||||
}
|
||||
@@ -1,23 +1,19 @@
|
||||
package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import java.time.Instant
|
||||
|
||||
@Document(collection = "subscriptions")
|
||||
data class Subscription(
|
||||
@Id val id: String? = null,
|
||||
@DBRef val user: User? = null,
|
||||
var id: Int? = null,
|
||||
val user: User,
|
||||
val endpoint: String,
|
||||
val auth: String,
|
||||
val p256dh: String,
|
||||
var isActive: Boolean,
|
||||
val createdAt: Instant = Instant.now(),
|
||||
)
|
||||
@CreatedDate val createdAt: Instant? = null,
|
||||
@LastModifiedDate val updatedAt: Instant? = null,
|
||||
|
||||
)
|
||||
|
||||
|
||||
data class SubscriptionDTO (
|
||||
val endpoint: String,
|
||||
val keys: Map<String, String>
|
||||
)
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import java.time.LocalDateTime
|
||||
import java.time.Instant
|
||||
|
||||
@Document(collection = "tokens")
|
||||
data class Token(
|
||||
@Id
|
||||
val id: String? = null,
|
||||
var id: Int? = null,
|
||||
val token: String,
|
||||
val username: String,
|
||||
val issuedAt: LocalDateTime,
|
||||
val expiresAt: LocalDateTime,
|
||||
val user: User,
|
||||
val issuedAt: Instant = Instant.now(),
|
||||
val expiresAt: Instant,
|
||||
val status: TokenStatus = TokenStatus.ACTIVE
|
||||
) {
|
||||
|
||||
|
||||
@@ -2,49 +2,31 @@ package space.luminic.finance.models
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.annotation.LastModifiedBy
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.annotation.ReadOnlyProperty
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import org.springframework.data.annotation.Transient
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
@Document(collection = "transactions")
|
||||
data class Transaction(
|
||||
@Id val id: String? = null,
|
||||
val spaceId: String,
|
||||
var parentId: String? = null,
|
||||
var id: Int? = null,
|
||||
val space: Space? = null,
|
||||
var parent: Transaction? = null,
|
||||
val type: TransactionType = TransactionType.EXPENSE,
|
||||
val kind: TransactionKind = TransactionKind.INSTANT,
|
||||
val categoryId: String,
|
||||
|
||||
val category: Category,
|
||||
val comment: String,
|
||||
val amount: BigDecimal,
|
||||
val fees: BigDecimal = BigDecimal.ZERO,
|
||||
val fromAccountId: String,
|
||||
val toAccountId: String? = null,
|
||||
val date: Instant = Instant.now(),
|
||||
val date: LocalDate = LocalDate.now(),
|
||||
var isDeleted: Boolean = false,
|
||||
@CreatedBy
|
||||
val createdById: String? = null,
|
||||
@CreatedDate
|
||||
val createdAt: Instant? = null,
|
||||
@LastModifiedBy
|
||||
val updatedById: String? = null,
|
||||
@LastModifiedDate
|
||||
val updatedAt: Instant? = null,
|
||||
val isDone: Boolean = false,
|
||||
@CreatedBy var createdBy: User? = null,
|
||||
@CreatedDate var createdAt: Instant? = null,
|
||||
@LastModifiedBy var updatedBy: User? = null,
|
||||
@LastModifiedDate var updatedAt: Instant? = null,
|
||||
) {
|
||||
|
||||
@ReadOnlyProperty var category: Category? = null
|
||||
@ReadOnlyProperty var toAccount: Account? = null
|
||||
@ReadOnlyProperty var fromAccount: Account? = null
|
||||
@ReadOnlyProperty var createdBy: User? = null
|
||||
@ReadOnlyProperty var updatedBy: User? = null
|
||||
|
||||
|
||||
|
||||
|
||||
enum class TransactionType(val displayName: String) {
|
||||
INCOME("Поступления"),
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
package space.luminic.finance.models
|
||||
|
||||
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
import org.springframework.data.annotation.Transient
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Document("users")
|
||||
|
||||
data class User(
|
||||
@Id
|
||||
var id: String? = null,
|
||||
var id: Int? = null,
|
||||
val username: String,
|
||||
var firstName: String,
|
||||
var tgId: String? = null,
|
||||
var tgUserName: String? = null,
|
||||
var password: String,
|
||||
var password: String? = null,
|
||||
var isActive: Boolean = true,
|
||||
var regDate: LocalDate = LocalDate.now(),
|
||||
val createdAt: LocalDateTime = LocalDateTime.now(),
|
||||
var roles: MutableList<String> = mutableListOf(),
|
||||
@CreatedDate val createdAt: Instant? = null,
|
||||
@LastModifiedDate var updatedAt: Instant? = null,
|
||||
var roles: List<String> = listOf(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import space.luminic.finance.models.Account
|
||||
|
||||
interface AccountRepo : ReactiveMongoRepository<Account, String> {
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import reactor.core.publisher.Flux
|
||||
import space.luminic.finance.models.Budget
|
||||
|
||||
@Repository
|
||||
interface BudgetRepo: ReactiveMongoRepository<Budget, String> {
|
||||
|
||||
suspend fun findBudgetsBySpaceIdAndIsDeletedFalse(spaceId: String): Flux<Budget>
|
||||
suspend fun findBudgetsBySpaceIdAndId(spaceId: String, budgetId: String): Flux<Budget>
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import space.luminic.finance.models.Category
|
||||
|
||||
interface CategoryRepo: ReactiveMongoRepository<Category, String> {
|
||||
|
||||
interface CategoryRepo {
|
||||
fun findBySpaceId(spaceId: Int): List<Category>
|
||||
fun findBySpaceIdAndId(spaceId: Int, id: Int): Category?
|
||||
fun create(category: Category, createdById: Int): Category
|
||||
fun update(category: Category, updatedById: Int): Category
|
||||
fun delete(categoryId: Int)
|
||||
}
|
||||
|
||||
interface CategoryEtalonRepo: ReactiveMongoRepository<Category.CategoryEtalon, String>
|
||||
interface CategoryEtalonRepo {
|
||||
fun findAll(): List<Category>
|
||||
}
|
||||
107
src/main/kotlin/space/luminic/finance/repos/CategoryRepoImpl.kt
Normal file
107
src/main/kotlin/space/luminic/finance/repos/CategoryRepoImpl.kt
Normal file
@@ -0,0 +1,107 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.dao.DuplicateKeyException
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.Category
|
||||
|
||||
@Repository
|
||||
class CategoryRepoImpl(
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||
) : CategoryRepo {
|
||||
private fun categoryRowMapper() = RowMapper<Category> { rs, _ ->
|
||||
Category(
|
||||
id = rs.getInt("id"),
|
||||
type = Category.CategoryType.valueOf(rs.getString("type")),
|
||||
name = rs.getString("name"),
|
||||
description = rs.getString("description"),
|
||||
icon = rs.getString("icon"),
|
||||
isDeleted = rs.getBoolean("is_deleted"),
|
||||
createdAt = rs.getTimestamp("created_at").toInstant(),
|
||||
updatedAt = if (rs.getTimestamp("updated_at") != null) rs.getTimestamp("updated_at").toInstant() else null
|
||||
)
|
||||
}
|
||||
|
||||
override fun findBySpaceId(spaceId: Int): List<Category> {
|
||||
val query = "select * from finance.categories where space_id = :space_id order by id"
|
||||
val params = mapOf("space_id" to spaceId)
|
||||
return jdbcTemplate.query(query, params, categoryRowMapper())
|
||||
}
|
||||
|
||||
override fun findBySpaceIdAndId(spaceId: Int, id: Int): Category? {
|
||||
val query = "select * from finance.categories where space_id = :space_id and id = :id"
|
||||
val params = mapOf("id" to id, "space_id" to spaceId)
|
||||
return jdbcTemplate.query(query, params, categoryRowMapper()).firstOrNull()
|
||||
}
|
||||
|
||||
override fun create(category: Category, createdById: Int): Category {
|
||||
if (category.id == null) {
|
||||
val query =
|
||||
"insert into finance.categories(space_id, type, name, description, icon, is_deleted, created_by_id) values (:space_id, :type, :name, :description, :icon, :isDeleted, :createdById) returning id"
|
||||
val params = mapOf(
|
||||
"space_id" to category.space!!.id,
|
||||
"type" to category.type.name,
|
||||
"name" to category.name,
|
||||
"description" to category.description,
|
||||
"icon" to category.icon,
|
||||
"isDeleted" to category.isDeleted,
|
||||
"createdById" to createdById
|
||||
)
|
||||
try {
|
||||
category.id = jdbcTemplate.queryForObject(query, params, Int::class.java)
|
||||
return category
|
||||
} catch (ex: DuplicateKeyException) {
|
||||
throw IllegalArgumentException("You cannot create a category with similar name in one space", ex)
|
||||
}
|
||||
} else throw IllegalArgumentException("Category id must by null")
|
||||
}
|
||||
|
||||
override fun update(category: Category, updatedById: Int): Category {
|
||||
if (category.id != null) {
|
||||
val query =
|
||||
"update finance.categories set type=:type, name=:name, description=:description, icon=:icon, is_deleted=:isDeleted , updated_by_id = :updatedById, updated_at = now() where id = :id"
|
||||
val params = mapOf(
|
||||
"id" to category.id!!,
|
||||
"type" to category.type.name,
|
||||
"name" to category.name,
|
||||
"description" to category.description,
|
||||
"icon" to category.icon,
|
||||
"isDeleted" to category.isDeleted,
|
||||
"updatedById" to updatedById
|
||||
|
||||
)
|
||||
jdbcTemplate.update(query, params)
|
||||
return category
|
||||
} else throw IllegalArgumentException("Category cannot be null")
|
||||
}
|
||||
|
||||
override fun delete(categoryId: Int) {
|
||||
val query = "update finance.categories set is_deleted=:isDeleted where id = :id"
|
||||
val params = mapOf(
|
||||
"id" to categoryId,
|
||||
"isDeleted" to true
|
||||
)
|
||||
jdbcTemplate.update(query, params)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Repository
|
||||
class CategoryEtalonRepoImpl(
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||
) : CategoryEtalonRepo {
|
||||
private val rowMapper = RowMapper { rs, _ ->
|
||||
Category(
|
||||
type = Category.CategoryType.valueOf(rs.getString("type")),
|
||||
name = rs.getString("name"),
|
||||
description = rs.getString("description"),
|
||||
icon = rs.getString("icon"),
|
||||
)
|
||||
}
|
||||
|
||||
override fun findAll(): List<Category> {
|
||||
val query = "select * from finance.categories_etalon"
|
||||
return jdbcTemplate.query(query, rowMapper)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,21 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.Currency
|
||||
import space.luminic.finance.models.CurrencyRate
|
||||
|
||||
interface CurrencyRepo: ReactiveMongoRepository<Currency, String>
|
||||
interface CurrencyRateRepo: ReactiveMongoRepository<CurrencyRate, String>
|
||||
@Repository
|
||||
interface CurrencyRepo {
|
||||
fun findAll(): List<Currency>
|
||||
fun findByCode(code: String): Currency?
|
||||
fun save(currency: Currency): Currency
|
||||
fun update(currency: Currency)
|
||||
fun delete(currencyCode: String)
|
||||
}
|
||||
|
||||
//
|
||||
//interface CurrencyRateRepo {
|
||||
// fun findByCode(code: String): List<CurrencyRate>
|
||||
// fun save(currencyRate: CurrencyRate)
|
||||
// fun update(currencyRate: CurrencyRate)
|
||||
// fun delete(id: Int)
|
||||
//}
|
||||
@@ -0,0 +1,50 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.Currency
|
||||
|
||||
@Repository
|
||||
class CurrencyRepoImpl(
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||
): CurrencyRepo {
|
||||
|
||||
private fun currencyRowMapper() = RowMapper<Currency> { rs, _ ->
|
||||
Currency(
|
||||
code = rs.getString("code"),
|
||||
name = rs.getString("name"),
|
||||
symbol = rs.getString("symbol"),
|
||||
)
|
||||
}
|
||||
|
||||
override fun findAll(): List<Currency> {
|
||||
val sql = "SELECT * FROM finance.currencies_ref"
|
||||
return jdbcTemplate.query(sql, currencyRowMapper())
|
||||
}
|
||||
|
||||
override fun findByCode(code: String): Currency? {
|
||||
val sql = "SELECT * FROM finance.currencies_ref WHERE code = :code"
|
||||
val params = mapOf("code" to code)
|
||||
return jdbcTemplate.queryForObject(sql, params, Currency::class.java)
|
||||
}
|
||||
|
||||
override fun save(currency: Currency): Currency {
|
||||
val sql = "insert into finance.currencies_ref (code, name, symbol) values (:code, :name, :symbol)"
|
||||
val params = mapOf("code" to currency.code, "name" to currency.name, "symbol" to currency.symbol)
|
||||
jdbcTemplate.update(sql, params)
|
||||
return currency
|
||||
}
|
||||
|
||||
override fun update(currency: Currency) {
|
||||
val sql = "update finance.currencies_ref set name=:name, symbol=:symbol where code = :code"
|
||||
val params = mapOf("name" to currency.code, "symbol" to currency.name)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
|
||||
override fun delete(currencyCode: String) {
|
||||
val sql = "delete from finance.currencies_ref where code = :code"
|
||||
val params = mapOf("code" to currencyCode)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
}
|
||||
20
src/main/kotlin/space/luminic/finance/repos/GoalRepo.kt
Normal file
20
src/main/kotlin/space/luminic/finance/repos/GoalRepo.kt
Normal file
@@ -0,0 +1,20 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import space.luminic.finance.models.Goal
|
||||
|
||||
interface GoalRepo {
|
||||
|
||||
fun findAllBySpaceId(spaceId: Int) : List<Goal>
|
||||
fun findBySpaceIdAndId(spaceId: Int, id: Int) : Goal?
|
||||
fun create(goal: Goal, createdById: Int): Int
|
||||
fun update(goal: Goal, updatedById: Int)
|
||||
fun delete(spaceId: Int, id: Int)
|
||||
fun getComponents(spaceId: Int, goalId: Int): List<Goal.GoalComponent>
|
||||
fun getComponent(spaceId: Int, goalId: Int, id: Int): Goal.GoalComponent?
|
||||
fun createComponent(goalId: Int, component: Goal.GoalComponent, createdById: Int): Int
|
||||
fun updateComponent(goalId: Int, componentId: Int, component: Goal.GoalComponent, updatedById: Int)
|
||||
fun deleteComponent(goalId: Int, componentId: Int)
|
||||
|
||||
fun assignTransaction(goalId: Int, transactionId: Int)
|
||||
fun refuseTransaction(goalId: Int, transactionId: Int)
|
||||
}
|
||||
283
src/main/kotlin/space/luminic/finance/repos/GoalRepoImpl.kt
Normal file
283
src/main/kotlin/space/luminic/finance/repos/GoalRepoImpl.kt
Normal file
@@ -0,0 +1,283 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.Goal
|
||||
import space.luminic.finance.models.User
|
||||
|
||||
@Repository
|
||||
class GoalRepoImpl(
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||
) : GoalRepo {
|
||||
private val goalRowMapper = RowMapper { rs, _ ->
|
||||
Goal(
|
||||
id = rs.getInt("g_id"),
|
||||
type = Goal.GoalType.valueOf(rs.getString("g_type")),
|
||||
name = rs.getString("g_name"),
|
||||
description = rs.getString("g_description"),
|
||||
amount = rs.getBigDecimal("g_amount"),
|
||||
components = listOf(),
|
||||
transactions = listOf(),
|
||||
untilDate = rs.getDate("g_until_date").toLocalDate(),
|
||||
createdBy = User(
|
||||
id = rs.getInt("created_by_id"),
|
||||
username = rs.getString("created_by_username"),
|
||||
firstName = rs.getString("created_by_first_name"),
|
||||
),
|
||||
createdAt = rs.getTimestamp("g_created_at").toInstant()
|
||||
)
|
||||
}
|
||||
|
||||
private val componentRowMapper = RowMapper { rs, _ ->
|
||||
Goal.GoalComponent(
|
||||
id = rs.getInt("gc_id"),
|
||||
name = rs.getString("gc_name"),
|
||||
amount = rs.getBigDecimal("gc_amount"),
|
||||
isDone = rs.getBoolean("gc_is_done"),
|
||||
date = rs.getDate("gc_date").toLocalDate()
|
||||
)
|
||||
}
|
||||
|
||||
override fun findAllBySpaceId(spaceId: Int): List<Goal> {
|
||||
val sql = """
|
||||
select
|
||||
g.id as g_id,
|
||||
g.type as g_type,
|
||||
g.name as g_name,
|
||||
g.description as g_description,
|
||||
g.amount as g_amount,
|
||||
created_by.id as created_by_id,
|
||||
created_by.username as created_by_username,
|
||||
created_by.first_name as created_by_first_name,
|
||||
g.created_at as g_created_at
|
||||
from finance.goals g
|
||||
join finance.users created_by on g.created_by_id = created_by.id
|
||||
where g.space_id = :spaceId
|
||||
|
||||
""".trimIndent()
|
||||
|
||||
val params = mapOf(
|
||||
"space_id" to spaceId,
|
||||
)
|
||||
return jdbcTemplate.query(sql, params, goalRowMapper)
|
||||
}
|
||||
|
||||
override fun findBySpaceIdAndId(spaceId: Int, id: Int): Goal? {
|
||||
val sql = """
|
||||
select
|
||||
g.id as g_id,
|
||||
g.type as g_type,
|
||||
g.name as g_name,
|
||||
g.description as g_description,
|
||||
g.amount as g_amount,
|
||||
created_by.id as created_by_id,
|
||||
created_by.username as created_by_username,
|
||||
created_by.first_name as created_by_first_name,
|
||||
g.created_at as g_created_at
|
||||
from finance.goals g
|
||||
join finance.users created_by on g.created_by_id = created_by.id
|
||||
where g.space_id = :spaceId and g.id = :id
|
||||
|
||||
""".trimIndent()
|
||||
|
||||
val params = mapOf(
|
||||
"space_id" to spaceId,
|
||||
"id" to id,
|
||||
)
|
||||
return jdbcTemplate.query(sql, params, goalRowMapper).firstOrNull()
|
||||
}
|
||||
|
||||
override fun create(goal: Goal, createdById: Int): Int {
|
||||
val sql = """
|
||||
insert into finance.goals(
|
||||
type,
|
||||
name,
|
||||
description,
|
||||
amount,
|
||||
until_date,
|
||||
created_by_id
|
||||
) values (
|
||||
:type,
|
||||
:name,
|
||||
:description,
|
||||
:amount,
|
||||
:until_date,
|
||||
:created_by_id
|
||||
)
|
||||
returning id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"type" to goal.type,
|
||||
"name" to goal.name,
|
||||
"description" to goal.description,
|
||||
"amount" to goal.amount,
|
||||
"until_date" to goal.untilDate,
|
||||
"created_by_id" to createdById
|
||||
)
|
||||
return jdbcTemplate.queryForObject(sql, params, Int::class.java)!!
|
||||
}
|
||||
|
||||
override fun update(goal: Goal, updatedById: Int) {
|
||||
val sql = """
|
||||
update finance.goals set
|
||||
type = :type,
|
||||
name = :name,
|
||||
description = :description,
|
||||
amount = :amount,
|
||||
until_date = :until_date,
|
||||
updated_by_id = :updated_by_id,
|
||||
updated_at = now()
|
||||
where id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"id" to goal.id,
|
||||
"type" to goal.type.name,
|
||||
"name" to goal.name,
|
||||
"description" to goal.description,
|
||||
"amount" to goal.amount,
|
||||
"until_date" to goal.untilDate,
|
||||
"updated_by_id" to updatedById
|
||||
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
|
||||
override fun delete(spaceId: Int, id: Int) {
|
||||
val sql = """
|
||||
delete from finance.goals where id = :id
|
||||
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"id" to id
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
|
||||
override fun getComponents(
|
||||
spaceId: Int,
|
||||
goalId: Int
|
||||
): List<Goal.GoalComponent> {
|
||||
val sql = """
|
||||
select
|
||||
gc.id as gc_id,
|
||||
gc.name as gc_name,
|
||||
gc.amount as gc_amount,
|
||||
gc.is_done as gc_is_done,
|
||||
gc.date as gc_date
|
||||
from finance.goals_components gc
|
||||
where gc.goal_id = :goal_id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"goal_id" to goalId
|
||||
)
|
||||
return jdbcTemplate.query(sql, params, componentRowMapper)
|
||||
|
||||
}
|
||||
|
||||
override fun getComponent(
|
||||
spaceId: Int,
|
||||
goalId: Int,
|
||||
id: Int
|
||||
): Goal.GoalComponent? {
|
||||
val sql = """
|
||||
select
|
||||
gc.id as gc_id,
|
||||
gc.name as gc_name,
|
||||
gc.amount as gc_amount,
|
||||
gc.is_done as gc_is_done,
|
||||
gc.date as gc_date
|
||||
from finance.goals_components gc
|
||||
where gc.goal_id = :goal_id and gc.id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"goal_id" to goalId,
|
||||
"id" to id
|
||||
)
|
||||
return jdbcTemplate.query(sql, params, componentRowMapper).firstOrNull()
|
||||
}
|
||||
|
||||
override fun createComponent(goalId: Int, component: Goal.GoalComponent, createdById: Int): Int {
|
||||
val sql = """
|
||||
insert into finance.goals_components(
|
||||
goal_id,
|
||||
name,
|
||||
amount,
|
||||
is_done,
|
||||
date,
|
||||
created_by_id
|
||||
) values (
|
||||
:goal_id,
|
||||
:name,
|
||||
:amount,
|
||||
:is_done,
|
||||
:date,
|
||||
:created_by_id)
|
||||
returning id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"goal_id" to goalId,
|
||||
"name" to component.name,
|
||||
"amount" to component.amount,
|
||||
"is_done" to component.isDone,
|
||||
"date" to component.date,
|
||||
"created_by_id" to createdById
|
||||
)
|
||||
return jdbcTemplate.queryForObject(sql, params, Int::class.java)!!
|
||||
}
|
||||
|
||||
override fun updateComponent(goalId: Int, componentId: Int, component: Goal.GoalComponent, updatedById: Int) {
|
||||
val sql = """
|
||||
update finance.goals_components set
|
||||
name = :name,
|
||||
amount = :amount,
|
||||
is_done = :is_done,
|
||||
updated_by_id = :updated_by_id
|
||||
where goal_id = :goalId and id = :componentId
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"goalId" to goalId,
|
||||
"componentId" to componentId,
|
||||
"name" to component.name,
|
||||
"amount" to component.amount,
|
||||
"is_done" to component.isDone,
|
||||
"date" to component.date,
|
||||
"updated_by_id" to updatedById
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
|
||||
override fun deleteComponent(goalId: Int, componentId: Int) {
|
||||
val sql = """
|
||||
delete from finance.goals_components where goal_id = :goalId and id = :componentId
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"goalId" to goalId,
|
||||
"componentId" to componentId
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
|
||||
override fun assignTransaction(goalId: Int, transactionId: Int) {
|
||||
val sql = """
|
||||
insert into finance.goals_transactions(goal_id, transactions_id)
|
||||
values (:goal_id, :transaction_id)
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"goal_id" to goalId,
|
||||
"transaction_id" to transactionId
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
|
||||
override fun refuseTransaction(goalId: Int, transactionId: Int) {
|
||||
val sql = """
|
||||
delete from finance.goals_transactions where goal_id = :goalId and transactions_id = :transactionId
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"goal_id" to goalId,
|
||||
"transaction_id" to transactionId
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import space.luminic.finance.models.RecurrentOperation
|
||||
|
||||
interface RecurrentOperationRepo {
|
||||
fun findAllBySpaceId(spaceId: Int): List<RecurrentOperation>
|
||||
fun findBySpaceIdAndId(spaceId: Int, id: Int): RecurrentOperation?
|
||||
fun create(operation: RecurrentOperation, createdById: Int): Int
|
||||
fun update(operation: RecurrentOperation, updatedById: Int)
|
||||
fun delete(id: Int)
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.Category
|
||||
import space.luminic.finance.models.RecurrentOperation
|
||||
import space.luminic.finance.models.Space
|
||||
import space.luminic.finance.models.User
|
||||
|
||||
@Repository
|
||||
class RecurrentOperationRepoImpl(
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||
) : RecurrentOperationRepo {
|
||||
|
||||
private fun operationRowMapper() = RowMapper<RecurrentOperation> { rs, _ ->
|
||||
RecurrentOperation(
|
||||
id = rs.getInt("r_id"),
|
||||
space = Space(
|
||||
id = rs.getInt("r_space_id"),
|
||||
name = rs.getString("s_name"),
|
||||
owner = User(
|
||||
rs.getInt("s_owner_id"),
|
||||
username = rs.getString("su_username"),
|
||||
firstName = rs.getString("su_first_name"),
|
||||
),
|
||||
participants = setOf()
|
||||
),
|
||||
category = Category(
|
||||
id = rs.getInt("r_category_id"),
|
||||
type = Category.CategoryType.valueOf(rs.getString("c_type")),
|
||||
name = rs.getString("c_name"),
|
||||
description = rs.getString("c_description"),
|
||||
icon = rs.getString("c_icon"),
|
||||
),
|
||||
name = rs.getString("r_name"),
|
||||
amount = rs.getBigDecimal("r_amount"),
|
||||
date = rs.getInt("r_date"),
|
||||
createdBy = User(
|
||||
id = rs.getInt("r_created_by_id"),
|
||||
username = rs.getString("r_created_by_username"),
|
||||
firstName = rs.getString("r_created_by_first_name"),
|
||||
),
|
||||
createdAt = rs.getTimestamp("r_created_at").toInstant()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun findAllBySpaceId(spaceId: Int): List<RecurrentOperation> {
|
||||
val sql = """
|
||||
select
|
||||
ro.id as r_id,
|
||||
ro.space_id AS r_space_id,
|
||||
s.name AS s_name,
|
||||
s.owner_id as s_owner_id,
|
||||
su.username as su_username,
|
||||
su.first_name AS su_first_name,
|
||||
ro.category_id as r_category_id,
|
||||
c.type AS c_type,
|
||||
c.name AS c_name,
|
||||
c.description AS c_description,
|
||||
c.icon AS c_icon,
|
||||
ro.name AS r_name,
|
||||
ro.amount AS r_amount,
|
||||
ro.date AS r_date,
|
||||
ro.created_by_id as r_created_by_id,
|
||||
r_created_by.username as r_created_by_username,
|
||||
r_created_by.first_name as r_created_by_first_name,
|
||||
ro.created_at as r_created_at
|
||||
from finance.recurrent_operations ro
|
||||
join finance.spaces s on ro.space_id = s.id
|
||||
join finance.users su on s.owner_id = su.id
|
||||
join finance.categories c on ro.category_id = c.id
|
||||
join finance.users r_created_by on ro.created_by_id = r_created_by.id
|
||||
where ro.space_id = :spaceId
|
||||
order by ro.date
|
||||
""".trimIndent()
|
||||
val params = mapOf("spaceId" to spaceId)
|
||||
return jdbcTemplate.query(sql, params, operationRowMapper())
|
||||
}
|
||||
|
||||
override fun findBySpaceIdAndId(
|
||||
spaceId: Int,
|
||||
id: Int
|
||||
): RecurrentOperation? {
|
||||
val sql = """
|
||||
select
|
||||
ro.id as r_id,
|
||||
ro.space_id AS r_space_id,
|
||||
s.name AS s_name,
|
||||
s.owner_id as s_owner_id,
|
||||
su.username as su_username,
|
||||
su.first_name AS su_first_name,
|
||||
ro.category_id as r_category_id,
|
||||
c.type AS c_type,
|
||||
c.name AS c_name,
|
||||
c.description AS c_description,
|
||||
c.icon AS c_icon,
|
||||
ro.name AS r_name,
|
||||
ro.amount AS r_amount,
|
||||
ro.date AS r_date,
|
||||
ro.created_by_id as r_created_by_id,
|
||||
r_created_by.username as r_created_by_username,
|
||||
r_created_by.first_name as r_created_by_first_name,
|
||||
ro.created_at as r_created_at
|
||||
from finance.recurrent_operations ro
|
||||
join finance.spaces s on ro.space_id = s.id
|
||||
join finance.users su on s.owner_id = su.id
|
||||
join finance.categories c on ro.category_id = c.id
|
||||
join finance.users r_created_by on ro.created_by_id = r_created_by.id
|
||||
where ro.space_id = :spaceId and ro.id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf("spaceId" to spaceId, "id" to id)
|
||||
return jdbcTemplate.query(sql, params, operationRowMapper()).firstOrNull()
|
||||
}
|
||||
|
||||
override fun create(operation: RecurrentOperation, createdById: Int): Int {
|
||||
val sql = """
|
||||
insert into finance.recurrent_operations (
|
||||
space_id,
|
||||
category_id,
|
||||
name,
|
||||
amount,
|
||||
date,
|
||||
created_by_id)
|
||||
values (
|
||||
:spaceId,
|
||||
:categoryId,
|
||||
:name,
|
||||
:amount,
|
||||
:date,
|
||||
:created_by
|
||||
)
|
||||
returning id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"spaceId" to operation.space.id,
|
||||
"categoryId" to operation.category.id,
|
||||
"name" to operation.name,
|
||||
"amount" to operation.amount,
|
||||
"date" to operation.date,
|
||||
"created_by" to createdById
|
||||
)
|
||||
return jdbcTemplate.queryForObject(sql, params, Int::class.java)!!
|
||||
}
|
||||
|
||||
override fun update(operation: RecurrentOperation, updatedById: Int) {
|
||||
val sql = """
|
||||
update finance.recurrent_operations set
|
||||
category_id = :categoryId,
|
||||
name = :name,
|
||||
amount = :amount,
|
||||
date = :date,
|
||||
updated_by_id = :updatedBy,
|
||||
updated_at = now()
|
||||
where id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"categoryId" to operation.category.id,
|
||||
"name" to operation.name,
|
||||
"amount" to operation.amount,
|
||||
"date" to operation.date,
|
||||
"updatedBy" to updatedById,
|
||||
"id" to operation.id
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
|
||||
override fun delete(id: Int) {
|
||||
val sql = """
|
||||
delete from finance.recurrent_operations
|
||||
where id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf("id" to id)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.Space
|
||||
|
||||
interface SpaceRepo: ReactiveMongoRepository<Space, String> {
|
||||
@Repository
|
||||
interface SpaceRepo {
|
||||
fun findSpacesAvailableForUser(userId: Int): List<Space>
|
||||
fun findSpaceById(id: Int, userId: Int): Space?
|
||||
fun create(space: Space, createdById: Int): Int
|
||||
fun update(space: Space, updatedById: Int): Int
|
||||
fun delete(id: Int)
|
||||
}
|
||||
221
src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt
Normal file
221
src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt
Normal file
@@ -0,0 +1,221 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.dtos.SpaceDTO
|
||||
import space.luminic.finance.models.Space
|
||||
import space.luminic.finance.models.User
|
||||
|
||||
@Repository
|
||||
class SpaceRepoImpl(
|
||||
private val userRepo: UserRepo,
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||
) : SpaceRepo {
|
||||
|
||||
private fun spaceRowMapper() = RowMapper { rs, _ ->
|
||||
Space(
|
||||
id = rs.getInt("id"),
|
||||
name = rs.getString("name"),
|
||||
owner = User(
|
||||
id = rs.getInt("owner_id"),
|
||||
username = rs.getString("username"),
|
||||
firstName = rs.getString("first_name"),
|
||||
password = rs.getString("password"),
|
||||
),
|
||||
participants = userRepo.findParticipantsBySpace(rs.getInt("id")).toSet(),
|
||||
isDeleted = rs.getBoolean("is_deleted"),
|
||||
createdAt = rs.getTimestamp("created_at").toInstant(),
|
||||
updatedAt = rs.getTimestamp("updated_at").toInstant()
|
||||
)
|
||||
}
|
||||
|
||||
private fun shortRowMapper() = RowMapper { rs, _ ->
|
||||
SpaceDTO.SpaceShortInfoDTO(
|
||||
id = rs.getInt("s_id"),
|
||||
name = rs.getString("s_name"),
|
||||
isOwner = rs.getBoolean("s_is_owner"),
|
||||
owner = User(
|
||||
rs.getInt("s_owner_id"),
|
||||
rs.getString("s_owner_username"),
|
||||
rs.getString("s_owner_firstname")
|
||||
),
|
||||
participant = User(rs.getInt("sp_uid"), rs.getString("sp_username"), rs.getString("sp_first_name")),
|
||||
createdAt = rs.getTimestamp("s_created_at").toInstant(),
|
||||
createdBy = User(
|
||||
rs.getInt("s_created_by"),
|
||||
rs.getString("s_created_by_username"),
|
||||
rs.getString("s_created_by_firstname")
|
||||
),
|
||||
updatedAt = if (rs.getTimestamp("s_updated_at") != null) rs.getTimestamp("s_updated_at")
|
||||
.toInstant() else null,
|
||||
updatedBy = if (rs.getInt("s_updated_by") != 0) User(
|
||||
rs.getInt("s_updated_by"),
|
||||
rs.getString("s_updated_by_username"),
|
||||
rs.getString("s_updated_by_firstname")
|
||||
) else null
|
||||
)
|
||||
}
|
||||
|
||||
private fun collectParticipants(spaces: List<SpaceDTO.SpaceShortInfoDTO>): List<Space> {
|
||||
val spaceMap = mutableMapOf<Int, Space>()
|
||||
spaces.forEach { row ->
|
||||
val entity = row.participant
|
||||
val existing = spaceMap[row.id]
|
||||
|
||||
if (existing == null) {
|
||||
spaceMap[row.id] = Space(
|
||||
id = row.id,
|
||||
name = row.name,
|
||||
owner = row.owner,
|
||||
participants = setOf(entity),
|
||||
createdBy = row.createdBy,
|
||||
createdAt = row.createdAt,
|
||||
updatedAt = row.updatedAt,
|
||||
updatedBy = row.updatedBy
|
||||
)
|
||||
} else {
|
||||
spaceMap[row.id] = existing.copy(
|
||||
participants = existing.participants + entity
|
||||
)
|
||||
}
|
||||
}
|
||||
return spaceMap.map { it.value }
|
||||
}
|
||||
|
||||
|
||||
override fun findSpacesAvailableForUser(userId: Int): List<Space> {
|
||||
val sql = """
|
||||
select s.id as s_id,
|
||||
s.name as s_name,
|
||||
s.created_at as s_created_at,
|
||||
s.owner_id = :user_id as s_is_owner,
|
||||
s.owner_id as s_owner_id,
|
||||
ou.username as s_owner_username,
|
||||
ou.first_name as s_owner_firstname,
|
||||
sp.participants_id as sp_uid,
|
||||
u.username as sp_username,
|
||||
u.first_name as sp_first_name,
|
||||
s.created_at as s_created_at,
|
||||
s.created_by_id as s_created_by,
|
||||
cau.username as s_created_by_username,
|
||||
cau.first_name as s_created_by_firstname,
|
||||
s.updated_at as s_updated_at,
|
||||
s.updated_by_id as s_updated_by,
|
||||
uau.username as s_updated_by_username,
|
||||
uau.first_name as s_updated_by_firstname
|
||||
|
||||
from finance.spaces s
|
||||
join finance.users ou on s.owner_id = ou.id
|
||||
join finance.spaces_participants sp on sp.space_id = s.id
|
||||
join finance.users u on sp.participants_id = u.id
|
||||
left join finance.users cau on s.created_by_id = cau.id
|
||||
left join finance.users uau on s.updated_by_id = uau.id
|
||||
where (s.owner_id = :user_id
|
||||
or sp.participants_id = :user_id)
|
||||
and s.is_deleted = false
|
||||
group by s.id, ou.username, ou.first_name, sp.participants_id, u.username, u.first_name, cau.username, cau.first_name,
|
||||
uau.username, uau.first_name;
|
||||
""".trimMargin()
|
||||
val params = mapOf(
|
||||
"user_id" to userId,
|
||||
)
|
||||
val spaces = jdbcTemplate.query(sql, params, shortRowMapper())
|
||||
return collectParticipants(spaces)
|
||||
}
|
||||
|
||||
override fun findSpaceById(id: Int, userId: Int): Space? {
|
||||
val sql = """
|
||||
select s.id as s_id,
|
||||
s.name as s_name,
|
||||
s.created_at as s_created_at,
|
||||
s.owner_id = :user_id as s_is_owner,
|
||||
s.owner_id as s_owner_id,
|
||||
ou.username as s_owner_username,
|
||||
ou.first_name as s_owner_firstname,
|
||||
sp.participants_id as sp_uid,
|
||||
u.username as sp_username,
|
||||
u.first_name as sp_first_name,
|
||||
s.created_at as s_created_at,
|
||||
s.created_by_id as s_created_by,
|
||||
cau.username as s_created_by_username,
|
||||
cau.first_name as s_created_by_firstname,
|
||||
s.updated_at as s_updated_at,
|
||||
s.updated_by_id as s_updated_by,
|
||||
uau.username as s_updated_byusername,
|
||||
uau.first_name as s_updated_byfirstname
|
||||
|
||||
from finance.spaces s
|
||||
join finance.users ou on s.owner_id = ou.id
|
||||
join finance.spaces_participants sp on sp.space_id = s.id
|
||||
join finance.users u on sp.participants_id = u.id
|
||||
left join finance.users cau on s.created_by_id = cau.id
|
||||
left join finance.users uau on s.updated_by_id = uau.id
|
||||
where (s.owner_id = :user_id
|
||||
or sp.participants_id = :user_id)
|
||||
and s.is_deleted = false and s.id = :spaceId
|
||||
group by s.id, ou.username, ou.first_name, sp.participants_id, u.username, u.first_name, cau.username, cau.first_name,
|
||||
uau.username, uau.first_name;
|
||||
""".trimMargin()
|
||||
val params = mapOf(
|
||||
"spaceId" to id,
|
||||
"user_id" to userId,
|
||||
)
|
||||
val spaces = jdbcTemplate.query(sql, params, shortRowMapper())
|
||||
return collectParticipants(spaces).firstOrNull()
|
||||
}
|
||||
|
||||
override fun create(space: Space, createdById: Int): Int {
|
||||
val sql = """
|
||||
insert into finance.spaces (name, owner_id, is_deleted, created_by_id) values (:name, :owner_id, :is_deleted, :created_by_id)
|
||||
RETURNING id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"name" to space.name,
|
||||
"owner_id" to space.owner.id,
|
||||
"is_deleted" to false,
|
||||
"created_by_id" to createdById
|
||||
)
|
||||
val createdSpaceId = jdbcTemplate.queryForObject(sql, params, Int::class.java)
|
||||
// 2) батч-вставка участников (если есть)
|
||||
if (space.participants.isNotEmpty()) {
|
||||
val batchParams = space.participants.map { p ->
|
||||
mapOf(
|
||||
"spaceId" to createdSpaceId,
|
||||
"participantId" to requireNotNull(p.id) { "participant.id is null" }
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
jdbcTemplate.batchUpdate(
|
||||
"""
|
||||
INSERT INTO finance.spaces_participants (space_id, participants_id)
|
||||
VALUES (:spaceId, :participantId)
|
||||
""".trimIndent(),
|
||||
batchParams
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return createdSpaceId!!
|
||||
}
|
||||
|
||||
override fun update(space: Space, updatedById: Int): Int {
|
||||
val sql = """
|
||||
update finance.spaces set name = :name, updated_by_id = :updated_by_id, updated_at = now() where id = :spaceId
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"name" to space.name,
|
||||
"spaceId" to space.id,
|
||||
"updated_by_id" to updatedById
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
return space.id!!
|
||||
}
|
||||
|
||||
override fun delete(id: Int) {
|
||||
val sql = """
|
||||
update finance.spaces set is_deleted = true where id = :id
|
||||
""".trimIndent()
|
||||
jdbcTemplate.update(sql, mapOf("id" to id))
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,7 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.mongodb.repository.Query
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import reactor.core.publisher.Flux
|
||||
import space.luminic.finance.models.Subscription
|
||||
|
||||
@Repository
|
||||
interface SubscriptionRepo : ReactiveMongoRepository<Subscription, String> {
|
||||
|
||||
@Query("{ \$and: [ " +
|
||||
"{ 'user': { '\$ref': 'users', '\$id': ?0 } }, " +
|
||||
"{ 'isActive': true } " +
|
||||
"]}")
|
||||
fun findByUserIdAndIsActive(userId: ObjectId): Flux<Subscription>
|
||||
|
||||
|
||||
}
|
||||
interface SubscriptionRepo
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import reactor.core.publisher.Mono
|
||||
import space.luminic.finance.models.Token
|
||||
import java.time.LocalDateTime
|
||||
import java.time.Instant
|
||||
|
||||
@Repository
|
||||
interface TokenRepo: ReactiveMongoRepository<Token, String> {
|
||||
interface TokenRepo {
|
||||
fun findByToken(token: String): Token?
|
||||
fun deleteByExpiresAtBefore(dateTime: Instant)
|
||||
|
||||
fun findByToken(token: String): Mono<Token>
|
||||
fun create(token: Token): Token
|
||||
fun update(token: Token): Token
|
||||
fun delete(id: Int)
|
||||
|
||||
fun deleteByExpiresAtBefore(dateTime: LocalDateTime)
|
||||
}
|
||||
|
||||
84
src/main/kotlin/space/luminic/finance/repos/TokenRepoImpl.kt
Normal file
84
src/main/kotlin/space/luminic/finance/repos/TokenRepoImpl.kt
Normal file
@@ -0,0 +1,84 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.Token
|
||||
import java.sql.Timestamp
|
||||
import java.time.Instant
|
||||
|
||||
@Repository
|
||||
class TokenRepoImpl(
|
||||
private val userRepo: UserRepo,
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||
) : TokenRepo {
|
||||
|
||||
private fun tokenRowMapper() = RowMapper { rs, _ ->
|
||||
Token(
|
||||
id = rs.getInt("id"),
|
||||
token = rs.getString("token"),
|
||||
user = userRepo.findById(rs.getInt("user_id")) ?: throw IllegalArgumentException("User not found"),
|
||||
issuedAt = rs.getTimestamp("issued_at").toInstant(),
|
||||
expiresAt = rs.getTimestamp("expires_at").toInstant(),
|
||||
status = Token.TokenStatus.valueOf(rs.getString("status")),
|
||||
)
|
||||
}
|
||||
|
||||
override fun findByToken(token: String): Token? {
|
||||
val sql = "SELECT * FROM finance.tokens WHERE token = :token"
|
||||
val params = mapOf(
|
||||
"token" to token,
|
||||
)
|
||||
return jdbcTemplate.query(sql, params, tokenRowMapper()).firstOrNull()
|
||||
}
|
||||
|
||||
override fun deleteByExpiresAtBefore(dateTime: Instant) {
|
||||
val sql = """
|
||||
update finance.tokens set status = :status where expires_at <= :dateTime
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"status" to Token.TokenStatus.ACTIVE,
|
||||
"dateTime" to dateTime
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
|
||||
override fun create(token: Token): Token {
|
||||
val sql = """
|
||||
insert into finance.tokens(token, user_id, expires_at, status) values (:token, :userId, :expiresAt, :status)
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"token" to token.token,
|
||||
"userId" to token.user.id,
|
||||
"expiresAt" to Timestamp.from(token.expiresAt),
|
||||
"status" to Token.TokenStatus.ACTIVE.name,
|
||||
)
|
||||
val createdTokenId = jdbcTemplate.update(sql, params)
|
||||
token.id = createdTokenId
|
||||
return token
|
||||
|
||||
}
|
||||
|
||||
override fun update(token: Token): Token {
|
||||
val sql = """
|
||||
update finance.tokens set status = :status where token = :token
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"token" to token,
|
||||
"status" to token.status
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
return token
|
||||
}
|
||||
|
||||
override fun delete(id: Int) {
|
||||
val sql = """
|
||||
update finance.tokens set status = :status where id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"id" to id,
|
||||
"status" to Token.TokenStatus.REVOKED
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import space.luminic.finance.models.Transaction
|
||||
|
||||
interface TransactionRepo: ReactiveMongoRepository<Transaction, String> {
|
||||
interface TransactionRepo {
|
||||
fun findAllBySpaceId(spaceId: Int): List<Transaction>
|
||||
fun findBySpaceIdAndId(spaceId: Int, id: Int): Transaction?
|
||||
fun create(transaction: Transaction, userId: Int): Int
|
||||
fun update(transaction: Transaction): Int
|
||||
fun delete(transactionId: Int)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.Category
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.models.User
|
||||
|
||||
@Repository
|
||||
class TransactionRepoImpl(
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate,
|
||||
) : TransactionRepo {
|
||||
|
||||
private fun transactionRowMapper() = RowMapper { rs, _ ->
|
||||
Transaction(
|
||||
id = rs.getInt("t_id"),
|
||||
parent = findBySpaceIdAndId(spaceId = rs.getInt("t_space_id"), rs.getInt("t_parent_id")),
|
||||
type = Transaction.TransactionType.valueOf(rs.getString("t_type")),
|
||||
kind = Transaction.TransactionKind.valueOf(rs.getString("t_kind")),
|
||||
category = Category(
|
||||
id = rs.getInt("c_id"),
|
||||
type = Category.CategoryType.valueOf(rs.getString("c_type")),
|
||||
name = rs.getString(("c_name")),
|
||||
description = rs.getString(("c_description")),
|
||||
icon = rs.getString("c_icon"),
|
||||
isDeleted = rs.getBoolean(("c_is_deleted")),
|
||||
createdAt = rs.getTimestamp("c_created_at").toInstant(),
|
||||
updatedAt = rs.getTimestamp("c_updated_at").toInstant(),
|
||||
),
|
||||
comment = rs.getString("t_comment"),
|
||||
amount = rs.getBigDecimal("t_amount"),
|
||||
fees = rs.getBigDecimal("t_fees"),
|
||||
date = rs.getDate("t_date").toLocalDate(),
|
||||
isDeleted = rs.getBoolean("t_is_deleted"),
|
||||
isDone = rs.getBoolean("t_is_done"),
|
||||
createdBy = User(
|
||||
id = rs.getInt("u_id"),
|
||||
username = rs.getString("u_username"),
|
||||
firstName = rs.getString("u_first_name")
|
||||
),
|
||||
createdAt = rs.getTimestamp("t_created_at").toInstant(),
|
||||
updatedAt = rs.getTimestamp("t_updated_at").toInstant(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun findAllBySpaceId(spaceId: Int): List<Transaction> {
|
||||
val sql = """
|
||||
SELECT
|
||||
t.id AS t_id,
|
||||
t.parent_id AS t_parent_id,
|
||||
t.space_id AS t_space_id,
|
||||
t.type AS t_type,
|
||||
t.kind AS t_kind,
|
||||
t.comment AS t_comment,
|
||||
t.amount AS t_amount,
|
||||
t.fees AS t_fees,
|
||||
t.date AS t_date,
|
||||
t.is_deleted AS t_is_deleted,
|
||||
t.is_done AS t_is_done,
|
||||
t.created_at AS t_created_at,
|
||||
t.updated_at AS t_updated_at,
|
||||
c.id AS c_id,
|
||||
c.type AS c_type,
|
||||
c.name AS c_name,
|
||||
c.description AS c_description,
|
||||
c.icon AS c_icon,
|
||||
c.is_deleted AS c_is_deleted,
|
||||
c.created_at AS c_created_at,
|
||||
c.updated_at AS c_updated_at,
|
||||
u.id AS u_id,
|
||||
u.username AS u_username,
|
||||
u.first_name AS u_first_name
|
||||
|
||||
FROM finance.transactions t
|
||||
JOIN finance.categories c ON t.category_id = c.id
|
||||
JOIN finance.users u ON u.id = t.created_by_id
|
||||
WHERE t.space_id = :spaceId and t.is_deleted = false
|
||||
ORDER BY t.date
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"spaceId" to spaceId,
|
||||
)
|
||||
return jdbcTemplate.query(sql, params, transactionRowMapper())
|
||||
}
|
||||
|
||||
override fun findBySpaceIdAndId(spaceId: Int, id: Int): Transaction? {
|
||||
val sql = """SELECT
|
||||
t.id AS t_id,
|
||||
t.parent_id AS t_parent_id,
|
||||
t.space_id AS t_space_id,
|
||||
t.type AS t_type,
|
||||
t.kind AS t_kind,
|
||||
t.comment AS t_comment,
|
||||
t.amount AS t_amount,
|
||||
t.fees AS t_fees,
|
||||
t.date AS t_date,
|
||||
t.is_deleted AS t_is_deleted,
|
||||
t.is_done AS t_is_done,
|
||||
t.created_at AS t_created_at,
|
||||
t.updated_at AS t_updated_at,
|
||||
c.id AS c_id,
|
||||
c.type AS c_type,
|
||||
c.name AS c_name,
|
||||
c.description AS c_description,
|
||||
c.icon AS c_icon,
|
||||
c.is_deleted AS c_is_deleted,
|
||||
c.created_at AS c_created_at,
|
||||
c.updated_at AS c_updated_at,
|
||||
u.id AS u_id,
|
||||
u.username AS u_username,
|
||||
u.first_name AS u_first_name
|
||||
FROM finance.transactions t
|
||||
JOIN finance.categories c ON t.category_id = c.id
|
||||
JOIN finance.users u ON u.id = t.created_by_id
|
||||
WHERE t.space_id = :spaceId and t.id = :id and t.is_deleted = false""".trimMargin()
|
||||
val params = mapOf(
|
||||
"spaceId" to spaceId,
|
||||
"id" to id,
|
||||
)
|
||||
return jdbcTemplate.query(sql, params, transactionRowMapper()).firstOrNull()
|
||||
}
|
||||
|
||||
override fun create(transaction: Transaction, userId: Int): Int {
|
||||
val sql = """
|
||||
INSERT INTO finance.transactions (space_id, parent_id, type, kind, category_id, comment, amount, fees, date, is_deleted, is_done, created_by_id) VALUES (
|
||||
:spaceId,
|
||||
:parentId,
|
||||
:type,
|
||||
:kind,
|
||||
:categoryId,
|
||||
:comment,
|
||||
:amount,
|
||||
:fees,
|
||||
:date,
|
||||
:is_deleted,
|
||||
:is_done,
|
||||
:createdById)
|
||||
returning id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"spaceId" to transaction.space!!.id,
|
||||
"parentId" to transaction.parent?.id,
|
||||
"type" to transaction.type.name,
|
||||
"kind" to transaction.kind.name,
|
||||
"categoryId" to transaction.category.id,
|
||||
"comment" to transaction.comment,
|
||||
"amount" to transaction.amount,
|
||||
"fees" to transaction.fees,
|
||||
"date" to transaction.date,
|
||||
"is_deleted" to transaction.isDeleted,
|
||||
"is_done" to transaction.isDone,
|
||||
"createdById" to userId
|
||||
)
|
||||
val createdTxId = jdbcTemplate.queryForObject(sql, params, Int::class.java)
|
||||
transaction.id = createdTxId
|
||||
return createdTxId!!
|
||||
}
|
||||
|
||||
override fun update(transaction: Transaction): Int {
|
||||
// val type: TransactionType = TransactionType.EXPENSE,
|
||||
// val kind: TransactionKind = TransactionKind.INSTANT,
|
||||
// val category: Int,
|
||||
// val comment: String,
|
||||
// val amount: BigDecimal,
|
||||
// val fees: BigDecimal = BigDecimal.ZERO,
|
||||
// val isDone: Boolean,
|
||||
// val date: Instant
|
||||
|
||||
val sql = """
|
||||
UPDATE finance.transactions
|
||||
set type = :type,
|
||||
kind = :kind,
|
||||
category_id = :categoryId,
|
||||
comment = :comment,
|
||||
amount = :amount,
|
||||
fees = :fees,
|
||||
is_done = :is_done,
|
||||
date = :date
|
||||
where id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"id" to transaction.id,
|
||||
"type" to transaction.type.name,
|
||||
"kind" to transaction.kind.name,
|
||||
"categoryId" to transaction.category.id,
|
||||
"comment" to transaction.comment,
|
||||
"amount" to transaction.amount,
|
||||
"fees" to transaction.fees,
|
||||
"date" to transaction.date,
|
||||
"is_done" to transaction.isDone,
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
return transaction.id!!
|
||||
}
|
||||
|
||||
override fun delete(transactionId: Int) {
|
||||
val sql = """
|
||||
update finance.transactions set is_deleted = true where id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"id" to transactionId,
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
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.finance.models.User
|
||||
|
||||
@Repository
|
||||
interface UserRepo : ReactiveMongoRepository<User, String> {
|
||||
|
||||
|
||||
@Query(value = "{ 'username': ?0 }", fields = "{ 'password': 0 }")
|
||||
fun findByUsernameWOPassword(username: String): Mono<User>
|
||||
|
||||
fun findByUsername(username: String): Mono<User>
|
||||
|
||||
fun findByTgId(id: String): Mono<User>
|
||||
interface UserRepo {
|
||||
fun findAll(): List<User>
|
||||
fun findById(id: Int): User?
|
||||
fun findByUsername(username: String): User?
|
||||
fun findParticipantsBySpace(spaceId: Int): Set<User>
|
||||
fun findByTgId(tgId: String): User?
|
||||
fun save(user: User): User
|
||||
fun update(user: User): User
|
||||
fun deleteById(id: Long)
|
||||
}
|
||||
80
src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt
Normal file
80
src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt
Normal file
@@ -0,0 +1,80 @@
|
||||
package space.luminic.finance.repos
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
import org.springframework.stereotype.Repository
|
||||
import space.luminic.finance.models.User
|
||||
@Repository
|
||||
class UserRepoImpl(
|
||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||
) : UserRepo{
|
||||
|
||||
private fun userRowMapper() = RowMapper { rs, _ ->
|
||||
User(
|
||||
id = rs.getInt("id"),
|
||||
username = rs.getString("username"),
|
||||
firstName = rs.getString("first_name"),
|
||||
tgId = rs.getString("tg_id"),
|
||||
tgUserName = rs.getString("tg_user_name"),
|
||||
password = rs.getString("password"),
|
||||
isActive = rs.getBoolean("is_active"),
|
||||
regDate = rs.getDate("reg_date").toLocalDate(),
|
||||
createdAt = rs.getTimestamp("created_at").toInstant(),
|
||||
updatedAt = rs.getTimestamp("updated_at").toInstant(),
|
||||
roles = (rs.getArray("roles")?.array as? Array<String>)?.toList() ?: emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
override fun findAll(): List<User> {
|
||||
val sql = "select * from finance.users order by created_at desc"
|
||||
return jdbcTemplate.query(sql, userRowMapper())
|
||||
}
|
||||
|
||||
override fun findById(id: Int): User? {
|
||||
val sql = "select * from finance.users where id = :userId"
|
||||
return jdbcTemplate.queryForObject(sql, mapOf("userId" to id), userRowMapper())
|
||||
}
|
||||
|
||||
override fun findByUsername(username: String): User? {
|
||||
val sql = "select * from finance.users where username = :username"
|
||||
return jdbcTemplate.query(sql, mapOf("username" to username), userRowMapper()).firstOrNull()
|
||||
}
|
||||
|
||||
override fun findParticipantsBySpace(spaceId: Int): Set<User> {
|
||||
val sql = "select * from finance.users u join finance.spaces_participants sp on sp.participants_id = u.id where sp.space_id = :spaceId"
|
||||
return jdbcTemplate.query(sql, mapOf("spaceId" to spaceId), userRowMapper()).toSet()
|
||||
}
|
||||
|
||||
override fun findByTgId(tgId: String): User? {
|
||||
val sql = """
|
||||
select * from finance.users u where tg_id = :tgId
|
||||
""".trimIndent()
|
||||
val params = mapOf("tgId" to tgId)
|
||||
return jdbcTemplate.queryForObject(sql, params, userRowMapper())
|
||||
}
|
||||
|
||||
override fun save(user: User): User {
|
||||
val sql = "insert into finance.users(username, first_name, tg_id, tg_user_name, password, is_active, reg_date) values (:username, :firstname, :tg_id, :tg_user_name, :password, :isActive, :regDate) returning ID"
|
||||
val params = mapOf(
|
||||
"username" to user.username,
|
||||
"firstname" to user.firstName,
|
||||
"tg_id" to user.tgId,
|
||||
"tg_user_name" to user.tgUserName,
|
||||
"password" to user.password,
|
||||
"isActive" to user.isActive,
|
||||
"regDate" to user.regDate,
|
||||
)
|
||||
val savedId = jdbcTemplate.queryForObject(sql, params, Int::class.java)
|
||||
user.id = savedId
|
||||
return user
|
||||
}
|
||||
|
||||
override fun update(user: User): User {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) {
|
||||
val sql = "update finance.users set is_active = false where id = :id"
|
||||
jdbcTemplate.update(sql, mapOf("id" to id))
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import space.luminic.finance.dtos.AccountDTO
|
||||
import space.luminic.finance.models.Account
|
||||
import space.luminic.finance.models.Transaction
|
||||
|
||||
interface AccountService {
|
||||
suspend fun getAccounts(spaceId: String): List<Account>
|
||||
suspend fun getAccount(spaceId: String, accountId: String): Account
|
||||
suspend fun getAccountTransactions(spaceId: String, accountId: String): List<Transaction>
|
||||
suspend fun createAccount(spaceId: String, account: AccountDTO.CreateAccountDTO): Account
|
||||
suspend fun updateAccount(spaceId: String, account: AccountDTO.UpdateAccountDTO): Account
|
||||
suspend fun deleteAccount(spaceId: String, accountId: String)
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import org.bson.Document
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields
|
||||
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.unwind
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||
import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||
import org.springframework.data.mongodb.core.aggregation.LookupOperation
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.AccountDTO
|
||||
import space.luminic.finance.models.Account
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.repos.AccountRepo
|
||||
|
||||
@Service
|
||||
class AccountServiceImpl(
|
||||
private val accountRepo: AccountRepo,
|
||||
private val mongoTemplate: ReactiveMongoTemplate,
|
||||
private val spaceService: SpaceService,
|
||||
private val transactionService: TransactionService
|
||||
): AccountService {
|
||||
|
||||
private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
||||
val addFieldsAsOJ = addFields()
|
||||
.addField("createdByOI")
|
||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||
.addField("updatedByOI")
|
||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||
.build()
|
||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||
val unwindCreatedBy = unwind("createdBy")
|
||||
|
||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||
val unwindUpdatedBy = unwind("updatedBy")
|
||||
|
||||
val matchCriteria = mutableListOf<Criteria>()
|
||||
matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||
matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
|
||||
return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getAccounts(spaceId: String): List<Account> {
|
||||
val basicAggregation = basicAggregation(spaceId)
|
||||
val aggregation = newAggregation(*basicAggregation.toTypedArray())
|
||||
return mongoTemplate.aggregate(aggregation, "accounts", Account::class.java)
|
||||
.collectList()
|
||||
.awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun getAccount(
|
||||
spaceId: String,
|
||||
accountId: String
|
||||
): Account {
|
||||
val basicAggregation = basicAggregation(spaceId)
|
||||
val matchStage = match (Criteria.where("_id").`is`(ObjectId(accountId)))
|
||||
val aggregation = newAggregation(matchStage, *basicAggregation.toTypedArray())
|
||||
return mongoTemplate.aggregate(aggregation, "accounts", Account::class.java)
|
||||
.awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun getAccountTransactions(
|
||||
spaceId: String,
|
||||
accountId: String
|
||||
): List<Transaction> {
|
||||
val space = spaceService.checkSpace(spaceId)
|
||||
val filter = TransactionService.TransactionsFilter(
|
||||
accountId = accountId,
|
||||
)
|
||||
return transactionService.getTransactions(spaceId, filter, "date", "ASC")
|
||||
}
|
||||
|
||||
override suspend fun createAccount(
|
||||
spaceId: String,
|
||||
account: AccountDTO.CreateAccountDTO
|
||||
): Account {
|
||||
val createdAccount = Account(
|
||||
type = account.type,
|
||||
spaceId = spaceId,
|
||||
name = account.name,
|
||||
currencyCode = account.currencyCode,
|
||||
amount = account.amount,
|
||||
goalId = account.goalId,
|
||||
)
|
||||
return accountRepo.save(createdAccount).awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun updateAccount(
|
||||
spaceId: String,
|
||||
account: AccountDTO.UpdateAccountDTO
|
||||
): Account {
|
||||
val existingAccount = getAccount(spaceId, account.id)
|
||||
val newAccount = existingAccount.copy(
|
||||
name = account.name,
|
||||
type = account.type,
|
||||
currencyCode = account.currencyCode,
|
||||
amount = account.amount,
|
||||
goalId = account.goalId,
|
||||
)
|
||||
return accountRepo.save(newAccount).awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun deleteAccount(spaceId: String, accountId: String) {
|
||||
val existingAccount = getAccount(spaceId, accountId)
|
||||
|
||||
existingAccount.isDeleted = true
|
||||
accountRepo.save(existingAccount).awaitSingle()
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.springframework.cache.annotation.Cacheable
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -13,14 +10,13 @@ import space.luminic.finance.models.Token
|
||||
import space.luminic.finance.models.User
|
||||
import space.luminic.finance.repos.UserRepo
|
||||
import space.luminic.finance.utils.JWTUtil
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
|
||||
@Service
|
||||
class AuthService(
|
||||
private val userRepository: UserRepo,
|
||||
private val userRepo: UserRepo,
|
||||
private val tokenService: TokenService,
|
||||
private val jwtUtil: JWTUtil,
|
||||
private val userService: UserService,
|
||||
@@ -28,18 +24,28 @@ class AuthService(
|
||||
) {
|
||||
private val passwordEncoder = BCryptPasswordEncoder()
|
||||
|
||||
suspend fun getSecurityUser(): User {
|
||||
val securityContextHolder = ReactiveSecurityContextHolder.getContext().awaitSingleOrNull()
|
||||
fun getSecurityUser(): User {
|
||||
val securityContextHolder = SecurityContextHolder.getContext()
|
||||
?: throw AuthException("Authentication failed")
|
||||
val authentication = securityContextHolder.authentication
|
||||
|
||||
val username = authentication.name
|
||||
// Получаем пользователя по имени
|
||||
return userService.getByUsername(username)
|
||||
return userService.getById(username.toInt())
|
||||
}
|
||||
|
||||
suspend fun login(username: String, password: String): String {
|
||||
val user = userRepository.findByUsername(username).awaitFirstOrNull()
|
||||
fun getSecurityUserId(): Int {
|
||||
val securityContextHolder = SecurityContextHolder.getContext()
|
||||
?: throw AuthException("Authentication failed")
|
||||
val authentication = securityContextHolder.authentication
|
||||
|
||||
val username = authentication.name
|
||||
// Получаем пользователя по имени
|
||||
return username.toInt()
|
||||
}
|
||||
|
||||
fun login(username: String, password: String): String {
|
||||
val user = userRepo.findByUsername(username)
|
||||
?: throw UsernameNotFoundException("Пользователь не найден")
|
||||
return if (passwordEncoder.matches(password, user.password)) {
|
||||
val token = jwtUtil.generateToken(user.username)
|
||||
@@ -47,10 +53,7 @@ class AuthService(
|
||||
tokenService.saveToken(
|
||||
token = token,
|
||||
username = username,
|
||||
expiresAt = LocalDateTime.ofInstant(
|
||||
expireAt.toInstant(),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
expiresAt = expireAt.toInstant()
|
||||
)
|
||||
token
|
||||
} else {
|
||||
@@ -58,26 +61,23 @@ class AuthService(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun tgLogin(tgId: String): String {
|
||||
fun tgLogin(tgId: String): String {
|
||||
val user =
|
||||
userRepository.findByTgId(tgId).awaitSingleOrNull() ?: throw UsernameNotFoundException("Пользователь не найден")
|
||||
userRepo.findByTgId(tgId) ?: throw UsernameNotFoundException("Пользователь не найден")
|
||||
|
||||
val token = jwtUtil.generateToken(user.username)
|
||||
val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10)
|
||||
tokenService.saveToken(
|
||||
token = token,
|
||||
username = user.username,
|
||||
expiresAt = LocalDateTime.ofInstant(
|
||||
expireAt.toInstant(),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
expiresAt = expireAt.toInstant()
|
||||
)
|
||||
return token
|
||||
|
||||
}
|
||||
|
||||
suspend fun register(username: String, password: String, firstName: String): User {
|
||||
val user = userRepository.findByUsername(username).awaitSingleOrNull()
|
||||
fun register(username: String, password: String, firstName: String): User {
|
||||
val user = userRepo.findByUsername(username)
|
||||
if (user == null) {
|
||||
var newUser = User(
|
||||
username = username,
|
||||
@@ -85,18 +85,18 @@ class AuthService(
|
||||
firstName = firstName,
|
||||
roles = mutableListOf("USER")
|
||||
)
|
||||
newUser = userRepository.save(newUser).awaitSingle()
|
||||
newUser = userRepo.save(newUser)
|
||||
return newUser
|
||||
} else throw IllegalArgumentException("Пользователь уже зарегистрирован")
|
||||
}
|
||||
|
||||
|
||||
@Cacheable(cacheNames = ["tokens"], key = "#token")
|
||||
suspend fun isTokenValid(token: String): User {
|
||||
val tokenDetails = tokenService.getToken(token).awaitFirstOrNull() ?: throw AuthException("Токен не валиден")
|
||||
fun isTokenValid(token: String): User {
|
||||
val tokenDetails = tokenService.getToken(token)
|
||||
when {
|
||||
tokenDetails.status == Token.TokenStatus.ACTIVE && tokenDetails.expiresAt.isAfter(LocalDateTime.now()) -> {
|
||||
return userService.getByUsername(tokenDetails.username)
|
||||
tokenDetails.status == Token.TokenStatus.ACTIVE && tokenDetails.expiresAt.isAfter(Instant.now()) -> {
|
||||
return tokenDetails.user
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import space.luminic.finance.dtos.BudgetDTO.*
|
||||
import space.luminic.finance.models.Budget
|
||||
import space.luminic.finance.models.Transaction
|
||||
|
||||
interface BudgetService {
|
||||
|
||||
suspend fun getBudgets(spaceId: String, sortBy: String, sortDirection: String): List<Budget>
|
||||
suspend fun getBudget(spaceId: String, budgetId: String): Budget
|
||||
suspend fun getBudgetTransactions(spaceId: String, budgetId: String): List<Transaction>
|
||||
suspend fun createBudget(spaceId: String, type: Budget.BudgetType, budgetDto: CreateBudgetDTO): Budget
|
||||
suspend fun updateBudget(spaceId: String, budgetDto: UpdateBudgetDTO): Budget
|
||||
suspend fun deleteBudget(spaceId: String, budgetId: String)
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import org.bson.Document
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.domain.Sort
|
||||
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.Aggregation.newAggregation
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.sort
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||
import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||
import org.springframework.data.mongodb.core.aggregation.LookupOperation
|
||||
import org.springframework.data.mongodb.core.aggregation.SetOperation.set
|
||||
import org.springframework.data.mongodb.core.aggregation.UnsetOperation.unset
|
||||
import org.springframework.data.mongodb.core.aggregation.VariableOperators
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.BudgetDTO
|
||||
import space.luminic.finance.models.Budget
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.repos.BudgetRepo
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Service
|
||||
class BudgetServiceImpl(
|
||||
private val budgetRepo: BudgetRepo,
|
||||
private val authService: AuthService,
|
||||
private val categoryService: CategoryService,
|
||||
private val mongoTemplate: ReactiveMongoTemplate,
|
||||
private val spaceService: SpaceService,
|
||||
private val transactionService: TransactionService,
|
||||
) : BudgetService {
|
||||
|
||||
private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
||||
|
||||
val unwindCategories = unwind("categories", true)
|
||||
val setCategoryIdOI = set("categories.categoryIdOI")
|
||||
.toValue(ConvertOperators.valueOf("categories.categoryId").convertToObjectId())
|
||||
val lookupCategory = lookup(
|
||||
"categories", // from
|
||||
"categories.categoryIdOI", // localField
|
||||
"_id", // foreignField
|
||||
"joinedCategory" // as
|
||||
)
|
||||
val unwindJoinedCategory = unwind("joinedCategory", true)
|
||||
val setEmbeddedCategory = set("categories.category").toValue("\$joinedCategory")
|
||||
val unsetTemps = unset("joinedCategory", "categories.categoryIdOI")
|
||||
val groupBack: AggregationOperation = AggregationOperation {
|
||||
Document(
|
||||
"\$group", Document()
|
||||
.append("_id", "\$_id")
|
||||
.append("doc", Document("\$first", "\$\$ROOT"))
|
||||
.append("categories", Document("\$push", "\$categories"))
|
||||
)
|
||||
}
|
||||
val setDocCategories: AggregationOperation = AggregationOperation {
|
||||
Document("\$set", Document("doc.categories", "\$categories"))
|
||||
}
|
||||
val replaceRootDoc = replaceRoot("doc")
|
||||
|
||||
val addFieldsAsOJ = addFields()
|
||||
.addField("createdByOI")
|
||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||
.addField("updatedByOI")
|
||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||
.build()
|
||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||
val unwindCreatedBy = unwind("createdBy")
|
||||
|
||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||
val unwindUpdatedBy = unwind("updatedBy")
|
||||
|
||||
|
||||
val matchCriteria = mutableListOf<Criteria>()
|
||||
matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||
matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
|
||||
return listOf(matchStage,
|
||||
unwindCategories,
|
||||
setCategoryIdOI,
|
||||
lookupCategory,
|
||||
unwindJoinedCategory,
|
||||
setEmbeddedCategory,
|
||||
unsetTemps,
|
||||
groupBack,
|
||||
setDocCategories,
|
||||
replaceRootDoc,
|
||||
addFieldsAsOJ,
|
||||
lookupCreatedBy,
|
||||
unwindCreatedBy,
|
||||
lookupUpdatedBy,
|
||||
unwindUpdatedBy)
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getBudgets(spaceId: String, sortBy: String, sortDirection: String): List<Budget> {
|
||||
|
||||
require(spaceId.isNotBlank()) { "Space ID must not be blank" }
|
||||
|
||||
val allowedSortFields = setOf("dateFrom", "dateTo", "amount", "categoryName", "createdAt")
|
||||
require(sortBy in allowedSortFields) { "Invalid sort field: $sortBy" }
|
||||
|
||||
val direction = when (sortDirection.uppercase()) {
|
||||
"ASC" -> Direction.ASC
|
||||
"DESC" -> Direction.DESC
|
||||
else -> throw IllegalArgumentException("Sort direction must be 'ASC' or 'DESC'")
|
||||
}
|
||||
val sort = sort(Sort.by(direction, sortBy))
|
||||
val basicAggregation = basicAggregation(spaceId)
|
||||
val aggregation =
|
||||
newAggregation(
|
||||
*basicAggregation.toTypedArray(),
|
||||
sort
|
||||
)
|
||||
|
||||
return mongoTemplate.aggregate(aggregation, "budgets", Budget::class.java)
|
||||
.collectList()
|
||||
.awaitSingle()
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getBudget(spaceId: String, budgetId: String): Budget {
|
||||
val basicAggregation = basicAggregation(spaceId)
|
||||
val matchStage = match(Criteria.where("_id").`is`(ObjectId(budgetId)))
|
||||
val aggregation = newAggregation(matchStage, *basicAggregation.toTypedArray(), )
|
||||
return mongoTemplate.aggregate(aggregation, "budgets", Budget::class.java).awaitFirstOrNull()
|
||||
?: throw NotFoundException("Budget not found")
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getBudgetTransactions(
|
||||
spaceId: String,
|
||||
budgetId: String
|
||||
): List<Transaction> {
|
||||
spaceService.checkSpace(spaceId)
|
||||
val budget = getBudget(spaceId, budgetId)
|
||||
val filter = TransactionService.TransactionsFilter(
|
||||
dateFrom = budget.dateFrom,
|
||||
dateTo = budget.dateTo
|
||||
)
|
||||
return transactionService.getTransactions(spaceId, filter, "date", "ASC")
|
||||
|
||||
}
|
||||
|
||||
|
||||
override suspend fun createBudget(
|
||||
spaceId: String,
|
||||
type: Budget.BudgetType,
|
||||
budgetDto: BudgetDTO.CreateBudgetDTO
|
||||
): Budget {
|
||||
val user = authService.getSecurityUser()
|
||||
val categories = categoryService.getCategories(spaceId)
|
||||
val budget = Budget(
|
||||
spaceId = spaceId,
|
||||
type = type,
|
||||
name = budgetDto.name,
|
||||
description = budgetDto.description,
|
||||
categories = categories.map { Budget.BudgetCategory(it.id!!, BigDecimal.ZERO) },
|
||||
dateFrom = budgetDto.dateFrom,
|
||||
dateTo = budgetDto.dateTo
|
||||
)
|
||||
return budgetRepo.save(budget).awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun updateBudget(
|
||||
spaceId: String,
|
||||
budgetDto: BudgetDTO.UpdateBudgetDTO
|
||||
): Budget {
|
||||
val budget = getBudget(spaceId, budgetDto.id)
|
||||
budgetDto.name?.let { name -> budget.name = name }
|
||||
budgetDto.description?.let { description -> budget.description = description }
|
||||
budgetDto.dateFrom?.let { dateFrom -> budget.dateFrom = dateFrom }
|
||||
budgetDto.dateTo?.let { dateTo -> budget.dateTo = dateTo }
|
||||
|
||||
return budgetRepo.save(budget).awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun deleteBudget(spaceId: String, budgetId: String) {
|
||||
val budget = getBudget(spaceId, budgetId)
|
||||
budget.isDeleted = true
|
||||
budgetRepo.save(budget).awaitSingle()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import space.luminic.finance.dtos.BudgetDTO
|
||||
import space.luminic.finance.dtos.CategoryDTO
|
||||
import space.luminic.finance.models.Category
|
||||
import space.luminic.finance.models.Space
|
||||
|
||||
interface CategoryService {
|
||||
suspend fun getCategories(spaceId: String): List<Category>
|
||||
suspend fun getCategory(spaceId: String, id: String): Category
|
||||
suspend fun createCategory(spaceId: String, category: CategoryDTO.CreateCategoryDTO): Category
|
||||
suspend fun updateCategory(spaceId: String,category: CategoryDTO.UpdateCategoryDTO): Category
|
||||
suspend fun deleteCategory(spaceId: String, id: String)
|
||||
suspend fun createCategoriesForSpace(spaceId: String): List<Category>
|
||||
fun getCategories(spaceId: Int): List<Category>
|
||||
fun getCategory(spaceId: Int, id: Int): Category
|
||||
fun createCategory(spaceId: Int, category: CategoryDTO.CreateCategoryDTO): Category
|
||||
fun createEtalonCategoriesForSpace(spaceId: Int): List<Category>
|
||||
fun updateCategory(spaceId: Int,categoryId:Int, category: CategoryDTO.UpdateCategoryDTO): Category
|
||||
fun deleteCategory(spaceId: Int, id: Int)
|
||||
}
|
||||
@@ -1,108 +1,105 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields
|
||||
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.unwind
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||
import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.jdbc.core.JdbcTemplate
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Propagation
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import space.luminic.finance.dtos.CategoryDTO
|
||||
import space.luminic.finance.models.Category
|
||||
import space.luminic.finance.models.Space
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.repos.CategoryEtalonRepo
|
||||
import space.luminic.finance.repos.CategoryRepo
|
||||
import space.luminic.finance.repos.SpaceRepo
|
||||
|
||||
|
||||
@Service
|
||||
class CategoryServiceImpl(
|
||||
private val categoryRepo: CategoryRepo,
|
||||
private val categoryEtalonRepo: CategoryEtalonRepo,
|
||||
private val reactiveMongoTemplate: ReactiveMongoTemplate,
|
||||
private val authService: AuthService,
|
||||
private val spaceRepo: SpaceRepo,
|
||||
private val categoriesRepo: CategoryRepo,
|
||||
private val categoriesEtalonRepo: CategoryEtalonRepo,
|
||||
private val authService: AuthService
|
||||
) : CategoryService {
|
||||
|
||||
private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
||||
val addFieldsAsOJ = addFields()
|
||||
.addField("createdByOI")
|
||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||
.addField("updatedByOI")
|
||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||
.build()
|
||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||
val unwindCreatedBy = unwind("createdBy")
|
||||
|
||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||
val unwindUpdatedBy = unwind("updatedBy")
|
||||
val matchCriteria = mutableListOf<Criteria>()
|
||||
matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||
matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
|
||||
return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
||||
override fun getCategories(spaceId: Int): List<Category> {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||
return categoriesRepo.findBySpaceId(spaceId)
|
||||
}
|
||||
|
||||
override suspend fun getCategories(spaceId: String): List<Category> {
|
||||
val basicAggregation = basicAggregation(spaceId)
|
||||
val aggregation = newAggregation(*basicAggregation.toTypedArray())
|
||||
return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).collectList().awaitSingle()
|
||||
override fun getCategory(spaceId: Int, id: Int): Category {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||
return categoriesRepo.findBySpaceIdAndId(spaceId, id)
|
||||
?: throw NotFoundException("Category with id $id not found")
|
||||
}
|
||||
|
||||
override suspend fun getCategory(spaceId: String, id: String): Category {
|
||||
val basicAggregation = basicAggregation(spaceId)
|
||||
val match = match(Criteria.where("_id").`is`(ObjectId(id)))
|
||||
val aggregation = newAggregation(*basicAggregation.toTypedArray(), match)
|
||||
return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).awaitSingle()
|
||||
}
|
||||
|
||||
|
||||
override suspend fun createCategory(
|
||||
spaceId: String,
|
||||
@Transactional
|
||||
override fun createCategory(
|
||||
spaceId: Int,
|
||||
category: CategoryDTO.CreateCategoryDTO
|
||||
): Category {
|
||||
val createdCategory = Category(
|
||||
spaceId = spaceId,
|
||||
type = category.type,
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||
val newCategory = Category(
|
||||
space = space,
|
||||
name = category.name,
|
||||
icon = category.icon
|
||||
)
|
||||
return categoryRepo.save(createdCategory).awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun updateCategory(
|
||||
spaceId: String,
|
||||
category: CategoryDTO.UpdateCategoryDTO
|
||||
): Category {
|
||||
val existingCategory = getCategory(spaceId, category.id)
|
||||
val updatedCategory = existingCategory.copy(
|
||||
type = category.type,
|
||||
name = category.name,
|
||||
description = category.description,
|
||||
icon = category.icon,
|
||||
)
|
||||
return categoryRepo.save(updatedCategory).awaitSingle()
|
||||
return categoriesRepo.create(newCategory, userId)
|
||||
}
|
||||
|
||||
override suspend fun deleteCategory(spaceId: String, id: String) {
|
||||
val existingCategory = getCategory(spaceId, id)
|
||||
existingCategory.isDeleted = true
|
||||
categoryRepo.save(existingCategory).awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun createCategoriesForSpace(spaceId: String): List<Category> {
|
||||
val etalonCategories = categoryEtalonRepo.findAll().collectList().awaitSingle()
|
||||
val toCreate = etalonCategories.map {
|
||||
Category(
|
||||
spaceId = spaceId,
|
||||
type = it.type,
|
||||
name = it.name,
|
||||
icon = it.icon
|
||||
@Transactional(propagation = Propagation.NESTED)
|
||||
override fun createEtalonCategoriesForSpace(
|
||||
spaceId: Int
|
||||
): List<Category> {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||
val categories = categoriesEtalonRepo.findAll()
|
||||
val newCategories = mutableListOf<Category>()
|
||||
categories.forEach { category ->
|
||||
newCategories.add(
|
||||
categoriesRepo.create(
|
||||
Category(
|
||||
space = space,
|
||||
name = category.name,
|
||||
description = category.description,
|
||||
type = category.type,
|
||||
icon = category.icon,
|
||||
), userId
|
||||
)
|
||||
)
|
||||
}
|
||||
return categoryRepo.saveAll(toCreate).collectList().awaitSingle()
|
||||
|
||||
return newCategories
|
||||
}
|
||||
@Transactional
|
||||
override fun updateCategory(
|
||||
spaceId: Int,
|
||||
categoryId: Int,
|
||||
category: CategoryDTO.UpdateCategoryDTO
|
||||
): Category {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||
val existingCategory = getCategory(spaceId, categoryId)
|
||||
val newCategory = Category(
|
||||
id = existingCategory.id,
|
||||
space = space,
|
||||
name = category.name,
|
||||
description = category.description,
|
||||
type = category.type,
|
||||
icon = category.icon,
|
||||
isDeleted = existingCategory.isDeleted,
|
||||
createdBy = existingCategory.createdBy,
|
||||
createdAt = existingCategory.createdAt,
|
||||
)
|
||||
return categoriesRepo.update(newCategory, userId)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
override fun deleteCategory(spaceId: Int, id: Int) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||
categoriesRepo.delete(id)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
//package space.luminic.finance.services
|
||||
//
|
||||
//import kotlinx.coroutines.reactive.awaitSingle
|
||||
//import org.bson.types.ObjectId
|
||||
//import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||
//import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields
|
||||
//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.unwind
|
||||
//import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||
//import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||
//import org.springframework.data.mongodb.core.query.Criteria
|
||||
//import org.springframework.stereotype.Service
|
||||
//import space.luminic.finance.dtos.CategoryDTO
|
||||
//import space.luminic.finance.models.Category
|
||||
//import space.luminic.finance.repos.CategoryEtalonRepo
|
||||
//import space.luminic.finance.repos.CategoryRepo
|
||||
//
|
||||
//@Service
|
||||
//class CategoryServiceMongoImpl(
|
||||
// private val categoryRepo: CategoryRepo,
|
||||
// private val categoryEtalonRepo: CategoryEtalonRepo,
|
||||
// private val reactiveMongoTemplate: ReactiveMongoTemplate,
|
||||
// private val authService: AuthService,
|
||||
//) : CategoryService {
|
||||
//
|
||||
// private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
||||
// val addFieldsAsOJ = addFields()
|
||||
// .addField("createdByOI")
|
||||
// .withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||
// .addField("updatedByOI")
|
||||
// .withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||
// .build()
|
||||
// val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||
// val unwindCreatedBy = unwind("createdBy")
|
||||
//
|
||||
// val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||
// val unwindUpdatedBy = unwind("updatedBy")
|
||||
// val matchCriteria = mutableListOf<Criteria>()
|
||||
// matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||
// matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
||||
// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
//
|
||||
// return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
||||
// }
|
||||
//
|
||||
// override suspend fun getCategories(spaceId: String): List<Category> {
|
||||
// val basicAggregation = basicAggregation(spaceId)
|
||||
// val aggregation = newAggregation(*basicAggregation.toTypedArray())
|
||||
// return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).collectList().awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun getCategory(spaceId: String, id: String): Category {
|
||||
// val basicAggregation = basicAggregation(spaceId)
|
||||
// val match = match(Criteria.where("_id").`is`(ObjectId(id)))
|
||||
// val aggregation = newAggregation(*basicAggregation.toTypedArray(), match)
|
||||
// return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).awaitSingle()
|
||||
// }
|
||||
//
|
||||
//
|
||||
// override suspend fun createCategory(
|
||||
// spaceId: String,
|
||||
// category: CategoryDTO.CreateCategoryDTO
|
||||
// ): Category {
|
||||
// val createdCategory = Category(
|
||||
// spaceId = spaceId,
|
||||
// type = category.type,
|
||||
// name = category.name,
|
||||
// icon = category.icon
|
||||
// )
|
||||
// return categoryRepo.save(createdCategory).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun updateCategory(
|
||||
// spaceId: String,
|
||||
// category: CategoryDTO.UpdateCategoryDTO
|
||||
// ): Category {
|
||||
// val existingCategory = getCategory(spaceId, category.id)
|
||||
// val updatedCategory = existingCategory.copy(
|
||||
// type = category.type,
|
||||
// name = category.name,
|
||||
// icon = category.icon,
|
||||
// )
|
||||
// return categoryRepo.save(updatedCategory).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun deleteCategory(spaceId: String, id: String) {
|
||||
// val existingCategory = getCategory(spaceId, id)
|
||||
// existingCategory.isDeleted = true
|
||||
// categoryRepo.save(existingCategory).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun createCategoriesForSpace(spaceId: String): List<Category> {
|
||||
// val etalonCategories = categoryEtalonRepo.findAll().collectList().awaitSingle()
|
||||
// val toCreate = etalonCategories.map {
|
||||
// Category(
|
||||
// spaceId = spaceId,
|
||||
// type = it.type,
|
||||
// name = it.name,
|
||||
// icon = it.icon
|
||||
// )
|
||||
// }
|
||||
// return categoryRepo.saveAll(toCreate).collectList().awaitSingle()
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
@@ -1,16 +0,0 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.reactor.mono
|
||||
import org.springframework.data.domain.ReactiveAuditorAware
|
||||
import org.springframework.stereotype.Component
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@Component
|
||||
class CoroutineAuditorAware(
|
||||
private val authService: AuthService
|
||||
) : ReactiveAuditorAware<String> {
|
||||
override fun getCurrentAuditor(): Mono<String> =
|
||||
mono {
|
||||
authService.getSecurityUser().id!!
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@ import space.luminic.finance.models.CurrencyRate
|
||||
|
||||
interface CurrencyService {
|
||||
|
||||
suspend fun getCurrencies(): List<Currency>
|
||||
suspend fun getCurrency(currencyCode: String): Currency
|
||||
suspend fun createCurrency(currency: CurrencyDTO): Currency
|
||||
suspend fun updateCurrency(currency: CurrencyDTO): Currency
|
||||
suspend fun deleteCurrency(currencyCode: String)
|
||||
suspend fun createCurrencyRate(currencyCode: String): CurrencyRate
|
||||
fun getCurrencies(): List<Currency>
|
||||
fun getCurrency(currencyCode: String): Currency
|
||||
fun createCurrency(currency: CurrencyDTO): Currency
|
||||
fun updateCurrency(currency: CurrencyDTO): Currency
|
||||
fun deleteCurrency(currencyCode: String)
|
||||
fun createCurrencyRate(currencyCode: String): CurrencyRate
|
||||
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.CurrencyDTO
|
||||
import space.luminic.finance.models.Currency
|
||||
import space.luminic.finance.models.CurrencyRate
|
||||
import space.luminic.finance.repos.CurrencyRateRepo
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.repos.CurrencyRepo
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
@@ -13,39 +12,39 @@ import java.time.LocalDate
|
||||
@Service
|
||||
class CurrencyServiceImpl(
|
||||
private val currencyRepo: CurrencyRepo,
|
||||
private val currencyRateRepo: CurrencyRateRepo
|
||||
) : CurrencyService {
|
||||
|
||||
override suspend fun getCurrencies(): List<Currency> {
|
||||
return currencyRepo.findAll().collectList().awaitSingle()
|
||||
override fun getCurrencies(): List<Currency> {
|
||||
return currencyRepo.findAll()
|
||||
}
|
||||
|
||||
override suspend fun getCurrency(currencyCode: String): Currency {
|
||||
return currencyRepo.findById(currencyCode).awaitSingle()
|
||||
override fun getCurrency(currencyCode: String): Currency {
|
||||
return currencyRepo.findByCode(currencyCode)
|
||||
?: throw NotFoundException("Currency code $currencyCode not found")
|
||||
}
|
||||
|
||||
override suspend fun createCurrency(currency: CurrencyDTO): Currency {
|
||||
val createdCurrency = Currency(currency.code, currency.name, currency.symbol)
|
||||
return currencyRepo.save(createdCurrency).awaitSingle()
|
||||
override fun createCurrency(currency: CurrencyDTO): Currency {
|
||||
val currency = Currency(currency.code, currency.name, currency.code)
|
||||
return currencyRepo.save(currency)
|
||||
}
|
||||
|
||||
override suspend fun updateCurrency(currency: CurrencyDTO): Currency {
|
||||
val existingCurrency = currencyRepo.findById(currency.code).awaitSingle()
|
||||
val newCurrency = existingCurrency.copy(name = currency.name, symbol = currency.symbol)
|
||||
return currencyRepo.save(newCurrency).awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun deleteCurrency(currencyCode: String) {
|
||||
currencyRepo.deleteById(currencyCode).awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun createCurrencyRate(currencyCode: String): CurrencyRate {
|
||||
return currencyRateRepo.save(
|
||||
CurrencyRate(
|
||||
currencyCode = currencyCode,
|
||||
rate = BigDecimal(12.0),
|
||||
date = LocalDate.now(),
|
||||
override fun updateCurrency(currency: CurrencyDTO): Currency {
|
||||
getCurrency(currency.code)
|
||||
val updatedCurrency =
|
||||
Currency(
|
||||
code = currency.code,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol
|
||||
)
|
||||
).awaitSingle()
|
||||
return currencyRepo.save(updatedCurrency)
|
||||
}
|
||||
|
||||
override fun deleteCurrency(currencyCode: String) {
|
||||
currencyRepo.delete(currencyCode)
|
||||
}
|
||||
|
||||
override fun createCurrencyRate(currencyCode: String): CurrencyRate {
|
||||
print("createCurrencyRate")
|
||||
val currency = getCurrency(currencyCode)
|
||||
return CurrencyRate(currency = currency, rate = BigDecimal.ZERO, date = LocalDate.now())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//package space.luminic.finance.services
|
||||
//
|
||||
//import kotlinx.coroutines.reactive.awaitSingle
|
||||
//import org.springframework.stereotype.Service
|
||||
//import space.luminic.finance.dtos.CurrencyDTO
|
||||
//import space.luminic.finance.models.Currency
|
||||
//import space.luminic.finance.models.CurrencyRate
|
||||
//import space.luminic.finance.repos.CurrencyRateRepo
|
||||
//import space.luminic.finance.repos.CurrencyRepo
|
||||
//import java.math.BigDecimal
|
||||
//import java.time.LocalDate
|
||||
//
|
||||
//@Service
|
||||
//class CurrencyServiceMongoImpl(
|
||||
// private val currencyRepo: CurrencyRepo,
|
||||
// private val currencyRateRepo: CurrencyRateRepo
|
||||
//) : CurrencyService {
|
||||
//
|
||||
// override suspend fun getCurrencies(): List<Currency> {
|
||||
// return currencyRepo.findAll().collectList().awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun getCurrency(currencyCode: String): Currency {
|
||||
// return currencyRepo.findById(currencyCode).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun createCurrency(currency: CurrencyDTO): Currency {
|
||||
// val createdCurrency = Currency(currency.code, currency.name, currency.symbol)
|
||||
// return currencyRepo.save(createdCurrency).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun updateCurrency(currency: CurrencyDTO): Currency {
|
||||
// val existingCurrency = currencyRepo.findById(currency.code).awaitSingle()
|
||||
// val newCurrency = existingCurrency.copy(name = currency.name, symbol = currency.symbol)
|
||||
// return currencyRepo.save(newCurrency).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun deleteCurrency(currencyCode: String) {
|
||||
// currencyRepo.deleteById(currencyCode).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun createCurrencyRate(currencyCode: String): CurrencyRate {
|
||||
// return currencyRateRepo.save(
|
||||
// CurrencyRate(
|
||||
// currencyCode = currencyCode,
|
||||
// rate = BigDecimal(12.0),
|
||||
// date = LocalDate.now(),
|
||||
// )
|
||||
// ).awaitSingle()
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,22 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import space.luminic.finance.dtos.GoalDTO
|
||||
import space.luminic.finance.models.Goal
|
||||
|
||||
interface GoalService {
|
||||
fun findAllBySpaceId(spaceId: Int): List<Goal>
|
||||
fun findBySpaceIdAndId(spaceId: Int, id: Int): Goal
|
||||
fun create(spaceId: Int,goal: GoalDTO.CreateGoalDTO): Int
|
||||
fun update(spaceId: Int, goalId: Int, goal: GoalDTO.UpdateGoalDTO)
|
||||
fun delete(spaceId: Int, id: Int)
|
||||
|
||||
fun getComponents(spaceId: Int, goalId: Int): List<Goal.GoalComponent>
|
||||
fun getComponent(spaceId: Int, goalId: Int, id: Int): Goal.GoalComponent?
|
||||
fun createComponent(spaceId: Int, goalId: Int, component: Goal.GoalComponent): Int
|
||||
fun updateComponent(spaceId: Int, goalId: Int, component: Goal.GoalComponent)
|
||||
fun deleteComponent(spaceId: Int, goalId: Int, id: Int)
|
||||
|
||||
fun assignTransaction(spaceId: Int, goalId: Int, transactionId: Int)
|
||||
fun refuseTransaction(spaceId: Int,goalId: Int, transactionId: Int)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.GoalDTO
|
||||
import space.luminic.finance.models.Goal
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.repos.GoalRepo
|
||||
import space.luminic.finance.repos.SpaceRepo
|
||||
import space.luminic.finance.repos.TransactionRepo
|
||||
|
||||
@Service
|
||||
class GoalServiceImpl(
|
||||
private val goalRepo: GoalRepo,
|
||||
private val spaceRepo: SpaceRepo,
|
||||
private val authService: AuthService,
|
||||
private val transactionRepo: TransactionRepo
|
||||
) : GoalService {
|
||||
override fun findAllBySpaceId(spaceId: Int): List<Goal> {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
return goalRepo.findAllBySpaceId(spaceId)
|
||||
}
|
||||
|
||||
override fun findBySpaceIdAndId(spaceId: Int, id: Int): Goal {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
return goalRepo.findBySpaceIdAndId(spaceId, userId) ?: throw NotFoundException("Goal $id not found")
|
||||
}
|
||||
|
||||
override fun create(spaceId: Int, goal: GoalDTO.CreateGoalDTO): Int {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
val creatingGoal = Goal(
|
||||
type = goal.type,
|
||||
name = goal.name,
|
||||
amount = goal.amount,
|
||||
untilDate = goal.date
|
||||
)
|
||||
return goalRepo.create(creatingGoal, userId)
|
||||
}
|
||||
|
||||
override fun update(spaceId: Int, goalId: Int, goal: GoalDTO.UpdateGoalDTO) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
val existingGoal =
|
||||
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||
val updatedGoal = existingGoal.copy(
|
||||
type = goal.type,
|
||||
name = goal.name,
|
||||
description = goal.description,
|
||||
amount = goal.amount,
|
||||
untilDate = goal.date
|
||||
)
|
||||
goalRepo.update(updatedGoal, userId)
|
||||
}
|
||||
|
||||
override fun delete(spaceId: Int, id: Int) {
|
||||
goalRepo.delete(spaceId, id)
|
||||
}
|
||||
|
||||
override fun getComponents(
|
||||
spaceId: Int,
|
||||
goalId: Int
|
||||
): List<Goal.GoalComponent> {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||
return goalRepo.getComponents(spaceId, goalId)
|
||||
}
|
||||
|
||||
override fun getComponent(
|
||||
spaceId: Int,
|
||||
goalId: Int,
|
||||
id: Int
|
||||
): Goal.GoalComponent? {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||
return goalRepo.getComponent(spaceId, goalId, id)
|
||||
}
|
||||
|
||||
override fun createComponent(
|
||||
spaceId: Int,
|
||||
goalId: Int,
|
||||
component: Goal.GoalComponent
|
||||
): Int {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||
return goalRepo.createComponent(goalId, component, userId)
|
||||
}
|
||||
|
||||
override fun updateComponent(
|
||||
spaceId: Int,
|
||||
goalId: Int,
|
||||
component: Goal.GoalComponent
|
||||
) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||
val existingComponent = goalRepo.getComponent(spaceId, goalId, component.id!!)
|
||||
?: throw NotFoundException("Component $goalId not found")
|
||||
val updatedComponent = existingComponent.copy(
|
||||
name = component.name,
|
||||
amount = component.amount,
|
||||
isDone = component.isDone,
|
||||
date = component.date
|
||||
)
|
||||
goalRepo.updateComponent(goalId, updatedComponent.id!!, updatedComponent, userId)
|
||||
}
|
||||
|
||||
override fun deleteComponent(spaceId: Int, goalId: Int, id: Int) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||
goalRepo.getComponent(spaceId, goalId, id) ?: throw NotFoundException("Component $goalId not found")
|
||||
goalRepo.deleteComponent(goalId, id)
|
||||
}
|
||||
|
||||
override fun assignTransaction(spaceId: Int, goalId: Int, transactionId: Int) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||
transactionRepo.findBySpaceIdAndId(spaceId, transactionId) ?: throw NotFoundException(
|
||||
"Transaction $transactionId not found"
|
||||
)
|
||||
goalRepo.assignTransaction(goalId, transactionId)
|
||||
|
||||
}
|
||||
|
||||
override fun refuseTransaction(spaceId: Int, goalId: Int, transactionId: Int) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||
transactionRepo.findBySpaceIdAndId(spaceId, transactionId) ?: throw NotFoundException(
|
||||
"Transaction $transactionId not found"
|
||||
)
|
||||
goalRepo.refuseTransaction(goalId, transactionId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import space.luminic.finance.dtos.RecurrentOperationDTO
|
||||
import space.luminic.finance.models.RecurrentOperation
|
||||
|
||||
|
||||
interface RecurrentOperationService {
|
||||
fun findBySpaceId(spaceId: Int): List<RecurrentOperation>
|
||||
fun findBySpaceIdAndId(spaceId: Int, id: Int): RecurrentOperation
|
||||
fun create(spaceId: Int, operation: RecurrentOperationDTO.CreateRecurrentOperationDTO): Int
|
||||
fun update(spaceId: Int, operationId: Int, operation: RecurrentOperationDTO.UpdateRecurrentOperationDTO)
|
||||
fun delete(spaceId: Int, id: Int)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.RecurrentOperationDTO
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.models.RecurrentOperation
|
||||
import space.luminic.finance.repos.RecurrentOperationRepo
|
||||
import space.luminic.finance.repos.SpaceRepo
|
||||
|
||||
@Service
|
||||
class RecurrentOperationServiceImpl(
|
||||
private val authService: AuthService,
|
||||
private val spaceRepo: SpaceRepo,
|
||||
private val recurrentOperationRepo: RecurrentOperationRepo,
|
||||
private val categoryService: CategoryService
|
||||
): RecurrentOperationService {
|
||||
override fun findBySpaceId(spaceId: Int): List<RecurrentOperation> {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
return recurrentOperationRepo.findAllBySpaceId(spaceId)
|
||||
}
|
||||
|
||||
override fun findBySpaceIdAndId(
|
||||
spaceId: Int,
|
||||
id: Int
|
||||
): RecurrentOperation {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
return recurrentOperationRepo.findBySpaceIdAndId(spaceId, id) ?: throw NotFoundException("Cannot find recurrent operation with id ${id}")
|
||||
}
|
||||
|
||||
override fun create(spaceId: Int, operation: RecurrentOperationDTO.CreateRecurrentOperationDTO): Int {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceRepo.findSpaceById(spaceId, userId) ?: throw NotFoundException("Cannot find space with id ${spaceId}")
|
||||
val category = categoryService.getCategory(spaceId, operation.categoryId)
|
||||
val creatingOperation = RecurrentOperation(
|
||||
space = space,
|
||||
category = category,
|
||||
name = operation.name,
|
||||
amount = operation.amount,
|
||||
date = operation.date
|
||||
)
|
||||
return recurrentOperationRepo.create(creatingOperation, userId)
|
||||
}
|
||||
|
||||
override fun update(spaceId: Int, operationId: Int, operation: RecurrentOperationDTO.UpdateRecurrentOperationDTO) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
val newCategory = categoryService.getCategory(spaceId, operation.categoryId)
|
||||
val existingOperation = recurrentOperationRepo.findBySpaceIdAndId(spaceId,operationId ) ?: throw NotFoundException("Cannot find operation with id $operationId")
|
||||
val updatedOperation = existingOperation.copy(
|
||||
category = newCategory,
|
||||
name = operation.name,
|
||||
amount = operation.amount,
|
||||
date = operation.date
|
||||
)
|
||||
recurrentOperationRepo.update(updatedOperation, userId)
|
||||
|
||||
}
|
||||
|
||||
override fun delete(spaceId: Int, id: Int) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
spaceRepo.findSpaceById(spaceId, userId)
|
||||
recurrentOperationRepo.delete(id)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import space.luminic.finance.dtos.SpaceDTO
|
||||
import space.luminic.finance.models.Budget
|
||||
import space.luminic.finance.models.Space
|
||||
|
||||
interface SpaceService {
|
||||
|
||||
suspend fun checkSpace(spaceId: String): Space
|
||||
suspend fun getSpaces(): List<Space>
|
||||
suspend fun getSpace(id: String): Space
|
||||
suspend fun createSpace(space: SpaceDTO.CreateSpaceDTO): Space
|
||||
suspend fun updateSpace(spaceId: String, space: SpaceDTO.UpdateSpaceDTO): Space
|
||||
suspend fun deleteSpace(spaceId: String)
|
||||
fun checkSpace(spaceId: Int): Space
|
||||
fun getSpaces(): List<Space>
|
||||
fun getSpace(id: Int): Space
|
||||
fun createSpace(space: SpaceDTO.CreateSpaceDTO): Int
|
||||
fun updateSpace(spaceId: Int, space: SpaceDTO.UpdateSpaceDTO): Int
|
||||
fun deleteSpace(spaceId: Int)
|
||||
|
||||
}
|
||||
@@ -1,149 +1,75 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import com.mongodb.client.model.Aggregates.sort
|
||||
import kotlinx.coroutines.reactive.awaitFirst
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import kotlinx.coroutines.reactive.awaitSingleOrNull
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.domain.Sort
|
||||
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.aggregation.ArrayOperators
|
||||
import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||
import org.springframework.data.mongodb.core.aggregation.VariableOperators
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.cache.annotation.CacheEvict
|
||||
import org.springframework.cache.annotation.Cacheable
|
||||
import org.springframework.jdbc.core.JdbcTemplate
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import space.luminic.finance.dtos.SpaceDTO
|
||||
import space.luminic.finance.models.Budget
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.models.Space
|
||||
import space.luminic.finance.models.User
|
||||
import space.luminic.finance.repos.SpaceRepo
|
||||
|
||||
@Service
|
||||
class SpaceServiceImpl(
|
||||
private val authService: AuthService,
|
||||
private val spaceRepo: SpaceRepo,
|
||||
private val mongoTemplate: ReactiveMongoTemplate,
|
||||
private val categoryService: CategoryService
|
||||
) : SpaceService {
|
||||
|
||||
private fun basicAggregation(user: User): List<AggregationOperation> {
|
||||
val addFieldsAsOJ = addFields()
|
||||
.addField("createdByOI")
|
||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||
.addField("updatedByOI")
|
||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||
.build()
|
||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||
val unwindCreatedBy = unwind("createdBy")
|
||||
|
||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||
val unwindUpdatedBy = unwind("updatedBy")
|
||||
|
||||
|
||||
|
||||
val matchCriteria = mutableListOf<Criteria>()
|
||||
matchCriteria.add(
|
||||
Criteria().orOperator(
|
||||
Criteria.where("ownerId").`is`(user.id),
|
||||
Criteria.where("participantsIds").`is`(user.id)
|
||||
)
|
||||
)
|
||||
matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
|
||||
return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
||||
override fun checkSpace(spaceId: Int): Space {
|
||||
return getSpace(spaceId)
|
||||
}
|
||||
|
||||
private fun ownerAndParticipantsLookups(): List<AggregationOperation>{
|
||||
val addOwnerAsOJ = addFields()
|
||||
.addField("ownerIdAsObjectId")
|
||||
.withValue(ConvertOperators.valueOf("ownerId").convertToObjectId())
|
||||
.addField("participantsIdsAsObjectId")
|
||||
.withValue(
|
||||
VariableOperators.Map.itemsOf("participantsIds")
|
||||
.`as`("id")
|
||||
.andApply(
|
||||
ConvertOperators.valueOf("$\$id").convertToObjectId()
|
||||
)
|
||||
)
|
||||
.build()
|
||||
val lookupOwner = lookup("users", "ownerIdAsObjectId", "_id", "owner")
|
||||
val unwindOwner = unwind("owner")
|
||||
val lookupUsers = lookup("users", "participantsIdsAsObjectId", "_id", "participants")
|
||||
return listOf(addOwnerAsOJ, lookupOwner, unwindOwner, lookupUsers)
|
||||
// @Cacheable(cacheNames = ["spaces"])
|
||||
override fun getSpaces(): List<Space> {
|
||||
val user = authService.getSecurityUserId()
|
||||
val spaces = spaceRepo.findSpacesAvailableForUser(user)
|
||||
return spaces
|
||||
}
|
||||
|
||||
override suspend fun checkSpace(spaceId: String): Space {
|
||||
override fun getSpace(id: Int): Space {
|
||||
val user = authService.getSecurityUserId()
|
||||
val space = spaceRepo.findSpaceById(id, user) ?: throw NotFoundException("Space with id $id not found")
|
||||
return space
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
override fun createSpace(space: SpaceDTO.CreateSpaceDTO): Int {
|
||||
val user = authService.getSecurityUser()
|
||||
val space = getSpace(spaceId)
|
||||
|
||||
// Проверяем доступ пользователя к пространству
|
||||
return if (space.participants!!.none { it.id.toString() == user.id }) {
|
||||
throw IllegalArgumentException("User does not have access to this Space")
|
||||
} else space
|
||||
}
|
||||
|
||||
override suspend fun getSpaces(): List<Space> {
|
||||
val user = authService.getSecurityUser()
|
||||
val basicAggregation = basicAggregation(user)
|
||||
val ownerAndParticipantsLookup = ownerAndParticipantsLookups()
|
||||
|
||||
val sort = sort(Sort.by(Sort.Direction.DESC, "createdAt"))
|
||||
val aggregation = newAggregation(
|
||||
*basicAggregation.toTypedArray(),
|
||||
*ownerAndParticipantsLookup.toTypedArray(),
|
||||
sort,
|
||||
)
|
||||
return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).collectList().awaitSingle()
|
||||
}
|
||||
|
||||
override suspend fun getSpace(id: String): Space {
|
||||
val user = authService.getSecurityUser()
|
||||
val basicAggregation = basicAggregation(user)
|
||||
val ownerAndParticipantsLookup = ownerAndParticipantsLookups()
|
||||
|
||||
val aggregation = newAggregation(
|
||||
*basicAggregation.toTypedArray(),
|
||||
*ownerAndParticipantsLookup.toTypedArray(),
|
||||
)
|
||||
return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).awaitFirstOrNull()
|
||||
?: throw NotFoundException("Space not found")
|
||||
|
||||
}
|
||||
|
||||
override suspend fun createSpace(space: SpaceDTO.CreateSpaceDTO): Space {
|
||||
val owner = authService.getSecurityUser()
|
||||
val createdSpace = Space(
|
||||
val creatingSpace = Space(
|
||||
name = space.name,
|
||||
ownerId = owner.id!!,
|
||||
|
||||
participantsIds = listOf(owner.id!!),
|
||||
|
||||
|
||||
)
|
||||
createdSpace.owner = owner
|
||||
createdSpace.participants?.toMutableList()?.add(owner)
|
||||
val savedSpace = spaceRepo.save(createdSpace).awaitSingle()
|
||||
owner = user,
|
||||
participants = setOf(user)
|
||||
)
|
||||
val userId = authService.getSecurityUserId()
|
||||
val savedSpace = spaceRepo.create(creatingSpace, userId)
|
||||
if (space.createBasicCategories) {
|
||||
categoryService.createEtalonCategoriesForSpace(savedSpace)
|
||||
}
|
||||
return savedSpace
|
||||
}
|
||||
|
||||
override suspend fun updateSpace(spaceId: String, space: SpaceDTO.UpdateSpaceDTO): Space {
|
||||
val existingSpace = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found")
|
||||
val updatedSpace = existingSpace.copy(
|
||||
@Transactional
|
||||
override fun updateSpace(
|
||||
spaceId: Int,
|
||||
space: SpaceDTO.UpdateSpaceDTO
|
||||
): Int {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val existingSpace = getSpace(spaceId)
|
||||
val updatedSpace = Space(
|
||||
id = existingSpace.id,
|
||||
name = space.name,
|
||||
)
|
||||
updatedSpace.owner = existingSpace.owner
|
||||
updatedSpace.participants = existingSpace.participants
|
||||
return spaceRepo.save(updatedSpace).awaitFirst()
|
||||
owner = existingSpace.owner,
|
||||
participants = existingSpace.participants,
|
||||
isDeleted = existingSpace.isDeleted,
|
||||
createdBy = existingSpace.createdBy,
|
||||
createdAt = existingSpace.createdAt,
|
||||
)
|
||||
return spaceRepo.update(updatedSpace, userId)
|
||||
}
|
||||
|
||||
override suspend fun deleteSpace(spaceId: String) {
|
||||
val space = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found")
|
||||
space.isDeleted = true
|
||||
spaceRepo.save(space).awaitFirst()
|
||||
@Transactional
|
||||
override fun deleteSpace(spaceId: Int) {
|
||||
spaceRepo.delete(spaceId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
//package space.luminic.finance.services
|
||||
//
|
||||
//import com.mongodb.client.model.Aggregates.sort
|
||||
//import kotlinx.coroutines.reactive.awaitFirst
|
||||
//import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
//import kotlinx.coroutines.reactive.awaitSingle
|
||||
//import org.springframework.data.domain.Sort
|
||||
//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.aggregation.ConvertOperators
|
||||
//import org.springframework.data.mongodb.core.aggregation.VariableOperators
|
||||
//import org.springframework.data.mongodb.core.query.Criteria
|
||||
//import org.springframework.stereotype.Service
|
||||
//import space.luminic.finance.dtos.SpaceDTO
|
||||
//import space.luminic.finance.models.NotFoundException
|
||||
//import space.luminic.finance.models.Space
|
||||
//import space.luminic.finance.models.User
|
||||
//import space.luminic.finance.repos.SpaceRepo
|
||||
//
|
||||
//@Service
|
||||
//class SpaceServiceMongoImpl(
|
||||
// private val authService: AuthService,
|
||||
// private val spaceRepo: SpaceRepo,
|
||||
// private val mongoTemplate: ReactiveMongoTemplate,
|
||||
//) : SpaceService {
|
||||
//
|
||||
// private fun basicAggregation(user: User): List<AggregationOperation> {
|
||||
// val addFieldsAsOJ = addFields()
|
||||
// .addField("createdByOI")
|
||||
// .withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||
// .addField("updatedByOI")
|
||||
// .withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||
// .build()
|
||||
// val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||
// val unwindCreatedBy = unwind("createdBy")
|
||||
//
|
||||
// val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||
// val unwindUpdatedBy = unwind("updatedBy")
|
||||
//
|
||||
//
|
||||
//
|
||||
// val matchCriteria = mutableListOf<Criteria>()
|
||||
// matchCriteria.add(
|
||||
// Criteria().orOperator(
|
||||
// Criteria.where("ownerId").`is`(user.id),
|
||||
// Criteria.where("participantsIds").`is`(user.id)
|
||||
// )
|
||||
// )
|
||||
// matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
||||
// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
//
|
||||
// return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
||||
// }
|
||||
//
|
||||
// private fun ownerAndParticipantsLookups(): List<AggregationOperation>{
|
||||
// val addOwnerAsOJ = addFields()
|
||||
// .addField("ownerIdAsObjectId")
|
||||
// .withValue(ConvertOperators.valueOf("ownerId").convertToObjectId())
|
||||
// .addField("participantsIdsAsObjectId")
|
||||
// .withValue(
|
||||
// VariableOperators.Map.itemsOf("participantsIds")
|
||||
// .`as`("id")
|
||||
// .andApply(
|
||||
// ConvertOperators.valueOf("$\$id").convertToObjectId()
|
||||
// )
|
||||
// )
|
||||
// .build()
|
||||
// val lookupOwner = lookup("users", "ownerIdAsObjectId", "_id", "owner")
|
||||
// val unwindOwner = unwind("owner")
|
||||
// val lookupUsers = lookup("users", "participantsIdsAsObjectId", "_id", "participants")
|
||||
// return listOf(addOwnerAsOJ, lookupOwner, unwindOwner, lookupUsers)
|
||||
// }
|
||||
//
|
||||
// override suspend fun checkSpace(spaceId: String): Space {
|
||||
// val user = authService.getSecurityUser()
|
||||
// val space = getSpace(spaceId)
|
||||
//
|
||||
// // Проверяем доступ пользователя к пространству
|
||||
// return if (space.participants!!.none { it.id.toString() == user.id }) {
|
||||
// throw IllegalArgumentException("User does not have access to this Space")
|
||||
// } else space
|
||||
// }
|
||||
//
|
||||
// override suspend fun getSpaces(): List<Space> {
|
||||
// val user = authService.getSecurityUser()
|
||||
// val basicAggregation = basicAggregation(user)
|
||||
// val ownerAndParticipantsLookup = ownerAndParticipantsLookups()
|
||||
//
|
||||
// val sort = sort(Sort.by(Sort.Direction.DESC, "createdAt"))
|
||||
// val aggregation = newAggregation(
|
||||
// *basicAggregation.toTypedArray(),
|
||||
// *ownerAndParticipantsLookup.toTypedArray(),
|
||||
// sort,
|
||||
// )
|
||||
// return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).collectList().awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun getSpace(id: String): Space {
|
||||
// val user = authService.getSecurityUser()
|
||||
// val basicAggregation = basicAggregation(user)
|
||||
// val ownerAndParticipantsLookup = ownerAndParticipantsLookups()
|
||||
//
|
||||
// val aggregation = newAggregation(
|
||||
// *basicAggregation.toTypedArray(),
|
||||
// *ownerAndParticipantsLookup.toTypedArray(),
|
||||
// )
|
||||
// return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).awaitFirstOrNull()
|
||||
// ?: throw NotFoundException("Space not found")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// override suspend fun createSpace(space: SpaceDTO.CreateSpaceDTO): Space {
|
||||
// val owner = authService.getSecurityUser()
|
||||
// val createdSpace = Space(
|
||||
// name = space.name,
|
||||
// ownerId = owner.id!!,
|
||||
//
|
||||
// participantsIds = listOf(owner.id!!),
|
||||
//
|
||||
//
|
||||
// )
|
||||
// createdSpace.owner = owner
|
||||
// createdSpace.participants?.toMutableList()?.add(owner)
|
||||
// val savedSpace = spaceRepo.save(createdSpace).awaitSingle()
|
||||
// return savedSpace
|
||||
// }
|
||||
//
|
||||
// override suspend fun updateSpace(spaceId: String, space: SpaceDTO.UpdateSpaceDTO): Space {
|
||||
// val existingSpace = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found")
|
||||
// val updatedSpace = existingSpace.copy(
|
||||
// name = space.name,
|
||||
// )
|
||||
// updatedSpace.owner = existingSpace.owner
|
||||
// updatedSpace.participants = existingSpace.participants
|
||||
// return spaceRepo.save(updatedSpace).awaitFirst()
|
||||
// }
|
||||
//
|
||||
// override suspend fun deleteSpace(spaceId: String) {
|
||||
// val space = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found")
|
||||
// space.isDeleted = true
|
||||
// spaceRepo.save(space).awaitFirst()
|
||||
// }
|
||||
//}
|
||||
@@ -1,113 +1,111 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
|
||||
import com.interaso.webpush.VapidKeys
|
||||
import com.interaso.webpush.WebPushService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.bson.types.ObjectId
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.dao.DuplicateKeyException
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.models.PushMessage
|
||||
import space.luminic.finance.models.Subscription
|
||||
import space.luminic.finance.models.SubscriptionDTO
|
||||
import space.luminic.finance.models.User
|
||||
import space.luminic.finance.repos.SubscriptionRepo
|
||||
import space.luminic.finance.services.VapidConstants.VAPID_PRIVATE_KEY
|
||||
import space.luminic.finance.services.VapidConstants.VAPID_PUBLIC_KEY
|
||||
import space.luminic.finance.services.VapidConstants.VAPID_SUBJECT
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.jvm.javaClass
|
||||
import kotlin.text.orEmpty
|
||||
|
||||
object VapidConstants {
|
||||
const val VAPID_PUBLIC_KEY =
|
||||
"BKmMyBUhpkcmzYWcYsjH_spqcy0zf_8eVtZo60f7949TgLztCmv3YD0E_vtV2dTfECQ4sdLdPK3ICDcyOkCqr84"
|
||||
const val VAPID_PRIVATE_KEY = "YeJH_0LhnVYN6RdxMidgR6WMYlpGXTJS3HjT9V3NSGI"
|
||||
const val VAPID_SUBJECT = "mailto:voroninvyu@gmail.com"
|
||||
}
|
||||
|
||||
@Service
|
||||
class SubscriptionService(private val subscriptionRepo: SubscriptionRepo) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
private val pushService =
|
||||
WebPushService(
|
||||
subject = VAPID_SUBJECT,
|
||||
vapidKeys = VapidKeys.fromUncompressedBytes(VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY)
|
||||
)
|
||||
|
||||
|
||||
suspend fun sendToSpaceOwner(ownerId: String, message: PushMessage) = coroutineScope {
|
||||
val ownerTokens = subscriptionRepo.findByUserIdAndIsActive(ObjectId(ownerId)).collectList().awaitSingle()
|
||||
|
||||
ownerTokens.forEach { token ->
|
||||
launch(Dispatchers.IO) { // Теперь мы точно в корутин скоупе
|
||||
try {
|
||||
sendNotification(token.endpoint, token.p256dh, token.auth, message)
|
||||
} catch (e: Exception) {
|
||||
logger.error("Ошибка при отправке уведомления: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun sendNotification(endpoint: String, p256dh: String, auth: String, payload: PushMessage) {
|
||||
try {
|
||||
pushService.send(
|
||||
payload = Json.encodeToString(payload),
|
||||
endpoint = endpoint,
|
||||
p256dh = p256dh,
|
||||
auth = auth
|
||||
)
|
||||
logger.info("Уведомление успешно отправлено на endpoint: $endpoint")
|
||||
|
||||
} catch (e: Exception) {
|
||||
logger.error("Ошибка при отправке уведомления на endpoint $endpoint: ${e.message}")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun sendToAll(payload: PushMessage) {
|
||||
|
||||
subscriptionRepo.findAll().collectList().awaitSingle().forEach { sub ->
|
||||
|
||||
try {
|
||||
sendNotification(sub.endpoint, sub.p256dh, sub.auth, payload)
|
||||
} catch (e: Exception) {
|
||||
sub.isActive = false
|
||||
subscriptionRepo.save(sub).awaitSingle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun subscribe(subscriptionDTO: SubscriptionDTO, user: User): String {
|
||||
val subscription = Subscription(
|
||||
id = null,
|
||||
user = user,
|
||||
endpoint = subscriptionDTO.endpoint,
|
||||
auth = subscriptionDTO.keys["auth"].orEmpty(),
|
||||
p256dh = subscriptionDTO.keys["p256dh"].orEmpty(),
|
||||
isActive = true
|
||||
)
|
||||
|
||||
return try {
|
||||
val savedSubscription = subscriptionRepo.save(subscription).awaitSingle()
|
||||
"Subscription created with ID: ${savedSubscription.id}"
|
||||
} catch (e: DuplicateKeyException) {
|
||||
logger.info("Subscription already exists. Skipping.")
|
||||
"Subscription already exists. Skipping."
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error while saving subscription: ${e.message}")
|
||||
throw kotlin.RuntimeException("Error while saving subscription")
|
||||
}
|
||||
}
|
||||
}
|
||||
//package space.luminic.finance.services
|
||||
//
|
||||
//
|
||||
//import com.interaso.webpush.VapidKeys
|
||||
//import com.interaso.webpush.WebPushService
|
||||
//import kotlinx.coroutines.Dispatchers
|
||||
//import kotlinx.coroutines.coroutineScope
|
||||
//import kotlinx.coroutines.launch
|
||||
//import kotlinx.coroutines.reactive.awaitSingle
|
||||
//import kotlinx.serialization.encodeToString
|
||||
//import kotlinx.serialization.json.Json
|
||||
//import org.slf4j.LoggerFactory
|
||||
//import org.springframework.dao.DuplicateKeyException
|
||||
//import org.springframework.stereotype.Service
|
||||
//import space.luminic.finance.models.PushMessage
|
||||
//import space.luminic.finance.models.Subscription
|
||||
//import space.luminic.finance.models.User
|
||||
//import space.luminic.finance.repos.SubscriptionRepo
|
||||
//import space.luminic.finance.services.VapidConstants.VAPID_PRIVATE_KEY
|
||||
//import space.luminic.finance.services.VapidConstants.VAPID_PUBLIC_KEY
|
||||
//import space.luminic.finance.services.VapidConstants.VAPID_SUBJECT
|
||||
//import kotlin.collections.forEach
|
||||
//import kotlin.jvm.javaClass
|
||||
//import kotlin.text.orEmpty
|
||||
//
|
||||
//object VapidConstants {
|
||||
// const val VAPID_PUBLIC_KEY =
|
||||
// "BKmMyBUhpkcmzYWcYsjH_spqcy0zf_8eVtZo60f7949TgLztCmv3YD0E_vtV2dTfECQ4sdLdPK3ICDcyOkCqr84"
|
||||
// const val VAPID_PRIVATE_KEY = "YeJH_0LhnVYN6RdxMidgR6WMYlpGXTJS3HjT9V3NSGI"
|
||||
// const val VAPID_SUBJECT = "mailto:voroninvyu@gmail.com"
|
||||
//}
|
||||
//
|
||||
//@Service
|
||||
//class SubscriptionService(private val subscriptionRepo: SubscriptionRepo) {
|
||||
//
|
||||
// private val logger = LoggerFactory.getLogger(javaClass)
|
||||
// private val pushService =
|
||||
// WebPushService(
|
||||
// subject = VAPID_SUBJECT,
|
||||
// vapidKeys = VapidKeys.fromUncompressedBytes(VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY)
|
||||
// )
|
||||
//
|
||||
//
|
||||
// suspend fun sendToSpaceOwner(ownerId: String, message: PushMessage) = coroutineScope {
|
||||
// val ownerTokens = subscriptionRepo.findByUserIdAndIsActive(ObjectId(ownerId)).collectList().awaitSingle()
|
||||
//
|
||||
// ownerTokens.forEach { token ->
|
||||
// launch(Dispatchers.IO) { // Теперь мы точно в корутин скоупе
|
||||
// try {
|
||||
// sendNotification(token.endpoint, token.p256dh, token.auth, message)
|
||||
// } catch (e: Exception) {
|
||||
// logger.error("Ошибка при отправке уведомления: ${e.message}", e)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// suspend fun sendNotification(endpoint: String, p256dh: String, auth: String, payload: PushMessage) {
|
||||
// try {
|
||||
// pushService.send(
|
||||
// payload = Json.encodeToString(payload),
|
||||
// endpoint = endpoint,
|
||||
// p256dh = p256dh,
|
||||
// auth = auth
|
||||
// )
|
||||
// logger.info("Уведомление успешно отправлено на endpoint: $endpoint")
|
||||
//
|
||||
// } catch (e: Exception) {
|
||||
// logger.error("Ошибка при отправке уведомления на endpoint $endpoint: ${e.message}")
|
||||
// throw e
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// suspend fun sendToAll(payload: PushMessage) {
|
||||
//
|
||||
// subscriptionRepo.findAll().collectList().awaitSingle().forEach { sub ->
|
||||
//
|
||||
// try {
|
||||
// sendNotification(sub.endpoint, sub.p256dh, sub.auth, payload)
|
||||
// } catch (e: Exception) {
|
||||
// sub.isActive = false
|
||||
// subscriptionRepo.save(sub).awaitSingle()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// suspend fun subscribe(subscriptionDTO: SubscriptionDTO, user: User): String {
|
||||
// val subscription = Subscription(
|
||||
// id = null,
|
||||
// user = user,
|
||||
// endpoint = subscriptionDTO.endpoint,
|
||||
// auth = subscriptionDTO.keys["auth"].orEmpty(),
|
||||
// p256dh = subscriptionDTO.keys["p256dh"].orEmpty(),
|
||||
// isActive = true
|
||||
// )
|
||||
//
|
||||
// return try {
|
||||
// val savedSubscription = subscriptionRepo.save(subscription).awaitSingle()
|
||||
// "Subscription created with ID: ${savedSubscription.id}"
|
||||
// } catch (e: DuplicateKeyException) {
|
||||
// logger.info("Subscription already exists. Skipping.")
|
||||
// "Subscription already exists. Skipping."
|
||||
// } catch (e: Exception) {
|
||||
// logger.error("Error while saving subscription: ${e.message}")
|
||||
// throw kotlin.RuntimeException("Error while saving subscription")
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -1,43 +1,51 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import org.springframework.cache.annotation.CacheEvict
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Mono
|
||||
import space.luminic.finance.configs.AuthException
|
||||
import space.luminic.finance.models.Token
|
||||
import space.luminic.finance.models.Token.TokenStatus
|
||||
import space.luminic.finance.repos.TokenRepo
|
||||
import java.time.LocalDateTime
|
||||
import java.time.Instant
|
||||
|
||||
@Service
|
||||
class TokenService(private val tokenRepository: TokenRepo) {
|
||||
class TokenService(
|
||||
private val userService: UserService,
|
||||
private val tokenRepo: TokenRepo) {
|
||||
|
||||
@CacheEvict("tokens", allEntries = true)
|
||||
suspend fun saveToken(token: String, username: String, expiresAt: LocalDateTime): Token {
|
||||
fun saveToken(token: String, username: String, expiresAt: Instant): Token {
|
||||
val user = userService.getByUsername(username)
|
||||
val newToken = Token(
|
||||
token = token,
|
||||
username = username,
|
||||
issuedAt = LocalDateTime.now(),
|
||||
user = user,
|
||||
issuedAt = Instant.now(),
|
||||
expiresAt = expiresAt
|
||||
)
|
||||
return tokenRepository.save(newToken).awaitSingle()
|
||||
return tokenRepo.create(newToken)
|
||||
}
|
||||
|
||||
fun getToken(token: String): Mono<Token> {
|
||||
return tokenRepository.findByToken(token)
|
||||
fun getToken(token: String): Token {
|
||||
return tokenRepo.findByToken(token) ?: throw AuthException("Токен не валиден")
|
||||
}
|
||||
|
||||
|
||||
fun revokeToken(token: String) {
|
||||
val tokenDetail =
|
||||
tokenRepository.findByToken(token).block()!!
|
||||
val updatedToken = tokenDetail.copy(status = TokenStatus.REVOKED)
|
||||
tokenRepository.save(updatedToken).block()
|
||||
val tokenDetail = getToken(token)
|
||||
val updatedToken = Token(
|
||||
id = tokenDetail.id,
|
||||
token = tokenDetail.token,
|
||||
user = tokenDetail.user,
|
||||
status = TokenStatus.REVOKED,
|
||||
issuedAt = tokenDetail.issuedAt,
|
||||
expiresAt = tokenDetail.expiresAt
|
||||
)
|
||||
tokenRepo.update(updatedToken)
|
||||
}
|
||||
|
||||
|
||||
@CacheEvict("tokens", allEntries = true)
|
||||
fun deleteExpiredTokens() {
|
||||
tokenRepository.deleteByExpiresAtBefore(LocalDateTime.now())
|
||||
tokenRepo.deleteByExpiresAtBefore(Instant.now())
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,13 @@ import java.time.LocalDate
|
||||
interface TransactionService {
|
||||
|
||||
data class TransactionsFilter(
|
||||
val accountId: String,
|
||||
val dateFrom: LocalDate? = null,
|
||||
val dateTo: LocalDate? = null,
|
||||
)
|
||||
|
||||
suspend fun getTransactions(spaceId: String, filter: TransactionsFilter, sortBy: String, sortDirection: String): List<Transaction>
|
||||
suspend fun getTransaction(spaceId: String, transactionId: String): Transaction
|
||||
suspend fun createTransaction(spaceId: String, transaction: TransactionDTO.CreateTransactionDTO): Transaction
|
||||
suspend fun updateTransaction(spaceId: String, transaction: TransactionDTO.UpdateTransactionDTO): Transaction
|
||||
suspend fun deleteTransaction(spaceId: String, transactionId: String)
|
||||
fun getTransactions(spaceId: Int, filter: TransactionsFilter, sortBy: String, sortDirection: String): List<Transaction>
|
||||
fun getTransaction(spaceId: Int, transactionId: Int): Transaction
|
||||
fun createTransaction(spaceId: Int, transaction: TransactionDTO.CreateTransactionDTO): Int
|
||||
fun updateTransaction(spaceId: Int, transactionId: Int, transaction: TransactionDTO.UpdateTransactionDTO): Int
|
||||
fun deleteTransaction(spaceId: Int, transactionId: Int)
|
||||
}
|
||||
@@ -1,20 +1,5 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.data.domain.Sort.Direction
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields
|
||||
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.sort
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.unwind
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||
import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
@@ -23,163 +8,89 @@ import space.luminic.finance.repos.TransactionRepo
|
||||
|
||||
@Service
|
||||
class TransactionServiceImpl(
|
||||
private val mongoTemplate: ReactiveMongoTemplate,
|
||||
private val transactionRepo: TransactionRepo,
|
||||
private val spaceService: SpaceService,
|
||||
private val categoryService: CategoryService,
|
||||
private val transactionRepo: TransactionRepo,
|
||||
private val authService: AuthService,
|
||||
) : TransactionService {
|
||||
|
||||
|
||||
private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
||||
val addFieldsOI = addFields()
|
||||
.addField("createdByOI")
|
||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||
.addField("updatedByOI")
|
||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||
.addField("fromAccountIdOI")
|
||||
.withValue(ConvertOperators.valueOf("fromAccountId").convertToObjectId())
|
||||
.addField("toAccountIdOI")
|
||||
.withValue(ConvertOperators.valueOf("toAccountId").convertToObjectId())
|
||||
.addField("categoryIdOI")
|
||||
.withValue(ConvertOperators.valueOf("categoryId").convertToObjectId())
|
||||
.build()
|
||||
|
||||
val lookupFromAccount = lookup("accounts", "fromAccountIdOI", "_id", "fromAccount")
|
||||
val unwindFromAccount = unwind("fromAccount")
|
||||
val lookupToAccount = lookup("accounts", "toAccountIdOI", "_id", "toAccount")
|
||||
val unwindToAccount = unwind("toAccount", true)
|
||||
|
||||
val lookupCategory = lookup("categories", "categoryIdOI", "_id", "category")
|
||||
val unwindCategory = unwind("category")
|
||||
|
||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||
val unwindCreatedBy = unwind("createdBy")
|
||||
|
||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||
val unwindUpdatedBy = unwind("updatedBy")
|
||||
val matchCriteria = mutableListOf<Criteria>()
|
||||
matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
|
||||
return listOf(
|
||||
matchStage,
|
||||
addFieldsOI,
|
||||
lookupFromAccount,
|
||||
unwindFromAccount,
|
||||
lookupToAccount,
|
||||
unwindToAccount,
|
||||
lookupCategory,
|
||||
unwindCategory,
|
||||
lookupCreatedBy,
|
||||
unwindCreatedBy,
|
||||
lookupUpdatedBy,
|
||||
unwindUpdatedBy
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getTransactions(
|
||||
spaceId: String,
|
||||
override fun getTransactions(
|
||||
spaceId: Int,
|
||||
filter: TransactionService.TransactionsFilter,
|
||||
sortBy: String,
|
||||
sortDirection: String
|
||||
): List<Transaction> {
|
||||
val allowedSortFields = setOf("date", "amount", "category.name", "createdAt")
|
||||
require(sortBy in allowedSortFields) { "Invalid sort field: $sortBy" }
|
||||
|
||||
val direction = when (sortDirection.uppercase()) {
|
||||
"ASC" -> Direction.ASC
|
||||
"DESC" -> Direction.DESC
|
||||
else -> throw IllegalArgumentException("Sort direction must be 'ASC' or 'DESC'")
|
||||
}
|
||||
val basicAggregation = basicAggregation(spaceId)
|
||||
|
||||
val sort = sort(Sort.by(direction, sortBy))
|
||||
val matchCriteria = mutableListOf<Criteria>()
|
||||
filter.dateFrom?.let { matchCriteria.add(Criteria.where("date").gte(it)) }
|
||||
filter.dateTo?.let { matchCriteria.add(Criteria.where("date").lte(it)) }
|
||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
val aggregation =
|
||||
newAggregation(
|
||||
matchStage,
|
||||
*basicAggregation.toTypedArray(),
|
||||
sort
|
||||
)
|
||||
|
||||
return mongoTemplate.aggregate(aggregation, "transactions", Transaction::class.java)
|
||||
.collectList()
|
||||
.awaitSingle()
|
||||
return transactionRepo.findAllBySpaceId(spaceId)
|
||||
}
|
||||
|
||||
override suspend fun getTransaction(
|
||||
spaceId: String,
|
||||
transactionId: String
|
||||
override fun getTransaction(
|
||||
spaceId: Int,
|
||||
transactionId: Int
|
||||
): Transaction {
|
||||
val matchCriteria = mutableListOf<Criteria>()
|
||||
matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||
matchCriteria.add(Criteria.where("_id").`is`(ObjectId(transactionId)))
|
||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
|
||||
val aggregation =
|
||||
newAggregation(
|
||||
matchStage,
|
||||
)
|
||||
return mongoTemplate.aggregate(aggregation, "transactions", Transaction::class.java)
|
||||
.awaitFirstOrNull() ?: throw NotFoundException("Transaction with ID $transactionId not found")
|
||||
spaceService.getSpace(spaceId)
|
||||
return transactionRepo.findBySpaceIdAndId(spaceId, transactionId)
|
||||
?: throw NotFoundException("Transaction with id $transactionId not found")
|
||||
}
|
||||
|
||||
override suspend fun createTransaction(
|
||||
spaceId: String,
|
||||
override fun createTransaction(
|
||||
spaceId: Int,
|
||||
transaction: TransactionDTO.CreateTransactionDTO
|
||||
): Transaction {
|
||||
if (transaction.type == Transaction.TransactionType.TRANSFER && transaction.toAccountId == null) {
|
||||
throw IllegalArgumentException("Cannot create a transaction with type TRANSFER without a toAccountId")
|
||||
}
|
||||
): Int {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceService.getSpace(spaceId)
|
||||
val category = categoryService.getCategory(spaceId, transaction.categoryId)
|
||||
if (transaction.type != Transaction.TransactionType.TRANSFER && transaction.type.name != category.type.name) {
|
||||
throw IllegalArgumentException("Transaction type should match with category type")
|
||||
}
|
||||
val transaction = Transaction(
|
||||
spaceId = spaceId,
|
||||
space = space,
|
||||
type = transaction.type,
|
||||
kind = transaction.kind,
|
||||
categoryId = transaction.categoryId,
|
||||
category = category,
|
||||
comment = transaction.comment,
|
||||
amount = transaction.amount,
|
||||
fees = transaction.fees,
|
||||
date = transaction.date,
|
||||
fromAccountId = transaction.fromAccountId,
|
||||
toAccountId = transaction.toAccountId,
|
||||
)
|
||||
return transactionRepo.save(transaction).awaitSingle()
|
||||
return transactionRepo.create(transaction, userId)
|
||||
}
|
||||
|
||||
override suspend fun updateTransaction(
|
||||
spaceId: String,
|
||||
override fun updateTransaction(
|
||||
spaceId: Int,
|
||||
transactionId: Int,
|
||||
transaction: TransactionDTO.UpdateTransactionDTO
|
||||
): Transaction {
|
||||
if (transaction.type == Transaction.TransactionType.TRANSFER && transaction.toAccountId == null) {
|
||||
throw IllegalArgumentException("Cannot edit a transaction with type TRANSFER without a toAccountId")
|
||||
}
|
||||
val exitingTx = getTransaction(spaceId, transaction.id)
|
||||
val transaction = exitingTx.copy(
|
||||
spaceId = exitingTx.spaceId,
|
||||
): Int {
|
||||
val space = spaceService.getSpace(spaceId)
|
||||
val existingTransaction = getTransaction(space.id!!, transactionId)
|
||||
val newCategory = categoryService.getCategory(spaceId, transaction.categoryId)
|
||||
// val id: Int,
|
||||
// val type: TransactionType = TransactionType.EXPENSE,
|
||||
// val kind: TransactionKind = TransactionKind.INSTANT,
|
||||
// val category: Int,
|
||||
// val comment: String,
|
||||
// val amount: BigDecimal,
|
||||
// val fees: BigDecimal = BigDecimal.ZERO,
|
||||
// val date: Instant
|
||||
val updatedTransaction = Transaction(
|
||||
id = existingTransaction.id,
|
||||
space = existingTransaction.space,
|
||||
parent = existingTransaction.parent,
|
||||
type = transaction.type,
|
||||
kind = transaction.kind,
|
||||
categoryId = transaction.category,
|
||||
category = newCategory,
|
||||
comment = transaction.comment,
|
||||
amount = transaction.amount,
|
||||
fees = transaction.fees,
|
||||
date = transaction.date,
|
||||
fromAccountId = transaction.fromAccountId,
|
||||
toAccountId = transaction.toAccountId,
|
||||
isDeleted = existingTransaction.isDeleted,
|
||||
isDone = transaction.isDone,
|
||||
createdBy = existingTransaction.createdBy,
|
||||
createdAt = existingTransaction.createdAt
|
||||
|
||||
)
|
||||
return transactionRepo.save(transaction).awaitSingle()
|
||||
return transactionRepo.update(updatedTransaction)
|
||||
}
|
||||
|
||||
override suspend fun deleteTransaction(spaceId: String, transactionId: String) {
|
||||
val transaction = getTransaction(spaceId, transactionId)
|
||||
transaction.isDeleted = true
|
||||
transactionRepo.save(transaction).awaitSingle()
|
||||
override fun deleteTransaction(spaceId: Int, transactionId: Int) {
|
||||
val space = spaceService.getSpace(spaceId)
|
||||
getTransaction(space.id!!, transactionId)
|
||||
transactionRepo.delete(transactionId)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
//package space.luminic.finance.services
|
||||
//
|
||||
//import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
//import kotlinx.coroutines.reactive.awaitSingle
|
||||
//import org.bson.types.ObjectId
|
||||
//import org.springframework.data.domain.Sort
|
||||
//import org.springframework.data.domain.Sort.Direction
|
||||
//import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||
//import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields
|
||||
//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.sort
|
||||
//import org.springframework.data.mongodb.core.aggregation.Aggregation.unwind
|
||||
//import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||
//import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||
//import org.springframework.data.mongodb.core.query.Criteria
|
||||
//import org.springframework.stereotype.Service
|
||||
//import space.luminic.finance.dtos.TransactionDTO
|
||||
//import space.luminic.finance.models.NotFoundException
|
||||
//import space.luminic.finance.models.Transaction
|
||||
//import space.luminic.finance.repos.TransactionRepo
|
||||
//
|
||||
//@Service
|
||||
//class TransactionServiceMongoImpl(
|
||||
// private val mongoTemplate: ReactiveMongoTemplate,
|
||||
// private val transactionRepo: TransactionRepo,
|
||||
// private val categoryService: CategoryService,
|
||||
//) : TransactionService {
|
||||
//
|
||||
//
|
||||
// private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
||||
// val addFieldsOI = addFields()
|
||||
// .addField("createdByOI")
|
||||
// .withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||
// .addField("updatedByOI")
|
||||
// .withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||
// .addField("fromAccountIdOI")
|
||||
// .withValue(ConvertOperators.valueOf("fromAccountId").convertToObjectId())
|
||||
// .addField("toAccountIdOI")
|
||||
// .withValue(ConvertOperators.valueOf("toAccountId").convertToObjectId())
|
||||
// .addField("categoryIdOI")
|
||||
// .withValue(ConvertOperators.valueOf("categoryId").convertToObjectId())
|
||||
// .build()
|
||||
//
|
||||
// val lookupFromAccount = lookup("accounts", "fromAccountIdOI", "_id", "fromAccount")
|
||||
// val unwindFromAccount = unwind("fromAccount")
|
||||
// val lookupToAccount = lookup("accounts", "toAccountIdOI", "_id", "toAccount")
|
||||
// val unwindToAccount = unwind("toAccount", true)
|
||||
//
|
||||
// val lookupCategory = lookup("categories", "categoryIdOI", "_id", "category")
|
||||
// val unwindCategory = unwind("category")
|
||||
//
|
||||
// val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||
// val unwindCreatedBy = unwind("createdBy")
|
||||
//
|
||||
// val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||
// val unwindUpdatedBy = unwind("updatedBy")
|
||||
// val matchCriteria = mutableListOf<Criteria>()
|
||||
// matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||
// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
//
|
||||
// return listOf(
|
||||
// matchStage,
|
||||
// addFieldsOI,
|
||||
// lookupFromAccount,
|
||||
// unwindFromAccount,
|
||||
// lookupToAccount,
|
||||
// unwindToAccount,
|
||||
// lookupCategory,
|
||||
// unwindCategory,
|
||||
// lookupCreatedBy,
|
||||
// unwindCreatedBy,
|
||||
// lookupUpdatedBy,
|
||||
// unwindUpdatedBy
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// override suspend fun getTransactions(
|
||||
// spaceId: String,
|
||||
// filter: TransactionService.TransactionsFilter,
|
||||
// sortBy: String,
|
||||
// sortDirection: String
|
||||
// ): List<Transaction> {
|
||||
// val allowedSortFields = setOf("date", "amount", "category.name", "createdAt")
|
||||
// require(sortBy in allowedSortFields) { "Invalid sort field: $sortBy" }
|
||||
//
|
||||
// val direction = when (sortDirection.uppercase()) {
|
||||
// "ASC" -> Direction.ASC
|
||||
// "DESC" -> Direction.DESC
|
||||
// else -> throw IllegalArgumentException("Sort direction must be 'ASC' or 'DESC'")
|
||||
// }
|
||||
// val basicAggregation = basicAggregation(spaceId)
|
||||
//
|
||||
// val sort = sort(Sort.by(direction, sortBy))
|
||||
// val matchCriteria = mutableListOf<Criteria>()
|
||||
// filter.dateFrom?.let { matchCriteria.add(Criteria.where("date").gte(it)) }
|
||||
// filter.dateTo?.let { matchCriteria.add(Criteria.where("date").lte(it)) }
|
||||
// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
// val aggregation =
|
||||
// newAggregation(
|
||||
// matchStage,
|
||||
// *basicAggregation.toTypedArray(),
|
||||
// sort
|
||||
// )
|
||||
//
|
||||
// return mongoTemplate.aggregate(aggregation, "transactions", Transaction::class.java)
|
||||
// .collectList()
|
||||
// .awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun getTransaction(
|
||||
// spaceId: String,
|
||||
// transactionId: String
|
||||
// ): Transaction {
|
||||
// val matchCriteria = mutableListOf<Criteria>()
|
||||
// matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||
// matchCriteria.add(Criteria.where("_id").`is`(ObjectId(transactionId)))
|
||||
// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||
//
|
||||
// val aggregation =
|
||||
// newAggregation(
|
||||
// matchStage,
|
||||
// )
|
||||
// return mongoTemplate.aggregate(aggregation, "transactions", Transaction::class.java)
|
||||
// .awaitFirstOrNull() ?: throw NotFoundException("Transaction with ID $transactionId not found")
|
||||
// }
|
||||
//
|
||||
// override suspend fun createTransaction(
|
||||
// spaceId: String,
|
||||
// transaction: TransactionDTO.CreateTransactionDTO
|
||||
// ): Transaction {
|
||||
// if (transaction.type == Transaction.TransactionType.TRANSFER && transaction.toAccountId == null) {
|
||||
// throw IllegalArgumentException("Cannot create a transaction with type TRANSFER without a toAccountId")
|
||||
// }
|
||||
// val category = categoryService.getCategory(spaceId, transaction.categoryId)
|
||||
// if (transaction.type != Transaction.TransactionType.TRANSFER && transaction.type.name != category.type.name) {
|
||||
// throw IllegalArgumentException("Transaction type should match with category type")
|
||||
// }
|
||||
// val transaction = Transaction(
|
||||
// spaceId = spaceId,
|
||||
// type = transaction.type,
|
||||
// kind = transaction.kind,
|
||||
// categoryId = transaction.categoryId,
|
||||
// comment = transaction.comment,
|
||||
// amount = transaction.amount,
|
||||
// fees = transaction.fees,
|
||||
// date = transaction.date,
|
||||
// fromAccountId = transaction.fromAccountId,
|
||||
// toAccountId = transaction.toAccountId,
|
||||
// )
|
||||
// return transactionRepo.save(transaction).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun updateTransaction(
|
||||
// spaceId: String,
|
||||
// transaction: TransactionDTO.UpdateTransactionDTO
|
||||
// ): Transaction {
|
||||
// if (transaction.type == Transaction.TransactionType.TRANSFER && transaction.toAccountId == null) {
|
||||
// throw IllegalArgumentException("Cannot edit a transaction with type TRANSFER without a toAccountId")
|
||||
// }
|
||||
// val exitingTx = getTransaction(spaceId, transaction.id)
|
||||
// val transaction = exitingTx.copy(
|
||||
// spaceId = exitingTx.spaceId,
|
||||
// type = transaction.type,
|
||||
// kind = transaction.kind,
|
||||
// categoryId = transaction.category,
|
||||
// comment = transaction.comment,
|
||||
// amount = transaction.amount,
|
||||
// fees = transaction.fees,
|
||||
// date = transaction.date,
|
||||
// fromAccountId = transaction.fromAccountId,
|
||||
// toAccountId = transaction.toAccountId,
|
||||
// )
|
||||
// return transactionRepo.save(transaction).awaitSingle()
|
||||
// }
|
||||
//
|
||||
// override suspend fun deleteTransaction(spaceId: String, transactionId: String) {
|
||||
// val transaction = getTransaction(spaceId, transactionId)
|
||||
// transaction.isDeleted = true
|
||||
// transactionRepo.save(transaction).awaitSingle()
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
@@ -1,12 +1,9 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.cache.annotation.Cacheable
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.mappers.UserMapper
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.models.User
|
||||
import space.luminic.finance.repos.UserRepo
|
||||
@@ -17,36 +14,30 @@ class UserService(val userRepo: UserRepo) {
|
||||
|
||||
|
||||
@Cacheable("users", key = "#username")
|
||||
suspend fun getByUsername(username: String): User {
|
||||
return userRepo.findByUsername(username).awaitSingleOrNull()
|
||||
?: throw NotFoundException("User with username: $username not found")
|
||||
fun getByUsername(username: String): User {
|
||||
return userRepo.findByUsername(username) ?: throw NotFoundException("User with username: $username not found")
|
||||
|
||||
}
|
||||
|
||||
suspend fun getById(id: String): User {
|
||||
return userRepo.findById(id).awaitSingleOrNull()
|
||||
?: throw NotFoundException("User with id: $id not found")
|
||||
fun getById(id: Int): User {
|
||||
return userRepo.findById(id) ?: throw NotFoundException("User with id: $id not found")
|
||||
}
|
||||
|
||||
suspend fun getUserByTelegramId(telegramId: Long): User {
|
||||
return userRepo.findByTgId(telegramId.toString()).awaitSingleOrNull()
|
||||
?: throw NotFoundException("User with telegramId: $telegramId not found")
|
||||
fun getUserByTelegramId(telegramId: Long): User {
|
||||
return userRepo.findByTgId(telegramId.toString())?: throw NotFoundException("User with telegramId: $telegramId not found")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Cacheable("users", key = "#username")
|
||||
suspend fun getByUserNameWoPass(username: String): User {
|
||||
return userRepo.findByUsernameWOPassword(username).awaitSingleOrNull()
|
||||
?: throw NotFoundException("User with username: $username not found")
|
||||
fun getByUserNameWoPass(username: String): User {
|
||||
return userRepo.findByUsername(username) ?: throw NotFoundException("User with username: $username not found")
|
||||
|
||||
}
|
||||
|
||||
@Cacheable("usersList")
|
||||
suspend fun getUsers(): List<User> {
|
||||
fun getUsers(): List<User> {
|
||||
return userRepo.findAll()
|
||||
.collectList()
|
||||
.awaitSingle()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +1,28 @@
|
||||
package space.luminic.finance.utils
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Component
|
||||
import space.luminic.finance.models.PushMessage
|
||||
import space.luminic.finance.services.SubscriptionService
|
||||
import space.luminic.finance.services.TokenService
|
||||
|
||||
|
||||
@Component
|
||||
class ScheduledTasks(private val subscriptionService: SubscriptionService,
|
||||
private val tokenService: TokenService) {
|
||||
private val logger = LoggerFactory.getLogger(ScheduledTasks::class.java)
|
||||
|
||||
@Scheduled(cron = "0 30 19 * * *")
|
||||
suspend fun sendNotificationOfMoneyFilling() {
|
||||
subscriptionService.sendToAll(
|
||||
PushMessage(
|
||||
title = "Время заполнять траты!🤑",
|
||||
body = "Проверь все ли траты внесены!",
|
||||
icon = "/apple-touch-icon.png",
|
||||
badge = "/apple-touch-icon.png",
|
||||
url = "https://luminic.space/transactions/create"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 0 * * *")
|
||||
fun deleteExpiredTokens() {
|
||||
tokenService.deleteExpiredTokens()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
//
|
||||
//@Component
|
||||
//class ScheduledTasks(private val subscriptionService: SubscriptionService,
|
||||
// private val tokenService: TokenService) {
|
||||
// private val logger = LoggerFactory.getLogger(ScheduledTasks::class.java)
|
||||
//
|
||||
// @Scheduled(cron = "0 30 19 * * *")
|
||||
// suspend fun sendNotificationOfMoneyFilling() {
|
||||
// subscriptionService.sendToAll(
|
||||
// PushMessage(
|
||||
// title = "Время заполнять траты!🤑",
|
||||
// body = "Проверь все ли траты внесены!",
|
||||
// icon = "/apple-touch-icon.png",
|
||||
// badge = "/apple-touch-icon.png",
|
||||
// url = "https://luminic.space/transactions/create"
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @Scheduled(cron = "0 0 0 * * *")
|
||||
// fun deleteExpiredTokens() {
|
||||
// tokenService.deleteExpiredTokens()
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
@@ -11,10 +11,10 @@ spring.data.mongodb.database=budger-app
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
|
||||
nlp.address=http://127.0.0.1:8000
|
||||
|
||||
|
||||
spring.datasource.url=jdbc:postgresql://213.183.51.243/familybudget_app
|
||||
spring.datasource.username=familybudget_app
|
||||
spring.datasource.password=FB1q2w3e4r!
|
||||
spring.datasource.url=jdbc:postgresql://213.226.71.138:5432/luminic-space-db
|
||||
spring.datasource.username=luminicspace
|
||||
spring.datasource.password=LS1q2w3e4r!
|
||||
|
||||
telegram.bot.token = 6972242509:AAGyXuL3T-BNE4XMoo_qvtaYxw_SuiS_dDs
|
||||
@@ -3,13 +3,26 @@ spring.data.mongodb.uri=mongodb://budger-app:BA1q2w3e4r!@luminic.space:27017/bud
|
||||
|
||||
logging.level.org.springframework.web=DEBUG
|
||||
logging.level.org.springframework.data = DEBUG
|
||||
logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=DEBUG
|
||||
logging.level.org.springframework.data.jpa = DEBUG
|
||||
#logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=DEBUG
|
||||
logging.level.org.springframework.security = DEBUG
|
||||
logging.level.org.springframework.data.mongodb.code = DEBUG
|
||||
#logging.level.org.springframework.data.mongodb.code = DEBUG
|
||||
logging.level.org.springframework.web.reactive=DEBUG
|
||||
logging.level.org.mongodb.driver.protocol.command = DEBUG
|
||||
logging.level.org.springframework.jdbc.core=DEBUG
|
||||
logging.level.org.springframework.jdbc.core.StatementCreatorUtils=TRACE
|
||||
logging.level.org.springframework.jdbc=DEBUG
|
||||
logging.level.org.springframework.jdbc.datasource=DEBUG
|
||||
logging.level.org.springframework.jdbc.support=DEBUG
|
||||
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
telegram.bot.token=6972242509:AAGyXuL3T-BNE4XMoo_qvtaYxw_SuiS_dDs
|
||||
nlp.address=http://127.0.0.1:8000
|
||||
nlp.address=http://127.0.0.1:8000
|
||||
|
||||
|
||||
|
||||
spring.datasource.url=jdbc:postgresql://213.226.71.138:5432/luminic-space-db
|
||||
spring.datasource.username=luminicspace
|
||||
spring.datasource.password=LS1q2w3e4r!
|
||||
@@ -19,3 +19,9 @@ logging.level.org.mongodb.driver.protocol.command = INFO
|
||||
|
||||
telegram.bot.token = 6662300972:AAFXjk_h0AUCy4bORC12UcdXbYnh2QSVKAY
|
||||
nlp.address=https://nlp.luminic.space
|
||||
|
||||
|
||||
|
||||
spring.datasource.url=jdbc:postgresql://213.226.71.138:5432/luminic-space-db
|
||||
spring.datasource.username=luminicspace
|
||||
spring.datasource.password=LS1q2w3e4r!
|
||||
@@ -1,11 +1,11 @@
|
||||
spring.application.name=budger-app
|
||||
|
||||
server.port=8082
|
||||
#server.servlet.context-path=/api
|
||||
spring.webflux.base-path=/api
|
||||
server.servlet.context-path=/api
|
||||
#spring.webflux.base-path=/api
|
||||
|
||||
spring.profiles.active=prod
|
||||
spring.main.web-application-type=reactive
|
||||
spring.main.web-application-type=servlet
|
||||
|
||||
server.compression.enabled=true
|
||||
server.compression.mime-types=application/json
|
||||
@@ -16,7 +16,7 @@ spring.servlet.multipart.max-request-size=10MB
|
||||
|
||||
storage.location: static
|
||||
|
||||
|
||||
spring.jackson.default-property-inclusion=non_null
|
||||
# Expose prometheus, health, and info endpoints
|
||||
#management.endpoints.web.exposure.include=prometheus,health,info
|
||||
management.endpoints.web.exposure.include=*
|
||||
@@ -25,5 +25,9 @@ management.endpoints.web.exposure.include=*
|
||||
management.prometheus.metrics.export.enabled=true
|
||||
|
||||
telegram.bot.username = expenses_diary_bot
|
||||
|
||||
|
||||
spring.flyway.enabled=true
|
||||
spring.flyway.locations=classpath:db/migration
|
||||
spring.flyway.baseline-on-migrate= false
|
||||
spring.flyway.schemas=finance
|
||||
spring.jpa.properties.hibernate.default_schema=finance
|
||||
spring.jpa.properties.hibernate.default_batch_fetch_size=50
|
||||
26
src/main/resources/db/migration/V10__.sql
Normal file
26
src/main/resources/db/migration/V10__.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
ALTER TABLE finance.users
|
||||
ALTER COLUMN created_at SEt DEFAULT now(),
|
||||
ALTER COLUMN updated_at SEt DEFAULT now();
|
||||
|
||||
ALTER TABLE finance.spaces
|
||||
ALTER COLUMN created_at SEt DEFAULT now(),
|
||||
ALTER COLUMN updated_at SEt DEFAULT now();
|
||||
|
||||
ALTER TABLE finance.transactions
|
||||
ALTER COLUMN type SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN kind SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN created_at SET DEFAULT now(),
|
||||
ALTER COLUMN updated_at SEt DEFAULT now();
|
||||
|
||||
|
||||
|
||||
ALTER TABLE finance.tokens
|
||||
ALTER COLUMN status SET DATA TYPE VARCHAR(255),
|
||||
ALTER COLUMN issued_at SET DEFAULT now();
|
||||
|
||||
|
||||
ALTER TABLE finance.categories_etalon
|
||||
ALTER COLUMN type SET DATA TYPE VARCHAR(255);
|
||||
|
||||
|
||||
|
||||
0
src/main/resources/db/migration/V11__.sql
Normal file
0
src/main/resources/db/migration/V11__.sql
Normal file
2
src/main/resources/db/migration/V12__.sql
Normal file
2
src/main/resources/db/migration/V12__.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
alter table finance.categories
|
||||
add column if not exists description varchar(1000);
|
||||
5
src/main/resources/db/migration/V13__.sql
Normal file
5
src/main/resources/db/migration/V13__.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
alter table finance.categories
|
||||
add column if not exists description varchar(1000);
|
||||
|
||||
alter table finance.categories
|
||||
alter column type set data type varchar;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user