diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..58fb3eb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# Step 1: Build the application using Eclipse Temurin JDK 21 +FROM maven:3.9.8-eclipse-temurin-21 AS build + +WORKDIR /app + +# Copy Gradle wrapper and dependencies for caching +COPY gradle gradle +COPY gradlew . +COPY build.gradle.kts . +COPY settings.gradle.kts . +COPY src src + +# Give execution permission to Gradle wrapper +RUN chmod +x ./gradlew + +# Build the JAR file +RUN ./gradlew bootJar + +# Step 2: Create a minimal runtime image (JDK is required for Spring Boot) + +FROM openjdk:21 +WORKDIR /app + +# Copy the built JAR from the builder stage +COPY --from=build /app/build/libs/*.jar app.jar + +# Expose port 8080 +EXPOSE 8080 + +# Run the application +ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..907c3df --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,77 @@ +plugins { + kotlin("jvm") version "1.9.25" + kotlin("plugin.spring") version "1.9.25" + id("org.springframework.boot") version "3.4.3" + id("io.spring.dependency-management") version "1.1.7" + kotlin("plugin.jpa") version "1.9.25" + kotlin("kapt") version "1.9.25" +} + +group = "org.octopus.internal" +version = "1.0-SNAPSHOT" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +configurations { + compileOnly { + extendsFrom(configurations.annotationProcessor.get()) + } +} + +kapt { + correctErrorTypes = true +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-mail") +// implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("org.springframework.boot:spring-boot-starter-actuator") + developmentOnly("org.springframework.boot:spring-boot-devtools") + implementation("org.postgresql:postgresql:42.7.2") + implementation("org.jetbrains.kotlin:kotlin-reflect") + compileOnly("org.projectlombok:lombok") + annotationProcessor("org.projectlombok:lombok") + + implementation("org.mapstruct:mapstruct:1.6.0") + kapt("org.mapstruct:mapstruct-processor:1.6.0") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.jetbrains.kotlin:kotlin-reflect") + kapt("org.springframework.boot:spring-boot-configuration-processor") + +// implementation("io.jsonwebtoken:jjwt-api:0.12.6") +// runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6") +// runtimeOnly("io.jsonwebtoken:jjwt-gson:0.12.6") +} + +kotlin { + compilerOptions { + freeCompilerArgs.addAll("-Xjsr305=strict") + } +} + +allOpen { + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") +} + +tasks.withType { + useJUnitPlatform() +} + +tasks.register("testJeko") { + commandLine("docker", "build", "-t", "be-planning-local:$version", ".") +} \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100755 index 0000000..e850bb4 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,38 @@ +services: + app: + image: be-planning-local + container_name: be + ports: + - "8080:8081" + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/planning_db + SPRING_DATASOURCE_USERNAME: lorca_usr + SPRING_DATASOURCE_PASSWORD: lorca_pwd + depends_on: + - db + + db: + container_name: postgres + image: postgres:latest + restart: always + environment: + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PW} + - POSTGRES_DB=${POSTGRES_DB} + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + + pgadmin: + container_name: pgadmin + image: dpage/pgadmin4:latest + environment: + - PGADMIN_DEFAULT_EMAIL=${PGADMIN_MAIL} + - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_PW} + ports: + - "5050:80" + restart: always + +volumes: + pgdata: \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e18bc25 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ad09733 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "OctopusPlanningApplication" + diff --git a/src/main/kotlin/org/octopus/internal/OctopusPlanningApplication.kt b/src/main/kotlin/org/octopus/internal/OctopusPlanningApplication.kt new file mode 100755 index 0000000..f3e090b --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/OctopusPlanningApplication.kt @@ -0,0 +1,11 @@ +package org.octopus.internal + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication(scanBasePackages = ["org.octopus.internal"]) +class OctopusPlanningApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/src/main/kotlin/org/octopus/internal/common/enums/EBusinessException.kt b/src/main/kotlin/org/octopus/internal/common/enums/EBusinessException.kt new file mode 100755 index 0000000..6351988 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/common/enums/EBusinessException.kt @@ -0,0 +1,6 @@ +package org.octopus.internal.common.enums + +enum class EBusinessException(val msg: String) { + ENTITY_WITH_ID_NOT_FOUND("%s with id %s not found"), + INVALID_REQUEST("Invalid request for %s with reason: %s") +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/common/enums/EEntryType.kt b/src/main/kotlin/org/octopus/internal/common/enums/EEntryType.kt new file mode 100644 index 0000000..b17397c --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/common/enums/EEntryType.kt @@ -0,0 +1,7 @@ +package org.octopus.internal.common.enums + +enum class EEntryType(val type: String) { + INCOME("INCOME"), + OUTCOME("OUTCOME"), + INVESTMENT("INVESTMENT") +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/common/exceptions/OctopusPlanningException.kt b/src/main/kotlin/org/octopus/internal/common/exceptions/OctopusPlanningException.kt new file mode 100755 index 0000000..68c9e34 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/common/exceptions/OctopusPlanningException.kt @@ -0,0 +1,13 @@ +package org.octopus.internal.common.exceptions + +import org.octopus.internal.common.enums.EBusinessException + +class OctopusPlanningException(val ex: EBusinessException, override val message: String) : Exception() { + + companion object { + fun create(exc: EBusinessException, vararg args: Any): OctopusPlanningException { + return OctopusPlanningException(exc, exc.msg.format(*args)) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/common/mappers/EntryMapper.kt b/src/main/kotlin/org/octopus/internal/common/mappers/EntryMapper.kt new file mode 100755 index 0000000..53934c2 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/common/mappers/EntryMapper.kt @@ -0,0 +1,20 @@ +package org.octopus.internal.common.mappers + +import org.mapstruct.Mapper +import org.octopus.internal.common.models.Entry +import org.octopus.internal.db.entities.EntryEntity + +@Mapper(componentModel = "spring") +interface EntryMapper : GenericMapper + + +/* +* +* +@Mapper(componentModel = "spring") +interface WorkerMapper : GenericMapper { + @Mapping(target = "authUser", ignore = true) + override fun toEntity(model: Worker): WorkerEntity +} +* +* */ \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/common/mappers/GenericMapper.kt b/src/main/kotlin/org/octopus/internal/common/mappers/GenericMapper.kt new file mode 100755 index 0000000..3635ca2 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/common/mappers/GenericMapper.kt @@ -0,0 +1,13 @@ +package org.octopus.internal.common.mappers + +interface GenericMapper { + + fun toEntity(model: M): E + fun toModel(entity: E): M + + fun toEntities(models: MutableList): MutableList = + models.map { toEntity(it) }.toMutableList() + + fun toModels(entities: MutableList): MutableList = + entities.map { toModel(it) }.toMutableList() +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/common/models/Entry.kt b/src/main/kotlin/org/octopus/internal/common/models/Entry.kt new file mode 100755 index 0000000..9350d04 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/common/models/Entry.kt @@ -0,0 +1,14 @@ +package org.octopus.internal.common.models + +import org.octopus.internal.common.enums.EEntryType +import java.util.* + +data class Entry( + var id: Long?, + val type: EEntryType, + val amount: Double, + val fixedDate: Date? = null, + val recurrent: Boolean? = false, + val recurrentMonths: List? = mutableListOf(), + val endDate: Date? = null +) \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/config/security/JwtAuthConfiguration.kt b/src/main/kotlin/org/octopus/internal/config/security/JwtAuthConfiguration.kt new file mode 100755 index 0000000..89ca218 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/config/security/JwtAuthConfiguration.kt @@ -0,0 +1,46 @@ +package org.octopus.internal.config.security +// +//import org.octopus.lorca_core.repositories.UserAuthRepository +//import org.springframework.context.annotation.Bean +//import org.springframework.context.annotation.Configuration +//import org.springframework.security.authentication.AuthenticationManager +//import org.springframework.security.authentication.AuthenticationProvider +//import org.springframework.security.authentication.dao.DaoAuthenticationProvider +//import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration +//import org.springframework.security.core.userdetails.UserDetailsService +//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +// +// +//@Configuration +class JwtAuthConfiguration( +// val userAuthRepository: UserAuthRepository +) { +// +// @Bean +// fun userDetailsService(): UserDetailsService { +// return UserDetailsService { username: String? -> +// userAuthRepository.getByUsername(username ?: "") +// } +// } +// +// @Bean +// fun passwordEncoder(): BCryptPasswordEncoder { +// return BCryptPasswordEncoder() +// } +// +// @Bean +// @Throws(Exception::class) +// fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager { +// return config.authenticationManager +// } +// +// @Bean +// fun authenticationProvider(): AuthenticationProvider { +// val authProvider = DaoAuthenticationProvider() +// +// authProvider.setUserDetailsService(userDetailsService()) +// authProvider.setPasswordEncoder(passwordEncoder()) +// +// return authProvider +// } +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/config/security/JwtAuthenticationFilter.kt b/src/main/kotlin/org/octopus/internal/config/security/JwtAuthenticationFilter.kt new file mode 100755 index 0000000..b1f85cb --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/config/security/JwtAuthenticationFilter.kt @@ -0,0 +1,68 @@ +package org.octopus.internal.config.security +// +//import jakarta.servlet.FilterChain +//import jakarta.servlet.ServletException +//import jakarta.servlet.http.HttpServletRequest +//import jakarta.servlet.http.HttpServletResponse +//import org.octopus.lorca_core.services.JwtService +//import org.springframework.lang.NonNull +//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +//import org.springframework.security.core.Authentication +//import org.springframework.security.core.authority.SimpleGrantedAuthority +//import org.springframework.security.core.context.SecurityContextHolder +//import org.springframework.security.core.userdetails.UserDetailsService +//import org.springframework.security.web.authentication.WebAuthenticationDetailsSource +//import org.springframework.stereotype.Component +//import org.springframework.web.filter.OncePerRequestFilter +//import org.springframework.web.servlet.HandlerExceptionResolver +//import java.io.IOException +// +//@Component +class JwtAuthenticationFilter( +// private val jwtService: JwtService, +// private val userDetailsService: UserDetailsService, +// private val handlerExceptionResolver: HandlerExceptionResolver +//) : OncePerRequestFilter() { +) { +// @Throws(ServletException::class, IOException::class) +// override fun doFilterInternal( +// @NonNull request: HttpServletRequest, +// @NonNull response: HttpServletResponse, +// @NonNull filterChain: FilterChain +// ) { +// val authHeader = request.getHeader("Authorization") +// +// if (authHeader == null || !authHeader.startsWith("Bearer ")) { +// filterChain.doFilter(request, response) +// return +// } +// +// try { +// val jwt = authHeader.substring(7) +// val userEmail = jwtService.extractUsername(jwt) +// +// val authentication: Authentication? = SecurityContextHolder.getContext().authentication +// +// if (authentication == null) { +// val userDetails = userDetailsService.loadUserByUsername(userEmail) +// +// val roles = jwtService.getRoles(jwt) +// +// if (jwtService.isTokenValid(jwt, userDetails)) { +// val authToken = UsernamePasswordAuthenticationToken( +// userDetails, +// null, +// roles.map { SimpleGrantedAuthority("ROLE_$it") } +// ) +// +// authToken.details = WebAuthenticationDetailsSource().buildDetails(request) +// SecurityContextHolder.getContext().authentication = authToken +// } +// } +// +// filterChain.doFilter(request, response) +// } catch (exception: Exception) { +// handlerExceptionResolver.resolveException(request, response, null, exception) +// } +// } +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/config/security/SecurityConfig.kt b/src/main/kotlin/org/octopus/internal/config/security/SecurityConfig.kt new file mode 100755 index 0000000..c65c4b6 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/config/security/SecurityConfig.kt @@ -0,0 +1,47 @@ +package org.octopus.internal.config.security +// +//import org.springframework.context.annotation.Bean +//import org.springframework.context.annotation.Configuration +//import org.springframework.security.authentication.AuthenticationProvider +//import org.springframework.security.authorization.AuthorizationDecision +//import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity +//import org.springframework.security.config.annotation.web.builders.HttpSecurity +//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +//import org.springframework.security.config.http.SessionCreationPolicy +//import org.springframework.security.web.SecurityFilterChain +//import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter +// +// +//@Configuration +//@EnableWebSecurity +//@EnableMethodSecurity(securedEnabled = true) + +class SecurityConfig( +// val authenticationProvider: AuthenticationProvider, +// val jwtAuthenticationFilter: JwtAuthenticationFilter +) { +// +// @Bean +// fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { +// http +// .authorizeHttpRequests { auth -> +// auth +// .requestMatchers("**").permitAll() +// .requestMatchers("/workers/register/**").access { authentication, _ -> +// AuthorizationDecision( +// authentication.get().isAuthenticated && authentication.get().authorities.isEmpty() +// ) +// } +// .anyRequest().hasAnyRole(*EUserRoles.entries.map { role -> role.role }.toTypedArray()) +// } +// .sessionManagement { session -> +// session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) +// } +// .authenticationProvider(authenticationProvider) +// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java) +// .csrf { it.disable() } +// +// return http.build() +// } +} + diff --git a/src/main/kotlin/org/octopus/internal/db/EntryJpa.kt b/src/main/kotlin/org/octopus/internal/db/EntryJpa.kt new file mode 100755 index 0000000..bc9589d --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/db/EntryJpa.kt @@ -0,0 +1,13 @@ +package org.octopus.internal.db + +import org.octopus.internal.common.enums.EEntryType +import org.octopus.internal.db.entities.EntryEntity +import org.springframework.data.jpa.repository.JpaRepository +import java.util.Date + +interface EntryJpa : JpaRepository { + fun findAllByType(type: EEntryType): MutableList + fun findAllByFixedDateIsNullAndRecurrentIsTrueAndEndDateIsNotNullAndEndDateBefore(endDate: Date): MutableList +// fun findAllByFixedDateIsNullAndRecurrentIsTrueAndRecurrentMonthsIsNotEmpty(): MutableList + fun findAllByFixedDateIsNotNullAndFixedDateBefore(fixedDate: Date): MutableList +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/db/entities/EntryEntity.kt b/src/main/kotlin/org/octopus/internal/db/entities/EntryEntity.kt new file mode 100644 index 0000000..b6d564e --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/db/entities/EntryEntity.kt @@ -0,0 +1,20 @@ +package org.octopus.internal.db.entities + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import org.octopus.internal.common.enums.EEntryType +import java.util.Date + +@Entity(name = "data_entries") +data class EntryEntity( + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long = 0L, + val type: EEntryType, + val amount: Double, + val fixedDate: Date? = null, + val recurrent: Boolean? = false, + val recurrentMonths: List? = mutableListOf(), + val endDate: Date? = null +) \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/repositories/EntryRepository.kt b/src/main/kotlin/org/octopus/internal/repositories/EntryRepository.kt new file mode 100755 index 0000000..f8d3e53 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/repositories/EntryRepository.kt @@ -0,0 +1,14 @@ +package org.octopus.internal.repositories + +import org.octopus.internal.common.enums.EEntryType +import org.octopus.internal.common.models.Entry +import java.util.Date + +interface EntryRepository { + fun getById(id: Long): Entry + fun createEntry(entry: Entry): Entry + fun getAllRecurrentMonthlyToEndDate(endDate: Date): MutableList + fun getAllRecurrentOnSpecificMonths(): MutableList + fun getAllFixedUpToEndDate(endDate: Date): MutableList + fun getAllByType(type: EEntryType): MutableList +} diff --git a/src/main/kotlin/org/octopus/internal/repositories/impl/EntryRepositoryImpl.kt b/src/main/kotlin/org/octopus/internal/repositories/impl/EntryRepositoryImpl.kt new file mode 100755 index 0000000..0cc608c --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/repositories/impl/EntryRepositoryImpl.kt @@ -0,0 +1,57 @@ +package org.octopus.internal.repositories.impl + +import org.octopus.internal.common.enums.EBusinessException +import org.octopus.internal.common.enums.EEntryType +import org.octopus.internal.common.exceptions.OctopusPlanningException +import org.octopus.internal.common.mappers.EntryMapper +import org.octopus.internal.common.models.Entry +import org.octopus.internal.db.EntryJpa +import org.octopus.internal.repositories.EntryRepository +import org.springframework.stereotype.Component +import java.util.* + +@Component +class EntryRepositoryImpl( + protected val jpa: EntryJpa, + protected val mapper: EntryMapper +) : EntryRepository { + + override fun getById(id: Long): Entry { + return mapper.toModel( + jpa.findById(id).orElseThrow { + OctopusPlanningException.create( + EBusinessException.ENTITY_WITH_ID_NOT_FOUND, + Entry::class.java.simpleName, + id + ) + }) + } + + override fun createEntry(entry: Entry): Entry { + return mapper.toModel(jpa.save(mapper.toEntity(entry))) + } + + override fun getAllByType(type: EEntryType): MutableList { + return mapper.toModels(jpa.findAllByType(type)) + } + + override fun getAllRecurrentMonthlyToEndDate(endDate: Date): MutableList { + return mapper.toModels( + jpa.findAllByFixedDateIsNullAndRecurrentIsTrueAndEndDateIsNotNullAndEndDateBefore(endDate) + ) + } + + override fun getAllRecurrentOnSpecificMonths(): MutableList { + return mapper.toModels( +// jpa.findAllByFixedDateIsNullAndRecurrentIsTrueAndRecurrentMonthsIsNotEmpty() + jpa.findAllByType(EEntryType.INCOME) + ) + } + + override fun getAllFixedUpToEndDate(endDate: Date): MutableList { + return mapper.toModels( + jpa.findAllByFixedDateIsNotNullAndFixedDateBefore(endDate) + ) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/services/EntryService.kt b/src/main/kotlin/org/octopus/internal/services/EntryService.kt new file mode 100755 index 0000000..c72d50c --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/services/EntryService.kt @@ -0,0 +1,7 @@ +package org.octopus.internal.services + +import org.octopus.internal.common.models.Entry + +interface EntryService { + fun getAllEntries(): MutableList +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/services/impl/EntryServiceImpl.kt b/src/main/kotlin/org/octopus/internal/services/impl/EntryServiceImpl.kt new file mode 100755 index 0000000..9eec26f --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/services/impl/EntryServiceImpl.kt @@ -0,0 +1,14 @@ +package org.octopus.internal.services.impl + +import org.octopus.internal.common.models.Entry +import org.octopus.internal.repositories.EntryRepository +import org.octopus.internal.services.EntryService +import org.springframework.stereotype.Service + +@Service +class EntryServiceImpl(val repo: EntryRepository) : EntryService { + override fun getAllEntries(): MutableList { + return repo.getAllRecurrentOnSpecificMonths() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/controllers/EntryController.kt b/src/main/kotlin/org/octopus/internal/web/controllers/EntryController.kt new file mode 100755 index 0000000..5d36a6e --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/controllers/EntryController.kt @@ -0,0 +1,30 @@ +package org.octopus.internal.web.controllers + +import lombok.AllArgsConstructor +import org.octopus.internal.common.models.Entry +import org.octopus.internal.services.EntryService +import org.octopus.internal.web.utils.responses.WebResponse +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/entries") +@AllArgsConstructor +class EntryController( + val entryService: EntryService +) { + + @GetMapping + fun getAllClients(): WebResponse> { + return WebResponse.ok(entryService.getAllEntries()) + } + + +// @GetMapping("/{id}") +// fun getClient(@PathVariable("id") id: String, @RequestParam( +// "includeDeactivated", +// required = false +// ) includeDeactivated: Boolean = false): WebResponse { +// return WebResponse.ok(clientService.getClientById(id.toLong())) +// } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/BaseAdvice.kt b/src/main/kotlin/org/octopus/internal/web/utils/BaseAdvice.kt new file mode 100755 index 0000000..19d6228 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/BaseAdvice.kt @@ -0,0 +1,126 @@ +package org.octopus.internal.web.utils + +import org.octopus.internal.common.enums.EBusinessException +import org.octopus.internal.common.exceptions.OctopusPlanningException +import org.octopus.internal.web.utils.responses.WebResponse +import org.springframework.http.HttpStatus +import org.springframework.http.converter.HttpMessageNotReadableException +//import org.springframework.security.authorization.AuthorizationDeniedException +import org.springframework.web.HttpRequestMethodNotSupportedException +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.context.request.ServletWebRequest +import org.springframework.web.context.request.WebRequest +import org.springframework.web.servlet.resource.NoResourceFoundException + +@RestControllerAdvice +class BaseAdvice { + + @ExceptionHandler(OctopusPlanningException::class) + fun handleLorcaBusinessException( + ex: OctopusPlanningException, + request: WebRequest + ): WebResponse { + return WebResponse.ko( + deductStatus(ex.ex), + ex.message + ) + } + + private fun deductStatus(ex: EBusinessException): HttpStatus { + return when (ex) { + EBusinessException.ENTITY_WITH_ID_NOT_FOUND -> HttpStatus.NOT_FOUND + EBusinessException.INVALID_REQUEST -> HttpStatus.BAD_REQUEST + } + } + + @ExceptionHandler(NumberFormatException::class) + fun handleNumberFormatException( + ex: NumberFormatException, + request: WebRequest + ): WebResponse { + return WebResponse.ko( + HttpStatus.BAD_REQUEST, + "${HttpStatus.BAD_REQUEST.reasonPhrase}: ${ex.message}" + ) + } + + + @ExceptionHandler(NoResourceFoundException::class) + fun handleNoResourceFoundException( + ex: NoResourceFoundException, + request: WebRequest + ): WebResponse { + return WebResponse.ko( + HttpStatus.NOT_FOUND, + "${HttpStatus.NOT_FOUND.reasonPhrase}: ${(request as ServletWebRequest).request.requestURI}" + + ) + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException::class) + fun handleHttpRequestMethodNotSupportedException( + ex: HttpRequestMethodNotSupportedException, + request: WebRequest + ): WebResponse { + return WebResponse.ko( + HttpStatus.METHOD_NOT_ALLOWED, + "${HttpStatus.METHOD_NOT_ALLOWED.reasonPhrase}: ${(request as ServletWebRequest).request.requestURI}" + + ) + } + + + @ExceptionHandler(MethodArgumentNotValidException::class) + fun handleMethodArgumentNotValidException( + ex: MethodArgumentNotValidException, + request: WebRequest + ): WebResponse { + val errors = + ex.bindingResult.fieldErrors.map { + "${it.field} - ${it.defaultMessage}" + } + return WebResponse.ko( + HttpStatus.NOT_ACCEPTABLE, + errors + ) + } + + + @ExceptionHandler(HttpMessageNotReadableException::class) + fun handleHttpMessageNotReadableException( + ex: HttpMessageNotReadableException, + request: WebRequest + ): WebResponse { + return WebResponse.ko( + HttpStatus.NOT_ACCEPTABLE, + "${HttpStatus.NOT_ACCEPTABLE.reasonPhrase}: JSON parse error" + + ) + } + +// @ExceptionHandler(AuthorizationDeniedException::class) +// fun handleAuthorizationDeniedException( +// ex: AuthorizationDeniedException, +// request: WebRequest +// ): WebResponse { +// return WebResponse.ko( +// HttpStatus.FORBIDDEN, +// "${HttpStatus.FORBIDDEN.reasonPhrase}: ${ex.message}" +// +// ) +// } + + + @ExceptionHandler(Exception::class) + fun handleException( + ex: Exception, + request: WebRequest + ): WebResponse { + return WebResponse.ko( + HttpStatus.INTERNAL_SERVER_ERROR, + ex.message + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/LoggingAspect.kt b/src/main/kotlin/org/octopus/internal/web/utils/LoggingAspect.kt new file mode 100755 index 0000000..59ee777 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/LoggingAspect.kt @@ -0,0 +1,39 @@ +package org.octopus.internal.web.utils + + +//@Aspect +//@Component +class LoggingAspect { +// @Around("execution(* org.octopus.lorca_core.web.controllers.*.*(..))") +// @Throws(Throwable::class) +// fun logMethodDetails(proceedingJoinPoint: ProceedingJoinPoint): Any? { +// val methodName = proceedingJoinPoint.signature.toShortString() +// val arguments = proceedingJoinPoint.args +// +// // Log method entry and arguments +// logger.info("Entering method: {} with arguments: {}", methodName, Arrays.toString(arguments)) +// +// var result: Any? = null +// +// try { +// // Proceed with method execution +// result = proceedingJoinPoint.proceed() +// logger.info("Exiting method: {} with result: {}", methodName, result) +// } catch (ex: Throwable) { +// // Handle exception logging +// logger.error( +// "Exception in method: {} with arguments: {} and exception: {}", +// methodName, +// arguments.contentToString(), +// ex.message +// ) +// throw ex // Re-throw the exception so that Spring's exception resolver can handle it +// } +// +// return result +// } +// +// companion object { +// private val logger: Logger = LoggerFactory.getLogger(LoggingAspect::class.java) +// } +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/AddRoleDto.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/AddRoleDto.kt new file mode 100755 index 0000000..480ba44 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/AddRoleDto.kt @@ -0,0 +1,8 @@ +package org.octopus.internal.web.utils.dtos + +import org.octopus.internal.web.utils.dtos.validators.UsernameValidator + +data class AddRoleDto( + @field:UsernameValidator.Validate + val username: String +) \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/ClientDto.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/ClientDto.kt new file mode 100755 index 0000000..b668209 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/ClientDto.kt @@ -0,0 +1,24 @@ +package org.octopus.internal.web.utils.dtos + +import org.octopus.internal.web.utils.dtos.validators.DniValidator +import org.octopus.internal.web.utils.dtos.validators.ExpValidator +import org.octopus.internal.web.utils.dtos.validators.NameValidator +import org.octopus.internal.web.utils.dtos.validators.PhoneValidator +import java.util.* + +data class ClientDto( + @field:NameValidator.Validate + val name: String, + @field:NameValidator.Validate + val surname: String, + @field:DniValidator.Validate + val dni: String, + @field:PhoneValidator.Validate + val phone: String, + @field:ExpValidator.Validate + val numExp: String, + @field:ExpValidator.Validate + val numUser: String, + val dateOfBirth: Date, + val deactivated: Boolean? = false +) \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/UserAuthDto.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/UserAuthDto.kt new file mode 100755 index 0000000..8124276 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/UserAuthDto.kt @@ -0,0 +1,11 @@ +package org.octopus.internal.web.utils.dtos + +import org.octopus.internal.web.utils.dtos.validators.B64Validator +import org.octopus.internal.web.utils.dtos.validators.UsernameValidator + +data class UserAuthDto( + @field:UsernameValidator.Validate + val username: String, + @field:B64Validator.Validate + val password: String +) \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/WorkerDto.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/WorkerDto.kt new file mode 100755 index 0000000..6681833 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/WorkerDto.kt @@ -0,0 +1,18 @@ +package org.octopus.internal.web.utils.dtos + +import org.octopus.internal.web.utils.dtos.validators.DniValidator +import org.octopus.internal.web.utils.dtos.validators.EmailValidator +import org.octopus.internal.web.utils.dtos.validators.NameValidator +import java.util.* + +data class WorkerDto( + @field:NameValidator.Validate + val name: String, + @field:NameValidator.Validate + val surname: String, + @field:DniValidator.Validate + val dni: String, + val dateOfBirth: Date, + @field:EmailValidator.Validate + val email: String +) \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/B64Validator.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/B64Validator.kt new file mode 100755 index 0000000..c7ee125 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/B64Validator.kt @@ -0,0 +1,33 @@ +package org.octopus.internal.web.utils.dtos.validators + +import jakarta.validation.Constraint +import jakarta.validation.ConstraintValidator +import jakarta.validation.ConstraintValidatorContext +import jakarta.validation.Payload +import kotlin.reflect.KClass + + +class B64Validator : ConstraintValidator { + override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean { + val pattern = Regex("^[-A-Za-z0-9+/]*={0,3}\$") + if (value == null || value.matches(pattern)) { + return true + } + + context.disableDefaultConstraintViolation() + context.buildConstraintViolationWithTemplate("Invalid value '$value' : this is not b64") + .addConstraintViolation() + + return false + } + + @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Constraint(validatedBy = [B64Validator::class]) + annotation class Validate( + val message: String = "", + val groups: Array> = [], + val payload: Array> = [] + ) +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/DniValidator.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/DniValidator.kt new file mode 100755 index 0000000..1dc763c --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/DniValidator.kt @@ -0,0 +1,34 @@ +package org.octopus.internal.web.utils.dtos.validators + +import jakarta.validation.Constraint +import jakarta.validation.ConstraintValidator +import jakarta.validation.ConstraintValidatorContext +import jakarta.validation.Payload +import kotlin.reflect.KClass + + +class DniValidator : ConstraintValidator { + override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean { + val pattern = Regex("^\\d{8}[A-Z]$") + + if (value == null || value.matches(pattern)) { + return true + } + + context.disableDefaultConstraintViolation() + context.buildConstraintViolationWithTemplate("Invalid value '$value' : 8 numbers and a letter are expected.") + .addConstraintViolation() + + return false + } + + @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Constraint(validatedBy = [DniValidator::class]) + annotation class Validate( + val message: String = "", + val groups: Array> = [], + val payload: Array> = [] + ) +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/EmailValidator.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/EmailValidator.kt new file mode 100755 index 0000000..82843bf --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/EmailValidator.kt @@ -0,0 +1,33 @@ +package org.octopus.internal.web.utils.dtos.validators + +import jakarta.validation.Constraint +import jakarta.validation.ConstraintValidator +import jakarta.validation.ConstraintValidatorContext +import jakarta.validation.Payload +import kotlin.reflect.KClass + + +class EmailValidator : ConstraintValidator { + override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean { + val pattern = Regex("^(?=.{1,64}@.{1,255}$)([a-zA-Z0-9._%+-]{1,64})@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$") + if (value == null || value.matches(pattern)) { + return true + } + + context.disableDefaultConstraintViolation() + context.buildConstraintViolationWithTemplate("Invalid value '$value' : Email pattern is wrong.") + .addConstraintViolation() + + return false + } + + @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Constraint(validatedBy = [EmailValidator::class]) + annotation class Validate( + val message: String = "", + val groups: Array> = [], + val payload: Array> = [] + ) +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/ExpValidator.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/ExpValidator.kt new file mode 100755 index 0000000..5d3db22 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/ExpValidator.kt @@ -0,0 +1,34 @@ +package org.octopus.internal.web.utils.dtos.validators + +import jakarta.validation.Constraint +import jakarta.validation.ConstraintValidator +import jakarta.validation.ConstraintValidatorContext +import jakarta.validation.Payload +import kotlin.reflect.KClass + + +class ExpValidator : ConstraintValidator { + override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean { + val pattern = Regex("\\d+/[0-9]{4}") + + if (value == null || value.matches(pattern)) { + return true + } + + context.disableDefaultConstraintViolation() + context.buildConstraintViolationWithTemplate("Invalid value '$value' : digits/full_year is expected") + .addConstraintViolation() + + return false + } + + @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Constraint(validatedBy = [ExpValidator::class]) + annotation class Validate( + val message: String = "", + val groups: Array> = [], + val payload: Array> = [] + ) +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/NameValidator.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/NameValidator.kt new file mode 100755 index 0000000..31a74b0 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/NameValidator.kt @@ -0,0 +1,34 @@ +package org.octopus.internal.web.utils.dtos.validators + +import jakarta.validation.Constraint +import jakarta.validation.ConstraintValidator +import jakarta.validation.ConstraintValidatorContext +import jakarta.validation.Payload +import kotlin.reflect.KClass + + +class NameValidator : ConstraintValidator { + override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean { + val pattern = Regex("^[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]+(?:[-' ][A-Za-zÁÉÍÓÚÜÑáéíóúüñ]+)*$") + + if (value == null || value.matches(pattern)) { + return true + } + + context.disableDefaultConstraintViolation() + context.buildConstraintViolationWithTemplate("Invalid value '$value' : Only letters, spaces, and dashes are allowed.") + .addConstraintViolation() + + return false + } + + @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Constraint(validatedBy = [NameValidator::class]) + annotation class Validate( + val message: String = "", + val groups: Array> = [], + val payload: Array> = [] + ) +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/PhoneValidator.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/PhoneValidator.kt new file mode 100755 index 0000000..2bd48f3 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/PhoneValidator.kt @@ -0,0 +1,34 @@ +package org.octopus.internal.web.utils.dtos.validators + +import jakarta.validation.Constraint +import jakarta.validation.ConstraintValidator +import jakarta.validation.ConstraintValidatorContext +import jakarta.validation.Payload +import kotlin.reflect.KClass + + +class PhoneValidator : ConstraintValidator { + override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean { + val pattern = Regex("^[6789]\\d{8}$") + + if (value == null || value.matches(pattern)) { + return true + } + + context.disableDefaultConstraintViolation() + context.buildConstraintViolationWithTemplate("Invalid value '$value' : Phone must start with [6,7,8,9] and 8 more numbers are expected.") + .addConstraintViolation() + + return false + } + + @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Constraint(validatedBy = [PhoneValidator::class]) + annotation class Validate( + val message: String = "", + val groups: Array> = [], + val payload: Array> = [] + ) +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/UsernameValidator.kt b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/UsernameValidator.kt new file mode 100755 index 0000000..e60d203 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/dtos/validators/UsernameValidator.kt @@ -0,0 +1,33 @@ +package org.octopus.internal.web.utils.dtos.validators + +import jakarta.validation.Constraint +import jakarta.validation.ConstraintValidator +import jakarta.validation.ConstraintValidatorContext +import jakarta.validation.Payload +import kotlin.reflect.KClass + + +class UsernameValidator : ConstraintValidator { + override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean { + val pattern = Regex("[a-z]{4,16}") + if (value == null || value.matches(pattern)) { + return true + } + + context.disableDefaultConstraintViolation() + context.buildConstraintViolationWithTemplate("Invalid value '$value' : Only 4 to 16 lowercase letters are allowed") + .addConstraintViolation() + + return false + } + + @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) + @Retention(AnnotationRetention.RUNTIME) + @MustBeDocumented + @Constraint(validatedBy = [UsernameValidator::class]) + annotation class Validate( + val message: String = "", + val groups: Array> = [], + val payload: Array> = [] + ) +} \ No newline at end of file diff --git a/src/main/kotlin/org/octopus/internal/web/utils/responses/WebResponse.kt b/src/main/kotlin/org/octopus/internal/web/utils/responses/WebResponse.kt new file mode 100755 index 0000000..ad6c940 --- /dev/null +++ b/src/main/kotlin/org/octopus/internal/web/utils/responses/WebResponse.kt @@ -0,0 +1,30 @@ +package org.octopus.internal.web.utils.responses + +import org.springframework.http.HttpStatus + + +data class WebResponse( + val status: HttpStatus, + val error: String? = null, + val data: T? = null +) { + + companion object { + fun ok(): WebResponse { + return WebResponse(status = HttpStatus.OK) + } + + fun ok(data: T): WebResponse { + return WebResponse(status = HttpStatus.OK, data = data) + } + + fun ko(status: HttpStatus, error: String?): WebResponse { + return WebResponse(status = status, error = error) + } + + fun ko(status: HttpStatus, errors: List): WebResponse { + return WebResponse(status = status, error = errors.toString()) + } + } + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100755 index 0000000..8415232 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.servlet.context-path=/api +spring.application.name=octopus-planning-application +# +# +# DATABASE +# +spring.datasource.url=jdbc:postgresql://0.0.0.0:5432/planning_db +spring.datasource.username=lorca_usr +spring.datasource.password=lorca_pwd +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.generate-ddl=true +spring.jpa.hibernate.ddl-auto=update +