From 7972ea0fdf52f855e46fb0aa5cdc0232a54e5e75 Mon Sep 17 00:00:00 2001 From: xds Date: Fri, 31 Oct 2025 15:31:55 +0300 Subject: [PATCH] init --- Dockerfile | 31 ++ build.gradle.kts | 17 +- src/main/kotlin/space/luminic/finance/Main.kt | 12 +- .../luminic/finance/api/AccountController.kt | 75 ----- .../luminic/finance/api/AuthController.kt | 25 +- .../luminic/finance/api/BudgetController.kt | 78 ----- .../luminic/finance/api/CategoryController.kt | 36 ++- .../luminic/finance/api/GoalController.kt | 19 ++ .../api/RecurrentOperationController.kt | 40 +++ .../finance/api/ReferenceController.kt | 19 +- .../luminic/finance/api/SpaceController.kt | 24 +- .../finance/api/TransactionController.kt | 25 +- .../api/exceptionHandlers/ExceptionHandler.kt | 121 ++++---- .../GlobalExceptionHandlerV2.kt | 84 +++--- .../finance/configs/BearerTokenFilter.kt | 98 +++--- .../luminic/finance/configs/CommonConfig.kt | 14 +- .../luminic/finance/configs/JpaConfig.kt | 31 ++ .../luminic/finance/configs/SecurityConfig.kt | 80 ++--- .../space/luminic/finance/dtos/AccountDTO.kt | 37 --- .../space/luminic/finance/dtos/BudgetDTO.kt | 69 ----- .../space/luminic/finance/dtos/CategoryDTO.kt | 15 +- .../space/luminic/finance/dtos/GoalDTO.kt | 25 +- .../finance/dtos/RecurrentOperationDTO.kt | 29 ++ .../space/luminic/finance/dtos/SpaceDTO.kt | 18 +- .../luminic/finance/dtos/SubscriptionDTO.kt | 6 + .../luminic/finance/dtos/TransactionDTO.kt | 37 +-- .../space/luminic/finance/dtos/UserDTO.kt | 4 +- .../luminic/finance/mappers/AccountMapper.kt | 26 -- .../luminic/finance/mappers/BudgetMapper.kt | 88 ------ .../luminic/finance/mappers/CategoryMapper.kt | 5 +- .../luminic/finance/mappers/GoalMapper.kt | 11 +- .../mappers/RecurrentOperationMapper.kt | 21 ++ .../luminic/finance/mappers/SpaceMapper.kt | 4 +- .../finance/mappers/TransactionMapper.kt | 22 +- .../luminic/finance/mappers/UserMapper.kt | 1 + .../space/luminic/finance/models/Account.kt | 51 ---- .../space/luminic/finance/models/Budget.kt | 62 ---- .../space/luminic/finance/models/Category.kt | 28 +- .../space/luminic/finance/models/Currency.kt | 7 +- .../luminic/finance/models/CurrencyRate.kt | 14 +- .../space/luminic/finance/models/Goal.kt | 42 +-- .../finance/models/RecurrentOperation.kt | 17 ++ .../space/luminic/finance/models/Space.kt | 34 +-- .../luminic/finance/models/Subscription.kt | 20 +- .../space/luminic/finance/models/Token.kt | 14 +- .../luminic/finance/models/Transaction.kt | 40 +-- .../space/luminic/finance/models/User.kt | 19 +- .../luminic/finance/repos/AccountRepo.kt | 7 - .../space/luminic/finance/repos/BudgetRepo.kt | 14 - .../luminic/finance/repos/CategoryRepo.kt | 13 +- .../luminic/finance/repos/CategoryRepoImpl.kt | 107 +++++++ .../luminic/finance/repos/CurrencyRepo.kt | 21 +- .../luminic/finance/repos/CurrencyRepoImpl.kt | 50 ++++ .../space/luminic/finance/repos/GoalRepo.kt | 20 ++ .../luminic/finance/repos/GoalRepoImpl.kt | 283 ++++++++++++++++++ .../finance/repos/RecurrentOperationRepo.kt | 11 + .../repos/RecurrentOperationRepoImpl.kt | 178 +++++++++++ .../space/luminic/finance/repos/SpaceRepo.kt | 11 +- .../luminic/finance/repos/SpaceRepoImpl.kt | 221 ++++++++++++++ .../luminic/finance/repos/SubscriptionRepo.kt | 16 +- .../space/luminic/finance/repos/TokenRepo.kt | 13 +- .../luminic/finance/repos/TokenRepoImpl.kt | 84 ++++++ .../luminic/finance/repos/TransactionRepo.kt | 9 +- .../finance/repos/TransactionRepoImpl.kt | 206 +++++++++++++ .../space/luminic/finance/repos/UserRepo.kt | 21 +- .../luminic/finance/repos/UserRepoImpl.kt | 80 +++++ .../finance/services/AccountService.kt | 14 - .../finance/services/AccountServiceImpl.kt | 119 -------- .../luminic/finance/services/AuthService.kt | 58 ++-- .../luminic/finance/services/BudgetService.kt | 18 -- .../finance/services/BudgetServiceImpl.kt | 188 ------------ .../finance/services/CategoryService.kt | 14 +- .../finance/services/CategoryServiceImpl.kt | 159 +++++----- .../services/CategoryServiceMongoImpl.kt | 108 +++++++ .../finance/services/CoroutineAuditorAware.kt | 16 - .../finance/services/CurrencyService.kt | 12 +- .../finance/services/CurrencyServiceImpl.kt | 55 ++-- .../services/CurrencyServiceMongoImpl.kt | 51 ++++ .../luminic/finance/services/GoalService.kt | 22 ++ .../finance/services/GoalServiceImpl.kt | 140 +++++++++ .../services/RecurrentOperationService.kt | 13 + .../services/RecurrentOperationServiceImpl.kt | 66 ++++ .../luminic/finance/services/SpaceService.kt | 13 +- .../finance/services/SpaceServiceImpl.kt | 172 +++-------- .../finance/services/SpaceServiceMongoImpl.kt | 145 +++++++++ .../finance/services/SubscriptionService.kt | 224 +++++++------- .../luminic/finance/services/TokenService.kt | 38 ++- .../finance/services/TransactionService.kt | 11 +- .../services/TransactionServiceImpl.kt | 189 ++++-------- .../services/TransactionServiceMongoImpl.kt | 185 ++++++++++++ .../luminic/finance/services/UserService.kt | 27 +- .../luminic/finance/utils/ScheduledTasks.kt | 59 ++-- .../application-dev-local.properties | 8 +- src/main/resources/application-dev.properties | 19 +- .../resources/application-prod.properties | 6 + src/main/resources/application.properties | 16 +- src/main/resources/db/migration/V10__.sql | 26 ++ src/main/resources/db/migration/V11__.sql | 0 src/main/resources/db/migration/V12__.sql | 2 + src/main/resources/db/migration/V13__.sql | 5 + src/main/resources/db/migration/V14__.sql | 2 + src/main/resources/db/migration/V15__.sql | 20 ++ src/main/resources/db/migration/V16__.sql | 15 + src/main/resources/db/migration/V17__.sql | 19 ++ src/main/resources/db/migration/V18__.sql | 40 +++ src/main/resources/db/migration/V19__.sql | 40 +++ src/main/resources/db/migration/V1__.sql | 196 ++++++++++++ src/main/resources/db/migration/V20__.sql | 40 +++ src/main/resources/db/migration/V21__.sql | 32 ++ src/main/resources/db/migration/V2__.sql | 3 + src/main/resources/db/migration/V3__.sql | 8 + src/main/resources/db/migration/V4__.sql | 10 + src/main/resources/db/migration/V5__.sql | 2 + src/main/resources/db/migration/V6__.sql | 1 + src/main/resources/db/migration/V7__.sql | 105 +++++++ src/main/resources/db/migration/V8__.sql | 8 + src/main/resources/db/migration/V9__.sql | 5 + 117 files changed, 3691 insertions(+), 2013 deletions(-) create mode 100644 Dockerfile delete mode 100644 src/main/kotlin/space/luminic/finance/api/AccountController.kt delete mode 100644 src/main/kotlin/space/luminic/finance/api/BudgetController.kt create mode 100644 src/main/kotlin/space/luminic/finance/api/GoalController.kt create mode 100644 src/main/kotlin/space/luminic/finance/api/RecurrentOperationController.kt create mode 100644 src/main/kotlin/space/luminic/finance/configs/JpaConfig.kt delete mode 100644 src/main/kotlin/space/luminic/finance/dtos/AccountDTO.kt delete mode 100644 src/main/kotlin/space/luminic/finance/dtos/BudgetDTO.kt create mode 100644 src/main/kotlin/space/luminic/finance/dtos/RecurrentOperationDTO.kt create mode 100644 src/main/kotlin/space/luminic/finance/dtos/SubscriptionDTO.kt delete mode 100644 src/main/kotlin/space/luminic/finance/mappers/AccountMapper.kt delete mode 100644 src/main/kotlin/space/luminic/finance/mappers/BudgetMapper.kt create mode 100644 src/main/kotlin/space/luminic/finance/mappers/RecurrentOperationMapper.kt delete mode 100644 src/main/kotlin/space/luminic/finance/models/Account.kt delete mode 100644 src/main/kotlin/space/luminic/finance/models/Budget.kt create mode 100644 src/main/kotlin/space/luminic/finance/models/RecurrentOperation.kt delete mode 100644 src/main/kotlin/space/luminic/finance/repos/AccountRepo.kt delete mode 100644 src/main/kotlin/space/luminic/finance/repos/BudgetRepo.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/CategoryRepoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/CurrencyRepoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/GoalRepo.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/GoalRepoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepo.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/TokenRepoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/TransactionRepoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt delete mode 100644 src/main/kotlin/space/luminic/finance/services/AccountService.kt delete mode 100644 src/main/kotlin/space/luminic/finance/services/AccountServiceImpl.kt delete mode 100644 src/main/kotlin/space/luminic/finance/services/BudgetService.kt delete mode 100644 src/main/kotlin/space/luminic/finance/services/BudgetServiceImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/services/CategoryServiceMongoImpl.kt delete mode 100644 src/main/kotlin/space/luminic/finance/services/CoroutineAuditorAware.kt create mode 100644 src/main/kotlin/space/luminic/finance/services/CurrencyServiceMongoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/services/GoalService.kt create mode 100644 src/main/kotlin/space/luminic/finance/services/GoalServiceImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/services/RecurrentOperationService.kt create mode 100644 src/main/kotlin/space/luminic/finance/services/RecurrentOperationServiceImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/services/SpaceServiceMongoImpl.kt create mode 100644 src/main/kotlin/space/luminic/finance/services/TransactionServiceMongoImpl.kt create mode 100644 src/main/resources/db/migration/V10__.sql create mode 100644 src/main/resources/db/migration/V11__.sql create mode 100644 src/main/resources/db/migration/V12__.sql create mode 100644 src/main/resources/db/migration/V13__.sql create mode 100644 src/main/resources/db/migration/V14__.sql create mode 100644 src/main/resources/db/migration/V15__.sql create mode 100644 src/main/resources/db/migration/V16__.sql create mode 100644 src/main/resources/db/migration/V17__.sql create mode 100644 src/main/resources/db/migration/V18__.sql create mode 100644 src/main/resources/db/migration/V19__.sql create mode 100644 src/main/resources/db/migration/V1__.sql create mode 100644 src/main/resources/db/migration/V20__.sql create mode 100644 src/main/resources/db/migration/V21__.sql create mode 100644 src/main/resources/db/migration/V2__.sql create mode 100644 src/main/resources/db/migration/V3__.sql create mode 100644 src/main/resources/db/migration/V4__.sql create mode 100644 src/main/resources/db/migration/V5__.sql create mode 100644 src/main/resources/db/migration/V6__.sql create mode 100644 src/main/resources/db/migration/V7__.sql create mode 100644 src/main/resources/db/migration/V8__.sql create mode 100644 src/main/resources/db/migration/V9__.sql diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b34f829 --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index fdeb014..da62362 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/src/main/kotlin/space/luminic/finance/Main.kt b/src/main/kotlin/space/luminic/finance/Main.kt index 95e7a32..ff4190f 100644 --- a/src/main/kotlin/space/luminic/finance/Main.kt +++ b/src/main/kotlin/space/luminic/finance/Main.kt @@ -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) { - TimeZone.setDefault(TimeZone.getTimeZone("Europe/Moscow")) runApplication
(*args) } diff --git a/src/main/kotlin/space/luminic/finance/api/AccountController.kt b/src/main/kotlin/space/luminic/finance/api/AccountController.kt deleted file mode 100644 index 05306f5..0000000 --- a/src/main/kotlin/space/luminic/finance/api/AccountController.kt +++ /dev/null @@ -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 { - 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 { - 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) - } - - -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/api/AuthController.kt b/src/main/kotlin/space/luminic/finance/api/AuthController.kt index 0e37688..806d915 100644 --- a/src/main/kotlin/space/luminic/finance/api/AuthController.kt +++ b/src/main/kotlin/space/luminic/finance/api/AuthController.kt @@ -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 { - val token = authService.login(request.username, request.password) + fun login(@RequestBody request: AuthUserDTO): Map { + 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 { + fun tgLogin(@RequestHeader("X-Tg-Id") tgId: String): Map { 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() } } diff --git a/src/main/kotlin/space/luminic/finance/api/BudgetController.kt b/src/main/kotlin/space/luminic/finance/api/BudgetController.kt deleted file mode 100644 index 9942f44..0000000 --- a/src/main/kotlin/space/luminic/finance/api/BudgetController.kt +++ /dev/null @@ -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 { - 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) - } -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/api/CategoryController.kt b/src/main/kotlin/space/luminic/finance/api/CategoryController.kt index f920c1c..ebe8128 100644 --- a/src/main/kotlin/space/luminic/finance/api/CategoryController.kt +++ b/src/main/kotlin/space/luminic/finance/api/CategoryController.kt @@ -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 { + fun getCategories(@PathVariable spaceId: Int): List { 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): List { + 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) } } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/api/GoalController.kt b/src/main/kotlin/space/luminic/finance/api/GoalController.kt new file mode 100644 index 0000000..bd99f1e --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/api/GoalController.kt @@ -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 { + return goalService.findAllBySpaceId(spaceId).map { it.toDto() } + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/api/RecurrentOperationController.kt b/src/main/kotlin/space/luminic/finance/api/RecurrentOperationController.kt new file mode 100644 index 0000000..6d0316d --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/api/RecurrentOperationController.kt @@ -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 { + 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 { + return mapOf("id" to recurrentOperationService.create(spaceId, createOperation)) + } + + @PutMapping("/{operationId}") + fun updateOperation(@PathVariable spaceId: Int, @PathVariable operationId: Int, @RequestBody operation: RecurrentOperationDTO.UpdateRecurrentOperationDTO): Map { + recurrentOperationService.update(spaceId, operationId, operation) + return mapOf("id" to operationId) + } + + @DeleteMapping("/{operationId}") + fun deleteOperation(@PathVariable spaceId: Int, @PathVariable operationId: Int) { + recurrentOperationService.delete(spaceId, operationId) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/api/ReferenceController.kt b/src/main/kotlin/space/luminic/finance/api/ReferenceController.kt index c1f5f8d..32d2823 100644 --- a/src/main/kotlin/space/luminic/finance/api/ReferenceController.kt +++ b/src/main/kotlin/space/luminic/finance/api/ReferenceController.kt @@ -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 { + fun getCurrencies(): List { 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) } } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/api/SpaceController.kt b/src/main/kotlin/space/luminic/finance/api/SpaceController.kt index a4a8972..1dfc0e2 100644 --- a/src/main/kotlin/space/luminic/finance/api/SpaceController.kt +++ b/src/main/kotlin/space/luminic/finance/api/SpaceController.kt @@ -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 { + fun getSpaces(): List { 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 { + 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 { + return mapOf("id" to spaceService.updateSpace(spaceId, space) ) } @DeleteMapping("/{spaceId}") - suspend fun deleteSpace(@PathVariable spaceId: String) { + fun deleteSpace(@PathVariable spaceId: Int) { spaceService.deleteSpace(spaceId) } } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/api/TransactionController.kt b/src/main/kotlin/space/luminic/finance/api/TransactionController.kt index 1270dae..b9b3530 100644 --- a/src/main/kotlin/space/luminic/finance/api/TransactionController.kt +++ b/src/main/kotlin/space/luminic/finance/api/TransactionController.kt @@ -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{ + fun getTransactions(@PathVariable spaceId: Int) : List{ 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 { + 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 { + 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) } diff --git a/src/main/kotlin/space/luminic/finance/api/exceptionHandlers/ExceptionHandler.kt b/src/main/kotlin/space/luminic/finance/api/exceptionHandlers/ExceptionHandler.kt index aed7415..0609968 100644 --- a/src/main/kotlin/space/luminic/finance/api/exceptionHandlers/ExceptionHandler.kt +++ b/src/main/kotlin/space/luminic/finance/api/exceptionHandlers/ExceptionHandler.kt @@ -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 { - 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>?> { + request: HttpServletRequest // <--- Изменённый тип + ): ResponseEntity? { 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>?> { + request: HttpServletRequest // <--- Изменённый тип + ): ResponseEntity? { 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>?> { + request: HttpServletRequest // <--- Изменённый тип + ): ResponseEntity? { 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>?> { + request: HttpServletRequest // <--- Изменённый тип + ): ResponseEntity? { 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 ) } -} +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/api/exceptionHandlers/GlobalExceptionHandlerV2.kt b/src/main/kotlin/space/luminic/finance/api/exceptionHandlers/GlobalExceptionHandlerV2.kt index f930942..d126ffa 100644 --- a/src/main/kotlin/space/luminic/finance/api/exceptionHandlers/GlobalExceptionHandlerV2.kt +++ b/src/main/kotlin/space/luminic/finance/api/exceptionHandlers/GlobalExceptionHandlerV2.kt @@ -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 { - return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse) - } - - private fun renderErrorResponse(request: ServerRequest): Mono { - 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 { +// return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse) +// } +// +// private fun renderErrorResponse(request: ServerRequest): Mono { +// val errorAttributesMap = getErrorAttributes( +// request, +// ErrorAttributeOptions.of( +// ErrorAttributeOptions.Include.MESSAGE +// ) +// ) +// return ServerResponse.status(401) +// .contentType(MediaType.APPLICATION_JSON) +// .body(BodyInserters.fromValue(errorAttributesMap)) +// } +// +// +//} diff --git a/src/main/kotlin/space/luminic/finance/configs/BearerTokenFilter.kt b/src/main/kotlin/space/luminic/finance/configs/BearerTokenFilter.kt index 448966e..0b6cc7b 100644 --- a/src/main/kotlin/space/luminic/finance/configs/BearerTokenFilter.kt +++ b/src/main/kotlin/space/luminic/finance/configs/BearerTokenFilter.kt @@ -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 { - 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) \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/configs/CommonConfig.kt b/src/main/kotlin/space/luminic/finance/configs/CommonConfig.kt index b2ef99e..76ddf95 100644 --- a/src/main/kotlin/space/luminic/finance/configs/CommonConfig.kt +++ b/src/main/kotlin/space/luminic/finance/configs/CommonConfig.kt @@ -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, -) \ No newline at end of file +// +//@ConfigurationProperties(prefix = "nlp") +//data class NLPConfig( +// val address: String, +//) \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/configs/JpaConfig.kt b/src/main/kotlin/space/luminic/finance/configs/JpaConfig.kt new file mode 100644 index 0000000..4b2e306 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/configs/JpaConfig.kt @@ -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 { // Убедитесь, что тип возвращаемого ID совпадает (Int) + + override fun getCurrentAuditor(): Optional { + 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() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/configs/SecurityConfig.kt b/src/main/kotlin/space/luminic/finance/configs/SecurityConfig.kt index 03d7f2f..e815be6 100644 --- a/src/main/kotlin/space/luminic/finance/configs/SecurityConfig.kt +++ b/src/main/kotlin/space/luminic/finance/configs/SecurityConfig.kt @@ -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 } -} +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/dtos/AccountDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/AccountDTO.kt deleted file mode 100644 index 178a642..0000000 --- a/src/main/kotlin/space/luminic/finance/dtos/AccountDTO.kt +++ /dev/null @@ -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, - ) -} diff --git a/src/main/kotlin/space/luminic/finance/dtos/BudgetDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/BudgetDTO.kt deleted file mode 100644 index 2b04669..0000000 --- a/src/main/kotlin/space/luminic/finance/dtos/BudgetDTO.kt +++ /dev/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, - val transactions: List, - val plannedIncomeTransactions: List, - val plannedExpenseTransactions: List, - val instantIncomeTransactions: List, - val instantExpenseTransactions: List - - ) -} - diff --git a/src/main/kotlin/space/luminic/finance/dtos/CategoryDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/CategoryDTO.kt index a5c864d..d7c2e23 100644 --- a/src/main/kotlin/space/luminic/finance/dtos/CategoryDTO.kt +++ b/src/main/kotlin/space/luminic/finance/dtos/CategoryDTO.kt @@ -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, ) } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/dtos/GoalDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/GoalDTO.kt index 9b0f706..4f74700 100644 --- a/src/main/kotlin/space/luminic/finance/dtos/GoalDTO.kt +++ b/src/main/kotlin/space/luminic/finance/dtos/GoalDTO.kt @@ -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, + val transactions: List, 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 + ) } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/dtos/RecurrentOperationDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/RecurrentOperationDTO.kt new file mode 100644 index 0000000..9ae777b --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/dtos/RecurrentOperationDTO.kt @@ -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, + ) +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/dtos/SpaceDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/SpaceDTO.kt index e048a07..e71cea9 100644 --- a/src/main/kotlin/space/luminic/finance/dtos/SpaceDTO.kt +++ b/src/main/kotlin/space/luminic/finance/dtos/SpaceDTO.kt @@ -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 = emptyList(), + val participants: Set = 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, diff --git a/src/main/kotlin/space/luminic/finance/dtos/SubscriptionDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/SubscriptionDTO.kt new file mode 100644 index 0000000..0dfd778 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/dtos/SubscriptionDTO.kt @@ -0,0 +1,6 @@ +package space.luminic.finance.dtos + +data class SubscriptionDTO ( + val endpoint: String, + val keys: Map +) diff --git a/src/main/kotlin/space/luminic/finance/dtos/TransactionDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/TransactionDTO.kt index add02dc..9365e0b 100644 --- a/src/main/kotlin/space/luminic/finance/dtos/TransactionDTO.kt +++ b/src/main/kotlin/space/luminic/finance/dtos/TransactionDTO.kt @@ -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 ) - } diff --git a/src/main/kotlin/space/luminic/finance/dtos/UserDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/UserDTO.kt index fc4b45b..34c19bb 100644 --- a/src/main/kotlin/space/luminic/finance/dtos/UserDTO.kt +++ b/src/main/kotlin/space/luminic/finance/dtos/UserDTO.kt @@ -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, diff --git a/src/main/kotlin/space/luminic/finance/mappers/AccountMapper.kt b/src/main/kotlin/space/luminic/finance/mappers/AccountMapper.kt deleted file mode 100644 index 497ec15..0000000 --- a/src/main/kotlin/space/luminic/finance/mappers/AccountMapper.kt +++ /dev/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, - ) - } - -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/mappers/BudgetMapper.kt b/src/main/kotlin/space/luminic/finance/mappers/BudgetMapper.kt deleted file mode 100644 index 368af34..0000000 --- a/src/main/kotlin/space/luminic/finance/mappers/BudgetMapper.kt +++ /dev/null @@ -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.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()}, - ) - } - - - - - -} diff --git a/src/main/kotlin/space/luminic/finance/mappers/CategoryMapper.kt b/src/main/kotlin/space/luminic/finance/mappers/CategoryMapper.kt index e4a4215..70a4db7 100644 --- a/src/main/kotlin/space/luminic/finance/mappers/CategoryMapper.kt +++ b/src/main/kotlin/space/luminic/finance/mappers/CategoryMapper.kt @@ -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 ) diff --git a/src/main/kotlin/space/luminic/finance/mappers/GoalMapper.kt b/src/main/kotlin/space/luminic/finance/mappers/GoalMapper.kt index 4af6363..bb9cc22 100644 --- a/src/main/kotlin/space/luminic/finance/mappers/GoalMapper.kt +++ b/src/main/kotlin/space/luminic/finance/mappers/GoalMapper.kt @@ -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 ) - } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/mappers/RecurrentOperationMapper.kt b/src/main/kotlin/space/luminic/finance/mappers/RecurrentOperationMapper.kt new file mode 100644 index 0000000..96405ea --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/mappers/RecurrentOperationMapper.kt @@ -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"), + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/mappers/SpaceMapper.kt b/src/main/kotlin/space/luminic/finance/mappers/SpaceMapper.kt index 4945ca5..fb701e5 100644 --- a/src/main/kotlin/space/luminic/finance/mappers/SpaceMapper.kt +++ b/src/main/kotlin/space/luminic/finance/mappers/SpaceMapper.kt @@ -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(), diff --git a/src/main/kotlin/space/luminic/finance/mappers/TransactionMapper.kt b/src/main/kotlin/space/luminic/finance/mappers/TransactionMapper.kt index 2123377..637c728 100644 --- a/src/main/kotlin/space/luminic/finance/mappers/TransactionMapper.kt +++ b/src/main/kotlin/space/luminic/finance/mappers/TransactionMapper.kt @@ -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() ) - - - - - - - } diff --git a/src/main/kotlin/space/luminic/finance/mappers/UserMapper.kt b/src/main/kotlin/space/luminic/finance/mappers/UserMapper.kt index 762dd83..b67dda6 100644 --- a/src/main/kotlin/space/luminic/finance/mappers/UserMapper.kt +++ b/src/main/kotlin/space/luminic/finance/mappers/UserMapper.kt @@ -13,4 +13,5 @@ object UserMapper { tgUserName = this.tgUserName, roles = this.roles ) + } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/models/Account.kt b/src/main/kotlin/space/luminic/finance/models/Account.kt deleted file mode 100644 index 2eae024..0000000 --- a/src/main/kotlin/space/luminic/finance/models/Account.kt +++ /dev/null @@ -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? = null - @ReadOnlyProperty var createdBy: User? = null - @ReadOnlyProperty var updatedBy: User? = null - - - enum class AccountType(displayName: String) { - SALARY("Зарплатный"), - COLLECTING("Накопительный"), - LOANS("Долговой"), - } -} diff --git a/src/main/kotlin/space/luminic/finance/models/Budget.kt b/src/main/kotlin/space/luminic/finance/models/Budget.kt deleted file mode 100644 index 7e6147d..0000000 --- a/src/main/kotlin/space/luminic/finance/models/Budget.kt +++ /dev/null @@ -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 = listOf(), - val categories: List = 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("Специальный") - } -} - - - diff --git a/src/main/kotlin/space/luminic/finance/models/Category.kt b/src/main/kotlin/space/luminic/finance/models/Category.kt index b84fa79..a0763ff 100644 --- a/src/main/kotlin/space/luminic/finance/models/Category.kt +++ b/src/main/kotlin/space/luminic/finance/models/Category.kt @@ -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 diff --git a/src/main/kotlin/space/luminic/finance/models/Currency.kt b/src/main/kotlin/space/luminic/finance/models/Currency.kt index cd5e938..41f44dd 100644 --- a/src/main/kotlin/space/luminic/finance/models/Currency.kt +++ b/src/main/kotlin/space/luminic/finance/models/Currency.kt @@ -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 ) diff --git a/src/main/kotlin/space/luminic/finance/models/CurrencyRate.kt b/src/main/kotlin/space/luminic/finance/models/CurrencyRate.kt index 57fe427..1f71f33 100644 --- a/src/main/kotlin/space/luminic/finance/models/CurrencyRate.kt +++ b/src/main/kotlin/space/luminic/finance/models/CurrencyRate.kt @@ -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 -} \ No newline at end of file +) \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/models/Goal.kt b/src/main/kotlin/space/luminic/finance/models/Goal.kt index 0a852bf..d9aca48 100644 --- a/src/main/kotlin/space/luminic/finance/models/Goal.kt +++ b/src/main/kotlin/space/luminic/finance/models/Goal.kt @@ -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 = emptyList(), + val transactions: List = 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("Отпуск", "🏖️"), diff --git a/src/main/kotlin/space/luminic/finance/models/RecurrentOperation.kt b/src/main/kotlin/space/luminic/finance/models/RecurrentOperation.kt new file mode 100644 index 0000000..e4cd055 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/models/RecurrentOperation.kt @@ -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, +) \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/models/Space.kt b/src/main/kotlin/space/luminic/finance/models/Space.kt index 899bc31..8902fa3 100644 --- a/src/main/kotlin/space/luminic/finance/models/Space.kt +++ b/src/main/kotlin/space/luminic/finance/models/Space.kt @@ -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 = emptyList(), + val owner: User, + var participants: Set, 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? = 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 } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/models/Subscription.kt b/src/main/kotlin/space/luminic/finance/models/Subscription.kt index c7c7229..a0e9173 100644 --- a/src/main/kotlin/space/luminic/finance/models/Subscription.kt +++ b/src/main/kotlin/space/luminic/finance/models/Subscription.kt @@ -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 -) diff --git a/src/main/kotlin/space/luminic/finance/models/Token.kt b/src/main/kotlin/space/luminic/finance/models/Token.kt index ec1a5ab..7e17a3d 100644 --- a/src/main/kotlin/space/luminic/finance/models/Token.kt +++ b/src/main/kotlin/space/luminic/finance/models/Token.kt @@ -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 ) { diff --git a/src/main/kotlin/space/luminic/finance/models/Transaction.kt b/src/main/kotlin/space/luminic/finance/models/Transaction.kt index 5133ca8..b707089 100644 --- a/src/main/kotlin/space/luminic/finance/models/Transaction.kt +++ b/src/main/kotlin/space/luminic/finance/models/Transaction.kt @@ -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("Поступления"), diff --git a/src/main/kotlin/space/luminic/finance/models/User.kt b/src/main/kotlin/space/luminic/finance/models/User.kt index 74f9c6b..bc1941a 100644 --- a/src/main/kotlin/space/luminic/finance/models/User.kt +++ b/src/main/kotlin/space/luminic/finance/models/User.kt @@ -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 = mutableListOf(), + @CreatedDate val createdAt: Instant? = null, + @LastModifiedDate var updatedAt: Instant? = null, + var roles: List = listOf(), ) diff --git a/src/main/kotlin/space/luminic/finance/repos/AccountRepo.kt b/src/main/kotlin/space/luminic/finance/repos/AccountRepo.kt deleted file mode 100644 index e9948b6..0000000 --- a/src/main/kotlin/space/luminic/finance/repos/AccountRepo.kt +++ /dev/null @@ -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 { -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/BudgetRepo.kt b/src/main/kotlin/space/luminic/finance/repos/BudgetRepo.kt deleted file mode 100644 index 3718a8e..0000000 --- a/src/main/kotlin/space/luminic/finance/repos/BudgetRepo.kt +++ /dev/null @@ -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 { - - suspend fun findBudgetsBySpaceIdAndIsDeletedFalse(spaceId: String): Flux - suspend fun findBudgetsBySpaceIdAndId(spaceId: String, budgetId: String): Flux -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/CategoryRepo.kt b/src/main/kotlin/space/luminic/finance/repos/CategoryRepo.kt index 1e054f9..4069a10 100644 --- a/src/main/kotlin/space/luminic/finance/repos/CategoryRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/CategoryRepo.kt @@ -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 { + +interface CategoryRepo { + fun findBySpaceId(spaceId: Int): List + 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 \ No newline at end of file +interface CategoryEtalonRepo { + fun findAll(): List +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/CategoryRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/CategoryRepoImpl.kt new file mode 100644 index 0000000..59041a7 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/CategoryRepoImpl.kt @@ -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 { 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 { + 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 { + val query = "select * from finance.categories_etalon" + return jdbcTemplate.query(query, rowMapper) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/CurrencyRepo.kt b/src/main/kotlin/space/luminic/finance/repos/CurrencyRepo.kt index 912d212..b17b16a 100644 --- a/src/main/kotlin/space/luminic/finance/repos/CurrencyRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/CurrencyRepo.kt @@ -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 -interface CurrencyRateRepo: ReactiveMongoRepository \ No newline at end of file +@Repository +interface CurrencyRepo { + fun findAll(): List + 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 +// fun save(currencyRate: CurrencyRate) +// fun update(currencyRate: CurrencyRate) +// fun delete(id: Int) +//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/CurrencyRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/CurrencyRepoImpl.kt new file mode 100644 index 0000000..e56f962 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/CurrencyRepoImpl.kt @@ -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 { rs, _ -> + Currency( + code = rs.getString("code"), + name = rs.getString("name"), + symbol = rs.getString("symbol"), + ) + } + + override fun findAll(): List { + 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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/GoalRepo.kt b/src/main/kotlin/space/luminic/finance/repos/GoalRepo.kt new file mode 100644 index 0000000..d5014ae --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/GoalRepo.kt @@ -0,0 +1,20 @@ +package space.luminic.finance.repos + +import space.luminic.finance.models.Goal + +interface GoalRepo { + + fun findAllBySpaceId(spaceId: Int) : List + 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 + 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) +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/GoalRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/GoalRepoImpl.kt new file mode 100644 index 0000000..1d8b50b --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/GoalRepoImpl.kt @@ -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 { + 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 { + 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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepo.kt b/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepo.kt new file mode 100644 index 0000000..bc7d493 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepo.kt @@ -0,0 +1,11 @@ +package space.luminic.finance.repos + +import space.luminic.finance.models.RecurrentOperation + +interface RecurrentOperationRepo { + fun findAllBySpaceId(spaceId: Int): List + fun findBySpaceIdAndId(spaceId: Int, id: Int): RecurrentOperation? + fun create(operation: RecurrentOperation, createdById: Int): Int + fun update(operation: RecurrentOperation, updatedById: Int) + fun delete(id: Int) +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepoImpl.kt new file mode 100644 index 0000000..d14a989 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/RecurrentOperationRepoImpl.kt @@ -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 { 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 { + 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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/SpaceRepo.kt b/src/main/kotlin/space/luminic/finance/repos/SpaceRepo.kt index ee6ef6e..6c84acd 100644 --- a/src/main/kotlin/space/luminic/finance/repos/SpaceRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/SpaceRepo.kt @@ -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 { +@Repository +interface SpaceRepo { + fun findSpacesAvailableForUser(userId: Int): List + 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) } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt new file mode 100644 index 0000000..3b0dbba --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt @@ -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): List { + val spaceMap = mutableMapOf() + 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 { + 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)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/SubscriptionRepo.kt b/src/main/kotlin/space/luminic/finance/repos/SubscriptionRepo.kt index 73c0262..3cb7a2b 100644 --- a/src/main/kotlin/space/luminic/finance/repos/SubscriptionRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/SubscriptionRepo.kt @@ -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 { - - @Query("{ \$and: [ " + - "{ 'user': { '\$ref': 'users', '\$id': ?0 } }, " + - "{ 'isActive': true } " + - "]}") - fun findByUserIdAndIsActive(userId: ObjectId): Flux - - -} \ No newline at end of file +interface SubscriptionRepo diff --git a/src/main/kotlin/space/luminic/finance/repos/TokenRepo.kt b/src/main/kotlin/space/luminic/finance/repos/TokenRepo.kt index 127ce93..1cdf340 100644 --- a/src/main/kotlin/space/luminic/finance/repos/TokenRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/TokenRepo.kt @@ -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 { +interface TokenRepo { + fun findByToken(token: String): Token? + fun deleteByExpiresAtBefore(dateTime: Instant) - fun findByToken(token: String): Mono + fun create(token: Token): Token + fun update(token: Token): Token + fun delete(id: Int) - fun deleteByExpiresAtBefore(dateTime: LocalDateTime) } diff --git a/src/main/kotlin/space/luminic/finance/repos/TokenRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/TokenRepoImpl.kt new file mode 100644 index 0000000..831ffae --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/TokenRepoImpl.kt @@ -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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/TransactionRepo.kt b/src/main/kotlin/space/luminic/finance/repos/TransactionRepo.kt index cf82f06..84ac511 100644 --- a/src/main/kotlin/space/luminic/finance/repos/TransactionRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/TransactionRepo.kt @@ -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 { +interface TransactionRepo { + fun findAllBySpaceId(spaceId: Int): List + fun findBySpaceIdAndId(spaceId: Int, id: Int): Transaction? + fun create(transaction: Transaction, userId: Int): Int + fun update(transaction: Transaction): Int + fun delete(transactionId: Int) + } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/TransactionRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/TransactionRepoImpl.kt new file mode 100644 index 0000000..ff41ce9 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/TransactionRepoImpl.kt @@ -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 { + 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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/UserRepo.kt b/src/main/kotlin/space/luminic/finance/repos/UserRepo.kt index cd3f7f8..b1ea9db 100644 --- a/src/main/kotlin/space/luminic/finance/repos/UserRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/UserRepo.kt @@ -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 { - - - @Query(value = "{ 'username': ?0 }", fields = "{ 'password': 0 }") - fun findByUsernameWOPassword(username: String): Mono - - fun findByUsername(username: String): Mono - - fun findByTgId(id: String): Mono +interface UserRepo { + fun findAll(): List + fun findById(id: Int): User? + fun findByUsername(username: String): User? + fun findParticipantsBySpace(spaceId: Int): Set + fun findByTgId(tgId: String): User? + fun save(user: User): User + fun update(user: User): User + fun deleteById(id: Long) } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt new file mode 100644 index 0000000..89832ec --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt @@ -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)?.toList() ?: emptyList() + ) + } + + override fun findAll(): List { + 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 { + 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)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/AccountService.kt b/src/main/kotlin/space/luminic/finance/services/AccountService.kt deleted file mode 100644 index df31b74..0000000 --- a/src/main/kotlin/space/luminic/finance/services/AccountService.kt +++ /dev/null @@ -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 - suspend fun getAccount(spaceId: String, accountId: String): Account - suspend fun getAccountTransactions(spaceId: String, accountId: String): List - 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) -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/AccountServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/AccountServiceImpl.kt deleted file mode 100644 index 1a39d30..0000000 --- a/src/main/kotlin/space/luminic/finance/services/AccountServiceImpl.kt +++ /dev/null @@ -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 { - 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() - 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 { - 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 { - 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() - - } -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/AuthService.kt b/src/main/kotlin/space/luminic/finance/services/AuthService.kt index 174d012..f2f9490 100644 --- a/src/main/kotlin/space/luminic/finance/services/AuthService.kt +++ b/src/main/kotlin/space/luminic/finance/services/AuthService.kt @@ -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 -> { diff --git a/src/main/kotlin/space/luminic/finance/services/BudgetService.kt b/src/main/kotlin/space/luminic/finance/services/BudgetService.kt deleted file mode 100644 index 8e0d2ea..0000000 --- a/src/main/kotlin/space/luminic/finance/services/BudgetService.kt +++ /dev/null @@ -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 - suspend fun getBudget(spaceId: String, budgetId: String): Budget - suspend fun getBudgetTransactions(spaceId: String, budgetId: String): List - 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) - - - -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/BudgetServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/BudgetServiceImpl.kt deleted file mode 100644 index eeda2e7..0000000 --- a/src/main/kotlin/space/luminic/finance/services/BudgetServiceImpl.kt +++ /dev/null @@ -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 { - - 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() - 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 { - - 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 { - 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() - } -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/CategoryService.kt b/src/main/kotlin/space/luminic/finance/services/CategoryService.kt index 23e3940..e02aac9 100644 --- a/src/main/kotlin/space/luminic/finance/services/CategoryService.kt +++ b/src/main/kotlin/space/luminic/finance/services/CategoryService.kt @@ -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 - 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 + fun getCategories(spaceId: Int): List + fun getCategory(spaceId: Int, id: Int): Category + fun createCategory(spaceId: Int, category: CategoryDTO.CreateCategoryDTO): Category + fun createEtalonCategoriesForSpace(spaceId: Int): List + fun updateCategory(spaceId: Int,categoryId:Int, category: CategoryDTO.UpdateCategoryDTO): Category + fun deleteCategory(spaceId: Int, id: Int) } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/CategoryServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/CategoryServiceImpl.kt index 865a935..f52f552 100644 --- a/src/main/kotlin/space/luminic/finance/services/CategoryServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/CategoryServiceImpl.kt @@ -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 { - 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() - 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 { + val userId = authService.getSecurityUserId() + val space = spaceRepo.findSpaceById(spaceId, userId) + return categoriesRepo.findBySpaceId(spaceId) } - override suspend fun getCategories(spaceId: String): List { - 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 { - 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 { + val userId = authService.getSecurityUserId() + val space = spaceRepo.findSpaceById(spaceId, userId) + val categories = categoriesEtalonRepo.findAll() + val newCategories = mutableListOf() + 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) } diff --git a/src/main/kotlin/space/luminic/finance/services/CategoryServiceMongoImpl.kt b/src/main/kotlin/space/luminic/finance/services/CategoryServiceMongoImpl.kt new file mode 100644 index 0000000..538a4a4 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/services/CategoryServiceMongoImpl.kt @@ -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 { +// 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() +// 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 { +// 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 { +// 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() +// } +// +// +//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/CoroutineAuditorAware.kt b/src/main/kotlin/space/luminic/finance/services/CoroutineAuditorAware.kt deleted file mode 100644 index e912647..0000000 --- a/src/main/kotlin/space/luminic/finance/services/CoroutineAuditorAware.kt +++ /dev/null @@ -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 { - override fun getCurrentAuditor(): Mono = - mono { - authService.getSecurityUser().id!! - } -} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/CurrencyService.kt b/src/main/kotlin/space/luminic/finance/services/CurrencyService.kt index 13949ec..e2baba5 100644 --- a/src/main/kotlin/space/luminic/finance/services/CurrencyService.kt +++ b/src/main/kotlin/space/luminic/finance/services/CurrencyService.kt @@ -6,11 +6,11 @@ import space.luminic.finance.models.CurrencyRate interface CurrencyService { - suspend fun getCurrencies(): List - 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 + fun getCurrency(currencyCode: String): Currency + fun createCurrency(currency: CurrencyDTO): Currency + fun updateCurrency(currency: CurrencyDTO): Currency + fun deleteCurrency(currencyCode: String) + fun createCurrencyRate(currencyCode: String): CurrencyRate } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/CurrencyServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/CurrencyServiceImpl.kt index d7c92e7..92835a3 100644 --- a/src/main/kotlin/space/luminic/finance/services/CurrencyServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/CurrencyServiceImpl.kt @@ -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 { - return currencyRepo.findAll().collectList().awaitSingle() + override fun getCurrencies(): List { + 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()) } } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/CurrencyServiceMongoImpl.kt b/src/main/kotlin/space/luminic/finance/services/CurrencyServiceMongoImpl.kt new file mode 100644 index 0000000..f769cbf --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/services/CurrencyServiceMongoImpl.kt @@ -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 { +// 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() +// } +//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/GoalService.kt b/src/main/kotlin/space/luminic/finance/services/GoalService.kt new file mode 100644 index 0000000..1757116 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/services/GoalService.kt @@ -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 + 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 + 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) + +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/GoalServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/GoalServiceImpl.kt new file mode 100644 index 0000000..7abcb63 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/services/GoalServiceImpl.kt @@ -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 { + 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 { + 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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/RecurrentOperationService.kt b/src/main/kotlin/space/luminic/finance/services/RecurrentOperationService.kt new file mode 100644 index 0000000..b73c2bd --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/services/RecurrentOperationService.kt @@ -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 + 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) +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/RecurrentOperationServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/RecurrentOperationServiceImpl.kt new file mode 100644 index 0000000..22b752d --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/services/RecurrentOperationServiceImpl.kt @@ -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 { + 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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/SpaceService.kt b/src/main/kotlin/space/luminic/finance/services/SpaceService.kt index 508a835..a37dcfe 100644 --- a/src/main/kotlin/space/luminic/finance/services/SpaceService.kt +++ b/src/main/kotlin/space/luminic/finance/services/SpaceService.kt @@ -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 - 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 + fun getSpace(id: Int): Space + fun createSpace(space: SpaceDTO.CreateSpaceDTO): Int + fun updateSpace(spaceId: Int, space: SpaceDTO.UpdateSpaceDTO): Int + fun deleteSpace(spaceId: Int) } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/SpaceServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/SpaceServiceImpl.kt index 4647851..8525548 100644 --- a/src/main/kotlin/space/luminic/finance/services/SpaceServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/SpaceServiceImpl.kt @@ -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 { - 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() - 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{ - 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 { + 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 { - 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) } } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/SpaceServiceMongoImpl.kt b/src/main/kotlin/space/luminic/finance/services/SpaceServiceMongoImpl.kt new file mode 100644 index 0000000..f9423a4 --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/services/SpaceServiceMongoImpl.kt @@ -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 { +// 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() +// 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{ +// 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 { +// 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() +// } +//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/SubscriptionService.kt b/src/main/kotlin/space/luminic/finance/services/SubscriptionService.kt index addb344..01e5688 100644 --- a/src/main/kotlin/space/luminic/finance/services/SubscriptionService.kt +++ b/src/main/kotlin/space/luminic/finance/services/SubscriptionService.kt @@ -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") +// } +// } +//} diff --git a/src/main/kotlin/space/luminic/finance/services/TokenService.kt b/src/main/kotlin/space/luminic/finance/services/TokenService.kt index 82b0a03..12c6198 100644 --- a/src/main/kotlin/space/luminic/finance/services/TokenService.kt +++ b/src/main/kotlin/space/luminic/finance/services/TokenService.kt @@ -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 { - 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()) } } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/TransactionService.kt b/src/main/kotlin/space/luminic/finance/services/TransactionService.kt index 646cac2..c166f91 100644 --- a/src/main/kotlin/space/luminic/finance/services/TransactionService.kt +++ b/src/main/kotlin/space/luminic/finance/services/TransactionService.kt @@ -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 - 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 + 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) } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt index a7d8a32..66596e9 100644 --- a/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt @@ -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 { - 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() - 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 { - 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() - 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() - 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) } - } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/TransactionServiceMongoImpl.kt b/src/main/kotlin/space/luminic/finance/services/TransactionServiceMongoImpl.kt new file mode 100644 index 0000000..f82276c --- /dev/null +++ b/src/main/kotlin/space/luminic/finance/services/TransactionServiceMongoImpl.kt @@ -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 { +// 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() +// 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 { +// 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() +// 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() +// 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() +// } +// +// +//} \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/UserService.kt b/src/main/kotlin/space/luminic/finance/services/UserService.kt index 6d95a50..b86ffb7 100644 --- a/src/main/kotlin/space/luminic/finance/services/UserService.kt +++ b/src/main/kotlin/space/luminic/finance/services/UserService.kt @@ -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 { + fun getUsers(): List { return userRepo.findAll() - .collectList() - .awaitSingle() } } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/utils/ScheduledTasks.kt b/src/main/kotlin/space/luminic/finance/utils/ScheduledTasks.kt index a7e8b01..b62ba32 100644 --- a/src/main/kotlin/space/luminic/finance/utils/ScheduledTasks.kt +++ b/src/main/kotlin/space/luminic/finance/utils/ScheduledTasks.kt @@ -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() - } - - -} \ No newline at end of file +// +//@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() +// } +// +// +//} \ No newline at end of file diff --git a/src/main/resources/application-dev-local.properties b/src/main/resources/application-dev-local.properties index 2b67d00..78b78b2 100644 --- a/src/main/resources/application-dev-local.properties +++ b/src/main/resources/application-dev-local.properties @@ -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 \ No newline at end of file diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 74fe26d..0f081e2 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -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 \ No newline at end of file +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! \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 7cc1984..9154035 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -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! \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 92caee5..e4f90cd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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 \ No newline at end of file diff --git a/src/main/resources/db/migration/V10__.sql b/src/main/resources/db/migration/V10__.sql new file mode 100644 index 0000000..c2dda8a --- /dev/null +++ b/src/main/resources/db/migration/V10__.sql @@ -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); + + + diff --git a/src/main/resources/db/migration/V11__.sql b/src/main/resources/db/migration/V11__.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/db/migration/V12__.sql b/src/main/resources/db/migration/V12__.sql new file mode 100644 index 0000000..4c2e1f5 --- /dev/null +++ b/src/main/resources/db/migration/V12__.sql @@ -0,0 +1,2 @@ +alter table finance.categories + add column if not exists description varchar(1000); \ No newline at end of file diff --git a/src/main/resources/db/migration/V13__.sql b/src/main/resources/db/migration/V13__.sql new file mode 100644 index 0000000..51187d1 --- /dev/null +++ b/src/main/resources/db/migration/V13__.sql @@ -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; \ No newline at end of file diff --git a/src/main/resources/db/migration/V14__.sql b/src/main/resources/db/migration/V14__.sql new file mode 100644 index 0000000..3609997 --- /dev/null +++ b/src/main/resources/db/migration/V14__.sql @@ -0,0 +1,2 @@ +ALTER TABLE finance.categories + ADD CONSTRAINT uq_categories_space_name UNIQUE (space_id, name); \ No newline at end of file diff --git a/src/main/resources/db/migration/V15__.sql b/src/main/resources/db/migration/V15__.sql new file mode 100644 index 0000000..bdc8135 --- /dev/null +++ b/src/main/resources/db/migration/V15__.sql @@ -0,0 +1,20 @@ +create table if not exists finance.recurrent_operations +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, + space_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + name VARCHAR(200) NOT NULL , + amount NUMERIC NOT NULL, + date INTEGER NOT NULL, + created_by_id INTEGER NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_by_id INTEGER NULL, + updated_at TIMESTAMP WITH TIME ZONE NULL +); + + +ALTER TABLE finance.recurrent_operations + ADD CONSTRAINT FK_SPACE_ON_RECURRENT_OPERATIONS FOREIGN KEY (space_id) REFERENCES finance.spaces (id), + ADD CONSTRAINT FK_CATEGORY_ON_RECURRENT_OPERATIONS FOREIGN KEY (category_id) REFERENCES finance.categories (id), + ADD CONSTRAINT FK_CREATED_BY_ON_RECURRENT_OPERATIONS FOREIGN KEY (created_by_id) REFERENCES finance.users (id), + ADD CONSTRAINT FK_UPDATED_BY_ON_RECURRENT_OPERATIONS FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); diff --git a/src/main/resources/db/migration/V16__.sql b/src/main/resources/db/migration/V16__.sql new file mode 100644 index 0000000..d209538 --- /dev/null +++ b/src/main/resources/db/migration/V16__.sql @@ -0,0 +1,15 @@ +alter table finance.goals + alter column space_id set data type integer; + +create table if not exists finance.goals_components +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, + goal_id INTEGER NOT NULL, + name VARCHAR(255) NOT NULL , + amount NUMERIC NOT NULL, + is_done BOOLEAN NOT NULL DEFAULT FALSE, + date TIMESTAMP WITH TIME ZONE NULL +); + +alter table finance.goals_components + add constraint fk_goal_on_components foreign key (goal_id) references finance.goals (id); \ No newline at end of file diff --git a/src/main/resources/db/migration/V17__.sql b/src/main/resources/db/migration/V17__.sql new file mode 100644 index 0000000..380be0d --- /dev/null +++ b/src/main/resources/db/migration/V17__.sql @@ -0,0 +1,19 @@ +drop table if exists finance.goals_components; +create table if not exists finance.goals_components +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, + goal_id INTEGER NOT NULL, + name VARCHAR(255) NOT NULL , + amount NUMERIC NOT NULL, + is_done BOOLEAN NOT NULL DEFAULT FALSE, + date TIMESTAMP WITH TIME ZONE NULL, + created_by_id INTEGER NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + updated_by_id INTEGER NULL, + updated_at TIMESTAMP WITH TIME ZONE NULL +); + +alter table finance.goals_components + add constraint fk_goal_on_components foreign key (goal_id) references finance.goals (id), + add constraint fk_created_by_on_components foreign key (created_by_id) references finance.users (id), + add constraint fk_updated_by_on_components foreign key (updated_by_id) references finance.users (id); diff --git a/src/main/resources/db/migration/V18__.sql b/src/main/resources/db/migration/V18__.sql new file mode 100644 index 0000000..2d1ec29 --- /dev/null +++ b/src/main/resources/db/migration/V18__.sql @@ -0,0 +1,40 @@ +drop table if exists finance.categories CASCADE ; + +CREATE TABLE finance.categories +( + id INTEGER NOT NULL, + space_id INTEGER NOT NULL, + type SMALLINT NOT NULL , + name VARCHAR(255) NOT NULL , + description VARCHAR(1000) NOT NULL, + icon VARCHAR(255) NOT NULL , + is_deleted BOOLEAN NOT NULL, + created_by_id INTEGER NOT NULL , + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), + updated_by_id INTEGER, + updated_at TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_categories PRIMARY KEY (id) +); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_CREATEDBY FOREIGN KEY (created_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_SPACE FOREIGN KEY (space_id) REFERENCES finance.spaces (id); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_UPDATEDBY FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); + + + +DROP TABLE IF EXISTS finance.categories_etalon; + +CREATE TABLE finance.categories_etalon +( + id INTEGER NOT NULL, + type SMALLINT NOT NULL, + name VARCHAR(255) NOT NULL, + description VARCHAR(1000) NOT NULL, + icon VARCHAR(255) NOT NULL, + CONSTRAINT pk_categories_etalon PRIMARY KEY (id) +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V19__.sql b/src/main/resources/db/migration/V19__.sql new file mode 100644 index 0000000..beaa216 --- /dev/null +++ b/src/main/resources/db/migration/V19__.sql @@ -0,0 +1,40 @@ +drop table if exists finance.categories CASCADE ; + +CREATE TABLE finance.categories +( + id INTEGER NOT NULL, + space_id INTEGER NOT NULL, + type VARCHAR NOT NULL , + name VARCHAR(255) NOT NULL , + description VARCHAR(1000) NOT NULL, + icon VARCHAR(255) NOT NULL , + is_deleted BOOLEAN NOT NULL, + created_by_id INTEGER NOT NULL , + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), + updated_by_id INTEGER, + updated_at TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_categories PRIMARY KEY (id) +); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_CREATEDBY FOREIGN KEY (created_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_SPACE FOREIGN KEY (space_id) REFERENCES finance.spaces (id); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_UPDATEDBY FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); + + + +DROP TABLE IF EXISTS finance.categories_etalon; + +CREATE TABLE finance.categories_etalon +( + id INTEGER NOT NULL, + type VARCHAR NOT NULL, + name VARCHAR(255) NOT NULL, + description VARCHAR(1000) NOT NULL, + icon VARCHAR(255) NOT NULL, + CONSTRAINT pk_categories_etalon PRIMARY KEY (id) +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__.sql b/src/main/resources/db/migration/V1__.sql new file mode 100644 index 0000000..82bf67a --- /dev/null +++ b/src/main/resources/db/migration/V1__.sql @@ -0,0 +1,196 @@ +CREATE TABLE finance.categories +( + id INTEGER NOT NULL, + space_id INTEGER, + type SMALLINT, + name VARCHAR(255), + icon VARCHAR(255), + is_deleted BOOLEAN NOT NULL, + created_by_id INTEGER, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_by_id INTEGER, + updated_at TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_categories PRIMARY KEY (id) +); + +CREATE TABLE finance.currencies_ref +( + code VARCHAR(255) NOT NULL, + name VARCHAR(255), + symbol VARCHAR(255), + CONSTRAINT pk_currencies_ref PRIMARY KEY (code) +); + +CREATE TABLE finance.currency_rates +( + id INTEGER NOT NULL, + currency_code VARCHAR(255), + rate DECIMAL, + date date, + CONSTRAINT pk_currency_rates PRIMARY KEY (id) +); + +CREATE TABLE finance.goals +( + id VARCHAR(255) NOT NULL, + space_id INTEGER, + type SMALLINT, + name VARCHAR(255), + description VARCHAR(255), + amount DECIMAL, + until_date date, + created_by_id INTEGER, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_by_id INTEGER, + updated_at TIMESTAMP WITHOUT TIME ZONE, + current_amount DECIMAL, + CONSTRAINT pk_goals PRIMARY KEY (id) +); + +CREATE TABLE finance.goals_transactions +( + goal_id VARCHAR(255) NOT NULL, + transactions_id INTEGER NOT NULL +); + +CREATE TABLE finance.spaces +( + id INTEGER NOT NULL, + name VARCHAR(255), + owner_id INTEGER, + is_deleted BOOLEAN NOT NULL, + created_by_id INTEGER, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_by_id INTEGER, + updated_at TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_spaces PRIMARY KEY (id) +); + +CREATE TABLE finance.spaces_participants +( + space_id INTEGER NOT NULL, + participants_id INTEGER NOT NULL +); + +CREATE TABLE finance.subscriptions +( + id INTEGER NOT NULL, + user_id INTEGER, + endpoint VARCHAR(255), + auth VARCHAR(255), + p256dh VARCHAR(255), + is_active BOOLEAN NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_subscriptions PRIMARY KEY (id) +); + +CREATE TABLE finance.tokens +( + id INTEGER NOT NULL, + token VARCHAR(255), + user_id INTEGER, + issued_at TIMESTAMP WITHOUT TIME ZONE, + expires_at TIMESTAMP WITHOUT TIME ZONE, + status SMALLINT, + CONSTRAINT pk_tokens PRIMARY KEY (id) +); + +CREATE TABLE finance.transactions +( + id INTEGER NOT NULL, + space_id INTEGER, + parent_id INTEGER, + type SMALLINT, + kind SMALLINT, + category_id INTEGER, + comment VARCHAR(255), + amount DECIMAL, + fees DECIMAL, + date TIMESTAMP WITHOUT TIME ZONE, + is_deleted BOOLEAN NOT NULL, + created_by_id INTEGER, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_by_id INTEGER, + updated_at TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_transactions PRIMARY KEY (id) +); + +CREATE TABLE finance.users +( + id INTEGER NOT NULL, + username VARCHAR(255), + first_name VARCHAR(255), + tg_id VARCHAR(255), + tg_user_name VARCHAR(255), + password VARCHAR(255), + is_active BOOLEAN NOT NULL, + reg_date date, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE, + roles TEXT[], + CONSTRAINT pk_users PRIMARY KEY (id) +); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_CREATEDBY FOREIGN KEY (created_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_SPACE FOREIGN KEY (space_id) REFERENCES finance.spaces (id); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_UPDATEDBY FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.currency_rates + ADD CONSTRAINT FK_CURRENCY_RATES_ON_CURRENCY_CODE FOREIGN KEY (currency_code) REFERENCES finance.currencies_ref (code); + +ALTER TABLE finance.goals + ADD CONSTRAINT FK_GOALS_ON_CREATEDBY FOREIGN KEY (created_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.goals + ADD CONSTRAINT FK_GOALS_ON_SPACE FOREIGN KEY (space_id) REFERENCES finance.spaces (id); + +ALTER TABLE finance.goals + ADD CONSTRAINT FK_GOALS_ON_UPDATEDBY FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.spaces + ADD CONSTRAINT FK_SPACES_ON_CREATEDBY FOREIGN KEY (created_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.spaces + ADD CONSTRAINT FK_SPACES_ON_OWNER FOREIGN KEY (owner_id) REFERENCES finance.users (id); + +ALTER TABLE finance.spaces + ADD CONSTRAINT FK_SPACES_ON_UPDATEDBY FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.subscriptions + ADD CONSTRAINT FK_SUBSCRIPTIONS_ON_USER FOREIGN KEY (user_id) REFERENCES finance.users (id); + +ALTER TABLE finance.tokens + ADD CONSTRAINT FK_TOKENS_ON_USER FOREIGN KEY (user_id) REFERENCES finance.users (id); + +ALTER TABLE finance.transactions + ADD CONSTRAINT FK_TRANSACTIONS_ON_CATEGORY FOREIGN KEY (category_id) REFERENCES finance.categories (id); + +ALTER TABLE finance.transactions + ADD CONSTRAINT FK_TRANSACTIONS_ON_CREATEDBY FOREIGN KEY (created_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.transactions + ADD CONSTRAINT FK_TRANSACTIONS_ON_PARENT FOREIGN KEY (parent_id) REFERENCES finance.transactions (id); + +ALTER TABLE finance.transactions + ADD CONSTRAINT FK_TRANSACTIONS_ON_SPACE FOREIGN KEY (space_id) REFERENCES finance.spaces (id); + +ALTER TABLE finance.transactions + ADD CONSTRAINT FK_TRANSACTIONS_ON_UPDATEDBY FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.goals_transactions + ADD CONSTRAINT fk_goatra_on_goal FOREIGN KEY (goal_id) REFERENCES finance.goals (id); + +ALTER TABLE finance.goals_transactions + ADD CONSTRAINT fk_goatra_on_transaction FOREIGN KEY (transactions_id) REFERENCES finance.transactions (id); + +ALTER TABLE finance.spaces_participants + ADD CONSTRAINT fk_spapar_on_space FOREIGN KEY (space_id) REFERENCES finance.spaces (id); + +ALTER TABLE finance.spaces_participants + ADD CONSTRAINT fk_spapar_on_user FOREIGN KEY (participants_id) REFERENCES finance.users (id); \ No newline at end of file diff --git a/src/main/resources/db/migration/V20__.sql b/src/main/resources/db/migration/V20__.sql new file mode 100644 index 0000000..c80dd17 --- /dev/null +++ b/src/main/resources/db/migration/V20__.sql @@ -0,0 +1,40 @@ +drop table if exists finance.categories CASCADE ; + +CREATE TABLE finance.categories +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL , + space_id INTEGER NOT NULL, + type VARCHAR NOT NULL , + name VARCHAR(255) NOT NULL , + description VARCHAR(1000) NOT NULL, + icon VARCHAR(255) NOT NULL , + is_deleted BOOLEAN NOT NULL, + created_by_id INTEGER NOT NULL , + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), + updated_by_id INTEGER, + updated_at TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_categories PRIMARY KEY (id) +); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_CREATEDBY FOREIGN KEY (created_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_SPACE FOREIGN KEY (space_id) REFERENCES finance.spaces (id); + +ALTER TABLE finance.categories + ADD CONSTRAINT FK_CATEGORIES_ON_UPDATEDBY FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); + + + +DROP TABLE IF EXISTS finance.categories_etalon; + +CREATE TABLE finance.categories_etalon +( + id INTEGER NOT NULL, + type VARCHAR NOT NULL, + name VARCHAR(255) NOT NULL, + description VARCHAR(1000) NOT NULL, + icon VARCHAR(255) NOT NULL, + CONSTRAINT pk_categories_etalon PRIMARY KEY (id) +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V21__.sql b/src/main/resources/db/migration/V21__.sql new file mode 100644 index 0000000..c4809d7 --- /dev/null +++ b/src/main/resources/db/migration/V21__.sql @@ -0,0 +1,32 @@ +drop table if exists finance.transactions cascade; + +create table finance.transactions +( + + id integer generated by default as identity not null, + space_id integer + constraint fk_transactions_on_space + references spaces, + parent_id integer + constraint fk_transactions_on_parent + references transactions, + type varchar(255), + kind varchar(255), + category_id integer, + comment varchar(255), + amount numeric, + fees numeric, + date date, + is_done boolean not null, + is_deleted boolean not null, + created_by_id integer + constraint fk_transactions_on_createdby + references users, + created_at timestamp default now(), + updated_by_id integer + constraint fk_transactions_on_updatedby + references users, + updated_at timestamp default now(), + + CONSTRAINT pk_transactions PRIMARY KEY (id) +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V2__.sql b/src/main/resources/db/migration/V2__.sql new file mode 100644 index 0000000..12bb47c --- /dev/null +++ b/src/main/resources/db/migration/V2__.sql @@ -0,0 +1,3 @@ + +ALTER TABLE finance.goals_transactions + ADD CONSTRAINT uc_goals_transactions_transactions UNIQUE (goal_id); \ No newline at end of file diff --git a/src/main/resources/db/migration/V3__.sql b/src/main/resources/db/migration/V3__.sql new file mode 100644 index 0000000..0641d9d --- /dev/null +++ b/src/main/resources/db/migration/V3__.sql @@ -0,0 +1,8 @@ + +ALTER TABLE finance.goals_transactions + DROP CONSTRAINT uc_goals_transactions_transactions; + + + +ALTER TABLE finance.goals_transactions + ADD CONSTRAINT uc_goals_transactions_transactions UNIQUE (transactions_id); \ No newline at end of file diff --git a/src/main/resources/db/migration/V4__.sql b/src/main/resources/db/migration/V4__.sql new file mode 100644 index 0000000..a062205 --- /dev/null +++ b/src/main/resources/db/migration/V4__.sql @@ -0,0 +1,10 @@ + + +CREATE TABLE finance.categories_etalon +( + id INTEGER NOT NULL, + type SMALLINT, + name VARCHAR(255), + icon VARCHAR(255), + CONSTRAINT pk_categories_etalon PRIMARY KEY (id) +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V5__.sql b/src/main/resources/db/migration/V5__.sql new file mode 100644 index 0000000..d6e655d --- /dev/null +++ b/src/main/resources/db/migration/V5__.sql @@ -0,0 +1,2 @@ +drop table finance.goals CASCADE; +-- drop table finance.goals_transactions; \ No newline at end of file diff --git a/src/main/resources/db/migration/V6__.sql b/src/main/resources/db/migration/V6__.sql new file mode 100644 index 0000000..6263e94 --- /dev/null +++ b/src/main/resources/db/migration/V6__.sql @@ -0,0 +1 @@ +drop table finance.goals_transactions CASCADE; \ No newline at end of file diff --git a/src/main/resources/db/migration/V7__.sql b/src/main/resources/db/migration/V7__.sql new file mode 100644 index 0000000..0888cfa --- /dev/null +++ b/src/main/resources/db/migration/V7__.sql @@ -0,0 +1,105 @@ + +CREATE TABLE finance.goals +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, + space_id INTEGER, + type SMALLINT, + name VARCHAR(255), + description VARCHAR(255), + amount DECIMAL, + until_date date, + created_by_id INTEGER, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_by_id INTEGER, + updated_at TIMESTAMP WITHOUT TIME ZONE, + current_amount DECIMAL, + CONSTRAINT pk_goals PRIMARY KEY (id) +); + +CREATE TABLE finance.goals_transactions +( + goal_id INTEGER NOT NULL, + transactions_id INTEGER NOT NULL +); + +ALTER TABLE finance.goals_transactions + ADD CONSTRAINT uc_goals_transactions_transactions UNIQUE (transactions_id); + +ALTER TABLE finance.goals + ADD CONSTRAINT FK_GOALS_ON_CREATEDBY FOREIGN KEY (created_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.goals + ADD CONSTRAINT FK_GOALS_ON_SPACE FOREIGN KEY (space_id) REFERENCES finance.spaces (id); + +ALTER TABLE finance.goals + ADD CONSTRAINT FK_GOALS_ON_UPDATEDBY FOREIGN KEY (updated_by_id) REFERENCES finance.users (id); + +ALTER TABLE finance.goals_transactions + ADD CONSTRAINT fk_goatra_on_goal FOREIGN KEY (goal_id) REFERENCES finance.goals (id); + +ALTER TABLE finance.goals_transactions + ADD CONSTRAINT fk_goatra_on_transaction FOREIGN KEY (transactions_id) REFERENCES finance.transactions (id); + +CREATE SEQUENCE IF NOT EXISTS finance.categories_id_seq; +ALTER TABLE finance.categories + ALTER COLUMN id SET NOT NULL; +ALTER TABLE finance.categories + ALTER COLUMN id SET DEFAULT nextval('finance.categories_id_seq'); + +ALTER SEQUENCE finance.categories_id_seq OWNED BY finance.categories.id; + +CREATE SEQUENCE IF NOT EXISTS finance.categories_etalon_id_seq; +ALTER TABLE finance.categories_etalon + ALTER COLUMN id SET NOT NULL; +ALTER TABLE finance.categories_etalon + ALTER COLUMN id SET DEFAULT nextval('finance.categories_etalon_id_seq'); + +ALTER SEQUENCE finance.categories_etalon_id_seq OWNED BY finance.categories_etalon.id; + +CREATE SEQUENCE IF NOT EXISTS finance.currency_rates_id_seq; +ALTER TABLE finance.currency_rates + ALTER COLUMN id SET NOT NULL; +ALTER TABLE finance.currency_rates + ALTER COLUMN id SET DEFAULT nextval('finance.currency_rates_id_seq'); + +ALTER SEQUENCE finance.currency_rates_id_seq OWNED BY finance.currency_rates.id; + +CREATE SEQUENCE IF NOT EXISTS finance.spaces_id_seq; +ALTER TABLE finance.spaces + ALTER COLUMN id SET NOT NULL; +ALTER TABLE finance.spaces + ALTER COLUMN id SET DEFAULT nextval('finance.spaces_id_seq'); + +ALTER SEQUENCE finance.spaces_id_seq OWNED BY finance.spaces.id; + +CREATE SEQUENCE IF NOT EXISTS finance.subscriptions_id_seq; +ALTER TABLE finance.subscriptions + ALTER COLUMN id SET NOT NULL; +ALTER TABLE finance.subscriptions + ALTER COLUMN id SET DEFAULT nextval('finance.subscriptions_id_seq'); + +ALTER SEQUENCE finance.subscriptions_id_seq OWNED BY finance.subscriptions.id; + +CREATE SEQUENCE IF NOT EXISTS finance.tokens_id_seq; +ALTER TABLE finance.tokens + ALTER COLUMN id SET NOT NULL; +ALTER TABLE finance.tokens + ALTER COLUMN id SET DEFAULT nextval('finance.tokens_id_seq'); + +ALTER SEQUENCE finance.tokens_id_seq OWNED BY finance.tokens.id; + +CREATE SEQUENCE IF NOT EXISTS finance.transactions_id_seq; +ALTER TABLE finance.transactions + ALTER COLUMN id SET NOT NULL; +ALTER TABLE finance.transactions + ALTER COLUMN id SET DEFAULT nextval('finance.transactions_id_seq'); + +ALTER SEQUENCE finance.transactions_id_seq OWNED BY finance.transactions.id; + +CREATE SEQUENCE IF NOT EXISTS finance.users_id_seq; +ALTER TABLE finance.users + ALTER COLUMN id SET NOT NULL; +ALTER TABLE finance.users + ALTER COLUMN id SET DEFAULT nextval('finance.users_id_seq'); + +ALTER SEQUENCE finance.users_id_seq OWNED BY finance.users.id; \ No newline at end of file diff --git a/src/main/resources/db/migration/V8__.sql b/src/main/resources/db/migration/V8__.sql new file mode 100644 index 0000000..9a278a9 --- /dev/null +++ b/src/main/resources/db/migration/V8__.sql @@ -0,0 +1,8 @@ +ALTER TABLE finance.transactions + ADD is_done BOOLEAN; + +ALTER TABLE finance.transactions + ALTER COLUMN is_done SET NOT NULL; + +ALTER TABLE finance.spaces_participants + ADD CONSTRAINT pk_spaces_participants PRIMARY KEY (space_id, participants_id); \ No newline at end of file diff --git a/src/main/resources/db/migration/V9__.sql b/src/main/resources/db/migration/V9__.sql new file mode 100644 index 0000000..09098eb --- /dev/null +++ b/src/main/resources/db/migration/V9__.sql @@ -0,0 +1,5 @@ +ALTER TABLE finance.categories + ALTER COLUMN created_at SEt DEFAULT now(); +ALTER TABLE finance.categories + ALTER COLUMN updated_at SEt DEFAULT now(); +