Compare commits
29 Commits
040da34ff7
...
recurrents
| Author | SHA1 | Date | |
|---|---|---|---|
| 12afd1f90e | |||
| d0cae182b7 | |||
| 0f02b53bc0 | |||
| b08ab909c8 | |||
| a65f46aff3 | |||
| 036ad00795 | |||
| 1c3605623e | |||
| aaa12fcb86 | |||
| cef82c483f | |||
| e83e3a2b65 | |||
| c68e6afb8a | |||
| 10b7c730ad | |||
| a0024def2e | |||
| d2458633db | |||
| 5b9d2366db | |||
| a30eb52f3f | |||
| 175c86d787 | |||
| 3541528e77 | |||
| 62fe88abdf | |||
| 61bfdf273a | |||
| 4a17b4f528 | |||
| 50607a8c42 | |||
| 3bb8c33094 | |||
| 1bc5932793 | |||
| 8d1b0f2a3c | |||
| 224fe18639 | |||
| d32062a485 | |||
| 0b54384258 | |||
| 7972ea0fdf |
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM eclipse-temurin:17-jre AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
USER root
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
|
||||||
|
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
|
||||||
|
COPY build/libs/luminic-space-v2.jar /app/luminic-space-v2.jar
|
||||||
|
USER app
|
||||||
|
|
||||||
|
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
|
||||||
|
EXPOSE 8080
|
||||||
|
HEALTHCHECK --interval=20s --timeout=3s --retries=3 CMD curl -fsS http://localhost:8080/actuator/health || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["java","-jar","/app/luminic-space-v2.jar"]
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.9.25"
|
kotlin("jvm") version "1.9.25"
|
||||||
kotlin("plugin.spring") 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("org.springframework.boot") version "3.4.0"
|
||||||
id("io.spring.dependency-management") version "1.1.6"
|
id("io.spring.dependency-management") version "1.1.6"
|
||||||
kotlin("plugin.serialization") version "2.1.0"
|
kotlin("plugin.serialization") version "2.1.0"
|
||||||
@@ -28,34 +29,45 @@ configurations {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Spring
|
// 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-cache")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
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)
|
// jackson для jsonb (если маппишь объекты в json)
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
|
|
||||||
implementation("commons-logging:commons-logging:1.3.4")
|
implementation("commons-logging:commons-logging:1.3.4")
|
||||||
|
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0")
|
||||||
|
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||||
|
implementation("io.micrometer:micrometer-registry-prometheus")
|
||||||
|
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
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-core:1.7.0")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.7.3")
|
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-api:0.11.5")
|
||||||
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
|
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
|
||||||
@@ -64,10 +76,11 @@ dependencies {
|
|||||||
|
|
||||||
implementation("io.micrometer:micrometer-registry-prometheus")
|
implementation("io.micrometer:micrometer-registry-prometheus")
|
||||||
|
|
||||||
implementation("org.telegram:telegrambots:6.9.7.1")
|
// implementation("org.telegram:telegrambots:6.9.7.1")
|
||||||
implementation("org.telegram:telegrambots-spring-boot-starter:6.9.7.1")
|
// implementation("org.telegram:telegrambots-spring-boot-starter:6.9.7.1")
|
||||||
implementation("com.opencsv:opencsv:5.10")
|
implementation("com.opencsv:opencsv:5.10")
|
||||||
|
|
||||||
|
implementation("io.github.kotlin-telegram-bot.kotlin-telegram-bot:telegram:6.3.0")
|
||||||
|
|
||||||
compileOnly("org.projectlombok:lombok")
|
compileOnly("org.projectlombok:lombok")
|
||||||
annotationProcessor("org.projectlombok:lombok")
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
|
|||||||
10
deploy.sh
Executable file
10
deploy.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
./gradlew bootJar || exit 1
|
||||||
|
|
||||||
|
scp build/libs/luminic-space-v2.jar root@213.226.71.138:/root/luminic/space/back
|
||||||
|
|
||||||
|
ssh root@213.226.71.138 "
|
||||||
|
cd /root/luminic/space/back &&
|
||||||
|
docker compose up -d --build &&
|
||||||
|
docker restart back-app-1
|
||||||
|
"
|
||||||
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
networks:
|
||||||
|
postgres-net:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: back-app
|
||||||
|
volumes:
|
||||||
|
- ./luminic-space-v2.jar:/app/luminic-space-v2.jar
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/luminic-space-db
|
||||||
|
SPRING_DATASOURCE_USERNAME: luminicspace
|
||||||
|
SPRING_DATASOURCE_PASSWORD: LS1q2w3e4r!
|
||||||
|
ports:
|
||||||
|
- "8089:8089"
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- postgres-net
|
||||||
1
gradle.properties
Normal file
1
gradle.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
kotlin.code.style=official
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#Wed Oct 08 22:52:02 GET 2025
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
234
gradlew
vendored
Executable file
234
gradlew
vendored
Executable file
@@ -0,0 +1,234 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
4
settings.gradle.kts
Normal file
4
settings.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
plugins {
|
||||||
|
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||||
|
}
|
||||||
|
rootProject.name = "luminic-space"
|
||||||
@@ -4,25 +4,17 @@ import org.springframework.boot.autoconfigure.SpringBootApplication
|
|||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
import org.springframework.cache.annotation.EnableCaching
|
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 org.springframework.scheduling.annotation.EnableScheduling
|
||||||
import java.util.TimeZone
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
|
|
||||||
|
|
||||||
@SpringBootApplication(scanBasePackages = ["space.luminic.finance"])
|
@SpringBootApplication(scanBasePackages = ["space.luminic.finance"])
|
||||||
@EnableReactiveMongoAuditing(auditorAwareRef = "coroutineAuditorAware")
|
|
||||||
@EnableCaching
|
@EnableCaching
|
||||||
@EnableAsync
|
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
//@EnableConfigurationProperties([TelegramBotProperties::class,)
|
@EnableWebSecurity
|
||||||
@ConfigurationPropertiesScan(basePackages = ["space.luminic.finance"])
|
@ConfigurationPropertiesScan(basePackages = ["space.luminic.finance"])
|
||||||
@EnableMongoRepositories(basePackages = ["space.luminic.finance.repos"])
|
|
||||||
class Main
|
class Main
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Moscow"))
|
|
||||||
runApplication<Main>(*args)
|
runApplication<Main>(*args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package space.luminic.finance.api
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
|
||||||
import jakarta.ws.rs.GET
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
|
||||||
import org.springframework.web.bind.annotation.PutMapping
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
import space.luminic.finance.dtos.AccountDTO
|
|
||||||
import space.luminic.finance.dtos.TransactionDTO
|
|
||||||
import space.luminic.finance.mappers.AccountMapper.toDto
|
|
||||||
import space.luminic.finance.mappers.TransactionMapper.toDto
|
|
||||||
import space.luminic.finance.models.Account
|
|
||||||
import space.luminic.finance.services.AccountService
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/spaces/{spaceId}/accounts")
|
|
||||||
@SecurityScheme(
|
|
||||||
name = "bearerAuth",
|
|
||||||
type = SecuritySchemeType.HTTP,
|
|
||||||
bearerFormat = "JWT",
|
|
||||||
scheme = "bearer"
|
|
||||||
)
|
|
||||||
class AccountController(
|
|
||||||
private val accountService: AccountService
|
|
||||||
) {
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
suspend fun getAccounts(@PathVariable spaceId: String): List<AccountDTO> {
|
|
||||||
return accountService.getAccounts(spaceId).map { it.toDto() }
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{accountId}")
|
|
||||||
suspend fun getAccount(@PathVariable spaceId: String, @PathVariable accountId: String): AccountDTO {
|
|
||||||
return accountService.getAccount(accountId, spaceId).toDto()
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{accountId}/transactions")
|
|
||||||
suspend fun getAccountTransactions(
|
|
||||||
@PathVariable spaceId: String,
|
|
||||||
@PathVariable accountId: String
|
|
||||||
): List<TransactionDTO> {
|
|
||||||
return accountService.getAccountTransactions(spaceId, accountId).map { it.toDto() }
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
suspend fun createAccount(
|
|
||||||
@PathVariable spaceId: String,
|
|
||||||
@RequestBody accountDTO: AccountDTO.CreateAccountDTO
|
|
||||||
): AccountDTO {
|
|
||||||
return accountService.createAccount(spaceId, accountDTO).toDto()
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping("/{accountId}")
|
|
||||||
suspend fun updateAccount(
|
|
||||||
@PathVariable spaceId: String,
|
|
||||||
@PathVariable accountId: String,
|
|
||||||
@RequestBody accountDTO: AccountDTO.UpdateAccountDTO
|
|
||||||
): AccountDTO {
|
|
||||||
return accountService.updateAccount(spaceId, accountDTO).toDto()
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/{accountId}")
|
|
||||||
suspend fun deleteAccount(@PathVariable spaceId: String, @PathVariable accountId: String) {
|
|
||||||
accountService.deleteAccount(accountId, spaceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,124 @@
|
|||||||
package space.luminic.finance.api
|
package space.luminic.finance.api
|
||||||
|
|
||||||
|
|
||||||
import kotlinx.coroutines.reactive.awaitSingle
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonBuilder
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
import space.luminic.finance.dtos.UserDTO.*
|
|
||||||
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.mappers.UserMapper.toDto
|
||||||
|
import space.luminic.finance.mappers.UserMapper.toTelegramMap
|
||||||
import space.luminic.finance.services.AuthService
|
import space.luminic.finance.services.AuthService
|
||||||
import space.luminic.finance.services.UserService
|
import java.net.URLDecoder
|
||||||
import kotlin.jvm.javaClass
|
import java.security.MessageDigest
|
||||||
import kotlin.to
|
import java.time.Instant
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
class AuthController(
|
class AuthController(
|
||||||
private val userService: UserService,
|
private val authService: AuthService,
|
||||||
private val authService: AuthService
|
@Value("\${telegram.bot.token}") private val botToken: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
|
fun verifyTelegramAuth(
|
||||||
|
loginData: Map<String, String>? = null, // from login widget
|
||||||
|
webAppInitData: String? = null
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
|
// --- LOGIN WIDGET CHECK ---
|
||||||
|
if (loginData != null) {
|
||||||
|
val hash = loginData["hash"]
|
||||||
|
if (hash != null) {
|
||||||
|
val dataCheckString = loginData
|
||||||
|
.filterKeys { it != "hash" }
|
||||||
|
.toSortedMap()
|
||||||
|
.map { "${it.key}=${it.value}" }
|
||||||
|
.joinToString("\n")
|
||||||
|
|
||||||
|
val secretKey = MessageDigest.getInstance("SHA-256")
|
||||||
|
.digest(botToken.toByteArray())
|
||||||
|
|
||||||
|
val hmac = Mac.getInstance("HmacSHA256").apply {
|
||||||
|
init(SecretKeySpec(secretKey, "HmacSHA256"))
|
||||||
|
}.doFinal(dataCheckString.toByteArray())
|
||||||
|
.joinToString("") { "%02x".format(it) }
|
||||||
|
|
||||||
|
val authDate = loginData["auth_date"]?.toLongOrNull() ?: return false
|
||||||
|
if (Instant.now().epochSecond - authDate > 3600) return false
|
||||||
|
|
||||||
|
if (hmac == hash) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- WEBAPP CHECK ---
|
||||||
|
// --- WEBAPP CHECK ---
|
||||||
|
if (webAppInitData != null) {
|
||||||
|
// Разбираем query string корректно (учитывая '=' внутри значения)
|
||||||
|
val pairs: Map<String, String> = webAppInitData.split("&")
|
||||||
|
.mapNotNull { part ->
|
||||||
|
val idx = part.indexOf('=')
|
||||||
|
if (idx <= 0) return@mapNotNull null
|
||||||
|
val k = part.substring(0, idx)
|
||||||
|
val v = part.substring(idx + 1)
|
||||||
|
k to URLDecoder.decode(v, Charsets.UTF_8.name())
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
val receivedHash = pairs["hash"] ?: return false
|
||||||
|
|
||||||
|
// Строка для подписи: все поля КРОМЕ hash, отсортированные по ключу, в формате key=value с \n
|
||||||
|
val dataCheckString = pairs
|
||||||
|
.filterKeys { it != "hash" }
|
||||||
|
.toSortedMap()
|
||||||
|
.entries
|
||||||
|
.joinToString("\n") { (k, v) -> "$k=$v" }
|
||||||
|
|
||||||
|
// ВАЖНО: secret_key = HMAC_SHA256(message=botToken, key="WebAppData")
|
||||||
|
val secretKeyBytes = Mac.getInstance("HmacSHA256").apply {
|
||||||
|
init(SecretKeySpec("WebAppData".toByteArray(Charsets.UTF_8), "HmacSHA256"))
|
||||||
|
}.doFinal(botToken.toByteArray(Charsets.UTF_8))
|
||||||
|
|
||||||
|
// hash = HMAC_SHA256(message=data_check_string, key=secret_key)
|
||||||
|
val calcHashHex = Mac.getInstance("HmacSHA256").apply {
|
||||||
|
init(SecretKeySpec(secretKeyBytes, "HmacSHA256"))
|
||||||
|
}.doFinal(dataCheckString.toByteArray(Charsets.UTF_8))
|
||||||
|
.joinToString("") { "%02x".format(it) }
|
||||||
|
|
||||||
|
// опциональная проверка свежести
|
||||||
|
val authDate = pairs["auth_date"]?.toLongOrNull() ?: return false
|
||||||
|
val now = Instant.now().epochSecond
|
||||||
|
val ttl = 6 * 3600L // например, 6 часов для WebApp
|
||||||
|
val skew = 300L // допускаем до 5 минут будущего/прошлого
|
||||||
|
val diff = now - authDate
|
||||||
|
if (diff > ttl || diff < -skew) return false
|
||||||
|
|
||||||
|
if (calcHashHex == receivedHash) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sha256(input: String): ByteArray =
|
||||||
|
MessageDigest.getInstance("SHA-256").digest(input.toByteArray())
|
||||||
|
|
||||||
|
private fun hmacSha256(secret: ByteArray, message: String): String {
|
||||||
|
val key = SecretKeySpec(secret, "HmacSHA256")
|
||||||
|
val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
mac.init(key)
|
||||||
|
val hashBytes = mac.doFinal(message.toByteArray())
|
||||||
|
return hashBytes.joinToString("") { "%02x".format(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/test")
|
@GetMapping("/test")
|
||||||
fun test(): String {
|
fun test(): String {
|
||||||
val authentication = SecurityContextHolder.getContext().authentication
|
val authentication = SecurityContextHolder.getContext().authentication
|
||||||
@@ -31,26 +127,60 @@ class AuthController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
suspend fun login(@RequestBody request: AuthUserDTO): Map<String, String> {
|
fun login(@RequestBody request: AuthUserDTO): Map<String, String> {
|
||||||
val token = authService.login(request.username, request.password)
|
val token = authService.login(request.username.lowercase(), request.password)
|
||||||
return mapOf("token" to token)
|
return mapOf("token" to token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/register")
|
@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()
|
return authService.register(request.username, request.password, request.firstName).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/tgLogin")
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
suspend fun tgLogin(@RequestHeader("X-Tg-Id") tgId: String): Map<String, String> {
|
|
||||||
val token = authService.tgLogin(tgId)
|
|
||||||
return mapOf("token" to token)
|
@PostMapping("/tg-login")
|
||||||
|
fun tgLogin(@RequestBody tgUser: UserDTO.TelegramAuthDTO): Map<String, String> {
|
||||||
|
// println(tgUser.hash)
|
||||||
|
// println(botToken)
|
||||||
|
if (tgUser.initData == null) {
|
||||||
|
if (verifyTelegramAuth(
|
||||||
|
loginData = tgUser.toTelegramMap(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return mapOf("token" to authService.tgAuth(tgUser))
|
||||||
|
} else throw IllegalArgumentException("Invalid Telegram login")
|
||||||
|
} else {
|
||||||
|
if (verifyTelegramAuth(webAppInitData = tgUser.initData)) {
|
||||||
|
val params = tgUser.initData.split("&").associate {
|
||||||
|
val (k, v) = it.split("=", limit = 2)
|
||||||
|
k to URLDecoder.decode(v, "UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
val userJson = params["user"] ?: error("No user data")
|
||||||
|
val jsonUser = json.decodeFromString<UserDTO.TelegramUserData>(userJson)
|
||||||
|
val newUser = UserDTO.TelegramAuthDTO(
|
||||||
|
jsonUser.id,
|
||||||
|
jsonUser.first_name,
|
||||||
|
jsonUser.last_name,
|
||||||
|
jsonUser.username,
|
||||||
|
jsonUser.photo_url,
|
||||||
|
null,
|
||||||
|
hash = tgUser.hash,
|
||||||
|
initData = null,
|
||||||
|
)
|
||||||
|
return mapOf("token" to authService.tgAuth(newUser))
|
||||||
|
} else throw IllegalArgumentException("Invalid Telegram login")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
suspend fun getMe(): UserDTO {
|
fun getMe(): UserDTO {
|
||||||
val securityContext = ReactiveSecurityContextHolder.getContext().awaitSingle()
|
logger.info("Get Me")
|
||||||
return userService.getByUsername(securityContext.authentication.name).toDto()
|
authService.getSecurityUser()
|
||||||
|
|
||||||
|
return authService.getSecurityUser().toDto()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
package space.luminic.finance.api
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
|
||||||
import org.springframework.web.bind.annotation.PutMapping
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
import space.luminic.finance.dtos.BudgetDTO
|
|
||||||
import space.luminic.finance.dtos.TransactionDTO
|
|
||||||
import space.luminic.finance.mappers.BudgetMapper.toDto
|
|
||||||
import space.luminic.finance.mappers.BudgetMapper.toShortDto
|
|
||||||
import space.luminic.finance.mappers.TransactionMapper.toDto
|
|
||||||
import space.luminic.finance.models.Budget
|
|
||||||
import space.luminic.finance.services.BudgetService
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/spaces/{spaceId}/budgets")
|
|
||||||
@SecurityScheme(
|
|
||||||
name = "bearerAuth",
|
|
||||||
type = SecuritySchemeType.HTTP,
|
|
||||||
bearerFormat = "JWT",
|
|
||||||
scheme = "bearer"
|
|
||||||
)
|
|
||||||
class BudgetController(
|
|
||||||
private val budgetService: BudgetService
|
|
||||||
) {
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
suspend fun getBudgets(
|
|
||||||
@PathVariable spaceId: String,
|
|
||||||
@RequestParam(value = "sort", defaultValue = "dateFrom") sortBy: String,
|
|
||||||
@RequestParam("direction", defaultValue = "DESC") sortDirection: String
|
|
||||||
): List<BudgetDTO.BudgetShortInfoDTO> {
|
|
||||||
return budgetService.getBudgets(spaceId, sortBy, sortDirection).map { it.toShortDto() }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/{budgetId}")
|
|
||||||
suspend fun getBudgetById(@PathVariable spaceId: String, @PathVariable budgetId: String): BudgetDTO {
|
|
||||||
return budgetService.getBudget(spaceId, budgetId).toDto()
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{budgetId}/transactions")
|
|
||||||
suspend fun getBudgetTransactions(
|
|
||||||
@PathVariable spaceId: String,
|
|
||||||
@PathVariable budgetId: String
|
|
||||||
): BudgetDTO.BudgetTransactionsDTO {
|
|
||||||
return budgetService.getBudgetTransactions(spaceId, budgetId).toDto()
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
suspend fun createBudget(
|
|
||||||
@PathVariable spaceId: String,
|
|
||||||
@RequestBody createBudgetDTO: BudgetDTO.CreateBudgetDTO
|
|
||||||
): BudgetDTO {
|
|
||||||
return budgetService.createBudget(spaceId, Budget.BudgetType.SPECIAL, createBudgetDTO).toDto()
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping("/{budgetId}")
|
|
||||||
suspend fun updateBudget(
|
|
||||||
@PathVariable spaceId: String,
|
|
||||||
@PathVariable budgetId: String,
|
|
||||||
@RequestBody updateBudgetDTO: BudgetDTO.UpdateBudgetDTO
|
|
||||||
): BudgetDTO {
|
|
||||||
return budgetService.updateBudget(spaceId, updateBudgetDTO).toDto()
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/{budgetId}")
|
|
||||||
suspend fun deleteBudget(@PathVariable spaceId: String, @PathVariable budgetId: String) {
|
|
||||||
budgetService.deleteBudget(spaceId, budgetId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,16 +2,10 @@ package space.luminic.finance.api
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping
|
import org.springframework.web.bind.annotation.*
|
||||||
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.CategoryDTO
|
import space.luminic.finance.dtos.CategoryDTO
|
||||||
import space.luminic.finance.mappers.CategoryMapper.toDto
|
import space.luminic.finance.mappers.CategoryMapper.toDto
|
||||||
|
import space.luminic.finance.models.Category
|
||||||
import space.luminic.finance.services.CategoryService
|
import space.luminic.finance.services.CategoryService
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -23,43 +17,47 @@ import space.luminic.finance.services.CategoryService
|
|||||||
scheme = "bearer"
|
scheme = "bearer"
|
||||||
)
|
)
|
||||||
class CategoryController(
|
class CategoryController(
|
||||||
private val categoryService: CategoryService,
|
private val categoryService: CategoryService
|
||||||
service: CategoryService
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
suspend fun getCategories(@PathVariable spaceId: String): List<CategoryDTO> {
|
fun getCategories(@PathVariable spaceId: Int): List<CategoryDTO> {
|
||||||
return categoryService.getCategories(spaceId).map { it.toDto() }
|
return categoryService.getCategories(spaceId).map { it.toDto() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{categoryId}")
|
@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()
|
return categoryService.getCategory(spaceId, categoryId).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
suspend fun createCategory(
|
fun createCategory(
|
||||||
@PathVariable spaceId: String,
|
@PathVariable spaceId: Int,
|
||||||
@RequestBody categoryDTO: CategoryDTO.CreateCategoryDTO
|
@RequestBody categoryDTO: CategoryDTO.CreateCategoryDTO
|
||||||
): CategoryDTO {
|
): CategoryDTO {
|
||||||
return categoryService.createCategory(spaceId, categoryDTO).toDto()
|
return categoryService.createCategory(spaceId, categoryDTO).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/_many")
|
||||||
|
fun createManyCategory(@PathVariable spaceId: Int, @RequestBody categoryDTOs: List<Category.CategoryEtalon>): List<CategoryDTO> {
|
||||||
|
return categoryService.createEtalonCategoriesForSpace(spaceId).map { it.toDto() }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@PutMapping("/{categoryId}")
|
@PutMapping("/{categoryId}")
|
||||||
suspend fun updateCategory(
|
fun updateCategory(
|
||||||
@PathVariable spaceId: String,
|
@PathVariable spaceId: Int,
|
||||||
@PathVariable categoryId: String,
|
@PathVariable categoryId: Int,
|
||||||
@RequestBody categoryDTO: CategoryDTO.UpdateCategoryDTO
|
@RequestBody categoryDTO: CategoryDTO.UpdateCategoryDTO
|
||||||
): CategoryDTO {
|
): CategoryDTO {
|
||||||
return categoryService.updateCategory(spaceId, categoryDTO).toDto()
|
return categoryService.updateCategory(spaceId, categoryId, categoryDTO).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@DeleteMapping("/{categoryId}")
|
@DeleteMapping("/{categoryId}")
|
||||||
suspend fun deleteCategory(@PathVariable spaceId: String, @PathVariable categoryId: String) {
|
fun deleteCategory(@PathVariable spaceId: Int, @PathVariable categoryId: Int) {
|
||||||
categoryService.deleteCategory(spaceId, categoryId)
|
categoryService.deleteCategory(spaceId, categoryId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
19
src/main/kotlin/space/luminic/finance/api/GoalController.kt
Normal file
19
src/main/kotlin/space/luminic/finance/api/GoalController.kt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package space.luminic.finance.api
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import space.luminic.finance.dtos.GoalDTO
|
||||||
|
import space.luminic.finance.mappers.GoalMapper.toDto
|
||||||
|
import space.luminic.finance.services.GoalService
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/spaces/{spaceId}/goals")
|
||||||
|
class GoalController(private val goalService: GoalService) {
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
fun findAll(@PathVariable spaceId: Int): List<GoalDTO> {
|
||||||
|
return goalService.findAllBySpaceId(spaceId).map { it.toDto() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package space.luminic.finance.api
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.*
|
||||||
|
import space.luminic.finance.dtos.RecurrentOperationDTO
|
||||||
|
import space.luminic.finance.mappers.RecurrentOperationMapper.toDTO
|
||||||
|
import space.luminic.finance.services.RecurrentOperationService
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value = ["/spaces/{spaceId}/recurrents"])
|
||||||
|
class RecurrentOperationController(private val recurrentOperationService: RecurrentOperationService) {
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
fun findAll(@PathVariable spaceId: Int): List<RecurrentOperationDTO> {
|
||||||
|
return recurrentOperationService.findBySpaceId(spaceId).map { it.toDTO() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{operationId}")
|
||||||
|
fun findById(@PathVariable spaceId: Int, @PathVariable operationId: Int): RecurrentOperationDTO {
|
||||||
|
return recurrentOperationService.findBySpaceIdAndId(spaceId, operationId).toDTO()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
fun createOperation(@PathVariable spaceId: Int, @RequestBody createOperation: RecurrentOperationDTO.CreateRecurrentOperationDTO): Map<String, Int> {
|
||||||
|
return mapOf("id" to recurrentOperationService.create(spaceId, createOperation))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{operationId}")
|
||||||
|
fun updateOperation(@PathVariable spaceId: Int, @PathVariable operationId: Int, @RequestBody operation: RecurrentOperationDTO.UpdateRecurrentOperationDTO): Map<String, Int> {
|
||||||
|
recurrentOperationService.update(spaceId, operationId, operation)
|
||||||
|
return mapOf("id" to operationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{operationId}")
|
||||||
|
fun deleteOperation(@PathVariable spaceId: Int, @PathVariable operationId: Int) {
|
||||||
|
recurrentOperationService.delete(spaceId, operationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,14 +2,7 @@ package space.luminic.finance.api
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping
|
import org.springframework.web.bind.annotation.*
|
||||||
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.CurrencyDTO
|
import space.luminic.finance.dtos.CurrencyDTO
|
||||||
import space.luminic.finance.mappers.CurrencyMapper.toDto
|
import space.luminic.finance.mappers.CurrencyMapper.toDto
|
||||||
import space.luminic.finance.services.CurrencyService
|
import space.luminic.finance.services.CurrencyService
|
||||||
@@ -27,27 +20,27 @@ class ReferenceController(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping("/currencies")
|
@GetMapping("/currencies")
|
||||||
suspend fun getCurrencies(): List<CurrencyDTO> {
|
fun getCurrencies(): List<CurrencyDTO> {
|
||||||
return currencyService.getCurrencies().map { it.toDto() }
|
return currencyService.getCurrencies().map { it.toDto() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/currencies/{currencyCode}")
|
@GetMapping("/currencies/{currencyCode}")
|
||||||
suspend fun getCurrency(@PathVariable currencyCode: String): CurrencyDTO {
|
fun getCurrency(@PathVariable currencyCode: String): CurrencyDTO {
|
||||||
return currencyService.getCurrency(currencyCode).toDto()
|
return currencyService.getCurrency(currencyCode).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/currencies")
|
@PostMapping("/currencies")
|
||||||
suspend fun createCurrency(@RequestBody currencyDTO: CurrencyDTO): CurrencyDTO {
|
fun createCurrency(@RequestBody currencyDTO: CurrencyDTO): CurrencyDTO {
|
||||||
return currencyService.createCurrency(currencyDTO).toDto()
|
return currencyService.createCurrency(currencyDTO).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/currencies/{currencyCode}")
|
@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()
|
return currencyService.updateCurrency(currencyDTO).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/currencies/{currencyCode}")
|
@DeleteMapping("/currencies/{currencyCode}")
|
||||||
suspend fun deleteCurrency(@PathVariable currencyCode: String) {
|
fun deleteCurrency(@PathVariable currencyCode: String) {
|
||||||
currencyService.deleteCurrency(currencyCode)
|
currencyService.deleteCurrency(currencyCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,9 @@ package space.luminic.finance.api
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping
|
import org.springframework.web.bind.annotation.*
|
||||||
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.SpaceDTO
|
import space.luminic.finance.dtos.SpaceDTO
|
||||||
import space.luminic.finance.mappers.SpaceMapper.toDto
|
import space.luminic.finance.mappers.SpaceMapper.toDto
|
||||||
import space.luminic.finance.models.Space
|
|
||||||
import space.luminic.finance.services.SpaceService
|
import space.luminic.finance.services.SpaceService
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -29,27 +21,27 @@ class SpaceController(
|
|||||||
|
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
suspend fun getSpaces(): List<SpaceDTO> {
|
fun getSpaces(): List<SpaceDTO> {
|
||||||
return spaceService.getSpaces().map { it.toDto() }
|
return spaceService.getSpaces().map { it.toDto() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{spaceId}")
|
@GetMapping("/{spaceId}")
|
||||||
suspend fun getSpace(@PathVariable spaceId: String): SpaceDTO {
|
fun getSpace(@PathVariable spaceId: Int): SpaceDTO {
|
||||||
return spaceService.getSpace(spaceId).toDto()
|
return spaceService.getSpace(spaceId, null).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
suspend fun createSpace(@RequestBody space: SpaceDTO.CreateSpaceDTO): SpaceDTO {
|
fun createSpace(@RequestBody space: SpaceDTO.CreateSpaceDTO): Map<String, Int> {
|
||||||
return spaceService.createSpace(space).toDto()
|
return mapOf("id" to spaceService.createSpace(space) )
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{spaceId}")
|
@PutMapping("/{spaceId}")
|
||||||
suspend fun updateSpace(@PathVariable spaceId: String, @RequestBody space: SpaceDTO.UpdateSpaceDTO): SpaceDTO {
|
fun updateSpace(@PathVariable spaceId: Int, @RequestBody space: SpaceDTO.UpdateSpaceDTO): Map<String, Int> {
|
||||||
return spaceService.updateSpace(spaceId, space).toDto()
|
return mapOf("id" to spaceService.updateSpace(spaceId, space) )
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{spaceId}")
|
@DeleteMapping("/{spaceId}")
|
||||||
suspend fun deleteSpace(@PathVariable spaceId: String) {
|
fun deleteSpace(@PathVariable spaceId: Int) {
|
||||||
spaceService.deleteSpace(spaceId)
|
spaceService.deleteSpace(spaceId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,7 @@ package space.luminic.finance.api
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping
|
import org.springframework.web.bind.annotation.*
|
||||||
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.TransactionDTO
|
import space.luminic.finance.dtos.TransactionDTO
|
||||||
import space.luminic.finance.mappers.TransactionMapper.toDto
|
import space.luminic.finance.mappers.TransactionMapper.toDto
|
||||||
import space.luminic.finance.services.TransactionService
|
import space.luminic.finance.services.TransactionService
|
||||||
@@ -25,34 +18,32 @@ import space.luminic.finance.services.TransactionService
|
|||||||
)
|
)
|
||||||
class TransactionController (
|
class TransactionController (
|
||||||
private val transactionService: TransactionService,
|
private val transactionService: TransactionService,
|
||||||
service: TransactionService,
|
|
||||||
transactionService1: TransactionService,
|
|
||||||
){
|
){
|
||||||
|
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
suspend fun getTransactions(@PathVariable spaceId: String) : List<TransactionDTO>{
|
fun getTransactions(@PathVariable spaceId: Int) : List<TransactionDTO>{
|
||||||
return transactionService.getTransactions(spaceId, TransactionService.TransactionsFilter(),"date", "DESC").map { it.toDto() }
|
return transactionService.getTransactions(spaceId, TransactionService.TransactionsFilter(),"date", "DESC").map { it.toDto() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{transactionId}")
|
@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()
|
return transactionService.getTransaction(spaceId, transactionId).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
suspend fun createTransaction(@PathVariable spaceId: String, @RequestBody transactionDTO: TransactionDTO.CreateTransactionDTO): TransactionDTO {
|
fun createTransaction(@PathVariable spaceId: Int, @RequestBody transactionDTO: TransactionDTO.CreateTransactionDTO): Map<String, Int> {
|
||||||
return transactionService.createTransaction(spaceId, transactionDTO).toDto()
|
return mapOf("id" to transactionService.createTransaction(spaceId, transactionDTO))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@PutMapping("/{transactionId}")
|
@PutMapping("/{transactionId}")
|
||||||
suspend fun updateTransaction(@PathVariable spaceId: String, @PathVariable transactionId: String, @RequestBody transactionDTO: TransactionDTO.UpdateTransactionDTO): TransactionDTO {
|
fun updateTransaction(@PathVariable spaceId: Int, @PathVariable transactionId: Int, @RequestBody transactionDTO: TransactionDTO.UpdateTransactionDTO): Map<String, Int> {
|
||||||
return transactionService.updateTransaction(spaceId, transactionDTO).toDto()
|
return mapOf("id" to transactionService.updateTransaction(spaceId, transactionId, transactionDTO))
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{transactionId}")
|
@DeleteMapping("/{transactionId}")
|
||||||
suspend fun deleteTransaction(@PathVariable spaceId: String, @PathVariable transactionId: String) {
|
fun deleteTransaction(@PathVariable spaceId: Int, @PathVariable transactionId: Int) {
|
||||||
transactionService.deleteTransaction(spaceId, transactionId)
|
transactionService.deleteTransaction(spaceId, transactionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,109 +1,112 @@
|
|||||||
package space.luminic.finance.api.exceptionHandlers
|
package space.luminic.finance.api.exceptionHandlers
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.ResponseEntity
|
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.ExceptionHandler
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
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.configs.AuthException
|
||||||
import space.luminic.finance.models.NotFoundException
|
import space.luminic.finance.models.NotFoundException
|
||||||
|
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
class GlobalExceptionHandler {
|
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(
|
fun constructErrorBody(
|
||||||
e: Exception,
|
e: Exception,
|
||||||
message: String,
|
message: String,
|
||||||
status: HttpStatus,
|
status: HttpStatus,
|
||||||
request: ServerHttpRequest
|
request: HttpServletRequest // <--- Изменённый тип
|
||||||
): Map<String, Any?> {
|
): ErrorResponse {
|
||||||
val errorResponse = mapOf(
|
return ErrorResponse(
|
||||||
"timestamp" to System.currentTimeMillis(),
|
status = status.value(),
|
||||||
"status" to status.value(),
|
error = message,
|
||||||
"error" to message,
|
message = e.message,
|
||||||
"message" to e.message,
|
path = request.requestURI // <--- Получаем путь через HttpServletRequest
|
||||||
"path" to request.path.value()
|
|
||||||
)
|
)
|
||||||
return errorResponse
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(AuthException::class)
|
@ExceptionHandler(AuthException::class)
|
||||||
|
// Изменённый параметр exchange
|
||||||
fun handleAuthenticationException(
|
fun handleAuthenticationException(
|
||||||
ex: AuthException,
|
ex: AuthException,
|
||||||
exchange: ServerWebExchange
|
request: HttpServletRequest // <--- Изменённый тип
|
||||||
): Mono<ResponseEntity<Map<String, Any?>>?> {
|
): ResponseEntity<ErrorResponse>? {
|
||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
|
|
||||||
return Mono.just(
|
return ResponseEntity(
|
||||||
ResponseEntity(
|
|
||||||
constructErrorBody(
|
constructErrorBody(
|
||||||
ex,
|
ex,
|
||||||
ex.message.toString(),
|
ex.message.toString(),
|
||||||
HttpStatus.UNAUTHORIZED,
|
HttpStatus.UNAUTHORIZED,
|
||||||
exchange.request
|
request // <--- Передаём HttpServletRequest
|
||||||
), HttpStatus.UNAUTHORIZED
|
),
|
||||||
)
|
HttpStatus.UNAUTHORIZED
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ExceptionHandler(NotFoundException::class)
|
@ExceptionHandler(NotFoundException::class)
|
||||||
|
// Изменённый параметр exchange
|
||||||
fun handleNotFoundException(
|
fun handleNotFoundException(
|
||||||
e: NotFoundException,
|
e: NotFoundException,
|
||||||
exchange: ServerWebExchange
|
request: HttpServletRequest // <--- Изменённый тип
|
||||||
): Mono<ResponseEntity<Map<String, Any?>>?> {
|
): ResponseEntity<ErrorResponse>? {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
||||||
return Mono.just(
|
return ResponseEntity(
|
||||||
ResponseEntity(
|
|
||||||
constructErrorBody(
|
constructErrorBody(
|
||||||
e,
|
e,
|
||||||
e.message.toString(),
|
e.message.toString(),
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
exchange.request
|
request // <--- Передаём HttpServletRequest
|
||||||
), HttpStatus.NOT_FOUND
|
),
|
||||||
)
|
HttpStatus.NOT_FOUND
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(IllegalArgumentException::class)
|
@ExceptionHandler(IllegalArgumentException::class)
|
||||||
|
// Изменённый параметр exchange
|
||||||
fun handleIllegalArgumentException(
|
fun handleIllegalArgumentException(
|
||||||
e: IllegalArgumentException,
|
e: IllegalArgumentException,
|
||||||
exchange: ServerWebExchange
|
request: HttpServletRequest // <--- Изменённый тип
|
||||||
): Mono<ResponseEntity<Map<String, Any?>>?> {
|
): ResponseEntity<ErrorResponse>? {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
||||||
return Mono.just(
|
return ResponseEntity(
|
||||||
ResponseEntity(
|
|
||||||
constructErrorBody(
|
constructErrorBody(
|
||||||
e,
|
e,
|
||||||
e.message.toString(),
|
e.message.toString(),
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
exchange.request
|
request // <--- Передаём HttpServletRequest
|
||||||
), HttpStatus.BAD_REQUEST
|
),
|
||||||
)
|
HttpStatus.BAD_REQUEST
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception::class)
|
@ExceptionHandler(Exception::class)
|
||||||
|
// Изменённый параметр exchange
|
||||||
fun handleGenericException(
|
fun handleGenericException(
|
||||||
e: Exception,
|
e: Exception,
|
||||||
exchange: ServerWebExchange
|
request: HttpServletRequest // <--- Изменённый тип
|
||||||
): Mono<out ResponseEntity<out Map<String, Any?>>?> {
|
): ResponseEntity<ErrorResponse>? {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
||||||
|
return ResponseEntity(
|
||||||
return Mono.just(
|
|
||||||
ResponseEntity(
|
|
||||||
constructErrorBody(
|
constructErrorBody(
|
||||||
e,
|
e,
|
||||||
e.message.toString(),
|
e.message.toString(),
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
exchange.request
|
request // <--- Передаём HttpServletRequest
|
||||||
), HttpStatus.INTERNAL_SERVER_ERROR
|
),
|
||||||
)
|
HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,49 +1,37 @@
|
|||||||
package space.luminic.finance.api.exceptionHandlers
|
package space.luminic.finance.api.exceptionHandlers
|
||||||
import org.springframework.boot.autoconfigure.web.WebProperties
|
//
|
||||||
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
|
//@Component
|
||||||
import org.springframework.boot.web.error.ErrorAttributeOptions
|
//@Order(-2)
|
||||||
import org.springframework.boot.web.reactive.error.ErrorAttributes
|
//class GlobalErrorWebExceptionHandler(
|
||||||
import org.springframework.context.ApplicationContext
|
// errorAttributes: ErrorAttributes,
|
||||||
import org.springframework.core.annotation.Order
|
// applicationContext: ApplicationContext,
|
||||||
import org.springframework.http.MediaType
|
// serverCodecConfigurer: ServerCodecConfigurer
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer
|
//) : AbstractErrorWebExceptionHandler(
|
||||||
import org.springframework.stereotype.Component
|
// errorAttributes,
|
||||||
import org.springframework.web.reactive.function.BodyInserters
|
// WebProperties.Resources(),
|
||||||
import org.springframework.web.reactive.function.server.*
|
// applicationContext
|
||||||
import reactor.core.publisher.Mono
|
//) {
|
||||||
|
//
|
||||||
@Component
|
// init {
|
||||||
@Order(-2)
|
// super.setMessageWriters(serverCodecConfigurer.writers)
|
||||||
class GlobalErrorWebExceptionHandler(
|
// super.setMessageReaders(serverCodecConfigurer.readers)
|
||||||
errorAttributes: ErrorAttributes,
|
// }
|
||||||
applicationContext: ApplicationContext,
|
//
|
||||||
serverCodecConfigurer: ServerCodecConfigurer
|
// override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
|
||||||
) : AbstractErrorWebExceptionHandler(
|
// return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse)
|
||||||
errorAttributes,
|
// }
|
||||||
WebProperties.Resources(),
|
//
|
||||||
applicationContext
|
// private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
|
||||||
) {
|
// val errorAttributesMap = getErrorAttributes(
|
||||||
|
// request,
|
||||||
init {
|
// ErrorAttributeOptions.of(
|
||||||
super.setMessageWriters(serverCodecConfigurer.writers)
|
// ErrorAttributeOptions.Include.MESSAGE
|
||||||
super.setMessageReaders(serverCodecConfigurer.readers)
|
// )
|
||||||
}
|
// )
|
||||||
|
// return ServerResponse.status(401)
|
||||||
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
|
// .contentType(MediaType.APPLICATION_JSON)
|
||||||
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse)
|
// .body(BodyInserters.fromValue(errorAttributesMap))
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
|
//
|
||||||
val errorAttributesMap = getErrorAttributes(
|
//}
|
||||||
request,
|
|
||||||
ErrorAttributeOptions.of(
|
|
||||||
ErrorAttributeOptions.Include.MESSAGE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return ServerResponse.status(401)
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(BodyInserters.fromValue(errorAttributesMap))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,56 +1,80 @@
|
|||||||
package space.luminic.finance.configs
|
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.http.HttpHeaders
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.security.core.context.SecurityContextImpl
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
|
||||||
import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter
|
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.server.ServerWebExchange
|
import org.springframework.web.filter.OncePerRequestFilter
|
||||||
import org.springframework.web.server.WebFilterChain
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import space.luminic.finance.services.AuthService
|
import space.luminic.finance.services.AuthService
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class BearerTokenFilter(private val authService: AuthService) : SecurityContextServerWebExchangeWebFilter() {
|
class BearerTokenFilter(
|
||||||
// private val logger = LoggerFactory.getLogger(BearerTokenFilter::class.java)
|
private val authService: AuthService
|
||||||
|
) : OncePerRequestFilter() {
|
||||||
|
|
||||||
|
// Публичные пути — как в твоём WebFlux-фильтре
|
||||||
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
|
private val publicMatchers = listOf(
|
||||||
val token = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)?.removePrefix("Bearer ")
|
AntPathRequestMatcher("/auth/login", "POST"),
|
||||||
|
AntPathRequestMatcher("/auth/register", "POST"),
|
||||||
if (exchange.request.path.value() in listOf(
|
AntPathRequestMatcher("/auth/tg-login", "POST"),
|
||||||
"/api/auth/login",
|
AntPathRequestMatcher("/actuator/**"),
|
||||||
"/api/auth/register",
|
AntPathRequestMatcher("/static/**"),
|
||||||
"/api/auth/tgLogin"
|
AntPathRequestMatcher("/wishlistexternal/**"),
|
||||||
) || exchange.request.path.value().startsWith("/api/actuator") || exchange.request.path.value()
|
AntPathRequestMatcher("/swagger-ui/**"),
|
||||||
.startsWith("/api/static/")
|
AntPathRequestMatcher("/v3/api-docs/**"),
|
||||||
|| 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ->
|
override fun shouldNotFilter(request: HttpServletRequest): Boolean {
|
||||||
chain.filter(exchange)
|
return publicMatchers.any { it.matches(request) }
|
||||||
.contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)))
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Mono.error(AuthException("Authorization token is missing"))
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
open class AuthException(msg: String) : RuntimeException(msg)
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
package space.luminic.finance.configs
|
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 {
|
//class CommonConfig {
|
||||||
// @Bean
|
// @Bean
|
||||||
@@ -12,8 +8,8 @@ import org.springframework.context.annotation.Configuration
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
//
|
||||||
@ConfigurationProperties(prefix = "nlp")
|
//@ConfigurationProperties(prefix = "nlp")
|
||||||
data class NLPConfig(
|
//data class NLPConfig(
|
||||||
val address: String,
|
// val address: String,
|
||||||
)
|
//)
|
||||||
31
src/main/kotlin/space/luminic/finance/configs/JpaConfig.kt
Normal file
31
src/main/kotlin/space/luminic/finance/configs/JpaConfig.kt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package space.luminic.finance.configs
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.data.domain.AuditorAware
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import space.luminic.finance.models.User
|
||||||
|
import space.luminic.finance.services.AuthService
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class JpaConfig // Класс для включения аудита
|
||||||
|
|
||||||
|
@Component // Помечаем как Spring-компонент
|
||||||
|
class AuditorAwareImpl(
|
||||||
|
private val authService: AuthService,
|
||||||
|
) : AuditorAware<User> { // Убедитесь, что тип возвращаемого ID совпадает (Int)
|
||||||
|
|
||||||
|
override fun getCurrentAuditor(): Optional<User> {
|
||||||
|
return try {
|
||||||
|
val currentUser = authService.getSecurityUser()
|
||||||
|
// Проверяем, что пользователь не null перед доступом к id
|
||||||
|
Optional.of(currentUser) // currentUser.id должен быть Int
|
||||||
|
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
// Логирование (по желанию)
|
||||||
|
// logger.debug("Authentication not found or invalid, auditor is null", ex)
|
||||||
|
// Если возникла ошибка при получении пользователя (например, не аутентифицирован), возвращаем Optional.empty()
|
||||||
|
Optional.empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +1,62 @@
|
|||||||
package space.luminic.finance.configs
|
package space.luminic.finance.configs
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
import org.springframework.security.config.web.server.SecurityWebFiltersOrder
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
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.bcrypt.BCryptPasswordEncoder
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
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
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
class SecurityConfig(
|
class SecurityConfig(
|
||||||
|
private val bearerTokenFilter: BearerTokenFilter, // см. примечание ниже
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun securityWebFilterChain(
|
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
http: ServerHttpSecurity,
|
http
|
||||||
bearerTokenFilter: BearerTokenFilter
|
.csrf {
|
||||||
): SecurityWebFilterChain {
|
it.disable()
|
||||||
return http
|
}
|
||||||
.csrf { it.disable() }
|
|
||||||
.cors { it.configurationSource(corsConfigurationSource()) }
|
.cors { it.configurationSource(corsConfigurationSource()) }
|
||||||
|
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
|
||||||
.logout { it.disable() }
|
.logout { it.disable() }
|
||||||
.authorizeExchange {
|
|
||||||
it.pathMatchers(HttpMethod.POST, "/auth/login", "/auth/register", "/auth/tgLogin").permitAll()
|
.authorizeHttpRequests {
|
||||||
it.pathMatchers("/actuator/**", "/static/**").permitAll()
|
it.requestMatchers(HttpMethod.POST, "/auth/login", "/auth/register", "/auth/tg-login").permitAll()
|
||||||
it.pathMatchers("/wishlistexternal/**").permitAll()
|
it.requestMatchers("/actuator/**", "/static/**").permitAll()
|
||||||
it.pathMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**").permitAll()
|
it.requestMatchers("/wishlistexternal/**").permitAll()
|
||||||
it.anyExchange().authenticated()
|
it.requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**").permitAll()
|
||||||
}
|
it.anyRequest().authenticated()
|
||||||
.addFilterAt(
|
|
||||||
bearerTokenFilter,
|
|
||||||
SecurityWebFiltersOrder.AUTHENTICATION
|
|
||||||
) // BearerTokenFilter только для authenticated
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ваш JWT-фильтр до стандартной аутентификации
|
||||||
|
.addFilterBefore(bearerTokenFilter, UsernamePasswordAuthenticationFilter::class.java)
|
||||||
|
|
||||||
@Bean
|
return http.build()
|
||||||
fun passwordEncoder(): PasswordEncoder {
|
|
||||||
return BCryptPasswordEncoder()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun corsConfigurationSource(): org.springframework.web.cors.reactive.CorsConfigurationSource {
|
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
||||||
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
|
|
||||||
|
|
||||||
val source = org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource()
|
@Bean
|
||||||
source.registerCorsConfiguration("/**", corsConfig)
|
fun corsConfigurationSource(): CorsConfigurationSource {
|
||||||
|
val cors = CorsConfiguration().apply {
|
||||||
|
allowedOrigins = listOf("https://app.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
|
return source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package space.luminic.finance.dtos
|
|
||||||
|
|
||||||
import space.luminic.finance.models.Account.AccountType
|
|
||||||
import space.luminic.finance.models.Currency
|
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
data class AccountDTO(
|
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
val type: AccountType = AccountType.COLLECTING,
|
|
||||||
val currencyCode: String,
|
|
||||||
val currency: CurrencyDTO? = null,
|
|
||||||
var balance: BigDecimal = BigDecimal.ZERO,
|
|
||||||
val goal: GoalDTO? = null,
|
|
||||||
val createdBy: String? = null,
|
|
||||||
val createdAt: Instant? = null,
|
|
||||||
val updatedBy: String? = null,
|
|
||||||
val updatedAt: Instant? = null,
|
|
||||||
){
|
|
||||||
data class CreateAccountDTO(
|
|
||||||
val name: String,
|
|
||||||
val type: AccountType,
|
|
||||||
val currencyCode: String,
|
|
||||||
val amount: BigDecimal,
|
|
||||||
val goalId: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class UpdateAccountDTO(
|
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
val type: AccountType,
|
|
||||||
val currencyCode: String,
|
|
||||||
val amount: BigDecimal,
|
|
||||||
val goalId: String? = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package space.luminic.finance.dtos
|
|
||||||
|
|
||||||
import space.luminic.finance.models.Budget
|
|
||||||
import space.luminic.finance.models.Category
|
|
||||||
import space.luminic.finance.models.Transaction
|
|
||||||
import space.luminic.finance.models.Transaction.TransactionKind
|
|
||||||
import space.luminic.finance.models.Transaction.TransactionType
|
|
||||||
import space.luminic.finance.models.User
|
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
data class BudgetDTO(
|
|
||||||
val id: String?,
|
|
||||||
val type: Budget.BudgetType,
|
|
||||||
var name: String,
|
|
||||||
var description: String? = null,
|
|
||||||
var dateFrom: LocalDate,
|
|
||||||
var dateTo: LocalDate,
|
|
||||||
val isActive: Boolean,
|
|
||||||
val createdBy: UserDTO?,
|
|
||||||
val createdAt: Instant,
|
|
||||||
var updatedBy: UserDTO?,
|
|
||||||
var updatedAt: Instant,
|
|
||||||
) {
|
|
||||||
|
|
||||||
data class BudgetShortInfoDTO(
|
|
||||||
val id: String,
|
|
||||||
val type: Budget.BudgetType,
|
|
||||||
val name: String,
|
|
||||||
val description: String?,
|
|
||||||
val dateFrom: LocalDate,
|
|
||||||
val dateTo: LocalDate,
|
|
||||||
val createdBy: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class BudgetCategoryDto(
|
|
||||||
val category: CategoryDTO,
|
|
||||||
val limit: BigDecimal,
|
|
||||||
val totalPlannedAmount: BigDecimal,
|
|
||||||
val totalSpendingAmount: BigDecimal
|
|
||||||
)
|
|
||||||
|
|
||||||
data class CreateBudgetDTO(
|
|
||||||
val name: String,
|
|
||||||
val description: String? = null,
|
|
||||||
val dateFrom: LocalDate,
|
|
||||||
val dateTo: LocalDate
|
|
||||||
)
|
|
||||||
|
|
||||||
data class UpdateBudgetDTO(
|
|
||||||
val id: String,
|
|
||||||
val name: String? = null,
|
|
||||||
val description: String? = null,
|
|
||||||
val dateFrom: LocalDate? = null,
|
|
||||||
val dateTo: LocalDate? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class BudgetTransactionsDTO(
|
|
||||||
val categories: List<BudgetCategoryDto>,
|
|
||||||
val transactions: List<TransactionDTO>,
|
|
||||||
val plannedIncomeTransactions: List<TransactionDTO>,
|
|
||||||
val plannedExpenseTransactions: List<TransactionDTO>,
|
|
||||||
val instantIncomeTransactions: List<TransactionDTO>,
|
|
||||||
val instantExpenseTransactions: List<TransactionDTO>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -4,26 +4,29 @@ import space.luminic.finance.models.Category.CategoryType
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class CategoryDTO(
|
data class CategoryDTO(
|
||||||
val id: String,
|
val id: Int,
|
||||||
val type: CategoryType,
|
val type: CategoryType,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val description: String,
|
||||||
val icon: String,
|
val icon: String,
|
||||||
val createdBy: UserDTO? = null,
|
val createdBy: UserDTO? = null,
|
||||||
val createdAt: Instant,
|
val createdAt: Instant? = null,
|
||||||
val updatedBy: UserDTO? = null,
|
val updatedBy: UserDTO? = null,
|
||||||
val updatedAt: Instant,
|
val updatedAt: Instant? = null,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
data class CreateCategoryDTO(
|
data class CreateCategoryDTO(
|
||||||
val name: String,
|
|
||||||
val type: CategoryType,
|
val type: CategoryType,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
val icon: String,
|
val icon: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class UpdateCategoryDTO(
|
data class UpdateCategoryDTO(
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
val type: CategoryType,
|
val type: CategoryType,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
val icon: String,
|
val icon: String,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,39 @@
|
|||||||
package space.luminic.finance.dtos
|
package space.luminic.finance.dtos
|
||||||
|
|
||||||
|
import space.luminic.finance.models.Goal
|
||||||
import space.luminic.finance.models.Goal.GoalType
|
import space.luminic.finance.models.Goal.GoalType
|
||||||
|
import space.luminic.finance.models.Transaction
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
data class GoalDTO(
|
data class GoalDTO(
|
||||||
val id: String,
|
val id: Int,
|
||||||
val type: GoalType,
|
val type: GoalType,
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
val amount: BigDecimal,
|
val amount: BigDecimal,
|
||||||
val date: LocalDate,
|
val date: LocalDate,
|
||||||
|
val components: List<Goal.GoalComponent>,
|
||||||
|
val transactions: List<Transaction>,
|
||||||
val createdBy: UserDTO,
|
val createdBy: UserDTO,
|
||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
val updatedBy: UserDTO,
|
val updatedBy: UserDTO? = null,
|
||||||
val updatedAt: Instant,
|
val updatedAt: Instant? = null,
|
||||||
) {
|
) {
|
||||||
|
data class CreateGoalDTO(
|
||||||
|
val type: GoalType,
|
||||||
|
val name: String,
|
||||||
|
val description: String?,
|
||||||
|
val amount: BigDecimal,
|
||||||
|
val date: LocalDate
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UpdateGoalDTO(
|
||||||
|
val type: GoalType,
|
||||||
|
val name: String,
|
||||||
|
val description: String?,
|
||||||
|
val amount: BigDecimal,
|
||||||
|
val date: LocalDate
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package space.luminic.finance.dtos
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
data class RecurrentOperationDTO (
|
||||||
|
val id: Int,
|
||||||
|
val category: CategoryDTO,
|
||||||
|
val name: String,
|
||||||
|
val amount: BigDecimal,
|
||||||
|
val date: Int,
|
||||||
|
val createdBy: UserDTO? = null,
|
||||||
|
val createdAt: Instant
|
||||||
|
) {
|
||||||
|
|
||||||
|
data class CreateRecurrentOperationDTO (
|
||||||
|
val categoryId: Int,
|
||||||
|
val name: String,
|
||||||
|
val amount: BigDecimal,
|
||||||
|
val date: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UpdateRecurrentOperationDTO (
|
||||||
|
val categoryId: Int,
|
||||||
|
val name: String,
|
||||||
|
val amount: BigDecimal,
|
||||||
|
val date: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,15 +4,29 @@ import space.luminic.finance.models.User
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class SpaceDTO(
|
data class SpaceDTO(
|
||||||
val id: String? = null,
|
val id: Int? = null,
|
||||||
val name: String,
|
val name: String,
|
||||||
val owner: UserDTO,
|
val owner: UserDTO,
|
||||||
val participants: List<UserDTO> = emptyList(),
|
val participants: Set<UserDTO> = emptySet(),
|
||||||
val createdBy: UserDTO? = null,
|
val createdBy: UserDTO? = null,
|
||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
var updatedBy: UserDTO? = null,
|
var updatedBy: UserDTO? = null,
|
||||||
var updatedAt: Instant,
|
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(
|
data class CreateSpaceDTO(
|
||||||
val name: String,
|
val name: String,
|
||||||
val createBasicCategories: Boolean = true,
|
val createBasicCategories: Boolean = true,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package space.luminic.finance.dtos
|
||||||
|
|
||||||
|
data class SubscriptionDTO (
|
||||||
|
val endpoint: String,
|
||||||
|
val keys: Map<String, String>
|
||||||
|
)
|
||||||
@@ -1,52 +1,44 @@
|
|||||||
package space.luminic.finance.dtos
|
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.TransactionKind
|
||||||
import space.luminic.finance.models.Transaction.TransactionType
|
import space.luminic.finance.models.Transaction.TransactionType
|
||||||
import space.luminic.finance.models.User
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.time.Instant
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
|
||||||
data class TransactionDTO(
|
data class TransactionDTO(
|
||||||
val id: String? = null,
|
val id: Int? = null,
|
||||||
var parentId: String? = null,
|
var parentId: Int? = null,
|
||||||
val type: TransactionType = TransactionType.EXPENSE,
|
val type: TransactionType = TransactionType.EXPENSE,
|
||||||
val kind: TransactionKind = TransactionKind.INSTANT,
|
val kind: TransactionKind = TransactionKind.INSTANT,
|
||||||
val categoryId: String,
|
|
||||||
val category: CategoryDTO? = null,
|
val category: CategoryDTO? = null,
|
||||||
val comment: String,
|
val comment: String,
|
||||||
val amount: BigDecimal,
|
val amount: BigDecimal,
|
||||||
val fees: BigDecimal = BigDecimal.ZERO,
|
val fees: BigDecimal = BigDecimal.ZERO,
|
||||||
val fromAccount: AccountDTO? = null,
|
val date: LocalDate,
|
||||||
val toAccount: AccountDTO? = null,
|
val isDone: Boolean,
|
||||||
val date: Instant,
|
val createdBy: UserDTO,
|
||||||
val createdBy: String? = null,
|
val updatedBy: UserDTO? = null,
|
||||||
val updatedBy: String? = null,
|
|
||||||
) {
|
) {
|
||||||
data class CreateTransactionDTO(
|
data class CreateTransactionDTO(
|
||||||
val type: TransactionType = TransactionType.EXPENSE,
|
val type: TransactionType = TransactionType.EXPENSE,
|
||||||
val kind: TransactionKind = TransactionKind.INSTANT,
|
val kind: TransactionKind = TransactionKind.INSTANT,
|
||||||
val categoryId: String,
|
val categoryId: Int? = null,
|
||||||
val comment: String,
|
val comment: String,
|
||||||
val amount: BigDecimal,
|
val amount: BigDecimal,
|
||||||
val fees: BigDecimal = BigDecimal.ZERO,
|
val fees: BigDecimal = BigDecimal.ZERO,
|
||||||
val fromAccountId: String,
|
val date: LocalDate,
|
||||||
val toAccountId: String? = null,
|
val recurrentId: Int? = null
|
||||||
val date: Instant
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data class UpdateTransactionDTO(
|
data class UpdateTransactionDTO(
|
||||||
val id: String,
|
|
||||||
val type: TransactionType = TransactionType.EXPENSE,
|
val type: TransactionType = TransactionType.EXPENSE,
|
||||||
val kind: TransactionKind = TransactionKind.INSTANT,
|
val kind: TransactionKind = TransactionKind.INSTANT,
|
||||||
val category: String,
|
val categoryId: Int? = null,
|
||||||
val comment: String,
|
val comment: String,
|
||||||
val amount: BigDecimal,
|
val amount: BigDecimal,
|
||||||
val fees: BigDecimal = BigDecimal.ZERO,
|
val fees: BigDecimal = BigDecimal.ZERO,
|
||||||
val fromAccountId: String,
|
val isDone: Boolean,
|
||||||
val toAccountId: String? = null,
|
val date: LocalDate
|
||||||
val date: Instant
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package space.luminic.finance.dtos
|
package space.luminic.finance.dtos
|
||||||
|
|
||||||
import space.luminic.finance.models.User
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
data class UserDTO(
|
data class UserDTO(
|
||||||
var id: String,
|
var id: Int,
|
||||||
val username: String,
|
val username: String,
|
||||||
var firstName: String,
|
var firstName: String,
|
||||||
var tgId: String? = null,
|
var tgId: Long? = null,
|
||||||
var tgUserName: String? = null,
|
var tgUserName: String? = null,
|
||||||
|
var photoUrl: String? = null,
|
||||||
var roles: List<String>
|
var roles: List<String>
|
||||||
|
|
||||||
) {
|
) {
|
||||||
@@ -24,6 +25,29 @@ data class UserDTO (
|
|||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class TelegramAuthDTO(
|
||||||
|
val id: Long?,
|
||||||
|
val first_name: String?,
|
||||||
|
val last_name: String?,
|
||||||
|
val username: String?,
|
||||||
|
val photo_url: String?,
|
||||||
|
val auth_date: Long?,
|
||||||
|
val hash: String,
|
||||||
|
val initData: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class TelegramUserData(
|
||||||
|
val id: Long,
|
||||||
|
val first_name: String,
|
||||||
|
val last_name: String? = null,
|
||||||
|
val username: String? = null,
|
||||||
|
val photo_url: String? = null,
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package space.luminic.finance.mappers
|
|
||||||
|
|
||||||
import space.luminic.finance.dtos.AccountDTO
|
|
||||||
import space.luminic.finance.mappers.CurrencyMapper.toDto
|
|
||||||
import space.luminic.finance.mappers.GoalMapper.toDto
|
|
||||||
import space.luminic.finance.models.Account
|
|
||||||
|
|
||||||
object AccountMapper {
|
|
||||||
|
|
||||||
fun Account.toDto(): AccountDTO {
|
|
||||||
return AccountDTO(
|
|
||||||
id = this.id ?: throw IllegalStateException("Account ID must not be null"),
|
|
||||||
name = this.name,
|
|
||||||
type = this.type,
|
|
||||||
currencyCode = this.currencyCode,
|
|
||||||
currency = this.currency?.toDto(),
|
|
||||||
balance = this.amount,
|
|
||||||
goal = this.goal?.toDto(),
|
|
||||||
createdBy = this.createdBy?.username,
|
|
||||||
createdAt = this.createdAt,
|
|
||||||
updatedBy = this.updatedBy?.username,
|
|
||||||
updatedAt = this.updatedAt,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package space.luminic.finance.mappers
|
|
||||||
|
|
||||||
|
|
||||||
import space.luminic.finance.dtos.BudgetDTO
|
|
||||||
import space.luminic.finance.dtos.TransactionDTO
|
|
||||||
import space.luminic.finance.mappers.CategoryMapper.toDto
|
|
||||||
import space.luminic.finance.mappers.TransactionMapper.toDto
|
|
||||||
import space.luminic.finance.mappers.UserMapper.toDto
|
|
||||||
import space.luminic.finance.models.Budget
|
|
||||||
import space.luminic.finance.models.Transaction
|
|
||||||
import space.luminic.finance.models.Transaction.TransactionKind
|
|
||||||
import space.luminic.finance.models.Transaction.TransactionType
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
object BudgetMapper {
|
|
||||||
|
|
||||||
|
|
||||||
fun Budget.toDto(): BudgetDTO {
|
|
||||||
val isActive = this.dateTo.isBefore(LocalDate.now())
|
|
||||||
|
|
||||||
return BudgetDTO(
|
|
||||||
id = this.id,
|
|
||||||
type = this.type,
|
|
||||||
name = this.name,
|
|
||||||
description = this.description,
|
|
||||||
dateFrom = this.dateFrom,
|
|
||||||
dateTo = this.dateTo,
|
|
||||||
isActive = isActive,
|
|
||||||
createdBy = this.createdBy?.toDto(),
|
|
||||||
createdAt = this.createdAt?: throw IllegalArgumentException("created at is null"),
|
|
||||||
updatedBy = this.updatedBy?.toDto(),
|
|
||||||
updatedAt = this.updatedAt?: throw IllegalArgumentException("updated at is null"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Budget.toShortDto(): BudgetDTO.BudgetShortInfoDTO = BudgetDTO.BudgetShortInfoDTO(
|
|
||||||
id = this.id!!,
|
|
||||||
type = this.type,
|
|
||||||
name = this.name,
|
|
||||||
description = this.description,
|
|
||||||
dateFrom = this.dateFrom,
|
|
||||||
dateTo = this.dateTo,
|
|
||||||
createdBy = this.createdBy?.username ?: throw IllegalArgumentException("created by is null"),
|
|
||||||
)
|
|
||||||
|
|
||||||
fun List<Transaction>.toDto(): BudgetDTO.BudgetTransactionsDTO {
|
|
||||||
|
|
||||||
val planningSpending = this.filter {
|
|
||||||
it.type == TransactionType.EXPENSE && it.kind == TransactionKind.PLANNING
|
|
||||||
}
|
|
||||||
|
|
||||||
val planningIncomes = this.filter {
|
|
||||||
it.type == TransactionType.INCOME && it.kind == TransactionKind.PLANNING
|
|
||||||
}
|
|
||||||
|
|
||||||
val instantSpendingTransactions = this.filter {
|
|
||||||
it.type == TransactionType.EXPENSE && it.kind == TransactionKind.INSTANT && it.parentId == null }
|
|
||||||
|
|
||||||
val instantIncomeTransactions = this.filter {
|
|
||||||
it.type == TransactionType.INCOME && it.kind == TransactionKind.INSTANT && it.parentId == null
|
|
||||||
}
|
|
||||||
val totalPlannedIncome = planningIncomes.sumOf { it.amount }
|
|
||||||
val totalPlannedSpending = planningSpending.sumOf { it.amount }
|
|
||||||
|
|
||||||
val categoriesWithPlannedAmounts = this.categories.map { cat ->
|
|
||||||
val totalPlannedAmount = planningSpending
|
|
||||||
.filter { it.id == cat.categoryId }
|
|
||||||
.sumOf { it.amount }
|
|
||||||
val totalInstantAmount = instantSpendingTransactions
|
|
||||||
.filter { it.id == cat.categoryId }
|
|
||||||
.sumOf { it.amount }
|
|
||||||
BudgetDTO.BudgetCategoryDto(cat.category?.toDto() ?: throw java.lang.IllegalArgumentException("category is not provided"), cat.limit, totalPlannedAmount, totalInstantAmount)
|
|
||||||
}
|
|
||||||
return BudgetDTO.BudgetTransactionsDTO(
|
|
||||||
categories = categoriesWithPlannedAmounts,
|
|
||||||
transactions = this.map{it.toDto()},
|
|
||||||
plannedIncomeTransactions = this.filter { it.kind == TransactionKind.PLANNING && it.type == TransactionType.INCOME }.map{it.toDto()},
|
|
||||||
plannedExpenseTransactions = this.filter { it.kind == TransactionKind.PLANNING && it.type == TransactionType.EXPENSE }.map{it.toDto()},
|
|
||||||
instantIncomeTransactions = this.filter { it.kind == TransactionKind.INSTANT && it.type == TransactionType.INCOME }.map{it.toDto()},
|
|
||||||
instantExpenseTransactions = this.filter { it.kind == TransactionKind.INSTANT && it.type == TransactionType.EXPENSE }.map{it.toDto()},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -11,11 +11,12 @@ object CategoryMapper {
|
|||||||
id = this.id ?: throw IllegalArgumentException("category id is not set"),
|
id = this.id ?: throw IllegalArgumentException("category id is not set"),
|
||||||
type = this.type,
|
type = this.type,
|
||||||
name = this.name,
|
name = this.name,
|
||||||
|
description = this.description,
|
||||||
icon = this.icon,
|
icon = this.icon,
|
||||||
createdBy = this.createdBy?.toDto(),
|
createdBy = this.createdBy?.toDto(),
|
||||||
createdAt = this.createdAt ?: throw IllegalArgumentException("created at is not set"),
|
createdAt = this.createdAt,
|
||||||
updatedBy = this.updatedBy?.toDto(),
|
updatedBy = this.updatedBy?.toDto(),
|
||||||
updatedAt = this.updatedAt ?: throw IllegalArgumentException("updated at is not set"),
|
updatedAt = this.updatedAt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ object GoalMapper {
|
|||||||
id = this.id ?: throw IllegalArgumentException("Goal id is not provided"),
|
id = this.id ?: throw IllegalArgumentException("Goal id is not provided"),
|
||||||
type = this.type,
|
type = this.type,
|
||||||
name = this.name,
|
name = this.name,
|
||||||
amount = this.goalAmount,
|
amount = this.amount,
|
||||||
date = this.goalDate,
|
date = this.untilDate,
|
||||||
|
components = this.components,
|
||||||
|
transactions = this.transactions,
|
||||||
createdBy = (this.createdBy ?: throw IllegalArgumentException("created by not provided")).toDto(),
|
createdBy = (this.createdBy ?: throw IllegalArgumentException("created by not provided")).toDto(),
|
||||||
createdAt = this.createdAt ?: throw IllegalArgumentException("created at not provided"),
|
createdAt = this.createdAt ?: throw IllegalArgumentException("created at not provided"),
|
||||||
updatedBy = this.updatedBy?.toDto() ?: throw IllegalArgumentException("updated by not provided"),
|
updatedBy = this.updatedBy?.toDto() ,
|
||||||
updatedAt = this.updatedAt ?: throw IllegalArgumentException("updatedAt not provided"),
|
updatedAt = this.updatedAt
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package space.luminic.finance.mappers
|
||||||
|
|
||||||
|
import space.luminic.finance.dtos.RecurrentOperationDTO
|
||||||
|
import space.luminic.finance.mappers.CategoryMapper.toDto
|
||||||
|
import space.luminic.finance.mappers.UserMapper.toDto
|
||||||
|
import space.luminic.finance.models.RecurrentOperation
|
||||||
|
|
||||||
|
object RecurrentOperationMapper {
|
||||||
|
|
||||||
|
fun RecurrentOperation.toDTO(): RecurrentOperationDTO {
|
||||||
|
return RecurrentOperationDTO(
|
||||||
|
id = this.id ?: throw IllegalArgumentException("id is null"),
|
||||||
|
category = this.category.toDto(),
|
||||||
|
name = this.name,
|
||||||
|
amount = this.amount,
|
||||||
|
date = this.date,
|
||||||
|
createdBy = this.createdBy?.toDto(),
|
||||||
|
createdAt = this.createdAt ?: throw NullPointerException("created at"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,8 @@ object SpaceMapper {
|
|||||||
fun Space.toDto() = SpaceDTO(
|
fun Space.toDto() = SpaceDTO(
|
||||||
id = this.id,
|
id = this.id,
|
||||||
name = this.name,
|
name = this.name,
|
||||||
owner = this.owner?.toDto() ?: throw IllegalArgumentException("Owner is not provided"),
|
owner = this.owner.toDto(),
|
||||||
participants = this.participants?.map { it.toDto() } ?: emptyList(),
|
participants = this.participants.map { it.toDto() }.toSet(),
|
||||||
createdBy = this.createdBy?.toDto(),
|
createdBy = this.createdBy?.toDto(),
|
||||||
createdAt = this.createdAt ?: throw IllegalArgumentException("createdAt is not provided"),
|
createdAt = this.createdAt ?: throw IllegalArgumentException("createdAt is not provided"),
|
||||||
updatedBy = this.updatedBy?.toDto(),
|
updatedBy = this.updatedBy?.toDto(),
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package space.luminic.finance.mappers
|
package space.luminic.finance.mappers
|
||||||
|
|
||||||
import space.luminic.finance.dtos.TransactionDTO
|
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.CategoryMapper.toDto
|
||||||
import space.luminic.finance.mappers.UserMapper.toDto
|
import space.luminic.finance.mappers.UserMapper.toDto
|
||||||
import space.luminic.finance.models.Transaction
|
import space.luminic.finance.models.Transaction
|
||||||
@@ -11,25 +8,16 @@ import space.luminic.finance.models.Transaction
|
|||||||
object TransactionMapper {
|
object TransactionMapper {
|
||||||
fun Transaction.toDto() = TransactionDTO(
|
fun Transaction.toDto() = TransactionDTO(
|
||||||
id = this.id,
|
id = this.id,
|
||||||
parentId = this.parentId,
|
parentId = this.parent?.id,
|
||||||
type = this.type,
|
type = this.type,
|
||||||
kind = this.kind,
|
kind = this.kind,
|
||||||
categoryId = this.categoryId,
|
|
||||||
category = this.category?.toDto(),
|
category = this.category?.toDto(),
|
||||||
comment = this.comment,
|
comment = this.comment,
|
||||||
amount = this.amount,
|
amount = this.amount,
|
||||||
fees = this.fees,
|
fees = this.fees,
|
||||||
fromAccount = this.fromAccount?.toDto(),
|
|
||||||
toAccount = this.toAccount?.toDto(),
|
|
||||||
date = this.date,
|
date = this.date,
|
||||||
createdBy = this.createdBy?.username,
|
isDone = this.isDone,
|
||||||
updatedBy = this.updatedBy?.username,
|
createdBy = this.createdBy?.toDto() ?: throw IllegalStateException("created by null"),
|
||||||
|
updatedBy = this.updatedBy?.toDto()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package space.luminic.finance.mappers
|
package space.luminic.finance.mappers
|
||||||
|
|
||||||
import space.luminic.finance.dtos.UserDTO
|
import space.luminic.finance.dtos.UserDTO
|
||||||
|
import space.luminic.finance.dtos.UserDTO.TelegramAuthDTO
|
||||||
import space.luminic.finance.models.User
|
import space.luminic.finance.models.User
|
||||||
|
|
||||||
object UserMapper {
|
object UserMapper {
|
||||||
@@ -11,6 +12,18 @@ object UserMapper {
|
|||||||
firstName = this.firstName,
|
firstName = this.firstName,
|
||||||
tgId = this.tgId,
|
tgId = this.tgId,
|
||||||
tgUserName = this.tgUserName,
|
tgUserName = this.tgUserName,
|
||||||
|
photoUrl = this.photoUrl,
|
||||||
roles = this.roles
|
roles = this.roles
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun TelegramAuthDTO.toTelegramMap(): Map<String, String> =
|
||||||
|
mapOf(
|
||||||
|
"id" to id.toString(),
|
||||||
|
"first_name" to (first_name ?: ""),
|
||||||
|
"last_name" to (last_name ?: ""),
|
||||||
|
"username" to (username ?: ""),
|
||||||
|
"photo_url" to (photo_url ?: ""),
|
||||||
|
"auth_date" to auth_date.toString(),
|
||||||
|
"hash" to hash
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package space.luminic.finance.models
|
|
||||||
|
|
||||||
import org.springframework.data.annotation.CreatedDate
|
|
||||||
import org.springframework.data.annotation.Id
|
|
||||||
import org.springframework.data.annotation.LastModifiedDate
|
|
||||||
import org.springframework.data.annotation.ReadOnlyProperty
|
|
||||||
import org.springframework.data.relational.core.mapping.Column
|
|
||||||
import org.springframework.data.relational.core.mapping.Table
|
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
@Table(name = "accounts")
|
|
||||||
data class Account (
|
|
||||||
@Id val id: Int? = null,
|
|
||||||
@Column("space_id")
|
|
||||||
val spaceId: Int,
|
|
||||||
val name: String,
|
|
||||||
val type: AccountType = AccountType.COLLECTING,
|
|
||||||
@Column("currency_code")
|
|
||||||
val currencyCode: String,
|
|
||||||
|
|
||||||
var amount: BigDecimal,
|
|
||||||
@Column("goal_id")
|
|
||||||
var goalId: Int? = null,
|
|
||||||
@Column("is_deleted")
|
|
||||||
var isDeleted: Boolean = false,
|
|
||||||
@Column("created_by")
|
|
||||||
val createdById: String? = null,
|
|
||||||
@CreatedDate
|
|
||||||
@Column("created_at")
|
|
||||||
val createdAt: Instant? = null,
|
|
||||||
@Column("updated_by")
|
|
||||||
val updatedById: String? = null,
|
|
||||||
@LastModifiedDate
|
|
||||||
@Column("updated_at")
|
|
||||||
val updatedAt: Instant? = null,
|
|
||||||
|
|
||||||
) {
|
|
||||||
@ReadOnlyProperty var goal: Goal? = null
|
|
||||||
@ReadOnlyProperty var currency: Currency? = null
|
|
||||||
@ReadOnlyProperty var transactions: List<Transaction>? = null
|
|
||||||
@ReadOnlyProperty var createdBy: User? = null
|
|
||||||
@ReadOnlyProperty var updatedBy: User? = null
|
|
||||||
|
|
||||||
|
|
||||||
enum class AccountType(displayName: String) {
|
|
||||||
SALARY("Зарплатный"),
|
|
||||||
COLLECTING("Накопительный"),
|
|
||||||
LOANS("Долговой"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package space.luminic.finance.models
|
|
||||||
|
|
||||||
import org.springframework.data.annotation.CreatedBy
|
|
||||||
import org.springframework.data.annotation.CreatedDate
|
|
||||||
import org.springframework.data.annotation.Id
|
|
||||||
import org.springframework.data.annotation.LastModifiedBy
|
|
||||||
import org.springframework.data.annotation.LastModifiedDate
|
|
||||||
import org.springframework.data.annotation.ReadOnlyProperty
|
|
||||||
import org.springframework.data.relational.core.mapping.Column
|
|
||||||
import org.springframework.data.relational.core.mapping.Table
|
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
|
|
||||||
@Table( "budgets")
|
|
||||||
data class Budget(
|
|
||||||
@Id var id: Int? = null,
|
|
||||||
@Column("space_id")
|
|
||||||
val spaceId: Int,
|
|
||||||
val type: BudgetType = BudgetType.SPECIAL,
|
|
||||||
var name: String,
|
|
||||||
var description: String? = null,
|
|
||||||
@Column("date_from")
|
|
||||||
var dateFrom: LocalDate,
|
|
||||||
@Column("date_to")
|
|
||||||
var dateTo: LocalDate,
|
|
||||||
val transactions: List<Transaction> = listOf(),
|
|
||||||
val categories: List<BudgetCategory> = listOf(),
|
|
||||||
@Column("is_deleted")
|
|
||||||
var isDeleted: Boolean = false,
|
|
||||||
@CreatedBy
|
|
||||||
@Column("created_by")
|
|
||||||
val createdById: Int? = null,
|
|
||||||
@CreatedDate
|
|
||||||
@Column("created_at")
|
|
||||||
var createdAt: Instant? = null,
|
|
||||||
@LastModifiedBy
|
|
||||||
@Column("updated_by")
|
|
||||||
var updatedById: Int? = null,
|
|
||||||
@LastModifiedDate
|
|
||||||
@Column("updated_at")
|
|
||||||
var updatedAt: Instant? = null,
|
|
||||||
) {
|
|
||||||
|
|
||||||
@ReadOnlyProperty var createdBy: User? = null
|
|
||||||
@ReadOnlyProperty var updatedBy: User? = null
|
|
||||||
data class BudgetCategory(
|
|
||||||
val categoryId: String,
|
|
||||||
val limit: BigDecimal
|
|
||||||
) {
|
|
||||||
@ReadOnlyProperty var category: Category? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class BudgetType(val displayName: String) {
|
|
||||||
MONTHLY("Месячный"),
|
|
||||||
SPECIAL("Специальный")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2,35 +2,24 @@ package space.luminic.finance.models
|
|||||||
|
|
||||||
import org.springframework.data.annotation.CreatedBy
|
import org.springframework.data.annotation.CreatedBy
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
|
||||||
import org.springframework.data.annotation.LastModifiedBy
|
import org.springframework.data.annotation.LastModifiedBy
|
||||||
import org.springframework.data.annotation.LastModifiedDate
|
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
|
import java.time.Instant
|
||||||
|
|
||||||
|
|
||||||
@Document(collection = "categories")
|
|
||||||
data class Category(
|
data class Category(
|
||||||
@Id val id: String? = null,
|
var id: Int? = null,
|
||||||
val spaceId: String,
|
val space: Space? = null,
|
||||||
val type: CategoryType,
|
val type: CategoryType,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val description: String,
|
||||||
val icon: String,
|
val icon: String,
|
||||||
var isDeleted: Boolean = false,
|
var isDeleted: Boolean = false,
|
||||||
|
|
||||||
@CreatedBy
|
@CreatedBy var createdBy: User? = null,
|
||||||
val createdById: String? = null,
|
@CreatedDate var createdAt: Instant? = null,
|
||||||
@CreatedDate
|
@LastModifiedBy var updatedBy: User? = null,
|
||||||
val createdAt: Instant? = null,
|
@LastModifiedDate var updatedAt: Instant? = null,
|
||||||
@LastModifiedBy
|
|
||||||
val updatedById: String? = null,
|
|
||||||
@LastModifiedDate
|
|
||||||
val updatedAt: Instant? = null,
|
|
||||||
) {
|
) {
|
||||||
@ReadOnlyProperty var createdBy: User? = null
|
|
||||||
@ReadOnlyProperty var updatedBy: User? = null
|
|
||||||
|
|
||||||
enum class CategoryType(val displayName: String) {
|
enum class CategoryType(val displayName: String) {
|
||||||
INCOME("Поступления"),
|
INCOME("Поступления"),
|
||||||
@@ -38,9 +27,8 @@ data class Category(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Document(collection = "categories_etalon")
|
|
||||||
data class CategoryEtalon(
|
data class CategoryEtalon(
|
||||||
@Id val id: String? = null,
|
var id: Int? = null,
|
||||||
val type: CategoryType,
|
val type: CategoryType,
|
||||||
val name: String,
|
val name: String,
|
||||||
val icon: String
|
val icon: String
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package space.luminic.finance.models
|
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(
|
data class Currency(
|
||||||
@Id val code: String,
|
val code: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val symbol: String
|
val symbol: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
package space.luminic.finance.models
|
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.math.BigDecimal
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
@Document(collection = "currency_rates")
|
|
||||||
data class CurrencyRate(
|
data class CurrencyRate(
|
||||||
@Id val id: String? = null,
|
var id: Int? = null,
|
||||||
val currencyCode: String,
|
val currency: Currency,
|
||||||
val rate: BigDecimal,
|
val rate: BigDecimal,
|
||||||
val date: LocalDate
|
val date: LocalDate
|
||||||
)
|
)
|
||||||
{
|
|
||||||
@ReadOnlyProperty var currency: Currency? = null
|
|
||||||
}
|
|
||||||
@@ -2,35 +2,43 @@ package space.luminic.finance.models
|
|||||||
|
|
||||||
import org.springframework.data.annotation.CreatedBy
|
import org.springframework.data.annotation.CreatedBy
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
|
||||||
import org.springframework.data.annotation.LastModifiedBy
|
import org.springframework.data.annotation.LastModifiedBy
|
||||||
import org.springframework.data.annotation.LastModifiedDate
|
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.math.BigDecimal
|
||||||
import java.text.Bidi
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
|
||||||
@Document(collection = "goals")
|
|
||||||
data class Goal(
|
data class Goal(
|
||||||
@Id val id: String? = null,
|
var id: Int? = null,
|
||||||
val spaceId: String,
|
val space: Space? = null,
|
||||||
val type: GoalType,
|
val type: GoalType,
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
val goalAmount: BigDecimal,
|
val amount: BigDecimal,
|
||||||
val goalDate: LocalDate,
|
val components: List<GoalComponent> = emptyList(),
|
||||||
@CreatedBy val createdById: String,
|
val transactions: List<Transaction> = emptyList(),
|
||||||
@Transient val createdBy: User? = null,
|
val untilDate: LocalDate,
|
||||||
@CreatedDate val createdAt: Instant? = null,
|
@CreatedBy var createdBy: User? = null,
|
||||||
@LastModifiedBy val updatedById: String,
|
|
||||||
@Transient val updatedBy: User? = null,
|
@CreatedDate var createdAt: Instant? = null,
|
||||||
@LastModifiedDate val updatedAt: 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) {
|
enum class GoalType(val displayName: String, val icon: String) {
|
||||||
AUTO("Авто", "🏎️"),
|
AUTO("Авто", "🏎️"),
|
||||||
VACATION("Отпуск", "🏖️"),
|
VACATION("Отпуск", "🏖️"),
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package space.luminic.finance.models
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
data class RecurrentOperation(
|
||||||
|
val id: Int? = null,
|
||||||
|
val space: Space,
|
||||||
|
val category: Category,
|
||||||
|
val name: String,
|
||||||
|
val amount: BigDecimal,
|
||||||
|
val date: Int,
|
||||||
|
val createdBy: User? = null,
|
||||||
|
val createdAt: Instant? = null,
|
||||||
|
val updatedBy: User? = null,
|
||||||
|
val updatedAt: Instant? = null,
|
||||||
|
)
|
||||||
@@ -2,35 +2,21 @@ package space.luminic.finance.models
|
|||||||
|
|
||||||
import org.springframework.data.annotation.CreatedBy
|
import org.springframework.data.annotation.CreatedBy
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
|
||||||
import org.springframework.data.annotation.LastModifiedBy
|
import org.springframework.data.annotation.LastModifiedBy
|
||||||
import org.springframework.data.annotation.LastModifiedDate
|
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
|
import java.time.Instant
|
||||||
|
|
||||||
|
|
||||||
@Document(collection = "spaces")
|
|
||||||
data class Space (
|
data class Space (
|
||||||
@Id val id: String? = null,
|
var id: Int? = null,
|
||||||
val name: String,
|
val name: String,
|
||||||
val ownerId: String,
|
val owner: User,
|
||||||
val participantsIds: List<String> = emptyList(),
|
var participants: Set<User>,
|
||||||
var isDeleted: Boolean = false,
|
var isDeleted: Boolean = false,
|
||||||
@CreatedBy val createdById: String? = null,
|
@CreatedBy var createdBy: User? = null,
|
||||||
@CreatedDate val createdAt: Instant? = null,
|
@CreatedDate var createdAt: Instant? = null,
|
||||||
@LastModifiedBy val updatedById: String? = null,
|
@LastModifiedBy var updatedBy: User? = null,
|
||||||
@LastModifiedDate var updatedAt: Instant ? = null,
|
@LastModifiedDate var updatedAt: Instant ? = null,
|
||||||
) {
|
) {
|
||||||
@ReadOnlyProperty var owner: User? = null
|
override fun equals(other: Any?) = this === other || (other is Space && id != null && id == other.id)
|
||||||
|
override fun hashCode() = id?.hashCode() ?: 0
|
||||||
@ReadOnlyProperty var participants: List<User>? = null
|
|
||||||
|
|
||||||
@ReadOnlyProperty var createdBy: User? = null
|
|
||||||
|
|
||||||
@ReadOnlyProperty var updatedBy: User? = null
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
16
src/main/kotlin/space/luminic/finance/models/State.kt
Normal file
16
src/main/kotlin/space/luminic/finance/models/State.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package space.luminic.finance.models
|
||||||
|
|
||||||
|
data class State(
|
||||||
|
val user: User,
|
||||||
|
val state: StateCode = StateCode.AWAIT_SPACE_SELECT,
|
||||||
|
val data: Map<String, String> = mapOf()
|
||||||
|
) {
|
||||||
|
enum class StateCode {
|
||||||
|
AWAIT_SPACE_SELECT,
|
||||||
|
SPACE_SELECTED,
|
||||||
|
AWAIT_TRANSACTION,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,23 +1,19 @@
|
|||||||
package space.luminic.finance.models
|
package space.luminic.finance.models
|
||||||
|
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.mongodb.core.mapping.DBRef
|
import org.springframework.data.annotation.LastModifiedDate
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Document(collection = "subscriptions")
|
|
||||||
data class Subscription(
|
data class Subscription(
|
||||||
@Id val id: String? = null,
|
var id: Int? = null,
|
||||||
@DBRef val user: User? = null,
|
val user: User,
|
||||||
val endpoint: String,
|
val endpoint: String,
|
||||||
val auth: String,
|
val auth: String,
|
||||||
val p256dh: String,
|
val p256dh: String,
|
||||||
var isActive: Boolean,
|
var isActive: Boolean,
|
||||||
val createdAt: Instant = Instant.now(),
|
@CreatedDate val createdAt: Instant? = null,
|
||||||
|
@LastModifiedDate val updatedAt: Instant? = null,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
data class SubscriptionDTO (
|
|
||||||
val endpoint: String,
|
|
||||||
val keys: Map<String, String>
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
package space.luminic.finance.models
|
package space.luminic.finance.models
|
||||||
|
|
||||||
import org.springframework.data.annotation.Id
|
import java.time.Instant
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
@Document(collection = "tokens")
|
|
||||||
data class Token(
|
data class Token(
|
||||||
@Id
|
var id: Int? = null,
|
||||||
val id: String? = null,
|
|
||||||
val token: String,
|
val token: String,
|
||||||
val username: String,
|
val user: User,
|
||||||
val issuedAt: LocalDateTime,
|
val issuedAt: Instant = Instant.now(),
|
||||||
val expiresAt: LocalDateTime,
|
val expiresAt: Instant,
|
||||||
val status: TokenStatus = TokenStatus.ACTIVE
|
val status: TokenStatus = TokenStatus.ACTIVE
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|||||||
@@ -2,49 +2,34 @@ package space.luminic.finance.models
|
|||||||
|
|
||||||
import org.springframework.data.annotation.CreatedBy
|
import org.springframework.data.annotation.CreatedBy
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
|
||||||
import org.springframework.data.annotation.LastModifiedBy
|
import org.springframework.data.annotation.LastModifiedBy
|
||||||
import org.springframework.data.annotation.LastModifiedDate
|
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.math.BigDecimal
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
@Document(collection = "transactions")
|
|
||||||
data class Transaction(
|
data class Transaction(
|
||||||
@Id val id: String? = null,
|
var id: Int? = null,
|
||||||
val spaceId: String,
|
val space: Space? = null,
|
||||||
var parentId: String? = null,
|
var parent: Transaction? = null,
|
||||||
val type: TransactionType = TransactionType.EXPENSE,
|
val type: TransactionType = TransactionType.EXPENSE,
|
||||||
val kind: TransactionKind = TransactionKind.INSTANT,
|
val kind: TransactionKind = TransactionKind.INSTANT,
|
||||||
val categoryId: String,
|
val category: Category? = null,
|
||||||
|
|
||||||
val comment: String,
|
val comment: String,
|
||||||
val amount: BigDecimal,
|
val amount: BigDecimal,
|
||||||
val fees: BigDecimal = BigDecimal.ZERO,
|
val fees: BigDecimal = BigDecimal.ZERO,
|
||||||
val fromAccountId: String,
|
val date: LocalDate = LocalDate.now(),
|
||||||
val toAccountId: String? = null,
|
|
||||||
val date: Instant = Instant.now(),
|
|
||||||
var isDeleted: Boolean = false,
|
var isDeleted: Boolean = false,
|
||||||
@CreatedBy
|
val isDone: Boolean = false,
|
||||||
val createdById: String? = null,
|
@CreatedBy var createdBy: User? = null,
|
||||||
@CreatedDate
|
@CreatedDate var createdAt: Instant? = null,
|
||||||
val createdAt: Instant? = null,
|
@LastModifiedBy var updatedBy: User? = null,
|
||||||
@LastModifiedBy
|
@LastModifiedDate var updatedAt: Instant? = null,
|
||||||
val updatedById: String? = null,
|
val tgChatId: Long? = null,
|
||||||
@LastModifiedDate
|
val tgMessageId: Long? = null,
|
||||||
val updatedAt: Instant? = null,
|
val recurrentId: Int? = 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) {
|
enum class TransactionType(val displayName: String) {
|
||||||
INCOME("Поступления"),
|
INCOME("Поступления"),
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
package space.luminic.finance.models
|
package space.luminic.finance.models
|
||||||
|
|
||||||
|
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
import org.springframework.data.annotation.LastModifiedDate
|
||||||
import org.springframework.data.annotation.Transient
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
@Document("users")
|
|
||||||
data class User(
|
data class User(
|
||||||
@Id
|
var id: Int? = null,
|
||||||
var id: String? = null,
|
|
||||||
val username: String,
|
val username: String,
|
||||||
var firstName: String,
|
var firstName: String,
|
||||||
var tgId: String? = null,
|
var tgId: Long? = null,
|
||||||
var tgUserName: String? = null,
|
var tgUserName: String? = null,
|
||||||
var password: String,
|
val photoUrl: String? = null,
|
||||||
|
var password: String? = null,
|
||||||
var isActive: Boolean = true,
|
var isActive: Boolean = true,
|
||||||
var regDate: LocalDate = LocalDate.now(),
|
var regDate: LocalDate = LocalDate.now(),
|
||||||
val createdAt: LocalDateTime = LocalDateTime.now(),
|
@CreatedDate val createdAt: Instant? = null,
|
||||||
var roles: MutableList<String> = mutableListOf(),
|
@LastModifiedDate var updatedAt: Instant? = null,
|
||||||
|
var roles: List<String> = listOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package space.luminic.finance.repos
|
|
||||||
|
|
||||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
|
||||||
import space.luminic.finance.models.Account
|
|
||||||
|
|
||||||
interface AccountRepo : ReactiveMongoRepository<Account, String> {
|
|
||||||
}
|
|
||||||
9
src/main/kotlin/space/luminic/finance/repos/BotRepo.kt
Normal file
9
src/main/kotlin/space/luminic/finance/repos/BotRepo.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import space.luminic.finance.models.State
|
||||||
|
|
||||||
|
interface BotRepo {
|
||||||
|
fun getState(tgUserId: Long): State?
|
||||||
|
fun setState(userId: Int, stateCode: State.StateCode, stateData: Map<String, String>)
|
||||||
|
fun clearState(userId: Int)
|
||||||
|
}
|
||||||
114
src/main/kotlin/space/luminic/finance/repos/BotRepoImpl.kt
Normal file
114
src/main/kotlin/space/luminic/finance/repos/BotRepoImpl.kt
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.ResultSetExtractor
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import space.luminic.finance.models.State
|
||||||
|
import space.luminic.finance.models.User
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class BotRepoImpl(
|
||||||
|
private val jdbcTemplate: NamedParameterJdbcTemplate,
|
||||||
|
) : BotRepo {
|
||||||
|
override fun getState(tgUserId: Long): State? {
|
||||||
|
val sql = """
|
||||||
|
select
|
||||||
|
bs.user_id as bs_user_id,
|
||||||
|
u.username as u_username,
|
||||||
|
u.first_name as u_first_name,
|
||||||
|
bs.state_code as bs_state_code,
|
||||||
|
bsd.data_code as bs_data_code,
|
||||||
|
bsd.data_value as bs_data_value
|
||||||
|
from finance.bot_states bs
|
||||||
|
join finance.users u on u.id = bs.user_id
|
||||||
|
left join finance.bot_states_data bsd on bsd.user_id = bs.user_id
|
||||||
|
where u.tg_id = :user_id
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val params = mapOf("user_id" to tgUserId)
|
||||||
|
|
||||||
|
return jdbcTemplate.query(sql, params, ResultSetExtractor { rs ->
|
||||||
|
var user: User? = null
|
||||||
|
var stateCode: State.StateCode? = null
|
||||||
|
val data = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
if (user == null) {
|
||||||
|
user = User(
|
||||||
|
id = rs.getInt("bs_user_id"),
|
||||||
|
username = rs.getString("u_username"),
|
||||||
|
firstName = rs.getString("u_first_name")
|
||||||
|
)
|
||||||
|
stateCode = rs.getString("bs_state_code")?.let { raw ->
|
||||||
|
runCatching { State.StateCode.valueOf(raw) }
|
||||||
|
.getOrElse { State.StateCode.AWAIT_SPACE_SELECT }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val code = rs.getString("bs_data_code")
|
||||||
|
val value = rs.getString("bs_data_value")
|
||||||
|
if (code != null && value != null) {
|
||||||
|
data[code] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user?.let {
|
||||||
|
State(
|
||||||
|
user = it,
|
||||||
|
state = stateCode ?: State.StateCode.AWAIT_SPACE_SELECT,
|
||||||
|
data = data.toMap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setState(
|
||||||
|
userId: Int,
|
||||||
|
stateCode: State.StateCode,
|
||||||
|
stateData: Map<String, String>
|
||||||
|
) {
|
||||||
|
// 1) UPSERT state (по user_id)
|
||||||
|
val upsertStateSql = """
|
||||||
|
INSERT INTO finance.bot_states (user_id, state_code)
|
||||||
|
VALUES (:user_id, :state_code)
|
||||||
|
ON CONFLICT (user_id) DO UPDATE
|
||||||
|
SET state_code = EXCLUDED.state_code
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
jdbcTemplate.update(
|
||||||
|
upsertStateSql,
|
||||||
|
mapOf(
|
||||||
|
"user_id" to userId,
|
||||||
|
// если в БД enum — чаще всего ок передать name(); если колонка TEXT/VARCHAR — тоже ок
|
||||||
|
"state_code" to stateCode.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2) Обновление data: вариант A — апсерты (рекомендуется)
|
||||||
|
if (stateData.isNotEmpty()) {
|
||||||
|
val upsertDataSql = """
|
||||||
|
INSERT INTO finance.bot_states_data (user_id, data_code, data_value)
|
||||||
|
VALUES (:user_id, :data_code, :data_value)
|
||||||
|
ON CONFLICT (user_id, data_code) DO UPDATE
|
||||||
|
SET data_value = EXCLUDED.data_value
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val batch = stateData.map { (code, value) ->
|
||||||
|
mapOf(
|
||||||
|
"user_id" to userId,
|
||||||
|
"data_code" to code,
|
||||||
|
"data_value" to value
|
||||||
|
)
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
jdbcTemplate.batchUpdate(upsertDataSql, batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если тебе принципиально "перезаписывать" состояние данных (вариант B):
|
||||||
|
// np.update("DELETE FROM finance.bot_states_data WHERE user_id = :user_id", mapOf("user_id" to userId))
|
||||||
|
// затем обычный INSERT batch без ON CONFLICT.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearState(userId: Int) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package space.luminic.finance.repos
|
|
||||||
|
|
||||||
import org.bson.types.ObjectId
|
|
||||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
|
||||||
import org.springframework.stereotype.Repository
|
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import space.luminic.finance.models.Budget
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
interface BudgetRepo: ReactiveMongoRepository<Budget, String> {
|
|
||||||
|
|
||||||
suspend fun findBudgetsBySpaceIdAndIsDeletedFalse(spaceId: String): Flux<Budget>
|
|
||||||
suspend fun findBudgetsBySpaceIdAndId(spaceId: String, budgetId: String): Flux<Budget>
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
package space.luminic.finance.repos
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
|
||||||
import space.luminic.finance.models.Category
|
import space.luminic.finance.models.Category
|
||||||
|
|
||||||
interface CategoryRepo: ReactiveMongoRepository<Category, String> {
|
|
||||||
|
interface CategoryRepo {
|
||||||
|
fun findBySpaceId(spaceId: Int): List<Category>
|
||||||
|
fun findBySpaceIdAndId(spaceId: Int, id: Int): Category?
|
||||||
|
fun create(category: Category, createdById: Int): Category
|
||||||
|
fun update(category: Category, updatedById: Int): Category
|
||||||
|
fun delete(categoryId: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CategoryEtalonRepo: ReactiveMongoRepository<Category.CategoryEtalon, String>
|
interface CategoryEtalonRepo {
|
||||||
|
fun findAll(): List<Category>
|
||||||
|
}
|
||||||
107
src/main/kotlin/space/luminic/finance/repos/CategoryRepoImpl.kt
Normal file
107
src/main/kotlin/space/luminic/finance/repos/CategoryRepoImpl.kt
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import org.springframework.dao.DuplicateKeyException
|
||||||
|
import org.springframework.jdbc.core.RowMapper
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import space.luminic.finance.models.Category
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class CategoryRepoImpl(
|
||||||
|
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||||
|
) : CategoryRepo {
|
||||||
|
private fun categoryRowMapper() = RowMapper<Category> { rs, _ ->
|
||||||
|
Category(
|
||||||
|
id = rs.getInt("id"),
|
||||||
|
type = Category.CategoryType.valueOf(rs.getString("type")),
|
||||||
|
name = rs.getString("name"),
|
||||||
|
description = rs.getString("description"),
|
||||||
|
icon = rs.getString("icon"),
|
||||||
|
isDeleted = rs.getBoolean("is_deleted"),
|
||||||
|
createdAt = rs.getTimestamp("created_at").toInstant(),
|
||||||
|
updatedAt = if (rs.getTimestamp("updated_at") != null) rs.getTimestamp("updated_at").toInstant() else null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findBySpaceId(spaceId: Int): List<Category> {
|
||||||
|
val query = "select * from finance.categories where space_id = :space_id order by name"
|
||||||
|
val params = mapOf("space_id" to spaceId)
|
||||||
|
return jdbcTemplate.query(query, params, categoryRowMapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findBySpaceIdAndId(spaceId: Int, id: Int): Category? {
|
||||||
|
val query = "select * from finance.categories where space_id = :space_id and id = :id"
|
||||||
|
val params = mapOf("id" to id, "space_id" to spaceId)
|
||||||
|
return jdbcTemplate.query(query, params, categoryRowMapper()).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(category: Category, createdById: Int): Category {
|
||||||
|
if (category.id == null) {
|
||||||
|
val query =
|
||||||
|
"insert into finance.categories(space_id, type, name, description, icon, is_deleted, created_by_id) values (:space_id, :type, :name, :description, :icon, :isDeleted, :createdById) returning id"
|
||||||
|
val params = mapOf(
|
||||||
|
"space_id" to category.space!!.id,
|
||||||
|
"type" to category.type.name,
|
||||||
|
"name" to category.name,
|
||||||
|
"description" to category.description,
|
||||||
|
"icon" to category.icon,
|
||||||
|
"isDeleted" to category.isDeleted,
|
||||||
|
"createdById" to createdById
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
category.id = jdbcTemplate.queryForObject(query, params, Int::class.java)
|
||||||
|
return category
|
||||||
|
} catch (ex: DuplicateKeyException) {
|
||||||
|
throw IllegalArgumentException("You cannot create a category with similar name in one space", ex)
|
||||||
|
}
|
||||||
|
} else throw IllegalArgumentException("Category id must by null")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(category: Category, updatedById: Int): Category {
|
||||||
|
if (category.id != null) {
|
||||||
|
val query =
|
||||||
|
"update finance.categories set type=:type, name=:name, description=:description, icon=:icon, is_deleted=:isDeleted , updated_by_id = :updatedById, updated_at = now() where id = :id"
|
||||||
|
val params = mapOf(
|
||||||
|
"id" to category.id!!,
|
||||||
|
"type" to category.type.name,
|
||||||
|
"name" to category.name,
|
||||||
|
"description" to category.description,
|
||||||
|
"icon" to category.icon,
|
||||||
|
"isDeleted" to category.isDeleted,
|
||||||
|
"updatedById" to updatedById
|
||||||
|
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(query, params)
|
||||||
|
return category
|
||||||
|
} else throw IllegalArgumentException("Category cannot be null")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(categoryId: Int) {
|
||||||
|
val query = "update finance.categories set is_deleted=:isDeleted where id = :id"
|
||||||
|
val params = mapOf(
|
||||||
|
"id" to categoryId,
|
||||||
|
"isDeleted" to true
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(query, params)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class CategoryEtalonRepoImpl(
|
||||||
|
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||||
|
) : CategoryEtalonRepo {
|
||||||
|
private val rowMapper = RowMapper { rs, _ ->
|
||||||
|
Category(
|
||||||
|
type = Category.CategoryType.valueOf(rs.getString("type")),
|
||||||
|
name = rs.getString("name"),
|
||||||
|
description = rs.getString("description"),
|
||||||
|
icon = rs.getString("icon"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAll(): List<Category> {
|
||||||
|
val query = "select * from finance.categories_etalon"
|
||||||
|
return jdbcTemplate.query(query, rowMapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,21 @@
|
|||||||
package space.luminic.finance.repos
|
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.Currency
|
||||||
import space.luminic.finance.models.CurrencyRate
|
|
||||||
|
|
||||||
interface CurrencyRepo: ReactiveMongoRepository<Currency, String>
|
@Repository
|
||||||
interface CurrencyRateRepo: ReactiveMongoRepository<CurrencyRate, String>
|
interface CurrencyRepo {
|
||||||
|
fun findAll(): List<Currency>
|
||||||
|
fun findByCode(code: String): Currency?
|
||||||
|
fun save(currency: Currency): Currency
|
||||||
|
fun update(currency: Currency)
|
||||||
|
fun delete(currencyCode: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//interface CurrencyRateRepo {
|
||||||
|
// fun findByCode(code: String): List<CurrencyRate>
|
||||||
|
// fun save(currencyRate: CurrencyRate)
|
||||||
|
// fun update(currencyRate: CurrencyRate)
|
||||||
|
// fun delete(id: Int)
|
||||||
|
//}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.RowMapper
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import space.luminic.finance.models.Currency
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class CurrencyRepoImpl(
|
||||||
|
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||||
|
): CurrencyRepo {
|
||||||
|
|
||||||
|
private fun currencyRowMapper() = RowMapper<Currency> { rs, _ ->
|
||||||
|
Currency(
|
||||||
|
code = rs.getString("code"),
|
||||||
|
name = rs.getString("name"),
|
||||||
|
symbol = rs.getString("symbol"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAll(): List<Currency> {
|
||||||
|
val sql = "SELECT * FROM finance.currencies_ref"
|
||||||
|
return jdbcTemplate.query(sql, currencyRowMapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByCode(code: String): Currency? {
|
||||||
|
val sql = "SELECT * FROM finance.currencies_ref WHERE code = :code"
|
||||||
|
val params = mapOf("code" to code)
|
||||||
|
return jdbcTemplate.queryForObject(sql, params, Currency::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun save(currency: Currency): Currency {
|
||||||
|
val sql = "insert into finance.currencies_ref (code, name, symbol) values (:code, :name, :symbol)"
|
||||||
|
val params = mapOf("code" to currency.code, "name" to currency.name, "symbol" to currency.symbol)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
return currency
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(currency: Currency) {
|
||||||
|
val sql = "update finance.currencies_ref set name=:name, symbol=:symbol where code = :code"
|
||||||
|
val params = mapOf("name" to currency.code, "symbol" to currency.name)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(currencyCode: String) {
|
||||||
|
val sql = "delete from finance.currencies_ref where code = :code"
|
||||||
|
val params = mapOf("code" to currencyCode)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/kotlin/space/luminic/finance/repos/GoalRepo.kt
Normal file
20
src/main/kotlin/space/luminic/finance/repos/GoalRepo.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import space.luminic.finance.models.Goal
|
||||||
|
|
||||||
|
interface GoalRepo {
|
||||||
|
|
||||||
|
fun findAllBySpaceId(spaceId: Int) : List<Goal>
|
||||||
|
fun findBySpaceIdAndId(spaceId: Int, id: Int) : Goal?
|
||||||
|
fun create(goal: Goal, createdById: Int): Int
|
||||||
|
fun update(goal: Goal, updatedById: Int)
|
||||||
|
fun delete(spaceId: Int, id: Int)
|
||||||
|
fun getComponents(spaceId: Int, goalId: Int): List<Goal.GoalComponent>
|
||||||
|
fun getComponent(spaceId: Int, goalId: Int, id: Int): Goal.GoalComponent?
|
||||||
|
fun createComponent(goalId: Int, component: Goal.GoalComponent, createdById: Int): Int
|
||||||
|
fun updateComponent(goalId: Int, componentId: Int, component: Goal.GoalComponent, updatedById: Int)
|
||||||
|
fun deleteComponent(goalId: Int, componentId: Int)
|
||||||
|
|
||||||
|
fun assignTransaction(goalId: Int, transactionId: Int)
|
||||||
|
fun refuseTransaction(goalId: Int, transactionId: Int)
|
||||||
|
}
|
||||||
283
src/main/kotlin/space/luminic/finance/repos/GoalRepoImpl.kt
Normal file
283
src/main/kotlin/space/luminic/finance/repos/GoalRepoImpl.kt
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.RowMapper
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import space.luminic.finance.models.Goal
|
||||||
|
import space.luminic.finance.models.User
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class GoalRepoImpl(
|
||||||
|
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||||
|
) : GoalRepo {
|
||||||
|
private val goalRowMapper = RowMapper { rs, _ ->
|
||||||
|
Goal(
|
||||||
|
id = rs.getInt("g_id"),
|
||||||
|
type = Goal.GoalType.valueOf(rs.getString("g_type")),
|
||||||
|
name = rs.getString("g_name"),
|
||||||
|
description = rs.getString("g_description"),
|
||||||
|
amount = rs.getBigDecimal("g_amount"),
|
||||||
|
components = listOf(),
|
||||||
|
transactions = listOf(),
|
||||||
|
untilDate = rs.getDate("g_until_date").toLocalDate(),
|
||||||
|
createdBy = User(
|
||||||
|
id = rs.getInt("created_by_id"),
|
||||||
|
username = rs.getString("created_by_username"),
|
||||||
|
firstName = rs.getString("created_by_first_name"),
|
||||||
|
),
|
||||||
|
createdAt = rs.getTimestamp("g_created_at").toInstant()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val componentRowMapper = RowMapper { rs, _ ->
|
||||||
|
Goal.GoalComponent(
|
||||||
|
id = rs.getInt("gc_id"),
|
||||||
|
name = rs.getString("gc_name"),
|
||||||
|
amount = rs.getBigDecimal("gc_amount"),
|
||||||
|
isDone = rs.getBoolean("gc_is_done"),
|
||||||
|
date = rs.getDate("gc_date").toLocalDate()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAllBySpaceId(spaceId: Int): List<Goal> {
|
||||||
|
val sql = """
|
||||||
|
select
|
||||||
|
g.id as g_id,
|
||||||
|
g.type as g_type,
|
||||||
|
g.name as g_name,
|
||||||
|
g.description as g_description,
|
||||||
|
g.amount as g_amount,
|
||||||
|
created_by.id as created_by_id,
|
||||||
|
created_by.username as created_by_username,
|
||||||
|
created_by.first_name as created_by_first_name,
|
||||||
|
g.created_at as g_created_at
|
||||||
|
from finance.goals g
|
||||||
|
join finance.users created_by on g.created_by_id = created_by.id
|
||||||
|
where g.space_id = :spaceId
|
||||||
|
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val params = mapOf(
|
||||||
|
"space_id" to spaceId,
|
||||||
|
)
|
||||||
|
return jdbcTemplate.query(sql, params, goalRowMapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findBySpaceIdAndId(spaceId: Int, id: Int): Goal? {
|
||||||
|
val sql = """
|
||||||
|
select
|
||||||
|
g.id as g_id,
|
||||||
|
g.type as g_type,
|
||||||
|
g.name as g_name,
|
||||||
|
g.description as g_description,
|
||||||
|
g.amount as g_amount,
|
||||||
|
created_by.id as created_by_id,
|
||||||
|
created_by.username as created_by_username,
|
||||||
|
created_by.first_name as created_by_first_name,
|
||||||
|
g.created_at as g_created_at
|
||||||
|
from finance.goals g
|
||||||
|
join finance.users created_by on g.created_by_id = created_by.id
|
||||||
|
where g.space_id = :spaceId and g.id = :id
|
||||||
|
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val params = mapOf(
|
||||||
|
"space_id" to spaceId,
|
||||||
|
"id" to id,
|
||||||
|
)
|
||||||
|
return jdbcTemplate.query(sql, params, goalRowMapper).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(goal: Goal, createdById: Int): Int {
|
||||||
|
val sql = """
|
||||||
|
insert into finance.goals(
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
amount,
|
||||||
|
until_date,
|
||||||
|
created_by_id
|
||||||
|
) values (
|
||||||
|
:type,
|
||||||
|
:name,
|
||||||
|
:description,
|
||||||
|
:amount,
|
||||||
|
:until_date,
|
||||||
|
:created_by_id
|
||||||
|
)
|
||||||
|
returning id
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"type" to goal.type,
|
||||||
|
"name" to goal.name,
|
||||||
|
"description" to goal.description,
|
||||||
|
"amount" to goal.amount,
|
||||||
|
"until_date" to goal.untilDate,
|
||||||
|
"created_by_id" to createdById
|
||||||
|
)
|
||||||
|
return jdbcTemplate.queryForObject(sql, params, Int::class.java)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(goal: Goal, updatedById: Int) {
|
||||||
|
val sql = """
|
||||||
|
update finance.goals set
|
||||||
|
type = :type,
|
||||||
|
name = :name,
|
||||||
|
description = :description,
|
||||||
|
amount = :amount,
|
||||||
|
until_date = :until_date,
|
||||||
|
updated_by_id = :updated_by_id,
|
||||||
|
updated_at = now()
|
||||||
|
where id = :id
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"id" to goal.id,
|
||||||
|
"type" to goal.type.name,
|
||||||
|
"name" to goal.name,
|
||||||
|
"description" to goal.description,
|
||||||
|
"amount" to goal.amount,
|
||||||
|
"until_date" to goal.untilDate,
|
||||||
|
"updated_by_id" to updatedById
|
||||||
|
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(spaceId: Int, id: Int) {
|
||||||
|
val sql = """
|
||||||
|
delete from finance.goals where id = :id
|
||||||
|
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"id" to id
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getComponents(
|
||||||
|
spaceId: Int,
|
||||||
|
goalId: Int
|
||||||
|
): List<Goal.GoalComponent> {
|
||||||
|
val sql = """
|
||||||
|
select
|
||||||
|
gc.id as gc_id,
|
||||||
|
gc.name as gc_name,
|
||||||
|
gc.amount as gc_amount,
|
||||||
|
gc.is_done as gc_is_done,
|
||||||
|
gc.date as gc_date
|
||||||
|
from finance.goals_components gc
|
||||||
|
where gc.goal_id = :goal_id
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"goal_id" to goalId
|
||||||
|
)
|
||||||
|
return jdbcTemplate.query(sql, params, componentRowMapper)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getComponent(
|
||||||
|
spaceId: Int,
|
||||||
|
goalId: Int,
|
||||||
|
id: Int
|
||||||
|
): Goal.GoalComponent? {
|
||||||
|
val sql = """
|
||||||
|
select
|
||||||
|
gc.id as gc_id,
|
||||||
|
gc.name as gc_name,
|
||||||
|
gc.amount as gc_amount,
|
||||||
|
gc.is_done as gc_is_done,
|
||||||
|
gc.date as gc_date
|
||||||
|
from finance.goals_components gc
|
||||||
|
where gc.goal_id = :goal_id and gc.id = :id
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"goal_id" to goalId,
|
||||||
|
"id" to id
|
||||||
|
)
|
||||||
|
return jdbcTemplate.query(sql, params, componentRowMapper).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createComponent(goalId: Int, component: Goal.GoalComponent, createdById: Int): Int {
|
||||||
|
val sql = """
|
||||||
|
insert into finance.goals_components(
|
||||||
|
goal_id,
|
||||||
|
name,
|
||||||
|
amount,
|
||||||
|
is_done,
|
||||||
|
date,
|
||||||
|
created_by_id
|
||||||
|
) values (
|
||||||
|
:goal_id,
|
||||||
|
:name,
|
||||||
|
:amount,
|
||||||
|
:is_done,
|
||||||
|
:date,
|
||||||
|
:created_by_id)
|
||||||
|
returning id
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"goal_id" to goalId,
|
||||||
|
"name" to component.name,
|
||||||
|
"amount" to component.amount,
|
||||||
|
"is_done" to component.isDone,
|
||||||
|
"date" to component.date,
|
||||||
|
"created_by_id" to createdById
|
||||||
|
)
|
||||||
|
return jdbcTemplate.queryForObject(sql, params, Int::class.java)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateComponent(goalId: Int, componentId: Int, component: Goal.GoalComponent, updatedById: Int) {
|
||||||
|
val sql = """
|
||||||
|
update finance.goals_components set
|
||||||
|
name = :name,
|
||||||
|
amount = :amount,
|
||||||
|
is_done = :is_done,
|
||||||
|
updated_by_id = :updated_by_id
|
||||||
|
where goal_id = :goalId and id = :componentId
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"goalId" to goalId,
|
||||||
|
"componentId" to componentId,
|
||||||
|
"name" to component.name,
|
||||||
|
"amount" to component.amount,
|
||||||
|
"is_done" to component.isDone,
|
||||||
|
"date" to component.date,
|
||||||
|
"updated_by_id" to updatedById
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteComponent(goalId: Int, componentId: Int) {
|
||||||
|
val sql = """
|
||||||
|
delete from finance.goals_components where goal_id = :goalId and id = :componentId
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"goalId" to goalId,
|
||||||
|
"componentId" to componentId
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun assignTransaction(goalId: Int, transactionId: Int) {
|
||||||
|
val sql = """
|
||||||
|
insert into finance.goals_transactions(goal_id, transactions_id)
|
||||||
|
values (:goal_id, :transaction_id)
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"goal_id" to goalId,
|
||||||
|
"transaction_id" to transactionId
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refuseTransaction(goalId: Int, transactionId: Int) {
|
||||||
|
val sql = """
|
||||||
|
delete from finance.goals_transactions where goal_id = :goalId and transactions_id = :transactionId
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"goal_id" to goalId,
|
||||||
|
"transaction_id" to transactionId
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import space.luminic.finance.models.RecurrentOperation
|
||||||
|
|
||||||
|
interface RecurrentOperationRepo {
|
||||||
|
fun findAllBySpaceId(spaceId: Int): List<RecurrentOperation>
|
||||||
|
fun findBySpaceIdAndId(spaceId: Int, id: Int): RecurrentOperation?
|
||||||
|
fun findByDate( date: Int): List<RecurrentOperation>
|
||||||
|
fun create(operation: RecurrentOperation, createdById: Int): Int
|
||||||
|
fun update(operation: RecurrentOperation, updatedById: Int)
|
||||||
|
fun delete(id: Int)
|
||||||
|
fun findRecurrentsToCreate(spaceId: Int): List<RecurrentOperation>
|
||||||
|
}
|
||||||
@@ -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.models.Category
|
||||||
|
import space.luminic.finance.models.RecurrentOperation
|
||||||
|
import space.luminic.finance.models.Space
|
||||||
|
import space.luminic.finance.models.User
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class RecurrentOperationRepoImpl(
|
||||||
|
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||||
|
) : RecurrentOperationRepo {
|
||||||
|
|
||||||
|
private fun operationRowMapper() = RowMapper<RecurrentOperation> { rs, _ ->
|
||||||
|
RecurrentOperation(
|
||||||
|
id = rs.getInt("r_id"),
|
||||||
|
space = Space(
|
||||||
|
id = rs.getInt("r_space_id"),
|
||||||
|
name = rs.getString("s_name"),
|
||||||
|
owner = User(
|
||||||
|
rs.getInt("s_owner_id"),
|
||||||
|
username = rs.getString("su_username"),
|
||||||
|
firstName = rs.getString("su_first_name"),
|
||||||
|
),
|
||||||
|
participants = setOf()
|
||||||
|
),
|
||||||
|
category = Category(
|
||||||
|
id = rs.getInt("r_category_id"),
|
||||||
|
type = Category.CategoryType.valueOf(rs.getString("c_type")),
|
||||||
|
name = rs.getString("c_name"),
|
||||||
|
description = rs.getString("c_description"),
|
||||||
|
icon = rs.getString("c_icon"),
|
||||||
|
),
|
||||||
|
name = rs.getString("r_name"),
|
||||||
|
amount = rs.getBigDecimal("r_amount"),
|
||||||
|
date = rs.getInt("r_date"),
|
||||||
|
createdBy = User(
|
||||||
|
id = rs.getInt("r_created_by_id"),
|
||||||
|
username = rs.getString("r_created_by_username"),
|
||||||
|
firstName = rs.getString("r_created_by_first_name"),
|
||||||
|
),
|
||||||
|
createdAt = rs.getTimestamp("r_created_at").toInstant()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun findAllBySpaceId(spaceId: Int): List<RecurrentOperation> {
|
||||||
|
val sql = """
|
||||||
|
select
|
||||||
|
ro.id as r_id,
|
||||||
|
ro.space_id AS r_space_id,
|
||||||
|
s.name AS s_name,
|
||||||
|
s.owner_id as s_owner_id,
|
||||||
|
su.username as su_username,
|
||||||
|
su.first_name AS su_first_name,
|
||||||
|
ro.category_id as r_category_id,
|
||||||
|
c.type AS c_type,
|
||||||
|
c.name AS c_name,
|
||||||
|
c.description AS c_description,
|
||||||
|
c.icon AS c_icon,
|
||||||
|
ro.name AS r_name,
|
||||||
|
ro.amount AS r_amount,
|
||||||
|
ro.date AS r_date,
|
||||||
|
ro.created_by_id as r_created_by_id,
|
||||||
|
r_created_by.username as r_created_by_username,
|
||||||
|
r_created_by.first_name as r_created_by_first_name,
|
||||||
|
ro.created_at as r_created_at
|
||||||
|
from finance.recurrent_operations ro
|
||||||
|
join finance.spaces s on ro.space_id = s.id
|
||||||
|
join finance.users su on s.owner_id = su.id
|
||||||
|
join finance.categories c on ro.category_id = c.id
|
||||||
|
join finance.users r_created_by on ro.created_by_id = r_created_by.id
|
||||||
|
where ro.space_id = :spaceId
|
||||||
|
order by ro.date
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf("spaceId" to spaceId)
|
||||||
|
return jdbcTemplate.query(sql, params, operationRowMapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findBySpaceIdAndId(
|
||||||
|
spaceId: Int,
|
||||||
|
id: Int
|
||||||
|
): RecurrentOperation? {
|
||||||
|
val sql = """
|
||||||
|
select
|
||||||
|
ro.id as r_id,
|
||||||
|
ro.space_id AS r_space_id,
|
||||||
|
s.name AS s_name,
|
||||||
|
s.owner_id as s_owner_id,
|
||||||
|
su.username as su_username,
|
||||||
|
su.first_name AS su_first_name,
|
||||||
|
ro.category_id as r_category_id,
|
||||||
|
c.type AS c_type,
|
||||||
|
c.name AS c_name,
|
||||||
|
c.description AS c_description,
|
||||||
|
c.icon AS c_icon,
|
||||||
|
ro.name AS r_name,
|
||||||
|
ro.amount AS r_amount,
|
||||||
|
ro.date AS r_date,
|
||||||
|
ro.created_by_id as r_created_by_id,
|
||||||
|
r_created_by.username as r_created_by_username,
|
||||||
|
r_created_by.first_name as r_created_by_first_name,
|
||||||
|
ro.created_at as r_created_at
|
||||||
|
from finance.recurrent_operations ro
|
||||||
|
join finance.spaces s on ro.space_id = s.id
|
||||||
|
join finance.users su on s.owner_id = su.id
|
||||||
|
join finance.categories c on ro.category_id = c.id
|
||||||
|
join finance.users r_created_by on ro.created_by_id = r_created_by.id
|
||||||
|
where ro.space_id = :spaceId and ro.id = :id
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf("spaceId" to spaceId, "id" to id)
|
||||||
|
return jdbcTemplate.query(sql, params, operationRowMapper()).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByDate(
|
||||||
|
date: Int
|
||||||
|
): List<RecurrentOperation> {
|
||||||
|
val sql = """
|
||||||
|
select
|
||||||
|
ro.id as r_id,
|
||||||
|
ro.space_id AS r_space_id,
|
||||||
|
s.name AS s_name,
|
||||||
|
s.owner_id as s_owner_id,
|
||||||
|
su.username as su_username,
|
||||||
|
su.first_name AS su_first_name,
|
||||||
|
ro.category_id as r_category_id,
|
||||||
|
c.type AS c_type,
|
||||||
|
c.name AS c_name,
|
||||||
|
c.description AS c_description,
|
||||||
|
c.icon AS c_icon,
|
||||||
|
ro.name AS r_name,
|
||||||
|
ro.amount AS r_amount,
|
||||||
|
ro.date AS r_date,
|
||||||
|
ro.created_by_id as r_created_by_id,
|
||||||
|
r_created_by.username as r_created_by_username,
|
||||||
|
r_created_by.first_name as r_created_by_first_name,
|
||||||
|
ro.created_at as r_created_at
|
||||||
|
from finance.recurrent_operations ro
|
||||||
|
join finance.spaces s on ro.space_id = s.id
|
||||||
|
join finance.users su on s.owner_id = su.id
|
||||||
|
join finance.categories c on ro.category_id = c.id
|
||||||
|
join finance.users r_created_by on ro.created_by_id = r_created_by.id
|
||||||
|
where ro.date = :date
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf( "date" to date)
|
||||||
|
return jdbcTemplate.query(sql, params, operationRowMapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findRecurrentsToCreate(spaceId: Int): List<RecurrentOperation> {
|
||||||
|
val sql = """
|
||||||
|
select * from finance.transactions where space_id = :spaceId and t.date >
|
||||||
|
""".trimIndent()
|
||||||
|
TODO("Not ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package space.luminic.finance.repos
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
import org.springframework.stereotype.Repository
|
||||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
|
||||||
import space.luminic.finance.models.Space
|
import space.luminic.finance.models.Space
|
||||||
|
|
||||||
interface SpaceRepo: ReactiveMongoRepository<Space, String> {
|
@Repository
|
||||||
|
interface SpaceRepo {
|
||||||
|
fun findSpacesAvailableForUser(userId: Int): List<Space>
|
||||||
|
fun findSpaceById(id: Int, userId: Int): Space?
|
||||||
|
fun create(space: Space, createdById: Int): Int
|
||||||
|
fun update(space: Space, updatedById: Int): Int
|
||||||
|
fun delete(id: Int)
|
||||||
}
|
}
|
||||||
221
src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt
Normal file
221
src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.RowMapper
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import space.luminic.finance.dtos.SpaceDTO
|
||||||
|
import space.luminic.finance.models.Space
|
||||||
|
import space.luminic.finance.models.User
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class SpaceRepoImpl(
|
||||||
|
private val userRepo: UserRepo,
|
||||||
|
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||||
|
) : SpaceRepo {
|
||||||
|
|
||||||
|
private fun spaceRowMapper() = RowMapper { rs, _ ->
|
||||||
|
Space(
|
||||||
|
id = rs.getInt("id"),
|
||||||
|
name = rs.getString("name"),
|
||||||
|
owner = User(
|
||||||
|
id = rs.getInt("owner_id"),
|
||||||
|
username = rs.getString("username"),
|
||||||
|
firstName = rs.getString("first_name"),
|
||||||
|
password = rs.getString("password"),
|
||||||
|
),
|
||||||
|
participants = userRepo.findParticipantsBySpace(rs.getInt("id")).toSet(),
|
||||||
|
isDeleted = rs.getBoolean("is_deleted"),
|
||||||
|
createdAt = rs.getTimestamp("created_at").toInstant(),
|
||||||
|
updatedAt = rs.getTimestamp("updated_at").toInstant()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shortRowMapper() = RowMapper { rs, _ ->
|
||||||
|
SpaceDTO.SpaceShortInfoDTO(
|
||||||
|
id = rs.getInt("s_id"),
|
||||||
|
name = rs.getString("s_name"),
|
||||||
|
isOwner = rs.getBoolean("s_is_owner"),
|
||||||
|
owner = User(
|
||||||
|
rs.getInt("s_owner_id"),
|
||||||
|
rs.getString("s_owner_username"),
|
||||||
|
rs.getString("s_owner_firstname")
|
||||||
|
),
|
||||||
|
participant = User(rs.getInt("sp_uid"), rs.getString("sp_username"), rs.getString("sp_first_name")),
|
||||||
|
createdAt = rs.getTimestamp("s_created_at").toInstant(),
|
||||||
|
createdBy = User(
|
||||||
|
rs.getInt("s_created_by"),
|
||||||
|
rs.getString("s_created_by_username"),
|
||||||
|
rs.getString("s_created_by_firstname")
|
||||||
|
),
|
||||||
|
updatedAt = if (rs.getTimestamp("s_updated_at") != null) rs.getTimestamp("s_updated_at")
|
||||||
|
.toInstant() else null,
|
||||||
|
updatedBy = if (rs.getInt("s_updated_by") != 0) User(
|
||||||
|
rs.getInt("s_updated_by"),
|
||||||
|
rs.getString("s_updated_by_username"),
|
||||||
|
rs.getString("s_updated_by_firstname")
|
||||||
|
) else null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectParticipants(spaces: List<SpaceDTO.SpaceShortInfoDTO>): List<Space> {
|
||||||
|
val spaceMap = mutableMapOf<Int, Space>()
|
||||||
|
spaces.forEach { row ->
|
||||||
|
val entity = row.participant
|
||||||
|
val existing = spaceMap[row.id]
|
||||||
|
|
||||||
|
if (existing == null) {
|
||||||
|
spaceMap[row.id] = Space(
|
||||||
|
id = row.id,
|
||||||
|
name = row.name,
|
||||||
|
owner = row.owner,
|
||||||
|
participants = setOf(entity),
|
||||||
|
createdBy = row.createdBy,
|
||||||
|
createdAt = row.createdAt,
|
||||||
|
updatedAt = row.updatedAt,
|
||||||
|
updatedBy = row.updatedBy
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
spaceMap[row.id] = existing.copy(
|
||||||
|
participants = existing.participants + entity
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spaceMap.map { it.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun findSpacesAvailableForUser(userId: Int): List<Space> {
|
||||||
|
val sql = """
|
||||||
|
select s.id as s_id,
|
||||||
|
s.name as s_name,
|
||||||
|
s.created_at as s_created_at,
|
||||||
|
s.owner_id = :user_id as s_is_owner,
|
||||||
|
s.owner_id as s_owner_id,
|
||||||
|
ou.username as s_owner_username,
|
||||||
|
ou.first_name as s_owner_firstname,
|
||||||
|
sp.participants_id as sp_uid,
|
||||||
|
u.username as sp_username,
|
||||||
|
u.first_name as sp_first_name,
|
||||||
|
s.created_at as s_created_at,
|
||||||
|
s.created_by_id as s_created_by,
|
||||||
|
cau.username as s_created_by_username,
|
||||||
|
cau.first_name as s_created_by_firstname,
|
||||||
|
s.updated_at as s_updated_at,
|
||||||
|
s.updated_by_id as s_updated_by,
|
||||||
|
uau.username as s_updated_by_username,
|
||||||
|
uau.first_name as s_updated_by_firstname
|
||||||
|
|
||||||
|
from finance.spaces s
|
||||||
|
join finance.users ou on s.owner_id = ou.id
|
||||||
|
join finance.spaces_participants sp on sp.space_id = s.id
|
||||||
|
join finance.users u on sp.participants_id = u.id
|
||||||
|
left join finance.users cau on s.created_by_id = cau.id
|
||||||
|
left join finance.users uau on s.updated_by_id = uau.id
|
||||||
|
where (s.owner_id = :user_id
|
||||||
|
or sp.participants_id = :user_id)
|
||||||
|
and s.is_deleted = false
|
||||||
|
group by s.id, ou.username, ou.first_name, sp.participants_id, u.username, u.first_name, cau.username, cau.first_name,
|
||||||
|
uau.username, uau.first_name;
|
||||||
|
""".trimMargin()
|
||||||
|
val params = mapOf(
|
||||||
|
"user_id" to userId,
|
||||||
|
)
|
||||||
|
val spaces = jdbcTemplate.query(sql, params, shortRowMapper())
|
||||||
|
return collectParticipants(spaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findSpaceById(id: Int, userId: Int): Space? {
|
||||||
|
val sql = """
|
||||||
|
select s.id as s_id,
|
||||||
|
s.name as s_name,
|
||||||
|
s.created_at as s_created_at,
|
||||||
|
s.owner_id = :user_id as s_is_owner,
|
||||||
|
s.owner_id as s_owner_id,
|
||||||
|
ou.username as s_owner_username,
|
||||||
|
ou.first_name as s_owner_firstname,
|
||||||
|
sp.participants_id as sp_uid,
|
||||||
|
u.username as sp_username,
|
||||||
|
u.first_name as sp_first_name,
|
||||||
|
s.created_at as s_created_at,
|
||||||
|
s.created_by_id as s_created_by,
|
||||||
|
cau.username as s_created_by_username,
|
||||||
|
cau.first_name as s_created_by_firstname,
|
||||||
|
s.updated_at as s_updated_at,
|
||||||
|
s.updated_by_id as s_updated_by,
|
||||||
|
uau.username as s_updated_byusername,
|
||||||
|
uau.first_name as s_updated_byfirstname
|
||||||
|
|
||||||
|
from finance.spaces s
|
||||||
|
join finance.users ou on s.owner_id = ou.id
|
||||||
|
join finance.spaces_participants sp on sp.space_id = s.id
|
||||||
|
join finance.users u on sp.participants_id = u.id
|
||||||
|
left join finance.users cau on s.created_by_id = cau.id
|
||||||
|
left join finance.users uau on s.updated_by_id = uau.id
|
||||||
|
where (s.owner_id = :user_id
|
||||||
|
or sp.participants_id = :user_id)
|
||||||
|
and s.is_deleted = false and s.id = :spaceId
|
||||||
|
group by s.id, ou.username, ou.first_name, sp.participants_id, u.username, u.first_name, cau.username, cau.first_name,
|
||||||
|
uau.username, uau.first_name;
|
||||||
|
""".trimMargin()
|
||||||
|
val params = mapOf(
|
||||||
|
"spaceId" to id,
|
||||||
|
"user_id" to userId,
|
||||||
|
)
|
||||||
|
val spaces = jdbcTemplate.query(sql, params, shortRowMapper())
|
||||||
|
return collectParticipants(spaces).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(space: Space, createdById: Int): Int {
|
||||||
|
val sql = """
|
||||||
|
insert into finance.spaces (name, owner_id, is_deleted, created_by_id) values (:name, :owner_id, :is_deleted, :created_by_id)
|
||||||
|
RETURNING id
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"name" to space.name,
|
||||||
|
"owner_id" to space.owner.id,
|
||||||
|
"is_deleted" to false,
|
||||||
|
"created_by_id" to createdById
|
||||||
|
)
|
||||||
|
val createdSpaceId = jdbcTemplate.queryForObject(sql, params, Int::class.java)
|
||||||
|
// 2) батч-вставка участников (если есть)
|
||||||
|
if (space.participants.isNotEmpty()) {
|
||||||
|
val batchParams = space.participants.map { p ->
|
||||||
|
mapOf(
|
||||||
|
"spaceId" to createdSpaceId,
|
||||||
|
"participantId" to requireNotNull(p.id) { "participant.id is null" }
|
||||||
|
)
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
jdbcTemplate.batchUpdate(
|
||||||
|
"""
|
||||||
|
INSERT INTO finance.spaces_participants (space_id, participants_id)
|
||||||
|
VALUES (:spaceId, :participantId)
|
||||||
|
""".trimIndent(),
|
||||||
|
batchParams
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return createdSpaceId!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(space: Space, updatedById: Int): Int {
|
||||||
|
val sql = """
|
||||||
|
update finance.spaces set name = :name, updated_by_id = :updated_by_id, updated_at = now() where id = :spaceId
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"name" to space.name,
|
||||||
|
"spaceId" to space.id,
|
||||||
|
"updated_by_id" to updatedById
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
return space.id!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(id: Int) {
|
||||||
|
val sql = """
|
||||||
|
update finance.spaces set is_deleted = true where id = :id
|
||||||
|
""".trimIndent()
|
||||||
|
jdbcTemplate.update(sql, mapOf("id" to id))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,7 @@
|
|||||||
package space.luminic.finance.repos
|
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 org.springframework.stereotype.Repository
|
||||||
import reactor.core.publisher.Flux
|
|
||||||
import space.luminic.finance.models.Subscription
|
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface SubscriptionRepo : ReactiveMongoRepository<Subscription, String> {
|
interface SubscriptionRepo
|
||||||
|
|
||||||
@Query("{ \$and: [ " +
|
|
||||||
"{ 'user': { '\$ref': 'users', '\$id': ?0 } }, " +
|
|
||||||
"{ 'isActive': true } " +
|
|
||||||
"]}")
|
|
||||||
fun findByUserIdAndIsActive(userId: ObjectId): Flux<Subscription>
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package space.luminic.finance.repos
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import space.luminic.finance.models.Token
|
import space.luminic.finance.models.Token
|
||||||
import java.time.LocalDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface TokenRepo: ReactiveMongoRepository<Token, String> {
|
interface TokenRepo {
|
||||||
|
fun findByToken(token: String): Token?
|
||||||
|
fun deleteByExpiresAtBefore(dateTime: Instant)
|
||||||
|
|
||||||
fun findByToken(token: String): Mono<Token>
|
fun create(token: Token): Token
|
||||||
|
fun update(token: Token): Token
|
||||||
|
fun delete(id: Int)
|
||||||
|
|
||||||
fun deleteByExpiresAtBefore(dateTime: LocalDateTime)
|
|
||||||
}
|
}
|
||||||
|
|||||||
84
src/main/kotlin/space/luminic/finance/repos/TokenRepoImpl.kt
Normal file
84
src/main/kotlin/space/luminic/finance/repos/TokenRepoImpl.kt
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.RowMapper
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import space.luminic.finance.models.Token
|
||||||
|
import java.sql.Timestamp
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class TokenRepoImpl(
|
||||||
|
private val userRepo: UserRepo,
|
||||||
|
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||||
|
) : TokenRepo {
|
||||||
|
|
||||||
|
private fun tokenRowMapper() = RowMapper { rs, _ ->
|
||||||
|
Token(
|
||||||
|
id = rs.getInt("id"),
|
||||||
|
token = rs.getString("token"),
|
||||||
|
user = userRepo.findById(rs.getInt("user_id")) ?: throw IllegalArgumentException("User not found"),
|
||||||
|
issuedAt = rs.getTimestamp("issued_at").toInstant(),
|
||||||
|
expiresAt = rs.getTimestamp("expires_at").toInstant(),
|
||||||
|
status = Token.TokenStatus.valueOf(rs.getString("status")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByToken(token: String): Token? {
|
||||||
|
val sql = "SELECT * FROM finance.tokens WHERE token = :token"
|
||||||
|
val params = mapOf(
|
||||||
|
"token" to token,
|
||||||
|
)
|
||||||
|
return jdbcTemplate.query(sql, params, tokenRowMapper()).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteByExpiresAtBefore(dateTime: Instant) {
|
||||||
|
val sql = """
|
||||||
|
update finance.tokens set status = :status where expires_at <= :dateTime
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"status" to Token.TokenStatus.ACTIVE,
|
||||||
|
"dateTime" to dateTime
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(token: Token): Token {
|
||||||
|
val sql = """
|
||||||
|
insert into finance.tokens(token, user_id, expires_at, status) values (:token, :userId, :expiresAt, :status)
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"token" to token.token,
|
||||||
|
"userId" to token.user.id,
|
||||||
|
"expiresAt" to Timestamp.from(token.expiresAt),
|
||||||
|
"status" to Token.TokenStatus.ACTIVE.name,
|
||||||
|
)
|
||||||
|
val createdTokenId = jdbcTemplate.update(sql, params)
|
||||||
|
token.id = createdTokenId
|
||||||
|
return token
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(token: Token): Token {
|
||||||
|
val sql = """
|
||||||
|
update finance.tokens set status = :status where token = :token
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"token" to token.token,
|
||||||
|
"status" to token.status.name
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(id: Int) {
|
||||||
|
val sql = """
|
||||||
|
update finance.tokens set status = :status where id = :id
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"id" to id,
|
||||||
|
"status" to Token.TokenStatus.REVOKED
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
package space.luminic.finance.repos
|
package space.luminic.finance.repos
|
||||||
|
|
||||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
|
||||||
import space.luminic.finance.models.Transaction
|
import space.luminic.finance.models.Transaction
|
||||||
|
|
||||||
interface TransactionRepo: ReactiveMongoRepository<Transaction, String> {
|
interface TransactionRepo {
|
||||||
|
fun findAllBySpaceId(spaceId: Int): List<Transaction>
|
||||||
|
fun findBySpaceIdAndId(spaceId: Int, id: Int): Transaction?
|
||||||
|
fun create(transaction: Transaction, userId: Int): Int
|
||||||
|
fun createBatch(transactions: List<Transaction>, userId: Int)
|
||||||
|
fun update(transaction: Transaction): Int
|
||||||
|
fun delete(transactionId: Int)
|
||||||
|
fun deleteByRecurrentId(spaceId: Int, recurrentId: Int)
|
||||||
|
|
||||||
|
fun setCategory(txId:Int, categoryId: Int)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
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, _ ->
|
||||||
|
val category = if (rs.getString("c_id") == null) null else 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(),
|
||||||
|
)
|
||||||
|
val parent = if (rs.getInt("t_parent_id") != 0) findBySpaceIdAndId(
|
||||||
|
spaceId = rs.getInt("t_space_id"),
|
||||||
|
rs.getInt("t_parent_id")
|
||||||
|
) else null
|
||||||
|
Transaction(
|
||||||
|
id = rs.getInt("t_id"),
|
||||||
|
parent = parent,
|
||||||
|
type = Transaction.TransactionType.valueOf(rs.getString("t_type")),
|
||||||
|
kind = Transaction.TransactionKind.valueOf(rs.getString("t_kind")),
|
||||||
|
category = category,
|
||||||
|
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(),
|
||||||
|
tgChatId = rs.getLong("tg_chat_id"),
|
||||||
|
tgMessageId = rs.getLong("tg_message_id"),
|
||||||
|
recurrentId = rs.getInt("t_recurrent_id"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAllBySpaceId(spaceId: Int): List<Transaction> {
|
||||||
|
val sql = """
|
||||||
|
SELECT
|
||||||
|
t.id AS t_id,
|
||||||
|
t.parent_id AS t_parent_id,
|
||||||
|
t.space_id AS t_space_id,
|
||||||
|
t.type AS t_type,
|
||||||
|
t.kind AS t_kind,
|
||||||
|
t.comment AS t_comment,
|
||||||
|
t.amount AS t_amount,
|
||||||
|
t.fees AS t_fees,
|
||||||
|
t.date AS t_date,
|
||||||
|
t.is_deleted AS t_is_deleted,
|
||||||
|
t.is_done AS t_is_done,
|
||||||
|
t.created_at AS t_created_at,
|
||||||
|
t.updated_at AS t_updated_at,
|
||||||
|
c.id AS c_id,
|
||||||
|
c.type AS c_type,
|
||||||
|
c.name AS c_name,
|
||||||
|
c.description AS c_description,
|
||||||
|
c.icon AS c_icon,
|
||||||
|
c.is_deleted AS c_is_deleted,
|
||||||
|
c.created_at AS c_created_at,
|
||||||
|
c.updated_at AS c_updated_at,
|
||||||
|
u.id AS u_id,
|
||||||
|
u.username AS u_username,
|
||||||
|
u.first_name AS u_first_name,
|
||||||
|
t.tg_chat_id AS tg_chat_id,
|
||||||
|
t.tg_message_id AS tg_message_id,
|
||||||
|
t.recurrent_id AS t_recurrent_id
|
||||||
|
FROM finance.transactions t
|
||||||
|
LEFT 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, t.id
|
||||||
|
""".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,
|
||||||
|
t.tg_chat_id AS tg_chat_id,
|
||||||
|
t.tg_message_id AS tg_message_id,
|
||||||
|
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,
|
||||||
|
t.recurrent_id AS t_recurrent_id
|
||||||
|
FROM finance.transactions t
|
||||||
|
LEFT 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, tg_chat_id, tg_message_id, recurrent_id) VALUES (
|
||||||
|
:spaceId,
|
||||||
|
:parentId,
|
||||||
|
:type,
|
||||||
|
:kind,
|
||||||
|
:categoryId,
|
||||||
|
:comment,
|
||||||
|
:amount,
|
||||||
|
:fees,
|
||||||
|
:date,
|
||||||
|
:is_deleted,
|
||||||
|
:is_done,
|
||||||
|
:createdById,
|
||||||
|
:tgChatId,
|
||||||
|
:tgMessageId,
|
||||||
|
:recurrentId)
|
||||||
|
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,
|
||||||
|
"tgChatId" to transaction.tgChatId,
|
||||||
|
"tgMessageId" to transaction.tgMessageId,
|
||||||
|
"recurrentId" to transaction.recurrentId,
|
||||||
|
)
|
||||||
|
val createdTxId = jdbcTemplate.queryForObject(sql, params, Int::class.java)
|
||||||
|
transaction.id = createdTxId
|
||||||
|
return createdTxId!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createBatch(transactions: List<Transaction>, userId: 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, tg_chat_id, tg_message_id, recurrent_id
|
||||||
|
) VALUES (
|
||||||
|
:spaceId, :parentId, :type, :kind, :categoryId, :comment, :amount, :fees, :date,
|
||||||
|
:is_deleted, :is_done, :createdById, :tgChatId, :tgMessageId, :recurrentId
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val batchValues = transactions.map { transaction ->
|
||||||
|
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,
|
||||||
|
"tgChatId" to transaction.tgChatId,
|
||||||
|
"tgMessageId" to transaction.tgMessageId,
|
||||||
|
"recurrentId" to transaction.recurrentId
|
||||||
|
)
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
jdbcTemplate.batchUpdate(sql, batchValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteByRecurrentId(spaceId: Int, recurrentId: Int) {
|
||||||
|
val sql = """
|
||||||
|
update finance.transactions set is_deleted = true where recurrent_id = :recurrentId
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"recurrentId" to recurrentId,
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCategory(txId: Int, categoryId: Int) {
|
||||||
|
val sql = """
|
||||||
|
UPDATE finance.transactions
|
||||||
|
SET category_id = :categoryId
|
||||||
|
where id = :txId
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf(
|
||||||
|
"categoryId" to categoryId,
|
||||||
|
"txId" to txId,
|
||||||
|
)
|
||||||
|
jdbcTemplate.update(sql, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
package space.luminic.finance.repos
|
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 org.springframework.stereotype.Repository
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import space.luminic.finance.models.User
|
import space.luminic.finance.models.User
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface UserRepo : ReactiveMongoRepository<User, String> {
|
interface UserRepo {
|
||||||
|
fun findAll(): List<User>
|
||||||
|
fun findById(id: Int): User?
|
||||||
@Query(value = "{ 'username': ?0 }", fields = "{ 'password': 0 }")
|
fun findByUsername(username: String): User?
|
||||||
fun findByUsernameWOPassword(username: String): Mono<User>
|
fun findParticipantsBySpace(spaceId: Int): Set<User>
|
||||||
|
fun findByTgId(tgId: Long): User?
|
||||||
fun findByUsername(username: String): Mono<User>
|
fun create(user: User): User
|
||||||
|
fun update(user: User): User
|
||||||
fun findByTgId(id: String): Mono<User>
|
fun deleteById(id: Long)
|
||||||
}
|
}
|
||||||
85
src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt
Normal file
85
src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
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.getLong("tg_id"),
|
||||||
|
tgUserName = rs.getString("tg_user_name"),
|
||||||
|
photoUrl = rs.getString("photo_url"),
|
||||||
|
password = rs.getString("password"),
|
||||||
|
isActive = rs.getBoolean("is_active"),
|
||||||
|
regDate = rs.getDate("reg_date").toLocalDate(),
|
||||||
|
createdAt = rs.getTimestamp("created_at").toInstant(),
|
||||||
|
updatedAt = rs.getTimestamp("updated_at").toInstant(),
|
||||||
|
roles = (rs.getArray("roles")?.array as? Array<String>)?.toList() ?: emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAll(): List<User> {
|
||||||
|
val sql = "select * from finance.users order by created_at desc"
|
||||||
|
return jdbcTemplate.query(sql, userRowMapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findById(id: Int): User? {
|
||||||
|
val sql = "select * from finance.users where id = :userId"
|
||||||
|
return jdbcTemplate.queryForObject(sql, mapOf("userId" to id), userRowMapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByUsername(username: String): User? {
|
||||||
|
val sql = "select * from finance.users where username = :username"
|
||||||
|
return jdbcTemplate.query(sql, mapOf("username" to username), userRowMapper()).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findParticipantsBySpace(spaceId: Int): Set<User> {
|
||||||
|
val sql =
|
||||||
|
"select * from finance.users u join finance.spaces_participants sp on sp.participants_id = u.id where sp.space_id = :spaceId"
|
||||||
|
return jdbcTemplate.query(sql, mapOf("spaceId" to spaceId), userRowMapper()).toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByTgId(tgId: Long): User? {
|
||||||
|
val sql = """
|
||||||
|
select * from finance.users u where tg_id = :tgId
|
||||||
|
""".trimIndent()
|
||||||
|
val params = mapOf("tgId" to tgId)
|
||||||
|
return jdbcTemplate.query(sql, params, userRowMapper()).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(user: User): User {
|
||||||
|
val sql =
|
||||||
|
"insert into finance.users(username, first_name, tg_id, tg_user_name, photo_url, password, is_active, reg_date) values (:username, :firstname, :tg_id, :tg_user_name, :photo_url, :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,
|
||||||
|
"photo_url" to user.photoUrl,
|
||||||
|
"password" to user.password,
|
||||||
|
"isActive" to user.isActive,
|
||||||
|
"regDate" to user.regDate,
|
||||||
|
)
|
||||||
|
val savedId = jdbcTemplate.queryForObject(sql, params, Int::class.java)
|
||||||
|
user.id = savedId
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(user: User): User {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteById(id: Long) {
|
||||||
|
val sql = "update finance.users set is_active = false where id = :id"
|
||||||
|
jdbcTemplate.update(sql, mapOf("id" to id))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package space.luminic.finance.services
|
|
||||||
|
|
||||||
import space.luminic.finance.dtos.AccountDTO
|
|
||||||
import space.luminic.finance.models.Account
|
|
||||||
import space.luminic.finance.models.Transaction
|
|
||||||
|
|
||||||
interface AccountService {
|
|
||||||
suspend fun getAccounts(spaceId: String): List<Account>
|
|
||||||
suspend fun getAccount(spaceId: String, accountId: String): Account
|
|
||||||
suspend fun getAccountTransactions(spaceId: String, accountId: String): List<Transaction>
|
|
||||||
suspend fun createAccount(spaceId: String, account: AccountDTO.CreateAccountDTO): Account
|
|
||||||
suspend fun updateAccount(spaceId: String, account: AccountDTO.UpdateAccountDTO): Account
|
|
||||||
suspend fun deleteAccount(spaceId: String, accountId: String)
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package space.luminic.finance.services
|
|
||||||
|
|
||||||
import kotlinx.coroutines.reactive.awaitSingle
|
|
||||||
import org.bson.Document
|
|
||||||
import org.bson.types.ObjectId
|
|
||||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.lookup
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.match
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.unwind
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.LookupOperation
|
|
||||||
import org.springframework.data.mongodb.core.query.Criteria
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import space.luminic.finance.dtos.AccountDTO
|
|
||||||
import space.luminic.finance.models.Account
|
|
||||||
import space.luminic.finance.models.Transaction
|
|
||||||
import space.luminic.finance.repos.AccountRepo
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class AccountServiceImpl(
|
|
||||||
private val accountRepo: AccountRepo,
|
|
||||||
private val mongoTemplate: ReactiveMongoTemplate,
|
|
||||||
private val spaceService: SpaceService,
|
|
||||||
private val transactionService: TransactionService
|
|
||||||
): AccountService {
|
|
||||||
|
|
||||||
private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
|
||||||
val addFieldsAsOJ = addFields()
|
|
||||||
.addField("createdByOI")
|
|
||||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
|
||||||
.addField("updatedByOI")
|
|
||||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
|
||||||
.build()
|
|
||||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
|
||||||
val unwindCreatedBy = unwind("createdBy")
|
|
||||||
|
|
||||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
|
||||||
val unwindUpdatedBy = unwind("updatedBy")
|
|
||||||
|
|
||||||
val matchCriteria = mutableListOf<Criteria>()
|
|
||||||
matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
|
||||||
matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
|
||||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
|
||||||
|
|
||||||
return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun getAccounts(spaceId: String): List<Account> {
|
|
||||||
val basicAggregation = basicAggregation(spaceId)
|
|
||||||
val aggregation = newAggregation(*basicAggregation.toTypedArray())
|
|
||||||
return mongoTemplate.aggregate(aggregation, "accounts", Account::class.java)
|
|
||||||
.collectList()
|
|
||||||
.awaitSingle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAccount(
|
|
||||||
spaceId: String,
|
|
||||||
accountId: String
|
|
||||||
): Account {
|
|
||||||
val basicAggregation = basicAggregation(spaceId)
|
|
||||||
val matchStage = match (Criteria.where("_id").`is`(ObjectId(accountId)))
|
|
||||||
val aggregation = newAggregation(matchStage, *basicAggregation.toTypedArray())
|
|
||||||
return mongoTemplate.aggregate(aggregation, "accounts", Account::class.java)
|
|
||||||
.awaitSingle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAccountTransactions(
|
|
||||||
spaceId: String,
|
|
||||||
accountId: String
|
|
||||||
): List<Transaction> {
|
|
||||||
val space = spaceService.checkSpace(spaceId)
|
|
||||||
val filter = TransactionService.TransactionsFilter(
|
|
||||||
accountId = accountId,
|
|
||||||
)
|
|
||||||
return transactionService.getTransactions(spaceId, filter, "date", "ASC")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun createAccount(
|
|
||||||
spaceId: String,
|
|
||||||
account: AccountDTO.CreateAccountDTO
|
|
||||||
): Account {
|
|
||||||
val createdAccount = Account(
|
|
||||||
type = account.type,
|
|
||||||
spaceId = spaceId,
|
|
||||||
name = account.name,
|
|
||||||
currencyCode = account.currencyCode,
|
|
||||||
amount = account.amount,
|
|
||||||
goalId = account.goalId,
|
|
||||||
)
|
|
||||||
return accountRepo.save(createdAccount).awaitSingle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateAccount(
|
|
||||||
spaceId: String,
|
|
||||||
account: AccountDTO.UpdateAccountDTO
|
|
||||||
): Account {
|
|
||||||
val existingAccount = getAccount(spaceId, account.id)
|
|
||||||
val newAccount = existingAccount.copy(
|
|
||||||
name = account.name,
|
|
||||||
type = account.type,
|
|
||||||
currencyCode = account.currencyCode,
|
|
||||||
amount = account.amount,
|
|
||||||
goalId = account.goalId,
|
|
||||||
)
|
|
||||||
return accountRepo.save(newAccount).awaitSingle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteAccount(spaceId: String, accountId: String) {
|
|
||||||
val existingAccount = getAccount(spaceId, accountId)
|
|
||||||
|
|
||||||
existingAccount.isDeleted = true
|
|
||||||
accountRepo.save(existingAccount).awaitSingle()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,24 @@
|
|||||||
package space.luminic.finance.services
|
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.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.core.userdetails.UsernameNotFoundException
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import space.luminic.finance.configs.AuthException
|
import space.luminic.finance.configs.AuthException
|
||||||
|
import space.luminic.finance.dtos.UserDTO
|
||||||
|
import space.luminic.finance.models.NotFoundException
|
||||||
import space.luminic.finance.models.Token
|
import space.luminic.finance.models.Token
|
||||||
import space.luminic.finance.models.User
|
import space.luminic.finance.models.User
|
||||||
import space.luminic.finance.repos.UserRepo
|
import space.luminic.finance.repos.UserRepo
|
||||||
import space.luminic.finance.utils.JWTUtil
|
import space.luminic.finance.utils.JWTUtil
|
||||||
import java.time.LocalDateTime
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class AuthService(
|
class AuthService(
|
||||||
private val userRepository: UserRepo,
|
private val userRepo: UserRepo,
|
||||||
private val tokenService: TokenService,
|
private val tokenService: TokenService,
|
||||||
private val jwtUtil: JWTUtil,
|
private val jwtUtil: JWTUtil,
|
||||||
private val userService: UserService,
|
private val userService: UserService,
|
||||||
@@ -28,18 +26,28 @@ class AuthService(
|
|||||||
) {
|
) {
|
||||||
private val passwordEncoder = BCryptPasswordEncoder()
|
private val passwordEncoder = BCryptPasswordEncoder()
|
||||||
|
|
||||||
suspend fun getSecurityUser(): User {
|
fun getSecurityUser(): User {
|
||||||
val securityContextHolder = ReactiveSecurityContextHolder.getContext().awaitSingleOrNull()
|
val securityContextHolder = SecurityContextHolder.getContext()
|
||||||
?: throw AuthException("Authentication failed")
|
?: throw AuthException("Authentication failed")
|
||||||
val authentication = securityContextHolder.authentication
|
val authentication = securityContextHolder.authentication
|
||||||
|
|
||||||
val username = authentication.name
|
val username = authentication.name
|
||||||
// Получаем пользователя по имени
|
// Получаем пользователя по имени
|
||||||
return userService.getByUsername(username)
|
return userService.getById(username.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun login(username: String, password: String): String {
|
fun getSecurityUserId(): Int {
|
||||||
val user = userRepository.findByUsername(username).awaitFirstOrNull()
|
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("Пользователь не найден")
|
?: throw UsernameNotFoundException("Пользователь не найден")
|
||||||
return if (passwordEncoder.matches(password, user.password)) {
|
return if (passwordEncoder.matches(password, user.password)) {
|
||||||
val token = jwtUtil.generateToken(user.username)
|
val token = jwtUtil.generateToken(user.username)
|
||||||
@@ -47,10 +55,7 @@ class AuthService(
|
|||||||
tokenService.saveToken(
|
tokenService.saveToken(
|
||||||
token = token,
|
token = token,
|
||||||
username = username,
|
username = username,
|
||||||
expiresAt = LocalDateTime.ofInstant(
|
expiresAt = expireAt.toInstant()
|
||||||
expireAt.toInstant(),
|
|
||||||
ZoneId.systemDefault()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
token
|
token
|
||||||
} else {
|
} else {
|
||||||
@@ -58,26 +63,40 @@ class AuthService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun tgLogin(tgId: String): String {
|
fun tgAuth(tgUser: UserDTO.TelegramAuthDTO): String {
|
||||||
val user =
|
val user: User = try {
|
||||||
userRepository.findByTgId(tgId).awaitSingleOrNull() ?: throw UsernameNotFoundException("Пользователь не найден")
|
tgLogin(tgUser.id!!)
|
||||||
|
} catch (e: NotFoundException) {
|
||||||
|
registerTg(tgUser)
|
||||||
|
}
|
||||||
val token = jwtUtil.generateToken(user.username)
|
val token = jwtUtil.generateToken(user.username)
|
||||||
val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10)
|
val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10)
|
||||||
tokenService.saveToken(
|
tokenService.saveToken(
|
||||||
token = token,
|
token = token,
|
||||||
username = user.username,
|
username = user.username,
|
||||||
expiresAt = LocalDateTime.ofInstant(
|
expiresAt = expireAt.toInstant()
|
||||||
expireAt.toInstant(),
|
|
||||||
ZoneId.systemDefault()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return token
|
return token
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun register(username: String, password: String, firstName: String): User {
|
fun registerTg(tgUser: UserDTO.TelegramAuthDTO): User {
|
||||||
val user = userRepository.findByUsername(username).awaitSingleOrNull()
|
val user = User(
|
||||||
|
username = tgUser.username ?: UUID.randomUUID().toString().split('-')[0],
|
||||||
|
firstName = tgUser.first_name ?: UUID.randomUUID().toString().split('-')[0],
|
||||||
|
tgId = tgUser.id,
|
||||||
|
tgUserName = tgUser.username,
|
||||||
|
photoUrl = tgUser.photo_url,
|
||||||
|
roles = mutableListOf("USER")
|
||||||
|
)
|
||||||
|
return userRepo.create(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tgLogin(tgId: Long): User {
|
||||||
|
return userRepo.findByTgId(tgId) ?: throw NotFoundException("User with provided TG id $tgId not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun register(username: String, password: String, firstName: String): User {
|
||||||
|
val user = userRepo.findByUsername(username)
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
var newUser = User(
|
var newUser = User(
|
||||||
username = username,
|
username = username,
|
||||||
@@ -85,18 +104,18 @@ class AuthService(
|
|||||||
firstName = firstName,
|
firstName = firstName,
|
||||||
roles = mutableListOf("USER")
|
roles = mutableListOf("USER")
|
||||||
)
|
)
|
||||||
newUser = userRepository.save(newUser).awaitSingle()
|
newUser = userRepo.create(newUser)
|
||||||
return newUser
|
return newUser
|
||||||
} else throw IllegalArgumentException("Пользователь уже зарегистрирован")
|
} else throw IllegalArgumentException("Пользователь уже зарегистрирован")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Cacheable(cacheNames = ["tokens"], key = "#token")
|
@Cacheable(cacheNames = ["tokens"], key = "#token")
|
||||||
suspend fun isTokenValid(token: String): User {
|
fun isTokenValid(token: String): User {
|
||||||
val tokenDetails = tokenService.getToken(token).awaitFirstOrNull() ?: throw AuthException("Токен не валиден")
|
val tokenDetails = tokenService.getToken(token)
|
||||||
when {
|
when {
|
||||||
tokenDetails.status == Token.TokenStatus.ACTIVE && tokenDetails.expiresAt.isAfter(LocalDateTime.now()) -> {
|
tokenDetails.status == Token.TokenStatus.ACTIVE && tokenDetails.expiresAt.isAfter(Instant.now()) -> {
|
||||||
return userService.getByUsername(tokenDetails.username)
|
return tokenDetails.user
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package space.luminic.finance.services
|
|
||||||
|
|
||||||
import space.luminic.finance.dtos.BudgetDTO.*
|
|
||||||
import space.luminic.finance.models.Budget
|
|
||||||
import space.luminic.finance.models.Transaction
|
|
||||||
|
|
||||||
interface BudgetService {
|
|
||||||
|
|
||||||
suspend fun getBudgets(spaceId: String, sortBy: String, sortDirection: String): List<Budget>
|
|
||||||
suspend fun getBudget(spaceId: String, budgetId: String): Budget
|
|
||||||
suspend fun getBudgetTransactions(spaceId: String, budgetId: String): List<Transaction>
|
|
||||||
suspend fun createBudget(spaceId: String, type: Budget.BudgetType, budgetDto: CreateBudgetDTO): Budget
|
|
||||||
suspend fun updateBudget(spaceId: String, budgetDto: UpdateBudgetDTO): Budget
|
|
||||||
suspend fun deleteBudget(spaceId: String, budgetId: String)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
package space.luminic.finance.services
|
|
||||||
|
|
||||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
|
||||||
import kotlinx.coroutines.reactive.awaitSingle
|
|
||||||
import org.bson.Document
|
|
||||||
import org.bson.types.ObjectId
|
|
||||||
import org.springframework.data.domain.Sort
|
|
||||||
import org.springframework.data.domain.Sort.Direction
|
|
||||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.*
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.sort
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.LookupOperation
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.SetOperation.set
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.UnsetOperation.unset
|
|
||||||
import org.springframework.data.mongodb.core.aggregation.VariableOperators
|
|
||||||
import org.springframework.data.mongodb.core.query.Criteria
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import space.luminic.finance.dtos.BudgetDTO
|
|
||||||
import space.luminic.finance.models.Budget
|
|
||||||
import space.luminic.finance.models.NotFoundException
|
|
||||||
import space.luminic.finance.models.Transaction
|
|
||||||
import space.luminic.finance.repos.BudgetRepo
|
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class BudgetServiceImpl(
|
|
||||||
private val budgetRepo: BudgetRepo,
|
|
||||||
private val authService: AuthService,
|
|
||||||
private val categoryService: CategoryService,
|
|
||||||
private val mongoTemplate: ReactiveMongoTemplate,
|
|
||||||
private val spaceService: SpaceService,
|
|
||||||
private val transactionService: TransactionService,
|
|
||||||
) : BudgetService {
|
|
||||||
|
|
||||||
private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
|
||||||
|
|
||||||
val unwindCategories = unwind("categories", true)
|
|
||||||
val setCategoryIdOI = set("categories.categoryIdOI")
|
|
||||||
.toValue(ConvertOperators.valueOf("categories.categoryId").convertToObjectId())
|
|
||||||
val lookupCategory = lookup(
|
|
||||||
"categories", // from
|
|
||||||
"categories.categoryIdOI", // localField
|
|
||||||
"_id", // foreignField
|
|
||||||
"joinedCategory" // as
|
|
||||||
)
|
|
||||||
val unwindJoinedCategory = unwind("joinedCategory", true)
|
|
||||||
val setEmbeddedCategory = set("categories.category").toValue("\$joinedCategory")
|
|
||||||
val unsetTemps = unset("joinedCategory", "categories.categoryIdOI")
|
|
||||||
val groupBack: AggregationOperation = AggregationOperation {
|
|
||||||
Document(
|
|
||||||
"\$group", Document()
|
|
||||||
.append("_id", "\$_id")
|
|
||||||
.append("doc", Document("\$first", "\$\$ROOT"))
|
|
||||||
.append("categories", Document("\$push", "\$categories"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val setDocCategories: AggregationOperation = AggregationOperation {
|
|
||||||
Document("\$set", Document("doc.categories", "\$categories"))
|
|
||||||
}
|
|
||||||
val replaceRootDoc = replaceRoot("doc")
|
|
||||||
|
|
||||||
val addFieldsAsOJ = addFields()
|
|
||||||
.addField("createdByOI")
|
|
||||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
|
||||||
.addField("updatedByOI")
|
|
||||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
|
||||||
.build()
|
|
||||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
|
||||||
val unwindCreatedBy = unwind("createdBy")
|
|
||||||
|
|
||||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
|
||||||
val unwindUpdatedBy = unwind("updatedBy")
|
|
||||||
|
|
||||||
|
|
||||||
val matchCriteria = mutableListOf<Criteria>()
|
|
||||||
matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
|
||||||
matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
|
||||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
|
||||||
|
|
||||||
return listOf(matchStage,
|
|
||||||
unwindCategories,
|
|
||||||
setCategoryIdOI,
|
|
||||||
lookupCategory,
|
|
||||||
unwindJoinedCategory,
|
|
||||||
setEmbeddedCategory,
|
|
||||||
unsetTemps,
|
|
||||||
groupBack,
|
|
||||||
setDocCategories,
|
|
||||||
replaceRootDoc,
|
|
||||||
addFieldsAsOJ,
|
|
||||||
lookupCreatedBy,
|
|
||||||
unwindCreatedBy,
|
|
||||||
lookupUpdatedBy,
|
|
||||||
unwindUpdatedBy)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun getBudgets(spaceId: String, sortBy: String, sortDirection: String): List<Budget> {
|
|
||||||
|
|
||||||
require(spaceId.isNotBlank()) { "Space ID must not be blank" }
|
|
||||||
|
|
||||||
val allowedSortFields = setOf("dateFrom", "dateTo", "amount", "categoryName", "createdAt")
|
|
||||||
require(sortBy in allowedSortFields) { "Invalid sort field: $sortBy" }
|
|
||||||
|
|
||||||
val direction = when (sortDirection.uppercase()) {
|
|
||||||
"ASC" -> Direction.ASC
|
|
||||||
"DESC" -> Direction.DESC
|
|
||||||
else -> throw IllegalArgumentException("Sort direction must be 'ASC' or 'DESC'")
|
|
||||||
}
|
|
||||||
val sort = sort(Sort.by(direction, sortBy))
|
|
||||||
val basicAggregation = basicAggregation(spaceId)
|
|
||||||
val aggregation =
|
|
||||||
newAggregation(
|
|
||||||
*basicAggregation.toTypedArray(),
|
|
||||||
sort
|
|
||||||
)
|
|
||||||
|
|
||||||
return mongoTemplate.aggregate(aggregation, "budgets", Budget::class.java)
|
|
||||||
.collectList()
|
|
||||||
.awaitSingle()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getBudget(spaceId: String, budgetId: String): Budget {
|
|
||||||
val basicAggregation = basicAggregation(spaceId)
|
|
||||||
val matchStage = match(Criteria.where("_id").`is`(ObjectId(budgetId)))
|
|
||||||
val aggregation = newAggregation(matchStage, *basicAggregation.toTypedArray(), )
|
|
||||||
return mongoTemplate.aggregate(aggregation, "budgets", Budget::class.java).awaitFirstOrNull()
|
|
||||||
?: throw NotFoundException("Budget not found")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getBudgetTransactions(
|
|
||||||
spaceId: String,
|
|
||||||
budgetId: String
|
|
||||||
): List<Transaction> {
|
|
||||||
spaceService.checkSpace(spaceId)
|
|
||||||
val budget = getBudget(spaceId, budgetId)
|
|
||||||
val filter = TransactionService.TransactionsFilter(
|
|
||||||
dateFrom = budget.dateFrom,
|
|
||||||
dateTo = budget.dateTo
|
|
||||||
)
|
|
||||||
return transactionService.getTransactions(spaceId, filter, "date", "ASC")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun createBudget(
|
|
||||||
spaceId: String,
|
|
||||||
type: Budget.BudgetType,
|
|
||||||
budgetDto: BudgetDTO.CreateBudgetDTO
|
|
||||||
): Budget {
|
|
||||||
val user = authService.getSecurityUser()
|
|
||||||
val categories = categoryService.getCategories(spaceId)
|
|
||||||
val budget = Budget(
|
|
||||||
spaceId = spaceId,
|
|
||||||
type = type,
|
|
||||||
name = budgetDto.name,
|
|
||||||
description = budgetDto.description,
|
|
||||||
categories = categories.map { Budget.BudgetCategory(it.id!!, BigDecimal.ZERO) },
|
|
||||||
dateFrom = budgetDto.dateFrom,
|
|
||||||
dateTo = budgetDto.dateTo
|
|
||||||
)
|
|
||||||
return budgetRepo.save(budget).awaitSingle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateBudget(
|
|
||||||
spaceId: String,
|
|
||||||
budgetDto: BudgetDTO.UpdateBudgetDTO
|
|
||||||
): Budget {
|
|
||||||
val budget = getBudget(spaceId, budgetDto.id)
|
|
||||||
budgetDto.name?.let { name -> budget.name = name }
|
|
||||||
budgetDto.description?.let { description -> budget.description = description }
|
|
||||||
budgetDto.dateFrom?.let { dateFrom -> budget.dateFrom = dateFrom }
|
|
||||||
budgetDto.dateTo?.let { dateTo -> budget.dateTo = dateTo }
|
|
||||||
|
|
||||||
return budgetRepo.save(budget).awaitSingle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteBudget(spaceId: String, budgetId: String) {
|
|
||||||
val budget = getBudget(spaceId, budgetId)
|
|
||||||
budget.isDeleted = true
|
|
||||||
budgetRepo.save(budget).awaitSingle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
package space.luminic.finance.services
|
package space.luminic.finance.services
|
||||||
|
|
||||||
import space.luminic.finance.dtos.BudgetDTO
|
|
||||||
import space.luminic.finance.dtos.CategoryDTO
|
import space.luminic.finance.dtos.CategoryDTO
|
||||||
import space.luminic.finance.models.Category
|
import space.luminic.finance.models.Category
|
||||||
import space.luminic.finance.models.Space
|
|
||||||
|
|
||||||
interface CategoryService {
|
interface CategoryService {
|
||||||
suspend fun getCategories(spaceId: String): List<Category>
|
fun getCategories(spaceId: Int): List<Category>
|
||||||
suspend fun getCategory(spaceId: String, id: String): Category
|
fun getCategory(spaceId: Int, id: Int): Category
|
||||||
suspend fun createCategory(spaceId: String, category: CategoryDTO.CreateCategoryDTO): Category
|
fun createCategory(spaceId: Int, category: CategoryDTO.CreateCategoryDTO): Category
|
||||||
suspend fun updateCategory(spaceId: String,category: CategoryDTO.UpdateCategoryDTO): Category
|
fun createEtalonCategoriesForSpace(spaceId: Int): List<Category>
|
||||||
suspend fun deleteCategory(spaceId: String, id: String)
|
fun updateCategory(spaceId: Int,categoryId:Int, category: CategoryDTO.UpdateCategoryDTO): Category
|
||||||
suspend fun createCategoriesForSpace(spaceId: String): List<Category>
|
fun deleteCategory(spaceId: Int, id: Int)
|
||||||
}
|
}
|
||||||
@@ -1,108 +1,105 @@
|
|||||||
package space.luminic.finance.services
|
package space.luminic.finance.services
|
||||||
|
|
||||||
import kotlinx.coroutines.reactive.awaitSingle
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
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 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.dtos.CategoryDTO
|
||||||
import space.luminic.finance.models.Category
|
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.CategoryEtalonRepo
|
||||||
import space.luminic.finance.repos.CategoryRepo
|
import space.luminic.finance.repos.CategoryRepo
|
||||||
|
import space.luminic.finance.repos.SpaceRepo
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class CategoryServiceImpl(
|
class CategoryServiceImpl(
|
||||||
private val categoryRepo: CategoryRepo,
|
private val spaceRepo: SpaceRepo,
|
||||||
private val categoryEtalonRepo: CategoryEtalonRepo,
|
private val categoriesRepo: CategoryRepo,
|
||||||
private val reactiveMongoTemplate: ReactiveMongoTemplate,
|
private val categoriesEtalonRepo: CategoryEtalonRepo,
|
||||||
private val authService: AuthService,
|
private val authService: AuthService
|
||||||
) : CategoryService {
|
) : CategoryService {
|
||||||
|
override fun getCategories(spaceId: Int): List<Category> {
|
||||||
private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
val userId = authService.getSecurityUserId()
|
||||||
val addFieldsAsOJ = addFields()
|
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||||
.addField("createdByOI")
|
return categoriesRepo.findBySpaceId(spaceId)
|
||||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
|
||||||
.addField("updatedByOI")
|
|
||||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
|
||||||
.build()
|
|
||||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
|
||||||
val unwindCreatedBy = unwind("createdBy")
|
|
||||||
|
|
||||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
|
||||||
val unwindUpdatedBy = unwind("updatedBy")
|
|
||||||
val matchCriteria = mutableListOf<Criteria>()
|
|
||||||
matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
|
||||||
matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
|
||||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
|
||||||
|
|
||||||
return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getCategories(spaceId: String): List<Category> {
|
override fun getCategory(spaceId: Int, id: Int): Category {
|
||||||
val basicAggregation = basicAggregation(spaceId)
|
val userId = authService.getSecurityUserId()
|
||||||
val aggregation = newAggregation(*basicAggregation.toTypedArray())
|
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||||
return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).collectList().awaitSingle()
|
return categoriesRepo.findBySpaceIdAndId(spaceId, id)
|
||||||
|
?: throw NotFoundException("Category with id $id not found")
|
||||||
}
|
}
|
||||||
|
@Transactional
|
||||||
override suspend fun getCategory(spaceId: String, id: String): Category {
|
override fun createCategory(
|
||||||
val basicAggregation = basicAggregation(spaceId)
|
spaceId: Int,
|
||||||
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: CategoryDTO.CreateCategoryDTO
|
||||||
): Category {
|
): Category {
|
||||||
val createdCategory = Category(
|
val userId = authService.getSecurityUserId()
|
||||||
spaceId = spaceId,
|
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||||
type = category.type,
|
val newCategory = Category(
|
||||||
|
space = space,
|
||||||
name = category.name,
|
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,
|
type = category.type,
|
||||||
name = category.name,
|
description = category.description,
|
||||||
icon = category.icon,
|
icon = category.icon,
|
||||||
)
|
)
|
||||||
return categoryRepo.save(updatedCategory).awaitSingle()
|
return categoriesRepo.create(newCategory, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteCategory(spaceId: String, id: String) {
|
@Transactional(propagation = Propagation.NESTED)
|
||||||
val existingCategory = getCategory(spaceId, id)
|
override fun createEtalonCategoriesForSpace(
|
||||||
existingCategory.isDeleted = true
|
spaceId: Int
|
||||||
categoryRepo.save(existingCategory).awaitSingle()
|
): List<Category> {
|
||||||
}
|
val userId = authService.getSecurityUserId()
|
||||||
|
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||||
override suspend fun createCategoriesForSpace(spaceId: String): List<Category> {
|
val categories = categoriesEtalonRepo.findAll()
|
||||||
val etalonCategories = categoryEtalonRepo.findAll().collectList().awaitSingle()
|
val newCategories = mutableListOf<Category>()
|
||||||
val toCreate = etalonCategories.map {
|
categories.forEach { category ->
|
||||||
|
newCategories.add(
|
||||||
|
categoriesRepo.create(
|
||||||
Category(
|
Category(
|
||||||
spaceId = spaceId,
|
space = space,
|
||||||
type = it.type,
|
name = category.name,
|
||||||
name = it.name,
|
description = category.description,
|
||||||
icon = it.icon
|
type = category.type,
|
||||||
|
icon = category.icon,
|
||||||
|
), userId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return categoryRepo.saveAll(toCreate).collectList().awaitSingle()
|
|
||||||
|
return newCategories
|
||||||
|
}
|
||||||
|
@Transactional
|
||||||
|
override fun updateCategory(
|
||||||
|
spaceId: Int,
|
||||||
|
categoryId: Int,
|
||||||
|
category: CategoryDTO.UpdateCategoryDTO
|
||||||
|
): Category {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
val existingCategory = getCategory(spaceId, categoryId)
|
||||||
|
val newCategory = Category(
|
||||||
|
id = existingCategory.id,
|
||||||
|
space = space,
|
||||||
|
name = category.name,
|
||||||
|
description = category.description,
|
||||||
|
type = category.type,
|
||||||
|
icon = category.icon,
|
||||||
|
isDeleted = existingCategory.isDeleted,
|
||||||
|
createdBy = existingCategory.createdBy,
|
||||||
|
createdAt = existingCategory.createdAt,
|
||||||
|
)
|
||||||
|
return categoriesRepo.update(newCategory, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
override fun deleteCategory(spaceId: Int, id: Int) {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
val space = spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
categoriesRepo.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
//package space.luminic.finance.services
|
||||||
|
//
|
||||||
|
//import kotlinx.coroutines.reactive.awaitSingle
|
||||||
|
//import org.bson.types.ObjectId
|
||||||
|
//import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.Aggregation.addFields
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.Aggregation.lookup
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.Aggregation.match
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.Aggregation.unwind
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||||
|
//import org.springframework.data.mongodb.core.query.Criteria
|
||||||
|
//import org.springframework.stereotype.Service
|
||||||
|
//import space.luminic.finance.dtos.CategoryDTO
|
||||||
|
//import space.luminic.finance.models.Category
|
||||||
|
//import space.luminic.finance.repos.CategoryEtalonRepo
|
||||||
|
//import space.luminic.finance.repos.CategoryRepo
|
||||||
|
//
|
||||||
|
//@Service
|
||||||
|
//class CategoryServiceMongoImpl(
|
||||||
|
// private val categoryRepo: CategoryRepo,
|
||||||
|
// private val categoryEtalonRepo: CategoryEtalonRepo,
|
||||||
|
// private val reactiveMongoTemplate: ReactiveMongoTemplate,
|
||||||
|
// private val authService: AuthService,
|
||||||
|
//) : CategoryService {
|
||||||
|
//
|
||||||
|
// private fun basicAggregation(spaceId: String): List<AggregationOperation> {
|
||||||
|
// val addFieldsAsOJ = addFields()
|
||||||
|
// .addField("createdByOI")
|
||||||
|
// .withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||||
|
// .addField("updatedByOI")
|
||||||
|
// .withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||||
|
// .build()
|
||||||
|
// val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||||
|
// val unwindCreatedBy = unwind("createdBy")
|
||||||
|
//
|
||||||
|
// val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||||
|
// val unwindUpdatedBy = unwind("updatedBy")
|
||||||
|
// val matchCriteria = mutableListOf<Criteria>()
|
||||||
|
// matchCriteria.add(Criteria.where("spaceId").`is`(spaceId))
|
||||||
|
// matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
||||||
|
// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||||
|
//
|
||||||
|
// return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun getCategories(spaceId: String): List<Category> {
|
||||||
|
// val basicAggregation = basicAggregation(spaceId)
|
||||||
|
// val aggregation = newAggregation(*basicAggregation.toTypedArray())
|
||||||
|
// return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).collectList().awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun getCategory(spaceId: String, id: String): Category {
|
||||||
|
// val basicAggregation = basicAggregation(spaceId)
|
||||||
|
// val match = match(Criteria.where("_id").`is`(ObjectId(id)))
|
||||||
|
// val aggregation = newAggregation(*basicAggregation.toTypedArray(), match)
|
||||||
|
// return reactiveMongoTemplate.aggregate(aggregation, "categories", Category::class.java).awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// override suspend fun createCategory(
|
||||||
|
// spaceId: String,
|
||||||
|
// category: CategoryDTO.CreateCategoryDTO
|
||||||
|
// ): Category {
|
||||||
|
// val createdCategory = Category(
|
||||||
|
// spaceId = spaceId,
|
||||||
|
// type = category.type,
|
||||||
|
// name = category.name,
|
||||||
|
// icon = category.icon
|
||||||
|
// )
|
||||||
|
// return categoryRepo.save(createdCategory).awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun updateCategory(
|
||||||
|
// spaceId: String,
|
||||||
|
// category: CategoryDTO.UpdateCategoryDTO
|
||||||
|
// ): Category {
|
||||||
|
// val existingCategory = getCategory(spaceId, category.id)
|
||||||
|
// val updatedCategory = existingCategory.copy(
|
||||||
|
// type = category.type,
|
||||||
|
// name = category.name,
|
||||||
|
// icon = category.icon,
|
||||||
|
// )
|
||||||
|
// return categoryRepo.save(updatedCategory).awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun deleteCategory(spaceId: String, id: String) {
|
||||||
|
// val existingCategory = getCategory(spaceId, id)
|
||||||
|
// existingCategory.isDeleted = true
|
||||||
|
// categoryRepo.save(existingCategory).awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun createCategoriesForSpace(spaceId: String): List<Category> {
|
||||||
|
// val etalonCategories = categoryEtalonRepo.findAll().collectList().awaitSingle()
|
||||||
|
// val toCreate = etalonCategories.map {
|
||||||
|
// Category(
|
||||||
|
// spaceId = spaceId,
|
||||||
|
// type = it.type,
|
||||||
|
// name = it.name,
|
||||||
|
// icon = it.icon
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// return categoryRepo.saveAll(toCreate).collectList().awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package space.luminic.finance.services
|
|
||||||
|
|
||||||
import kotlinx.coroutines.reactor.mono
|
|
||||||
import org.springframework.data.domain.ReactiveAuditorAware
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class CoroutineAuditorAware(
|
|
||||||
private val authService: AuthService
|
|
||||||
) : ReactiveAuditorAware<String> {
|
|
||||||
override fun getCurrentAuditor(): Mono<String> =
|
|
||||||
mono {
|
|
||||||
authService.getSecurityUser().id!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,11 +6,11 @@ import space.luminic.finance.models.CurrencyRate
|
|||||||
|
|
||||||
interface CurrencyService {
|
interface CurrencyService {
|
||||||
|
|
||||||
suspend fun getCurrencies(): List<Currency>
|
fun getCurrencies(): List<Currency>
|
||||||
suspend fun getCurrency(currencyCode: String): Currency
|
fun getCurrency(currencyCode: String): Currency
|
||||||
suspend fun createCurrency(currency: CurrencyDTO): Currency
|
fun createCurrency(currency: CurrencyDTO): Currency
|
||||||
suspend fun updateCurrency(currency: CurrencyDTO): Currency
|
fun updateCurrency(currency: CurrencyDTO): Currency
|
||||||
suspend fun deleteCurrency(currencyCode: String)
|
fun deleteCurrency(currencyCode: String)
|
||||||
suspend fun createCurrencyRate(currencyCode: String): CurrencyRate
|
fun createCurrencyRate(currencyCode: String): CurrencyRate
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package space.luminic.finance.services
|
package space.luminic.finance.services
|
||||||
|
|
||||||
import kotlinx.coroutines.reactive.awaitSingle
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import space.luminic.finance.dtos.CurrencyDTO
|
import space.luminic.finance.dtos.CurrencyDTO
|
||||||
import space.luminic.finance.models.Currency
|
import space.luminic.finance.models.Currency
|
||||||
import space.luminic.finance.models.CurrencyRate
|
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 space.luminic.finance.repos.CurrencyRepo
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
@@ -13,39 +12,39 @@ import java.time.LocalDate
|
|||||||
@Service
|
@Service
|
||||||
class CurrencyServiceImpl(
|
class CurrencyServiceImpl(
|
||||||
private val currencyRepo: CurrencyRepo,
|
private val currencyRepo: CurrencyRepo,
|
||||||
private val currencyRateRepo: CurrencyRateRepo
|
|
||||||
) : CurrencyService {
|
) : CurrencyService {
|
||||||
|
override fun getCurrencies(): List<Currency> {
|
||||||
override suspend fun getCurrencies(): List<Currency> {
|
return currencyRepo.findAll()
|
||||||
return currencyRepo.findAll().collectList().awaitSingle()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getCurrency(currencyCode: String): Currency {
|
override fun getCurrency(currencyCode: String): Currency {
|
||||||
return currencyRepo.findById(currencyCode).awaitSingle()
|
return currencyRepo.findByCode(currencyCode)
|
||||||
|
?: throw NotFoundException("Currency code $currencyCode not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createCurrency(currency: CurrencyDTO): Currency {
|
override fun createCurrency(currency: CurrencyDTO): Currency {
|
||||||
val createdCurrency = Currency(currency.code, currency.name, currency.symbol)
|
val currency = Currency(currency.code, currency.name, currency.code)
|
||||||
return currencyRepo.save(createdCurrency).awaitSingle()
|
return currencyRepo.save(currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateCurrency(currency: CurrencyDTO): Currency {
|
override fun updateCurrency(currency: CurrencyDTO): Currency {
|
||||||
val existingCurrency = currencyRepo.findById(currency.code).awaitSingle()
|
getCurrency(currency.code)
|
||||||
val newCurrency = existingCurrency.copy(name = currency.name, symbol = currency.symbol)
|
val updatedCurrency =
|
||||||
return currencyRepo.save(newCurrency).awaitSingle()
|
Currency(
|
||||||
}
|
code = currency.code,
|
||||||
|
name = currency.name,
|
||||||
override suspend fun deleteCurrency(currencyCode: String) {
|
symbol = currency.symbol
|
||||||
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()
|
return currencyRepo.save(updatedCurrency)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteCurrency(currencyCode: String) {
|
||||||
|
currencyRepo.delete(currencyCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCurrencyRate(currencyCode: String): CurrencyRate {
|
||||||
|
print("createCurrencyRate")
|
||||||
|
val currency = getCurrency(currencyCode)
|
||||||
|
return CurrencyRate(currency = currency, rate = BigDecimal.ZERO, date = LocalDate.now())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//package space.luminic.finance.services
|
||||||
|
//
|
||||||
|
//import kotlinx.coroutines.reactive.awaitSingle
|
||||||
|
//import org.springframework.stereotype.Service
|
||||||
|
//import space.luminic.finance.dtos.CurrencyDTO
|
||||||
|
//import space.luminic.finance.models.Currency
|
||||||
|
//import space.luminic.finance.models.CurrencyRate
|
||||||
|
//import space.luminic.finance.repos.CurrencyRateRepo
|
||||||
|
//import space.luminic.finance.repos.CurrencyRepo
|
||||||
|
//import java.math.BigDecimal
|
||||||
|
//import java.time.LocalDate
|
||||||
|
//
|
||||||
|
//@Service
|
||||||
|
//class CurrencyServiceMongoImpl(
|
||||||
|
// private val currencyRepo: CurrencyRepo,
|
||||||
|
// private val currencyRateRepo: CurrencyRateRepo
|
||||||
|
//) : CurrencyService {
|
||||||
|
//
|
||||||
|
// override suspend fun getCurrencies(): List<Currency> {
|
||||||
|
// return currencyRepo.findAll().collectList().awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun getCurrency(currencyCode: String): Currency {
|
||||||
|
// return currencyRepo.findById(currencyCode).awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun createCurrency(currency: CurrencyDTO): Currency {
|
||||||
|
// val createdCurrency = Currency(currency.code, currency.name, currency.symbol)
|
||||||
|
// return currencyRepo.save(createdCurrency).awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun updateCurrency(currency: CurrencyDTO): Currency {
|
||||||
|
// val existingCurrency = currencyRepo.findById(currency.code).awaitSingle()
|
||||||
|
// val newCurrency = existingCurrency.copy(name = currency.name, symbol = currency.symbol)
|
||||||
|
// return currencyRepo.save(newCurrency).awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun deleteCurrency(currencyCode: String) {
|
||||||
|
// currencyRepo.deleteById(currencyCode).awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun createCurrencyRate(currencyCode: String): CurrencyRate {
|
||||||
|
// return currencyRateRepo.save(
|
||||||
|
// CurrencyRate(
|
||||||
|
// currencyCode = currencyCode,
|
||||||
|
// rate = BigDecimal(12.0),
|
||||||
|
// date = LocalDate.now(),
|
||||||
|
// )
|
||||||
|
// ).awaitSingle()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package space.luminic.finance.services
|
||||||
|
|
||||||
|
import space.luminic.finance.dtos.GoalDTO
|
||||||
|
import space.luminic.finance.models.Goal
|
||||||
|
|
||||||
|
interface GoalService {
|
||||||
|
fun findAllBySpaceId(spaceId: Int): List<Goal>
|
||||||
|
fun findBySpaceIdAndId(spaceId: Int, id: Int): Goal
|
||||||
|
fun create(spaceId: Int,goal: GoalDTO.CreateGoalDTO): Int
|
||||||
|
fun update(spaceId: Int, goalId: Int, goal: GoalDTO.UpdateGoalDTO)
|
||||||
|
fun delete(spaceId: Int, id: Int)
|
||||||
|
|
||||||
|
fun getComponents(spaceId: Int, goalId: Int): List<Goal.GoalComponent>
|
||||||
|
fun getComponent(spaceId: Int, goalId: Int, id: Int): Goal.GoalComponent?
|
||||||
|
fun createComponent(spaceId: Int, goalId: Int, component: Goal.GoalComponent): Int
|
||||||
|
fun updateComponent(spaceId: Int, goalId: Int, component: Goal.GoalComponent)
|
||||||
|
fun deleteComponent(spaceId: Int, goalId: Int, id: Int)
|
||||||
|
|
||||||
|
fun assignTransaction(spaceId: Int, goalId: Int, transactionId: Int)
|
||||||
|
fun refuseTransaction(spaceId: Int,goalId: Int, transactionId: Int)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package space.luminic.finance.services
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import space.luminic.finance.dtos.GoalDTO
|
||||||
|
import space.luminic.finance.models.Goal
|
||||||
|
import space.luminic.finance.models.NotFoundException
|
||||||
|
import space.luminic.finance.repos.GoalRepo
|
||||||
|
import space.luminic.finance.repos.SpaceRepo
|
||||||
|
import space.luminic.finance.repos.TransactionRepo
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class GoalServiceImpl(
|
||||||
|
private val goalRepo: GoalRepo,
|
||||||
|
private val spaceRepo: SpaceRepo,
|
||||||
|
private val authService: AuthService,
|
||||||
|
private val transactionRepo: TransactionRepo
|
||||||
|
) : GoalService {
|
||||||
|
override fun findAllBySpaceId(spaceId: Int): List<Goal> {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
return goalRepo.findAllBySpaceId(spaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findBySpaceIdAndId(spaceId: Int, id: Int): Goal {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
return goalRepo.findBySpaceIdAndId(spaceId, userId) ?: throw NotFoundException("Goal $id not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(spaceId: Int, goal: GoalDTO.CreateGoalDTO): Int {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
val creatingGoal = Goal(
|
||||||
|
type = goal.type,
|
||||||
|
name = goal.name,
|
||||||
|
amount = goal.amount,
|
||||||
|
untilDate = goal.date
|
||||||
|
)
|
||||||
|
return goalRepo.create(creatingGoal, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(spaceId: Int, goalId: Int, goal: GoalDTO.UpdateGoalDTO) {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
val existingGoal =
|
||||||
|
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||||
|
val updatedGoal = existingGoal.copy(
|
||||||
|
type = goal.type,
|
||||||
|
name = goal.name,
|
||||||
|
description = goal.description,
|
||||||
|
amount = goal.amount,
|
||||||
|
untilDate = goal.date
|
||||||
|
)
|
||||||
|
goalRepo.update(updatedGoal, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(spaceId: Int, id: Int) {
|
||||||
|
goalRepo.delete(spaceId, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getComponents(
|
||||||
|
spaceId: Int,
|
||||||
|
goalId: Int
|
||||||
|
): List<Goal.GoalComponent> {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||||
|
return goalRepo.getComponents(spaceId, goalId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getComponent(
|
||||||
|
spaceId: Int,
|
||||||
|
goalId: Int,
|
||||||
|
id: Int
|
||||||
|
): Goal.GoalComponent? {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||||
|
return goalRepo.getComponent(spaceId, goalId, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createComponent(
|
||||||
|
spaceId: Int,
|
||||||
|
goalId: Int,
|
||||||
|
component: Goal.GoalComponent
|
||||||
|
): Int {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||||
|
return goalRepo.createComponent(goalId, component, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateComponent(
|
||||||
|
spaceId: Int,
|
||||||
|
goalId: Int,
|
||||||
|
component: Goal.GoalComponent
|
||||||
|
) {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||||
|
val existingComponent = goalRepo.getComponent(spaceId, goalId, component.id!!)
|
||||||
|
?: throw NotFoundException("Component $goalId not found")
|
||||||
|
val updatedComponent = existingComponent.copy(
|
||||||
|
name = component.name,
|
||||||
|
amount = component.amount,
|
||||||
|
isDone = component.isDone,
|
||||||
|
date = component.date
|
||||||
|
)
|
||||||
|
goalRepo.updateComponent(goalId, updatedComponent.id!!, updatedComponent, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteComponent(spaceId: Int, goalId: Int, id: Int) {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||||
|
goalRepo.getComponent(spaceId, goalId, id) ?: throw NotFoundException("Component $goalId not found")
|
||||||
|
goalRepo.deleteComponent(goalId, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun assignTransaction(spaceId: Int, goalId: Int, transactionId: Int) {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||||
|
transactionRepo.findBySpaceIdAndId(spaceId, transactionId) ?: throw NotFoundException(
|
||||||
|
"Transaction $transactionId not found"
|
||||||
|
)
|
||||||
|
goalRepo.assignTransaction(goalId, transactionId)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refuseTransaction(spaceId: Int, goalId: Int, transactionId: Int) {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
goalRepo.findBySpaceIdAndId(spaceId, goalId) ?: throw NotFoundException("Goal $goalId not found")
|
||||||
|
transactionRepo.findBySpaceIdAndId(spaceId, transactionId) ?: throw NotFoundException(
|
||||||
|
"Transaction $transactionId not found"
|
||||||
|
)
|
||||||
|
goalRepo.refuseTransaction(goalId, transactionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package space.luminic.finance.services
|
||||||
|
|
||||||
|
import space.luminic.finance.dtos.RecurrentOperationDTO
|
||||||
|
import space.luminic.finance.models.RecurrentOperation
|
||||||
|
|
||||||
|
|
||||||
|
interface RecurrentOperationService {
|
||||||
|
fun findBySpaceId(spaceId: Int): List<RecurrentOperation>
|
||||||
|
fun findBySpaceIdAndId(spaceId: Int, id: Int): RecurrentOperation
|
||||||
|
fun create(spaceId: Int, operation: RecurrentOperationDTO.CreateRecurrentOperationDTO): Int
|
||||||
|
fun update(spaceId: Int, operationId: Int, operation: RecurrentOperationDTO.UpdateRecurrentOperationDTO)
|
||||||
|
fun delete(spaceId: Int, id: Int)
|
||||||
|
|
||||||
|
fun createRecurrentTransactions()
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package space.luminic.finance.services
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import space.luminic.finance.dtos.RecurrentOperationDTO
|
||||||
|
import space.luminic.finance.models.Category
|
||||||
|
import space.luminic.finance.models.NotFoundException
|
||||||
|
import space.luminic.finance.models.RecurrentOperation
|
||||||
|
import space.luminic.finance.models.Transaction
|
||||||
|
import space.luminic.finance.repos.RecurrentOperationRepo
|
||||||
|
import space.luminic.finance.repos.SpaceRepo
|
||||||
|
import space.luminic.finance.repos.TransactionRepo
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class RecurrentOperationServiceImpl(
|
||||||
|
private val authService: AuthService,
|
||||||
|
private val spaceRepo: SpaceRepo,
|
||||||
|
private val recurrentOperationRepo: RecurrentOperationRepo,
|
||||||
|
private val categoryService: CategoryService,
|
||||||
|
private val transactionService: TransactionService,
|
||||||
|
private val transactionRepo: TransactionRepo
|
||||||
|
) : RecurrentOperationService {
|
||||||
|
private val logger = LoggerFactory.getLogger(this.javaClass)
|
||||||
|
private val serviceScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||||
|
|
||||||
|
override fun findBySpaceId(spaceId: Int): List<RecurrentOperation> {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
return recurrentOperationRepo.findAllBySpaceId(spaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findBySpaceIdAndId(
|
||||||
|
spaceId: Int,
|
||||||
|
id: Int
|
||||||
|
): RecurrentOperation {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId)
|
||||||
|
return recurrentOperationRepo.findBySpaceIdAndId(spaceId, id)
|
||||||
|
?: throw NotFoundException("Cannot find recurrent operation with id ${id}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(spaceId: Int, operation: RecurrentOperationDTO.CreateRecurrentOperationDTO): Int {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
val space =
|
||||||
|
spaceRepo.findSpaceById(spaceId, userId) ?: throw NotFoundException("Cannot find space with id ${spaceId}")
|
||||||
|
val category = categoryService.getCategory(spaceId, operation.categoryId)
|
||||||
|
val creatingOperation = RecurrentOperation(
|
||||||
|
space = space,
|
||||||
|
category = category,
|
||||||
|
name = operation.name,
|
||||||
|
amount = operation.amount,
|
||||||
|
date = operation.date
|
||||||
|
)
|
||||||
|
|
||||||
|
val createdRecurrentId = recurrentOperationRepo.create(creatingOperation, userId)
|
||||||
|
val transactionsToCreate = mutableListOf<Transaction>()
|
||||||
|
serviceScope.launch {
|
||||||
|
runCatching {
|
||||||
|
val date = LocalDate.now()
|
||||||
|
for (i in 1..12) {
|
||||||
|
transactionsToCreate.add(
|
||||||
|
Transaction(
|
||||||
|
space = space,
|
||||||
|
type = if (category.type == Category.CategoryType.EXPENSE) Transaction.TransactionType.EXPENSE else Transaction.TransactionType.INCOME,
|
||||||
|
kind = Transaction.TransactionKind.PLANNING,
|
||||||
|
category = category,
|
||||||
|
comment = creatingOperation.name,
|
||||||
|
amount = creatingOperation.amount,
|
||||||
|
date = date.plusMonths(i.toLong()),
|
||||||
|
recurrentId = createdRecurrentId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
transactionRepo.createBatch(transactionsToCreate, userId)
|
||||||
|
// transactionService.batchCreate(spaceId, transactionsToCreate, userId)
|
||||||
|
}.onFailure {
|
||||||
|
logger.error("Error creating recurring operation", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdRecurrentId
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createRecurrentTransactions() {
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val recurrents = recurrentOperationRepo.findByDate(today.dayOfMonth)
|
||||||
|
recurrents.forEach {
|
||||||
|
transactionRepo.create(
|
||||||
|
Transaction(
|
||||||
|
space = it.space,
|
||||||
|
type = if (it.category.type == Category.CategoryType.EXPENSE) Transaction.TransactionType.EXPENSE else Transaction.TransactionType.INCOME,
|
||||||
|
kind = Transaction.TransactionKind.PLANNING,
|
||||||
|
category = it.category,
|
||||||
|
comment = it.name,
|
||||||
|
amount = it.amount,
|
||||||
|
date = today.plusMonths(13),
|
||||||
|
recurrentId = it.id
|
||||||
|
), it.createdBy?.id!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
20
src/main/kotlin/space/luminic/finance/services/Scheduler.kt
Normal file
20
src/main/kotlin/space/luminic/finance/services/Scheduler.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package space.luminic.finance.services
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@EnableScheduling
|
||||||
|
@Service
|
||||||
|
class Scheduler(
|
||||||
|
private val recurrentOperationService: RecurrentOperationService
|
||||||
|
) {
|
||||||
|
private val log = LoggerFactory.getLogger(Scheduler::class.java)
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 3 * * *")
|
||||||
|
fun createRecurrentAfter13Month() {
|
||||||
|
log.info("Creating recurrent after 13 month")
|
||||||
|
recurrentOperationService.createRecurrentTransactions()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
package space.luminic.finance.services
|
package space.luminic.finance.services
|
||||||
|
|
||||||
import space.luminic.finance.dtos.SpaceDTO
|
import space.luminic.finance.dtos.SpaceDTO
|
||||||
import space.luminic.finance.models.Budget
|
|
||||||
import space.luminic.finance.models.Space
|
import space.luminic.finance.models.Space
|
||||||
|
|
||||||
interface SpaceService {
|
interface SpaceService {
|
||||||
|
|
||||||
suspend fun checkSpace(spaceId: String): Space
|
fun checkSpace(spaceId: Int): Space
|
||||||
suspend fun getSpaces(): List<Space>
|
fun getSpaces(): List<Space>
|
||||||
suspend fun getSpace(id: String): Space
|
fun getSpace(id: Int, userId: Int?): Space
|
||||||
suspend fun createSpace(space: SpaceDTO.CreateSpaceDTO): Space
|
fun createSpace(space: SpaceDTO.CreateSpaceDTO): Int
|
||||||
suspend fun updateSpace(spaceId: String, space: SpaceDTO.UpdateSpaceDTO): Space
|
fun updateSpace(spaceId: Int, space: SpaceDTO.UpdateSpaceDTO): Int
|
||||||
suspend fun deleteSpace(spaceId: String)
|
fun deleteSpace(spaceId: Int)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,149 +1,73 @@
|
|||||||
package space.luminic.finance.services
|
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.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import space.luminic.finance.dtos.SpaceDTO
|
import space.luminic.finance.dtos.SpaceDTO
|
||||||
import space.luminic.finance.models.Budget
|
|
||||||
import space.luminic.finance.models.NotFoundException
|
import space.luminic.finance.models.NotFoundException
|
||||||
import space.luminic.finance.models.Space
|
import space.luminic.finance.models.Space
|
||||||
import space.luminic.finance.models.User
|
|
||||||
import space.luminic.finance.repos.SpaceRepo
|
import space.luminic.finance.repos.SpaceRepo
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class SpaceServiceImpl(
|
class SpaceServiceImpl(
|
||||||
private val authService: AuthService,
|
private val authService: AuthService,
|
||||||
private val spaceRepo: SpaceRepo,
|
private val spaceRepo: SpaceRepo,
|
||||||
private val mongoTemplate: ReactiveMongoTemplate,
|
private val categoryService: CategoryService
|
||||||
) : SpaceService {
|
) : SpaceService {
|
||||||
|
override fun checkSpace(spaceId: Int): Space {
|
||||||
private fun basicAggregation(user: User): List<AggregationOperation> {
|
return getSpace(spaceId, null)
|
||||||
val addFieldsAsOJ = addFields()
|
|
||||||
.addField("createdByOI")
|
|
||||||
.withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
|
||||||
.addField("updatedByOI")
|
|
||||||
.withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
|
||||||
.build()
|
|
||||||
val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
|
||||||
val unwindCreatedBy = unwind("createdBy")
|
|
||||||
|
|
||||||
val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
|
||||||
val unwindUpdatedBy = unwind("updatedBy")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val matchCriteria = mutableListOf<Criteria>()
|
|
||||||
matchCriteria.add(
|
|
||||||
Criteria().orOperator(
|
|
||||||
Criteria.where("ownerId").`is`(user.id),
|
|
||||||
Criteria.where("participantsIds").`is`(user.id)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
|
||||||
val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
|
||||||
|
|
||||||
return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ownerAndParticipantsLookups(): List<AggregationOperation>{
|
// @Cacheable(cacheNames = ["spaces"])
|
||||||
val addOwnerAsOJ = addFields()
|
override fun getSpaces(): List<Space> {
|
||||||
.addField("ownerIdAsObjectId")
|
val user = authService.getSecurityUserId()
|
||||||
.withValue(ConvertOperators.valueOf("ownerId").convertToObjectId())
|
val spaces = spaceRepo.findSpacesAvailableForUser(user)
|
||||||
.addField("participantsIdsAsObjectId")
|
return spaces
|
||||||
.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 {
|
|
||||||
|
override fun getSpace(id: Int, userId: Int?): Space {
|
||||||
|
val user = userId ?: 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 user = authService.getSecurityUser()
|
||||||
val space = getSpace(spaceId)
|
val creatingSpace = Space(
|
||||||
|
|
||||||
// Проверяем доступ пользователя к пространству
|
|
||||||
return if (space.participants!!.none { it.id.toString() == user.id }) {
|
|
||||||
throw IllegalArgumentException("User does not have access to this Space")
|
|
||||||
} else space
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSpaces(): List<Space> {
|
|
||||||
val user = authService.getSecurityUser()
|
|
||||||
val basicAggregation = basicAggregation(user)
|
|
||||||
val ownerAndParticipantsLookup = ownerAndParticipantsLookups()
|
|
||||||
|
|
||||||
val sort = sort(Sort.by(Sort.Direction.DESC, "createdAt"))
|
|
||||||
val aggregation = newAggregation(
|
|
||||||
*basicAggregation.toTypedArray(),
|
|
||||||
*ownerAndParticipantsLookup.toTypedArray(),
|
|
||||||
sort,
|
|
||||||
)
|
|
||||||
return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).collectList().awaitSingle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSpace(id: String): Space {
|
|
||||||
val user = authService.getSecurityUser()
|
|
||||||
val basicAggregation = basicAggregation(user)
|
|
||||||
val ownerAndParticipantsLookup = ownerAndParticipantsLookups()
|
|
||||||
|
|
||||||
val aggregation = newAggregation(
|
|
||||||
*basicAggregation.toTypedArray(),
|
|
||||||
*ownerAndParticipantsLookup.toTypedArray(),
|
|
||||||
)
|
|
||||||
return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).awaitFirstOrNull()
|
|
||||||
?: throw NotFoundException("Space not found")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun createSpace(space: SpaceDTO.CreateSpaceDTO): Space {
|
|
||||||
val owner = authService.getSecurityUser()
|
|
||||||
val createdSpace = Space(
|
|
||||||
name = space.name,
|
name = space.name,
|
||||||
ownerId = owner.id!!,
|
owner = user,
|
||||||
|
participants = setOf(user)
|
||||||
participantsIds = listOf(owner.id!!),
|
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
createdSpace.owner = owner
|
val userId = authService.getSecurityUserId()
|
||||||
createdSpace.participants?.toMutableList()?.add(owner)
|
val savedSpace = spaceRepo.create(creatingSpace, userId)
|
||||||
val savedSpace = spaceRepo.save(createdSpace).awaitSingle()
|
if (space.createBasicCategories) {
|
||||||
|
categoryService.createEtalonCategoriesForSpace(savedSpace)
|
||||||
|
}
|
||||||
return savedSpace
|
return savedSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateSpace(spaceId: String, space: SpaceDTO.UpdateSpaceDTO): Space {
|
@Transactional
|
||||||
val existingSpace = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found")
|
override fun updateSpace(
|
||||||
val updatedSpace = existingSpace.copy(
|
spaceId: Int,
|
||||||
|
space: SpaceDTO.UpdateSpaceDTO
|
||||||
|
): Int {
|
||||||
|
val userId = authService.getSecurityUserId()
|
||||||
|
val existingSpace = getSpace(spaceId, null)
|
||||||
|
val updatedSpace = Space(
|
||||||
|
id = existingSpace.id,
|
||||||
name = space.name,
|
name = space.name,
|
||||||
|
owner = existingSpace.owner,
|
||||||
|
participants = existingSpace.participants,
|
||||||
|
isDeleted = existingSpace.isDeleted,
|
||||||
|
createdBy = existingSpace.createdBy,
|
||||||
|
createdAt = existingSpace.createdAt,
|
||||||
)
|
)
|
||||||
updatedSpace.owner = existingSpace.owner
|
return spaceRepo.update(updatedSpace, userId)
|
||||||
updatedSpace.participants = existingSpace.participants
|
|
||||||
return spaceRepo.save(updatedSpace).awaitFirst()
|
|
||||||
}
|
}
|
||||||
|
@Transactional
|
||||||
override suspend fun deleteSpace(spaceId: String) {
|
override fun deleteSpace(spaceId: Int) {
|
||||||
val space = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found")
|
spaceRepo.delete(spaceId)
|
||||||
space.isDeleted = true
|
|
||||||
spaceRepo.save(space).awaitFirst()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
//package space.luminic.finance.services
|
||||||
|
//
|
||||||
|
//import com.mongodb.client.model.Aggregates.sort
|
||||||
|
//import kotlinx.coroutines.reactive.awaitFirst
|
||||||
|
//import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||||
|
//import kotlinx.coroutines.reactive.awaitSingle
|
||||||
|
//import org.springframework.data.domain.Sort
|
||||||
|
//import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.Aggregation.*
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||||
|
//
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.ConvertOperators
|
||||||
|
//import org.springframework.data.mongodb.core.aggregation.VariableOperators
|
||||||
|
//import org.springframework.data.mongodb.core.query.Criteria
|
||||||
|
//import org.springframework.stereotype.Service
|
||||||
|
//import space.luminic.finance.dtos.SpaceDTO
|
||||||
|
//import space.luminic.finance.models.NotFoundException
|
||||||
|
//import space.luminic.finance.models.Space
|
||||||
|
//import space.luminic.finance.models.User
|
||||||
|
//import space.luminic.finance.repos.SpaceRepo
|
||||||
|
//
|
||||||
|
//@Service
|
||||||
|
//class SpaceServiceMongoImpl(
|
||||||
|
// private val authService: AuthService,
|
||||||
|
// private val spaceRepo: SpaceRepo,
|
||||||
|
// private val mongoTemplate: ReactiveMongoTemplate,
|
||||||
|
//) : SpaceService {
|
||||||
|
//
|
||||||
|
// private fun basicAggregation(user: User): List<AggregationOperation> {
|
||||||
|
// val addFieldsAsOJ = addFields()
|
||||||
|
// .addField("createdByOI")
|
||||||
|
// .withValue(ConvertOperators.valueOf("createdById").convertToObjectId())
|
||||||
|
// .addField("updatedByOI")
|
||||||
|
// .withValue(ConvertOperators.valueOf("updatedById").convertToObjectId())
|
||||||
|
// .build()
|
||||||
|
// val lookupCreatedBy = lookup("users", "createdByOI", "_id", "createdBy")
|
||||||
|
// val unwindCreatedBy = unwind("createdBy")
|
||||||
|
//
|
||||||
|
// val lookupUpdatedBy = lookup("users", "updatedByOI", "_id", "updatedBy")
|
||||||
|
// val unwindUpdatedBy = unwind("updatedBy")
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// val matchCriteria = mutableListOf<Criteria>()
|
||||||
|
// matchCriteria.add(
|
||||||
|
// Criteria().orOperator(
|
||||||
|
// Criteria.where("ownerId").`is`(user.id),
|
||||||
|
// Criteria.where("participantsIds").`is`(user.id)
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// matchCriteria.add(Criteria.where("isDeleted").`is`(false))
|
||||||
|
// val matchStage = match(Criteria().andOperator(*matchCriteria.toTypedArray()))
|
||||||
|
//
|
||||||
|
// return listOf(addFieldsAsOJ, lookupCreatedBy, unwindCreatedBy, lookupUpdatedBy, unwindUpdatedBy, matchStage)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun ownerAndParticipantsLookups(): List<AggregationOperation>{
|
||||||
|
// val addOwnerAsOJ = addFields()
|
||||||
|
// .addField("ownerIdAsObjectId")
|
||||||
|
// .withValue(ConvertOperators.valueOf("ownerId").convertToObjectId())
|
||||||
|
// .addField("participantsIdsAsObjectId")
|
||||||
|
// .withValue(
|
||||||
|
// VariableOperators.Map.itemsOf("participantsIds")
|
||||||
|
// .`as`("id")
|
||||||
|
// .andApply(
|
||||||
|
// ConvertOperators.valueOf("$\$id").convertToObjectId()
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// .build()
|
||||||
|
// val lookupOwner = lookup("users", "ownerIdAsObjectId", "_id", "owner")
|
||||||
|
// val unwindOwner = unwind("owner")
|
||||||
|
// val lookupUsers = lookup("users", "participantsIdsAsObjectId", "_id", "participants")
|
||||||
|
// return listOf(addOwnerAsOJ, lookupOwner, unwindOwner, lookupUsers)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun checkSpace(spaceId: String): Space {
|
||||||
|
// val user = authService.getSecurityUser()
|
||||||
|
// val space = getSpace(spaceId)
|
||||||
|
//
|
||||||
|
// // Проверяем доступ пользователя к пространству
|
||||||
|
// return if (space.participants!!.none { it.id.toString() == user.id }) {
|
||||||
|
// throw IllegalArgumentException("User does not have access to this Space")
|
||||||
|
// } else space
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun getSpaces(): List<Space> {
|
||||||
|
// val user = authService.getSecurityUser()
|
||||||
|
// val basicAggregation = basicAggregation(user)
|
||||||
|
// val ownerAndParticipantsLookup = ownerAndParticipantsLookups()
|
||||||
|
//
|
||||||
|
// val sort = sort(Sort.by(Sort.Direction.DESC, "createdAt"))
|
||||||
|
// val aggregation = newAggregation(
|
||||||
|
// *basicAggregation.toTypedArray(),
|
||||||
|
// *ownerAndParticipantsLookup.toTypedArray(),
|
||||||
|
// sort,
|
||||||
|
// )
|
||||||
|
// return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).collectList().awaitSingle()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun getSpace(id: String): Space {
|
||||||
|
// val user = authService.getSecurityUser()
|
||||||
|
// val basicAggregation = basicAggregation(user)
|
||||||
|
// val ownerAndParticipantsLookup = ownerAndParticipantsLookups()
|
||||||
|
//
|
||||||
|
// val aggregation = newAggregation(
|
||||||
|
// *basicAggregation.toTypedArray(),
|
||||||
|
// *ownerAndParticipantsLookup.toTypedArray(),
|
||||||
|
// )
|
||||||
|
// return mongoTemplate.aggregate(aggregation, "spaces", Space::class.java).awaitFirstOrNull()
|
||||||
|
// ?: throw NotFoundException("Space not found")
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun createSpace(space: SpaceDTO.CreateSpaceDTO): Space {
|
||||||
|
// val owner = authService.getSecurityUser()
|
||||||
|
// val createdSpace = Space(
|
||||||
|
// name = space.name,
|
||||||
|
// ownerId = owner.id!!,
|
||||||
|
//
|
||||||
|
// participantsIds = listOf(owner.id!!),
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// )
|
||||||
|
// createdSpace.owner = owner
|
||||||
|
// createdSpace.participants?.toMutableList()?.add(owner)
|
||||||
|
// val savedSpace = spaceRepo.save(createdSpace).awaitSingle()
|
||||||
|
// return savedSpace
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun updateSpace(spaceId: String, space: SpaceDTO.UpdateSpaceDTO): Space {
|
||||||
|
// val existingSpace = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found")
|
||||||
|
// val updatedSpace = existingSpace.copy(
|
||||||
|
// name = space.name,
|
||||||
|
// )
|
||||||
|
// updatedSpace.owner = existingSpace.owner
|
||||||
|
// updatedSpace.participants = existingSpace.participants
|
||||||
|
// return spaceRepo.save(updatedSpace).awaitFirst()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override suspend fun deleteSpace(spaceId: String) {
|
||||||
|
// val space = spaceRepo.findById(spaceId).awaitFirstOrNull() ?: throw NotFoundException("Space not found")
|
||||||
|
// space.isDeleted = true
|
||||||
|
// spaceRepo.save(space).awaitFirst()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -1,113 +1,111 @@
|
|||||||
package space.luminic.finance.services
|
//package space.luminic.finance.services
|
||||||
|
//
|
||||||
|
//
|
||||||
import com.interaso.webpush.VapidKeys
|
//import com.interaso.webpush.VapidKeys
|
||||||
import com.interaso.webpush.WebPushService
|
//import com.interaso.webpush.WebPushService
|
||||||
import kotlinx.coroutines.Dispatchers
|
//import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.coroutineScope
|
//import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.launch
|
//import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.reactive.awaitSingle
|
//import kotlinx.coroutines.reactive.awaitSingle
|
||||||
import kotlinx.serialization.encodeToString
|
//import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
//import kotlinx.serialization.json.Json
|
||||||
import org.bson.types.ObjectId
|
//import org.slf4j.LoggerFactory
|
||||||
import org.slf4j.LoggerFactory
|
//import org.springframework.dao.DuplicateKeyException
|
||||||
import org.springframework.dao.DuplicateKeyException
|
//import org.springframework.stereotype.Service
|
||||||
import org.springframework.stereotype.Service
|
//import space.luminic.finance.models.PushMessage
|
||||||
import space.luminic.finance.models.PushMessage
|
//import space.luminic.finance.models.Subscription
|
||||||
import space.luminic.finance.models.Subscription
|
//import space.luminic.finance.models.User
|
||||||
import space.luminic.finance.models.SubscriptionDTO
|
//import space.luminic.finance.repos.SubscriptionRepo
|
||||||
import space.luminic.finance.models.User
|
//import space.luminic.finance.services.VapidConstants.VAPID_PRIVATE_KEY
|
||||||
import space.luminic.finance.repos.SubscriptionRepo
|
//import space.luminic.finance.services.VapidConstants.VAPID_PUBLIC_KEY
|
||||||
import space.luminic.finance.services.VapidConstants.VAPID_PRIVATE_KEY
|
//import space.luminic.finance.services.VapidConstants.VAPID_SUBJECT
|
||||||
import space.luminic.finance.services.VapidConstants.VAPID_PUBLIC_KEY
|
//import kotlin.collections.forEach
|
||||||
import space.luminic.finance.services.VapidConstants.VAPID_SUBJECT
|
//import kotlin.jvm.javaClass
|
||||||
import kotlin.collections.forEach
|
//import kotlin.text.orEmpty
|
||||||
import kotlin.jvm.javaClass
|
//
|
||||||
import kotlin.text.orEmpty
|
//object VapidConstants {
|
||||||
|
// const val VAPID_PUBLIC_KEY =
|
||||||
object VapidConstants {
|
// "BKmMyBUhpkcmzYWcYsjH_spqcy0zf_8eVtZo60f7949TgLztCmv3YD0E_vtV2dTfECQ4sdLdPK3ICDcyOkCqr84"
|
||||||
const val VAPID_PUBLIC_KEY =
|
// const val VAPID_PRIVATE_KEY = "YeJH_0LhnVYN6RdxMidgR6WMYlpGXTJS3HjT9V3NSGI"
|
||||||
"BKmMyBUhpkcmzYWcYsjH_spqcy0zf_8eVtZo60f7949TgLztCmv3YD0E_vtV2dTfECQ4sdLdPK3ICDcyOkCqr84"
|
// const val VAPID_SUBJECT = "mailto:voroninvyu@gmail.com"
|
||||||
const val VAPID_PRIVATE_KEY = "YeJH_0LhnVYN6RdxMidgR6WMYlpGXTJS3HjT9V3NSGI"
|
//}
|
||||||
const val VAPID_SUBJECT = "mailto:voroninvyu@gmail.com"
|
//
|
||||||
}
|
//@Service
|
||||||
|
//class SubscriptionService(private val subscriptionRepo: SubscriptionRepo) {
|
||||||
@Service
|
//
|
||||||
class SubscriptionService(private val subscriptionRepo: SubscriptionRepo) {
|
// private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
// private val pushService =
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
// WebPushService(
|
||||||
private val pushService =
|
// subject = VAPID_SUBJECT,
|
||||||
WebPushService(
|
// vapidKeys = VapidKeys.fromUncompressedBytes(VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY)
|
||||||
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()
|
||||||
suspend fun sendToSpaceOwner(ownerId: String, message: PushMessage) = coroutineScope {
|
//
|
||||||
val ownerTokens = subscriptionRepo.findByUserIdAndIsActive(ObjectId(ownerId)).collectList().awaitSingle()
|
// ownerTokens.forEach { token ->
|
||||||
|
// launch(Dispatchers.IO) { // Теперь мы точно в корутин скоупе
|
||||||
ownerTokens.forEach { token ->
|
// try {
|
||||||
launch(Dispatchers.IO) { // Теперь мы точно в корутин скоупе
|
// sendNotification(token.endpoint, token.p256dh, token.auth, message)
|
||||||
try {
|
// } catch (e: Exception) {
|
||||||
sendNotification(token.endpoint, token.p256dh, token.auth, message)
|
// logger.error("Ошибка при отправке уведомления: ${e.message}", e)
|
||||||
} catch (e: Exception) {
|
// }
|
||||||
logger.error("Ошибка при отправке уведомления: ${e.message}", e)
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
}
|
//
|
||||||
|
// suspend fun sendNotification(endpoint: String, p256dh: String, auth: String, payload: PushMessage) {
|
||||||
|
// try {
|
||||||
suspend fun sendNotification(endpoint: String, p256dh: String, auth: String, payload: PushMessage) {
|
// pushService.send(
|
||||||
try {
|
// payload = Json.encodeToString(payload),
|
||||||
pushService.send(
|
// endpoint = endpoint,
|
||||||
payload = Json.encodeToString(payload),
|
// p256dh = p256dh,
|
||||||
endpoint = endpoint,
|
// auth = auth
|
||||||
p256dh = p256dh,
|
// )
|
||||||
auth = auth
|
// logger.info("Уведомление успешно отправлено на endpoint: $endpoint")
|
||||||
)
|
//
|
||||||
logger.info("Уведомление успешно отправлено на endpoint: $endpoint")
|
// } catch (e: Exception) {
|
||||||
|
// logger.error("Ошибка при отправке уведомления на endpoint $endpoint: ${e.message}")
|
||||||
} catch (e: Exception) {
|
// throw e
|
||||||
logger.error("Ошибка при отправке уведомления на endpoint $endpoint: ${e.message}")
|
// }
|
||||||
throw e
|
// }
|
||||||
}
|
//
|
||||||
}
|
//
|
||||||
|
// suspend fun sendToAll(payload: PushMessage) {
|
||||||
|
//
|
||||||
suspend fun sendToAll(payload: PushMessage) {
|
// subscriptionRepo.findAll().collectList().awaitSingle().forEach { sub ->
|
||||||
|
//
|
||||||
subscriptionRepo.findAll().collectList().awaitSingle().forEach { sub ->
|
// try {
|
||||||
|
// sendNotification(sub.endpoint, sub.p256dh, sub.auth, payload)
|
||||||
try {
|
// } catch (e: Exception) {
|
||||||
sendNotification(sub.endpoint, sub.p256dh, sub.auth, payload)
|
// sub.isActive = false
|
||||||
} catch (e: Exception) {
|
// subscriptionRepo.save(sub).awaitSingle()
|
||||||
sub.isActive = false
|
// }
|
||||||
subscriptionRepo.save(sub).awaitSingle()
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
}
|
//
|
||||||
|
// suspend fun subscribe(subscriptionDTO: SubscriptionDTO, user: User): String {
|
||||||
|
// val subscription = Subscription(
|
||||||
suspend fun subscribe(subscriptionDTO: SubscriptionDTO, user: User): String {
|
// id = null,
|
||||||
val subscription = Subscription(
|
// user = user,
|
||||||
id = null,
|
// endpoint = subscriptionDTO.endpoint,
|
||||||
user = user,
|
// auth = subscriptionDTO.keys["auth"].orEmpty(),
|
||||||
endpoint = subscriptionDTO.endpoint,
|
// p256dh = subscriptionDTO.keys["p256dh"].orEmpty(),
|
||||||
auth = subscriptionDTO.keys["auth"].orEmpty(),
|
// isActive = true
|
||||||
p256dh = subscriptionDTO.keys["p256dh"].orEmpty(),
|
// )
|
||||||
isActive = true
|
//
|
||||||
)
|
// return try {
|
||||||
|
// val savedSubscription = subscriptionRepo.save(subscription).awaitSingle()
|
||||||
return try {
|
// "Subscription created with ID: ${savedSubscription.id}"
|
||||||
val savedSubscription = subscriptionRepo.save(subscription).awaitSingle()
|
// } catch (e: DuplicateKeyException) {
|
||||||
"Subscription created with ID: ${savedSubscription.id}"
|
// logger.info("Subscription already exists. Skipping.")
|
||||||
} catch (e: DuplicateKeyException) {
|
// "Subscription already exists. Skipping."
|
||||||
logger.info("Subscription already exists. Skipping.")
|
// } catch (e: Exception) {
|
||||||
"Subscription already exists. Skipping."
|
// logger.error("Error while saving subscription: ${e.message}")
|
||||||
} catch (e: Exception) {
|
// throw kotlin.RuntimeException("Error while saving subscription")
|
||||||
logger.error("Error while saving subscription: ${e.message}")
|
// }
|
||||||
throw kotlin.RuntimeException("Error while saving subscription")
|
// }
|
||||||
}
|
//}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,43 +1,51 @@
|
|||||||
package space.luminic.finance.services
|
package space.luminic.finance.services
|
||||||
|
|
||||||
import kotlinx.coroutines.reactor.awaitSingle
|
|
||||||
import org.springframework.cache.annotation.CacheEvict
|
import org.springframework.cache.annotation.CacheEvict
|
||||||
import org.springframework.stereotype.Service
|
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
|
||||||
import space.luminic.finance.models.Token.TokenStatus
|
import space.luminic.finance.models.Token.TokenStatus
|
||||||
import space.luminic.finance.repos.TokenRepo
|
import space.luminic.finance.repos.TokenRepo
|
||||||
import java.time.LocalDateTime
|
import java.time.Instant
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class TokenService(private val tokenRepository: TokenRepo) {
|
class TokenService(
|
||||||
|
private val userService: UserService,
|
||||||
|
private val tokenRepo: TokenRepo) {
|
||||||
|
|
||||||
@CacheEvict("tokens", allEntries = true)
|
@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(
|
val newToken = Token(
|
||||||
token = token,
|
token = token,
|
||||||
username = username,
|
user = user,
|
||||||
issuedAt = LocalDateTime.now(),
|
issuedAt = Instant.now(),
|
||||||
expiresAt = expiresAt
|
expiresAt = expiresAt
|
||||||
)
|
)
|
||||||
return tokenRepository.save(newToken).awaitSingle()
|
return tokenRepo.create(newToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getToken(token: String): Mono<Token> {
|
fun getToken(token: String): Token {
|
||||||
return tokenRepository.findByToken(token)
|
return tokenRepo.findByToken(token) ?: throw AuthException("Токен не валиден")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun revokeToken(token: String) {
|
fun revokeToken(token: String) {
|
||||||
val tokenDetail =
|
val tokenDetail = getToken(token)
|
||||||
tokenRepository.findByToken(token).block()!!
|
val updatedToken = Token(
|
||||||
val updatedToken = tokenDetail.copy(status = TokenStatus.REVOKED)
|
id = tokenDetail.id,
|
||||||
tokenRepository.save(updatedToken).block()
|
token = tokenDetail.token,
|
||||||
|
user = tokenDetail.user,
|
||||||
|
status = TokenStatus.REVOKED,
|
||||||
|
issuedAt = tokenDetail.issuedAt,
|
||||||
|
expiresAt = tokenDetail.expiresAt
|
||||||
|
)
|
||||||
|
tokenRepo.update(updatedToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@CacheEvict("tokens", allEntries = true)
|
@CacheEvict("tokens", allEntries = true)
|
||||||
fun deleteExpiredTokens() {
|
fun deleteExpiredTokens() {
|
||||||
tokenRepository.deleteByExpiresAtBefore(LocalDateTime.now())
|
tokenRepo.deleteByExpiresAtBefore(Instant.now())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,14 +7,21 @@ import java.time.LocalDate
|
|||||||
interface TransactionService {
|
interface TransactionService {
|
||||||
|
|
||||||
data class TransactionsFilter(
|
data class TransactionsFilter(
|
||||||
val accountId: String,
|
|
||||||
val dateFrom: LocalDate? = null,
|
val dateFrom: LocalDate? = null,
|
||||||
val dateTo: LocalDate? = null,
|
val dateTo: LocalDate? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun getTransactions(spaceId: String, filter: TransactionsFilter, sortBy: String, sortDirection: String): List<Transaction>
|
fun getTransactions(
|
||||||
suspend fun getTransaction(spaceId: String, transactionId: String): Transaction
|
spaceId: Int,
|
||||||
suspend fun createTransaction(spaceId: String, transaction: TransactionDTO.CreateTransactionDTO): Transaction
|
filter: TransactionsFilter,
|
||||||
suspend fun updateTransaction(spaceId: String, transaction: TransactionDTO.UpdateTransactionDTO): Transaction
|
sortBy: String,
|
||||||
suspend fun deleteTransaction(spaceId: String, transactionId: String)
|
sortDirection: String
|
||||||
|
): List<Transaction>
|
||||||
|
|
||||||
|
fun getTransaction(spaceId: Int, transactionId: Int): Transaction
|
||||||
|
fun createTransaction(spaceId: Int, transaction: TransactionDTO.CreateTransactionDTO): Int
|
||||||
|
fun batchCreate(spaceId: Int, transactions: List<TransactionDTO.CreateTransactionDTO>, createdById: Int?)
|
||||||
|
fun updateTransaction(spaceId: Int, transactionId: Int, transaction: TransactionDTO.UpdateTransactionDTO): Int
|
||||||
|
fun deleteTransaction(spaceId: Int, transactionId: Int)
|
||||||
|
fun deleteByRecurrentId(spaceId: Int, recurrentId: Int)
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user