pcoletta 7 цаг өмнө
commit
36a0c98cca
57 өөрчлөгдсөн 22258 нэмэгдсэн , 0 устгасан
  1. 43 0
      .gitignore
  2. 41 0
      Dockerfile
  3. 60 0
      README.md
  4. 47 0
      build.gradle
  5. 19258 0
      build.log
  6. 8 0
      gradle.properties
  7. BIN
      gradle/wrapper/gradle-wrapper.jar
  8. 5 0
      gradle/wrapper/gradle-wrapper.properties
  9. 185 0
      gradlew
  10. 104 0
      gradlew.bat
  11. 11 0
      settings.gradle
  12. 97 0
      src/main/docker/Dockerfile.jvm
  13. 93 0
      src/main/docker/Dockerfile.legacy-jar
  14. 27 0
      src/main/docker/Dockerfile.native
  15. 30 0
      src/main/docker/Dockerfile.native-micro
  16. 15 0
      src/main/java/it/pcdev/dokskan/central/annotation/MaliciousUploadProtection.java
  17. 14 0
      src/main/java/it/pcdev/dokskan/central/client/AiClient.java
  18. 20 0
      src/main/java/it/pcdev/dokskan/central/config/CorsFilter.java
  19. 61 0
      src/main/java/it/pcdev/dokskan/central/controller/AuthenticationController.java
  20. 39 0
      src/main/java/it/pcdev/dokskan/central/controller/CategoryController.java
  21. 125 0
      src/main/java/it/pcdev/dokskan/central/controller/DocumentController.java
  22. 130 0
      src/main/java/it/pcdev/dokskan/central/dto/AiClientDtos.java
  23. 61 0
      src/main/java/it/pcdev/dokskan/central/dto/CategoryDTO.java
  24. 143 0
      src/main/java/it/pcdev/dokskan/central/dto/DocumentDto.java
  25. 87 0
      src/main/java/it/pcdev/dokskan/central/dto/DocumentShareDTO.java
  26. 31 0
      src/main/java/it/pcdev/dokskan/central/dto/JwtDto.java
  27. 42 0
      src/main/java/it/pcdev/dokskan/central/dto/OwnershipDTO.java
  28. 31 0
      src/main/java/it/pcdev/dokskan/central/dto/UserDto.java
  29. 9 0
      src/main/java/it/pcdev/dokskan/central/exception/SharePermissionException.java
  30. 9 0
      src/main/java/it/pcdev/dokskan/central/exception/UploadException.java
  31. 114 0
      src/main/java/it/pcdev/dokskan/central/interceptor/FileUploadSecurityInterceptor.java
  32. 57 0
      src/main/java/it/pcdev/dokskan/central/mapper/DocumentMapper.java
  33. 32 0
      src/main/java/it/pcdev/dokskan/central/mapper/UserMapper.java
  34. 46 0
      src/main/java/it/pcdev/dokskan/central/model/ConfDocumentCategoryModel.java
  35. 73 0
      src/main/java/it/pcdev/dokskan/central/model/DocumentCategoryCrossModel.java
  36. 110 0
      src/main/java/it/pcdev/dokskan/central/model/DocumentModel.java
  37. 122 0
      src/main/java/it/pcdev/dokskan/central/model/UserDocumentCrossModel.java
  38. 77 0
      src/main/java/it/pcdev/dokskan/central/model/UserModel.java
  39. 40 0
      src/main/java/it/pcdev/dokskan/central/repository/ConfDocumentCategoryRepository.java
  40. 38 0
      src/main/java/it/pcdev/dokskan/central/repository/DocumentCategoryCrossRepository.java
  41. 22 0
      src/main/java/it/pcdev/dokskan/central/repository/DocumentRepository.java
  42. 74 0
      src/main/java/it/pcdev/dokskan/central/repository/UserDocumentCrossRepository.java
  43. 32 0
      src/main/java/it/pcdev/dokskan/central/repository/UserRepository.java
  44. 18 0
      src/main/java/it/pcdev/dokskan/central/repository/abs/GenericAbstractRepository.java
  45. 9 0
      src/main/java/it/pcdev/dokskan/central/service/IAuthenticationService.java
  46. 15 0
      src/main/java/it/pcdev/dokskan/central/service/IConfDocumentCategoryService.java
  47. 9 0
      src/main/java/it/pcdev/dokskan/central/service/IDocumentSecurityService.java
  48. 19 0
      src/main/java/it/pcdev/dokskan/central/service/IDocumentUploadService.java
  49. 15 0
      src/main/java/it/pcdev/dokskan/central/service/IUserService.java
  50. 57 0
      src/main/java/it/pcdev/dokskan/central/service/impl/AuthenticationServiceImpl.java
  51. 50 0
      src/main/java/it/pcdev/dokskan/central/service/impl/ConfDocumentCategoryServiceImpl.java
  52. 47 0
      src/main/java/it/pcdev/dokskan/central/service/impl/UserServiceImpl.java
  53. 60 0
      src/main/java/it/pcdev/dokskan/central/service/impl/strategy/docupload/DocumentUploadServiceAiImpl.java
  54. 201 0
      src/main/java/it/pcdev/dokskan/central/service/impl/strategy/docupload/DocumentUploadServiceCommon.java
  55. 69 0
      src/main/java/it/pcdev/dokskan/central/service/impl/strategy/docupload/DocumentUploadServiceManualImpl.java
  56. 13 0
      src/main/resources/application.properties
  57. 13 0
      src/main/resources/banner.txt

+ 43 - 0
.gitignore

@@ -0,0 +1,43 @@
+# Gradle
+.gradle/
+build/
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env
+
+# Plugin directory
+/.quarkus/cli/plugins/
+# TLS Certificates
+.certs/
+/src/main/resources/privateKey.pem
+/src/main/resources/publicKey.pem

+ 41 - 0
Dockerfile

@@ -0,0 +1,41 @@
+FROM gradle:8.4.0-jdk17-jammy AS build
+LABEL authors="pcoletta"
+WORKDIR /app
+
+# Installa OpenSSL se non presente
+RUN apt-get update && apt-get install -y openssl && rm -rf /var/lib/apt/lists/*
+
+# Copy dependency definitions first (for better caching)
+COPY build.gradle settings.gradle gradle.properties ./
+
+# Download dependencies (cached unless build files change)
+RUN gradle dependencies --no-daemon || true
+
+# Copy source code
+COPY src/ ./src
+
+# Genera le chiavi JWT se non esistono già
+RUN mkdir -p src/main/resources && \
+    if [ ! -f src/main/resources/privateKey.pem ]; then \
+        openssl genrsa -out src/main/resources/privateKey.pem 2048 && \
+        openssl rsa -in src/main/resources/privateKey.pem -pubout -out src/main/resources/publicKey.pem; \
+    fi
+
+# Build with more verbose output
+RUN gradle build --no-daemon --stacktrace --info
+
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.20
+WORKDIR /app
+
+COPY --from=build --chown=185 /app/build/quarkus-app/lib/ /deployments/lib/
+COPY --from=build --chown=185 /app/build/quarkus-app/*.jar /deployments/
+COPY --from=build --chown=185 /app/build/quarkus-app/app/ /deployments/app/
+COPY --from=build --chown=185 /app/build/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+
+ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
+ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

+ 60 - 0
README.md

@@ -0,0 +1,60 @@
+# dokskan_auth
+
+This project uses Quarkus, the Supersonic Subatomic Java Framework.
+
+If you want to learn more about Quarkus, please visit its website: <https://quarkus.io/>.
+
+## Running the application in dev mode
+
+You can run your application in dev mode that enables live coding using:
+
+```shell script
+./gradlew quarkusDev
+```
+
+> **_NOTE:_**  Quarkus now ships with a Dev UI, which is available in dev mode only at <http://localhost:8080/q/dev/>.
+
+## Packaging and running the application
+
+The application can be packaged using:
+
+```shell script
+./gradlew build
+```
+
+It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory.
+Be aware that it’s not an _über-jar_ as the dependencies are copied into the `build/quarkus-app/lib/` directory.
+
+The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`.
+
+If you want to build an _über-jar_, execute the following command:
+
+```shell script
+./gradlew build -Dquarkus.package.jar.type=uber-jar
+```
+
+The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`.
+
+## Creating a native executable
+
+You can create a native executable using:
+
+```shell script
+./gradlew build -Dquarkus.native.enabled=true
+```
+
+Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
+
+```shell script
+./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.container-build=true
+```
+
+You can then execute your native executable with: `./build/dokskan_auth-1.0-SNAPSHOT-runner`
+
+If you want to learn more about building native executables, please consult <https://quarkus.io/guides/gradle-tooling>.
+
+## Related Guides
+
+- SmallRye JWT ([guide](https://quarkus.io/guides/security-jwt)): Secure your applications with JSON Web Token
+- SmallRye JWT Build ([guide](https://quarkus.io/guides/security-jwt-build)): Create JSON Web Token with SmallRye JWT
+  Build API

+ 47 - 0
build.gradle

@@ -0,0 +1,47 @@
+plugins {
+    id 'java'
+    id 'io.quarkus'
+}
+
+repositories {
+    mavenCentral()
+    mavenLocal()
+}
+
+dependencies {
+    implementation 'io.quarkus:quarkus-resteasy-client-jackson'
+    implementation 'io.quarkus:quarkus-resteasy-client'
+    implementation 'io.quarkus:quarkus-smallrye-openapi'
+    implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
+    implementation 'io.quarkus:quarkus-resteasy-jackson'
+    implementation 'io.quarkus:quarkus-smallrye-jwt'
+    implementation 'io.quarkus:quarkus-smallrye-jwt-build'
+    implementation 'io.quarkus:quarkus-arc'
+    implementation 'io.quarkus:quarkus-jdbc-postgresql'
+    implementation 'io.quarkus:quarkus-hibernate-orm-panache'
+    testImplementation 'io.quarkus:quarkus-junit5'
+    implementation group: 'org.mapstruct', name: 'mapstruct', version: '1.6.3'
+    annotationProcessor group: 'org.mapstruct', name: 'mapstruct-processor', version: '1.6.3'
+}
+
+group 'it.pcdev'
+version '1.0-SNAPSHOT'
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_17
+    targetCompatibility = JavaVersion.VERSION_17
+}
+
+test {
+    systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager"
+}
+compileJava {
+    options.encoding = 'UTF-8'
+    options.compilerArgs << '-parameters'
+}
+
+compileTestJava {
+    options.encoding = 'UTF-8'
+}
+
+

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 19258 - 0
build.log


+ 8 - 0
gradle.properties

@@ -0,0 +1,8 @@
+# Gradle properties
+
+# Gradle properties
+quarkusPluginId=io.quarkus
+quarkusPluginVersion=3.17.4
+quarkusPlatformGroupId=io.quarkus.platform
+quarkusPlatformArtifactId=quarkus-bom
+quarkusPlatformVersion=3.17.4

BIN
gradle/wrapper/gradle-wrapper.jar


+ 5 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or 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 UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$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 "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# 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
+    ;;
+  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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 104 - 0
gradlew.bat

@@ -0,0 +1,104 @@
+@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 init
+
+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 init
+
+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
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+: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 %CMD_LINE_ARGS%
+
+: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

+ 11 - 0
settings.gradle

@@ -0,0 +1,11 @@
+pluginManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+        mavenLocal()
+    }
+    plugins {
+        id "${quarkusPluginId}" version "${quarkusPluginVersion}"
+    }
+}
+rootProject.name = 'dokskan_central'

+ 97 - 0
src/main/docker/Dockerfile.jvm

@@ -0,0 +1,97 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./gradlew build
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/dokskan_auth-jvm .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/dokskan_auth-jvm
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005 being the default) like this :  EXPOSE 8080 5005.
+# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
+# when running the container
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/dokskan_auth-jvm
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+#   in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+#   used to calculate a default maximal heap memory based on a containers restriction.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+#   of the container available memory as set here. The default is `50` which means 50%
+#   of the available memory is used as an upper boundary. You can skip this mechanism by
+#   setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+#   is used to calculate a default initial heap memory based on the maximum heap memory.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+#   of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+#   is used as the initial heap size. You can skip this mechanism by setting this value
+#   to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+#   This is used to calculate the maximum value of the initial heap memory. If used in
+#   a container without any memory constraints for the container then this option has
+#   no effect. If there is a memory constraint then `-Xms` is limited to the value set
+#   here. The default is 4096MB which means the calculated value of `-Xms` never will
+#   be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+#   when things are happening. This option, if set to true, will set
+#  `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+#    true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+#   https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+#   (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+#   (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+#   (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+#   previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+#   contain the necessary JRE command-line options to specify the required GC, which
+#   will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+#   accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+
+
+ENV LANGUAGE='en_US:en'
+
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 build/quarkus-app/*.jar /deployments/
+COPY --chown=185 build/quarkus-app/app/ /deployments/app/
+COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
+ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
+

+ 93 - 0
src/main/docker/Dockerfile.legacy-jar

@@ -0,0 +1,93 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./gradlew build -Dquarkus.package.jar.type=legacy-jar
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/dokskan_auth-legacy-jar .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/dokskan_auth-legacy-jar
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005 being the default) like this :  EXPOSE 8080 5005.
+# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
+# when running the container
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/dokskan_auth-legacy-jar
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+#   in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+#   used to calculate a default maximal heap memory based on a containers restriction.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+#   of the container available memory as set here. The default is `50` which means 50%
+#   of the available memory is used as an upper boundary. You can skip this mechanism by
+#   setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+#   is used to calculate a default initial heap memory based on the maximum heap memory.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+#   of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+#   is used as the initial heap size. You can skip this mechanism by setting this value
+#   to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+#   This is used to calculate the maximum value of the initial heap memory. If used in
+#   a container without any memory constraints for the container then this option has
+#   no effect. If there is a memory constraint then `-Xms` is limited to the value set
+#   here. The default is 4096MB which means the calculated value of `-Xms` never will
+#   be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+#   when things are happening. This option, if set to true, will set
+#  `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+#    true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+#   https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+#   (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+#   (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+#   (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+#   previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+#   contain the necessary JRE command-line options to specify the required GC, which
+#   will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+#   accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.20
+
+ENV LANGUAGE='en_US:en'
+
+
+COPY build/lib/* /deployments/lib/
+COPY build/*-runner.jar /deployments/quarkus-run.jar
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
+ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

+ 27 - 0
src/main/docker/Dockerfile.native

@@ -0,0 +1,27 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+#
+# Before building the container image run:
+#
+# ./gradlew build -Dquarkus.native.enabled=true
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/dokskan_auth .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/dokskan_auth
+#
+###
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.10
+WORKDIR /work/
+RUN chown 1001 /work \
+    && chmod "g+rwX" /work \
+    && chown 1001:root /work
+COPY --chown=1001:root build/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

+ 30 - 0
src/main/docker/Dockerfile.native-micro

@@ -0,0 +1,30 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+# It uses a micro base image, tuned for Quarkus native executables.
+# It reduces the size of the resulting container image.
+# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
+#
+# Before building the container image run:
+#
+# ./gradlew build -Dquarkus.native.enabled=true
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/dokskan_auth .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/dokskan_auth
+#
+###
+FROM quay.io/quarkus/quarkus-micro-image:2.0
+WORKDIR /work/
+RUN chown 1001 /work \
+    && chmod "g+rwX" /work \
+    && chown 1001:root /work
+COPY --chown=1001:root build/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

+ 15 - 0
src/main/java/it/pcdev/dokskan/central/annotation/MaliciousUploadProtection.java

@@ -0,0 +1,15 @@
+package it.pcdev.dokskan.central.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+import jakarta.interceptor.InterceptorBinding;
+
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@InterceptorBinding
+public @interface MaliciousUploadProtection {
+    
+}

+ 14 - 0
src/main/java/it/pcdev/dokskan/central/client/AiClient.java

@@ -0,0 +1,14 @@
+package it.pcdev.dokskan.central.client;
+
+import it.pcdev.dokskan.central.dto.AiClientDtos;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient
+public interface AiClient {
+
+
+    @POST
+    Response processData(AiClientDtos.RequestDto requestDto);
+}

+ 20 - 0
src/main/java/it/pcdev/dokskan/central/config/CorsFilter.java

@@ -0,0 +1,20 @@
+package it.pcdev.dokskan.central.config;
+
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerResponseContext;
+import jakarta.ws.rs.container.ContainerResponseFilter;
+import jakarta.ws.rs.ext.Provider;
+
+import java.io.IOException;
+
+@Provider
+public class CorsFilter implements ContainerResponseFilter {
+    @Override
+    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
+        responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
+        responseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
+        responseContext.getHeaders().add("Access-Control-Allow-Headers", "*");
+        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
+        responseContext.getHeaders().add("Access-Control-Max-Age", "100000");
+    }
+}

+ 61 - 0
src/main/java/it/pcdev/dokskan/central/controller/AuthenticationController.java

@@ -0,0 +1,61 @@
+package it.pcdev.dokskan.central.controller;
+
+import it.pcdev.dokskan.central.dto.UserDto;
+import it.pcdev.dokskan.central.service.impl.AuthenticationServiceImpl;
+import it.pcdev.dokskan.central.service.impl.strategy.docupload.DocumentUploadServiceAiImpl;
+import it.pcdev.dokskan.central.service.impl.UserServiceImpl;
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.persistence.PersistenceException;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.SecurityContext;
+
+@ApplicationScoped
+@Path("/api/v1/auth")
+public class AuthenticationController {
+
+    @Inject
+    UserServiceImpl userService;
+
+    @Inject
+    AuthenticationServiceImpl authenticationServiceImpl;
+
+    @Inject
+    DocumentUploadServiceAiImpl documentUploadServiceImpl;
+
+    @Inject
+    SecurityContext securityContext;
+
+    @POST
+    @Path("/signup")
+    public Response createUser(UserDto userDto) {
+        userService.persist(userDto);
+        return Response.status(Response.Status.CREATED).build();
+    }
+
+    @GET
+    @Path("/signin")
+    public Response signIn(@HeaderParam("USERNAME") String username, @HeaderParam("PASSWORD") String password) {
+        try {
+            return Response.status(Response.Status.OK).entity(authenticationServiceImpl.authenticate(username, password)).build();
+        } catch (PersistenceException exc) {
+            return Response.status(Response.Status.UNAUTHORIZED).build();
+        } catch (Exception exc) {
+            exc.printStackTrace();
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+    }
+
+    @DELETE
+    @Path("/deleteUserAndRelatedData")
+    @RolesAllowed("user")
+    public Response deleteMe() {
+        documentUploadServiceImpl.deleteDocumentDataByUser(securityContext.getUserPrincipal().getName());
+        userService.deleteByUsername(securityContext.getUserPrincipal().getName());
+        return Response.status(Response.Status.OK).build();
+    }
+
+
+}

+ 39 - 0
src/main/java/it/pcdev/dokskan/central/controller/CategoryController.java

@@ -0,0 +1,39 @@
+package it.pcdev.dokskan.central.controller;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import it.pcdev.dokskan.central.service.IConfDocumentCategoryService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+@ApplicationScoped
+@Path("/api/v1/categories")
+public class CategoryController {
+
+    @Inject
+    IConfDocumentCategoryService IConfDocumentCategoryService;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/getAll")
+    public Response getAllCategories(){
+        return Response
+                .status(HttpResponseStatus.OK.code())
+                .entity(IConfDocumentCategoryService.getAllCategories())
+                .build();
+
+    }
+
+    @POST
+    @Path("/push")
+    public Response pushCategory(@QueryParam("categoryName") String categoryName){
+        IConfDocumentCategoryService.persist(categoryName);
+        return Response
+                .status(HttpResponseStatus.CREATED.code())
+                .build();
+    }
+
+
+}

+ 125 - 0
src/main/java/it/pcdev/dokskan/central/controller/DocumentController.java

@@ -0,0 +1,125 @@
+package it.pcdev.dokskan.central.controller;
+
+import java.util.List;
+
+import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import it.pcdev.dokskan.central.dto.DocumentDto;
+import it.pcdev.dokskan.central.dto.DocumentShareDTO;
+import it.pcdev.dokskan.central.dto.OwnershipDTO;
+import it.pcdev.dokskan.central.exception.UploadException;
+import it.pcdev.dokskan.central.service.IDocumentUploadService;
+import it.pcdev.dokskan.central.service.impl.strategy.docupload.DocumentUploadServiceAiImpl;
+import it.pcdev.dokskan.central.service.impl.strategy.docupload.DocumentUploadServiceManualImpl;
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.SecurityContext;
+
+@ApplicationScoped
+@Path("/api/v1/document")
+public class DocumentController {
+
+    @Inject
+    SecurityContext securityContext;
+
+    @Inject
+    DocumentUploadServiceAiImpl documentUploadServiceAi;
+
+    @Inject
+    DocumentUploadServiceManualImpl documentUploadServiceManual;
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @RolesAllowed("user")
+    @Path("/upload")
+    public Response uploadDocument(DocumentDto body, @QueryParam("aiProcessing") Boolean aiProcessing, @QueryParam("manualConfirm") Boolean manualConfirm) {
+
+        IDocumentUploadService iDocumentUploadService = aiProcessing
+                ? documentUploadServiceAi
+                : documentUploadServiceManual;
+
+        try {
+
+            DocumentDto docInfo = iDocumentUploadService.uploadDocument(body, securityContext.getUserPrincipal().getName());
+
+            if(iDocumentUploadService instanceof DocumentUploadServiceAiImpl && !manualConfirm){
+                iDocumentUploadService = documentUploadServiceManual;
+                iDocumentUploadService.uploadDocument(docInfo, securityContext.getUserPrincipal().getName());
+                iDocumentUploadService = documentUploadServiceAi;
+            }
+
+            return Response.status(HttpResponseStatus.CREATED.code())
+                    .entity(docInfo)
+                    .build();
+                    
+        } catch (UploadException exc) {
+            return Response.status(HttpResponseStatus.BAD_REQUEST.code())
+                    .build();
+        }
+    }
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @RolesAllowed("user")
+    public Response getDocument() {
+        return Response.status(HttpResponseStatus.OK.code())
+                .entity(documentUploadServiceManual.getDocumentsByUser(securityContext.getUserPrincipal().getName()))
+                .build();
+    }
+
+    @DELETE
+    @RolesAllowed("user")
+    @Path("/deleteByDocumentId")
+    public Response deleteByDocumentId(@QueryParam("value") Integer documentId) {
+        documentUploadServiceManual.disassociateUserOrDeleteIfOwner(documentId,
+                securityContext.getUserPrincipal().getName());
+        return Response.status(HttpResponseStatus.OK.code())
+                .build();
+    }
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @RolesAllowed("user")
+    @Path("/isOwner")
+    public Response isOwner(@QueryParam("documentId") Integer documentId) {
+
+        OwnershipDTO ownerDetails = documentUploadServiceManual.isOwner(documentId,
+                securityContext.getUserPrincipal().getName());
+
+        if (ownerDetails.isOwner() == null)
+            return Response.status(HttpResponseStatus.NO_CONTENT.code())
+                    .build();
+
+        return Response.status(HttpResponseStatus.OK.code())
+                .entity(ownerDetails).build();
+
+    }
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @RolesAllowed("user")
+    @Path("/share/getDocumentShareDetails")
+    public Response getDocumentShareDetails(@QueryParam("documentId") Integer documentId) {
+
+        return Response.status(HttpResponseStatus.OK.code())
+                .entity(documentUploadServiceManual.getDocumentShareDetails(documentId,securityContext.getUserPrincipal().getName()))
+                .build();
+
+    }
+
+    @POST
+    @RolesAllowed("user")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Path("/share/updateDocumentShareDetails")
+    public Response updateDocumentShareDetails(@RequestBody List<DocumentShareDTO> body, @QueryParam("documentId") Integer documentId){
+        documentUploadServiceManual.saveOrUpdateShareInfo(body, documentId, securityContext.getUserPrincipal().getName());
+        return Response.status(HttpResponseStatus.OK.code()).build();
+    }
+
+
+}

+ 130 - 0
src/main/java/it/pcdev/dokskan/central/dto/AiClientDtos.java

@@ -0,0 +1,130 @@
+package it.pcdev.dokskan.central.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+public class AiClientDtos {
+
+    public static class RequestDto {
+        @JsonProperty("document_content")
+        private String documentContent;
+
+        @JsonProperty("labels")
+        private List<String> categories;
+
+        public RequestDto(String documentContent, List<String> categories) {
+            this.documentContent = documentContent;
+            this.categories = categories;
+        }
+
+        public RequestDto() {
+        }
+
+        private RequestDto(RequestDto.builder builder) {
+            this.documentContent = builder.documentContent;
+            this.categories = builder.categories;
+        }
+
+        public String getDocumentContent() {
+            return documentContent;
+        }
+
+        public void setDocumentContent(String documentContent) {
+            this.documentContent = documentContent;
+        }
+
+        public List<String> getCategories() {
+            return categories;
+        }
+
+        public void setCategories(List<String> categories) {
+            this.categories = categories;
+        }
+
+        //BUILDER CLASS
+        public static class builder {
+            private String documentContent;
+            private List<String> categories;
+
+            public builder() {
+
+            }
+
+            public builder documentContent(String documentContent) {
+                this.documentContent = documentContent;
+                return this;
+            }
+
+            public builder categories(List<String> categories) {
+                this.categories = categories;
+                return this;
+            }
+
+            public RequestDto build() {
+                return new RequestDto(this);
+            }
+        }
+    }
+
+    public static class ResponseDto {
+        private String summary;
+        private String category;
+
+        public ResponseDto(String summary, String category) {
+            this.summary = summary;
+            this.category = category;
+        }
+
+        private ResponseDto(ResponseDto.builder builder) {
+            this.summary = builder.summary;
+            this.category = builder.category;
+        }
+
+        public ResponseDto() {
+        }
+
+        public String getSummary() {
+            return summary;
+        }
+
+        public void setSummary(String summary) {
+            this.summary = summary;
+        }
+
+        public String getCategory() {
+            return category;
+        }
+
+        public void setCategory(String category) {
+            this.category = category;
+        }
+
+        //BUILDER CLASS
+
+        public static class builder {
+            private String summary;
+            private String category;
+
+            public builder() {
+
+            }
+
+            public builder summary(String summary) {
+                this.summary = summary;
+                return this;
+            }
+
+            public builder category(String category) {
+                this.category = category;
+                return this;
+            }
+
+            public ResponseDto build() {
+                return new ResponseDto(this);
+            }
+        }
+    }
+
+
+}

+ 61 - 0
src/main/java/it/pcdev/dokskan/central/dto/CategoryDTO.java

@@ -0,0 +1,61 @@
+package it.pcdev.dokskan.central.dto;
+
+public class CategoryDTO {
+    private String label;
+    private Boolean isDeletable;
+
+    public CategoryDTO() {
+    }
+
+    public CategoryDTO(String label, Boolean isDeletable) {
+        this.label = label;
+        this.isDeletable = isDeletable;
+    }
+
+    private CategoryDTO(Builder builder) {
+        this.label = builder.label;
+        this.isDeletable = builder.isDeletable;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    public Boolean getIsDeletable() {
+        return isDeletable;
+    }
+
+    public void setIsDeletable(Boolean isDeletable) {
+        this.isDeletable = isDeletable;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private String label;
+        private Boolean isDeletable;
+
+        private Builder() {
+        }
+
+        public Builder label(String label) {
+            this.label = label;
+            return this;
+        }
+
+        public Builder isDeletable(Boolean isDeletable) {
+            this.isDeletable = isDeletable;
+            return this;
+        }
+
+        public CategoryDTO build() {
+            return new CategoryDTO(this);
+        }
+    }
+}

+ 143 - 0
src/main/java/it/pcdev/dokskan/central/dto/DocumentDto.java

@@ -0,0 +1,143 @@
+package it.pcdev.dokskan.central.dto;
+
+
+import java.util.List;
+
+public class DocumentDto {
+
+    /*
+     * INNER CLASSES
+     */
+
+    public static class BaseData {
+        private Integer documentId;
+        private String base64Content;
+        private String filename;
+        private String extension;
+        private String contentType;
+
+        public BaseData(String base64Content, String filename, String extension, String contentType, Integer documentId) {
+            this.base64Content = base64Content;
+            this.filename = filename;
+            this.extension = extension;
+            this.contentType = contentType;
+            this.documentId = documentId;
+        }
+
+        public BaseData() {
+        }
+
+        public Integer getDocumentId() {
+            return documentId;
+        }
+
+        public void setDocumentId(Integer documentId) {
+            this.documentId = documentId;
+        }
+
+        public String getBase64Content() {
+            return base64Content;
+        }
+
+        public void setBase64Content(String base64Content) {
+            this.base64Content = base64Content;
+        }
+
+        public String getFilename() {
+            return filename;
+        }
+
+        public void setFilename(String filename) {
+            this.filename = filename;
+        }
+
+        public String getExtension() {
+            return extension;
+        }
+
+        public void setExtension(String extension) {
+            this.extension = extension;
+        }
+
+        public String getContentType() {
+            return contentType;
+        }
+
+        public void setContentType(String contentType) {
+            this.contentType = contentType;
+        }
+    }
+
+    public static class AdditionalData {
+        private String description;
+        private List<String> categories;
+        private List<String> associatedUsers;
+
+        public AdditionalData(String description, List<String> categories, List<String> associatedUsers) {
+            this.description = description;
+            this.categories = categories;
+            this.associatedUsers = associatedUsers;
+        }
+
+        // --- //
+
+        public AdditionalData() {
+        }
+
+        public List<String> getAssociatedUsers() {
+            return associatedUsers;
+        }
+
+        public void setAssociatedUsers(List<String> associatedUsers) {
+            this.associatedUsers = associatedUsers;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public void setDescription(String description) {
+            this.description = description;
+        }
+
+        public List<String> getCategories() {
+            return categories;
+        }
+
+        public void setCategories(List<String> categories) {
+            this.categories = categories;
+        }
+
+
+
+    }
+
+    // --------------------------------------------------------------------------- //
+
+    private BaseData baseData;
+    private AdditionalData additionalData;
+
+    public DocumentDto(BaseData baseData, AdditionalData additionalData) {
+        this.baseData = baseData;
+        this.additionalData = additionalData;
+    }
+
+    public DocumentDto() {
+    }
+
+    public BaseData getBaseData() {
+        return baseData;
+    }
+
+    public void setBaseData(BaseData baseData) {
+        this.baseData = baseData;
+    }
+
+    public AdditionalData getAdditionalData() {
+        return additionalData;
+    }
+
+    public void setAdditionalData(AdditionalData additionalData) {
+        this.additionalData = additionalData;
+    }
+}

+ 87 - 0
src/main/java/it/pcdev/dokskan/central/dto/DocumentShareDTO.java

@@ -0,0 +1,87 @@
+package it.pcdev.dokskan.central.dto;
+
+import java.util.Objects;
+
+import it.pcdev.dokskan.central.model.UserDocumentCrossModel;
+
+public class DocumentShareDTO {
+    private final Integer documentId;
+    private final boolean enabled;
+    private final String username;
+
+    private DocumentShareDTO(Integer documentId, boolean enabled, String username) {
+        this.documentId = documentId;
+        this.enabled = enabled;
+        this.username = username;
+    }
+
+    public Integer getDocumentId() {
+        return documentId;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private Integer documentId;
+        private boolean enabled;
+        private String username;
+
+        public Builder withDocumentId(Integer documentId) {
+            this.documentId = documentId;
+            return this;
+        }
+
+        public Builder withEnabled(boolean enabled) {
+            this.enabled = enabled;
+            return this;
+        }
+
+        public Builder withUsername(String username) {
+            this.username = username;
+            return this;
+        }
+
+        public DocumentShareDTO build() {
+
+            return new DocumentShareDTO(documentId, enabled, username);
+        }
+    }
+
+    public static DocumentShareDTO mapToDocumentShareDTO(UserDocumentCrossModel userDocumentCrossModel, Boolean enabled) {
+
+        return DocumentShareDTO
+                .builder()
+                .withDocumentId(userDocumentCrossModel.getDocument() != null ?  userDocumentCrossModel.getDocument().getId() : null)
+                .withUsername(userDocumentCrossModel.getUser().getUsername())
+                .withEnabled(enabled)
+                .build();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        DocumentShareDTO that = (DocumentShareDTO) o;
+        return enabled == that.enabled
+                && Objects.equals(documentId, that.documentId)
+                && Objects.equals(username, that.username);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(documentId, enabled, username);
+    }
+
+}

+ 31 - 0
src/main/java/it/pcdev/dokskan/central/dto/JwtDto.java

@@ -0,0 +1,31 @@
+package it.pcdev.dokskan.central.dto;
+
+public class JwtDto {
+
+    private String jwt;
+    private Boolean authenticated;
+
+    public JwtDto(String jwt, Boolean authenticated) {
+        this.jwt = jwt;
+        this.authenticated = authenticated;
+    }
+
+    public JwtDto() {
+    }
+
+    public String getJwt() {
+        return jwt;
+    }
+
+    public void setJwt(String jwt) {
+        this.jwt = jwt;
+    }
+
+    public Boolean getAuthenticated() {
+        return authenticated;
+    }
+
+    public void setAuthenticated(Boolean authenticated) {
+        this.authenticated = authenticated;
+    }
+}

+ 42 - 0
src/main/java/it/pcdev/dokskan/central/dto/OwnershipDTO.java

@@ -0,0 +1,42 @@
+package it.pcdev.dokskan.central.dto;
+
+public class OwnershipDTO {
+    private final Boolean isOwner;
+    private final String ownerName;
+
+    private OwnershipDTO(boolean isOwner, String ownerName) {
+        this.isOwner = isOwner;
+        this.ownerName = ownerName;
+    }
+
+    public Boolean isOwner() {
+        return isOwner;
+    }
+
+    public String getOwnerName() {
+        return ownerName;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private Boolean isOwner;
+        private String ownerName;
+
+        public Builder withIsOwner(Boolean isOwner) {
+            this.isOwner = isOwner;
+            return this;
+        }
+
+        public Builder withOwnerName(String ownerName) {
+            this.ownerName = ownerName;
+            return this;
+        }
+
+        public OwnershipDTO build() {
+            return new OwnershipDTO(isOwner, ownerName);
+        }
+    }
+}

+ 31 - 0
src/main/java/it/pcdev/dokskan/central/dto/UserDto.java

@@ -0,0 +1,31 @@
+package it.pcdev.dokskan.central.dto;
+
+public class UserDto {
+
+    private String username;
+    private String password;
+
+    public UserDto(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    public UserDto() {
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

+ 9 - 0
src/main/java/it/pcdev/dokskan/central/exception/SharePermissionException.java

@@ -0,0 +1,9 @@
+package it.pcdev.dokskan.central.exception;
+
+public class SharePermissionException extends RuntimeException {
+
+    public SharePermissionException() {
+        super();
+    }
+
+}

+ 9 - 0
src/main/java/it/pcdev/dokskan/central/exception/UploadException.java

@@ -0,0 +1,9 @@
+package it.pcdev.dokskan.central.exception;
+
+public class UploadException extends Exception {
+
+    public UploadException(){
+        super();
+    }
+
+}

+ 114 - 0
src/main/java/it/pcdev/dokskan/central/interceptor/FileUploadSecurityInterceptor.java

@@ -0,0 +1,114 @@
+package it.pcdev.dokskan.central.interceptor;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import it.pcdev.dokskan.central.annotation.MaliciousUploadProtection;
+import it.pcdev.dokskan.central.service.IDocumentSecurityService;
+import jakarta.annotation.Priority;
+import jakarta.inject.Inject;
+import jakarta.interceptor.AroundInvoke;
+import jakarta.interceptor.Interceptor;
+import jakarta.interceptor.InvocationContext;
+
+@MaliciousUploadProtection
+@Interceptor
+@Priority(Interceptor.Priority.APPLICATION)
+public class FileUploadSecurityInterceptor {
+
+    @Inject
+    IDocumentSecurityService documentSecurityService;
+
+    @AroundInvoke
+    public Object validateDocumentInRestController(InvocationContext ctx) throws Exception {
+
+        Set<Object> visited = new HashSet<>();
+
+        for (Object arg : ctx.getParameters()) {
+            if (arg != null) {
+                this.scanArguments(arg, visited);
+            }
+        }
+
+        return ctx.proceed();
+    }
+
+    private void scanArguments(Object arg, Set<Object> visited) throws Exception{
+
+        if(arg == null || !visited.add(arg))
+            return;
+
+        List<byte[]> byteFile = new ArrayList<>();
+
+          if (arg == null || !visited.add(arg)) {
+            return;
+        }
+
+        List<byte[]> bytefile = new ArrayList<>();
+
+
+        if (arg instanceof byte[] bytes) {
+            bytefile.add(bytes);
+        }
+
+        else if (arg instanceof String str && isBase64(str)) {
+            bytefile.add(documentSecurityService.getBase64Bytes(str));
+        }
+
+        else if (arg instanceof Iterable<?> iterable) {
+            for (Object item : iterable) {
+                scanArguments(arg, visited);
+            }
+        }
+
+        else if (arg.getClass().isArray()) {
+            for (Object item : (Object[]) arg) {
+                scanArguments(item, visited);
+            }
+        }
+
+        else if (arg instanceof Map<?, ?> map) {
+            for (Object value : map.values()) {
+                scanArguments(value, visited);
+            }
+        }
+
+        else {
+            Class<?> clazz = arg.getClass();
+            if (shouldSkipClass(clazz)) return;
+
+            for (Field field : clazz.getDeclaredFields()) {
+                field.setAccessible(true);
+                scanArguments(field.get(arg), visited);
+            }
+        }
+
+        bytefile.forEach(documentSecurityService::securityValidation);
+
+    }
+
+    private boolean shouldSkipClass(Class<?> clazz) {
+        if (clazz.isPrimitive())
+            return true;
+        String name = clazz.getName();
+        return name.startsWith("java.")
+                || name.startsWith("jakarta.")
+                || name.startsWith("org.jboss.")
+                || name.startsWith("io.quarkus.");
+    }
+
+    private boolean isBase64(String s) {
+        try {
+            Base64.getDecoder().decode(s);
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+
+}

+ 57 - 0
src/main/java/it/pcdev/dokskan/central/mapper/DocumentMapper.java

@@ -0,0 +1,57 @@
+package it.pcdev.dokskan.central.mapper;
+
+import it.pcdev.dokskan.central.dto.DocumentDto;
+import it.pcdev.dokskan.central.dto.OwnershipDTO;
+import it.pcdev.dokskan.central.model.DocumentModel;
+import it.pcdev.dokskan.central.repository.DocumentCategoryCrossRepository;
+import org.mapstruct.Context;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+import java.util.Base64;
+import java.util.List;
+
+@Mapper
+public interface DocumentMapper {
+
+    DocumentMapper INSTANCE = Mappers.getMapper(DocumentMapper.class);
+
+    @Mapping(target = "baseData.base64Content", expression = "java(byteArrayToBase64String(documentModel.getBase64Content()))")
+    @Mapping(target = "baseData.filename", source = "source.filename")
+    @Mapping(target = "baseData.extension", source = "source.extension")
+    @Mapping(target = "baseData.contentType", source = "source.contentType")
+    @Mapping(target = "baseData.documentId", source = "source.id")
+    @Mapping(target = "additionalData.description", source = "source.description")
+    @Mapping(target = "additionalData.categories", expression = "java(getCategories(documentModel.getId(),documentCategoryCrossRepository))")
+    DocumentDto toDto(DocumentModel source, @Context DocumentCategoryCrossRepository documentCategoryCrossRepository);
+    //N.B => @Context ci serve a garantire che la variabile in input sia definita in ogni metodo generato nella Impl del mapper
+
+
+    @Mapping(target = "base64Content", expression = "java(base64StringToByteArray(source.getBaseData().getBase64Content()))")
+    @Mapping(target = "id", ignore = true)
+    @Mapping(target = "creationTms", ignore = true)
+    @Mapping(target = "filename", source = "source.baseData.filename")
+    @Mapping(target = "extension", source = "source.baseData.extension")
+    @Mapping(target = "contentType", source = "source.baseData.contentType")
+    @Mapping(target = "description", source = "source.additionalData.description")
+    DocumentModel toModel(DocumentDto source, @Context DocumentCategoryCrossRepository documentCategoryCrossRepository);
+    
+    default byte[] base64StringToByteArray(String base64String){
+        return Base64.getDecoder().decode(base64String);
+    }
+
+    default String byteArrayToBase64String(byte[] source){
+        return new String(Base64.getEncoder().encode(source));
+    }
+
+    default List<String> getCategories(Integer documentId, DocumentCategoryCrossRepository documentCategoryCrossRepository){
+        return documentCategoryCrossRepository
+                .findByDocumentId(documentId)
+                .stream()
+                .map(el -> {
+                    return el.getCategory().getName();
+                })
+                .toList();
+    }
+
+}

+ 32 - 0
src/main/java/it/pcdev/dokskan/central/mapper/UserMapper.java

@@ -0,0 +1,32 @@
+package it.pcdev.dokskan.central.mapper;
+
+import it.pcdev.dokskan.central.dto.UserDto;
+import it.pcdev.dokskan.central.model.UserModel;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+@Mapper
+public interface UserMapper {
+
+    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
+
+    @Mapping(target = "password", expression = "java(hashPassword(userDto))")
+    @Mapping(target = "creationTms", ignore = true)
+    UserModel toModel(UserDto userDto);
+
+    @Mapping(target = "password", ignore = true)
+    UserDto toDto(UserModel userModel);
+
+    default byte[] hashPassword(UserDto source){
+        try{
+            return MessageDigest.getInstance("SHA-256").digest(source.getPassword().getBytes());
+        }catch(NoSuchAlgorithmException e){
+            throw new RuntimeException(e);
+        }
+    }
+
+}

+ 46 - 0
src/main/java/it/pcdev/dokskan/central/model/ConfDocumentCategoryModel.java

@@ -0,0 +1,46 @@
+package it.pcdev.dokskan.central.model;
+
+
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import it.pcdev.dokskan.central.repository.ConfDocumentCategoryRepository;
+import jakarta.inject.Inject;
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "conf_document_category")
+public class ConfDocumentCategoryModel extends PanacheEntityBase {
+
+    @Inject
+    ConfDocumentCategoryRepository confDocumentCategoryRepository;
+
+    @Id
+    @Column(name = "name")
+    private String name;
+
+    public ConfDocumentCategoryModel(String name) {
+        this.name = name;
+    }
+
+    public ConfDocumentCategoryModel() {
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @PrePersist
+    public void prePersist() {
+        this.name = this.name.toUpperCase();
+    }
+
+    // @Transient
+    // public Boolean isDeletable(){
+
+    // }
+
+
+}

+ 73 - 0
src/main/java/it/pcdev/dokskan/central/model/DocumentCategoryCrossModel.java

@@ -0,0 +1,73 @@
+package it.pcdev.dokskan.central.model;
+
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "document_category_cross")
+public class DocumentCategoryCrossModel extends PanacheEntityBase {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY) // Considera di usare IDENTITY o SEQUENCE
+    private Integer id;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "document_id", referencedColumnName = "id")
+    private DocumentModel document;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "category_name", referencedColumnName = "name")
+    private ConfDocumentCategoryModel category;
+
+    // Costruttore privato per il Builder
+    private DocumentCategoryCrossModel(DocumentCategoryCrossModel.builder builder) {
+        this.id = builder.id;
+        this.document = builder.document;
+        this.category = builder.category;
+    }
+
+    public DocumentCategoryCrossModel() {
+
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public DocumentModel getDocument() {
+        return document;
+    }
+
+    public ConfDocumentCategoryModel getCategory() {
+        return category;
+    }
+
+    // Classe Builder
+    public static class builder {
+        private Integer id;
+        private DocumentModel document;
+        private ConfDocumentCategoryModel category;
+
+        public builder() {
+        }
+
+        public DocumentCategoryCrossModel.builder id(Integer id) {
+            this.id = id;
+            return this;
+        }
+
+        public DocumentCategoryCrossModel.builder document(DocumentModel document) {
+            this.document = document;
+            return this;
+        }
+
+        public DocumentCategoryCrossModel.builder category(ConfDocumentCategoryModel category) {
+            this.category = category;
+            return this;
+        }
+
+        public DocumentCategoryCrossModel build() {
+            return new DocumentCategoryCrossModel(this);
+        }
+    }
+}

+ 110 - 0
src/main/java/it/pcdev/dokskan/central/model/DocumentModel.java

@@ -0,0 +1,110 @@
+package it.pcdev.dokskan.central.model;
+
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import jakarta.persistence.*;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "documents")
+public class DocumentModel extends PanacheEntityBase {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    @Column(name = "id")
+    private Integer id;
+
+    @Column(name = "base64_content")
+    private byte[] base64Content;
+
+    @Column(name = "filename")
+    private String filename;
+
+    @Column(name = "extension")
+    private String extension;
+
+    @Column(name = "content_type")
+    private String contentType;
+
+    @Column(name = "description")
+    private String description;
+
+    @Column(name = "creation_tms")
+    private LocalDateTime creationTms;
+
+
+    public DocumentModel(Integer id, byte[] base64Content, String filename, String extension, String contentType, String description, LocalDateTime creationTms) {
+        this.id = id;
+        this.base64Content = base64Content;
+        this.filename = filename;
+        this.extension = extension;
+        this.contentType = contentType;
+        this.description = description;
+        this.creationTms = creationTms;
+    }
+
+    public DocumentModel() {
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public byte[] getBase64Content() {
+        return base64Content;
+    }
+
+    public void setBase64Content(byte[] base64Content) {
+        this.base64Content = base64Content;
+    }
+
+    public String getFilename() {
+        return filename;
+    }
+
+    public void setFilename(String filename) {
+        this.filename = filename;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public void setExtension(String extension) {
+        this.extension = extension;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public LocalDateTime getCreationTms() {
+        return creationTms;
+    }
+
+    public void setCreationTms(LocalDateTime creationTms) {
+        this.creationTms = creationTms;
+    }
+
+    @PrePersist
+    public void prePersist() {
+        this.creationTms = LocalDateTime.now();
+    }
+
+}

+ 122 - 0
src/main/java/it/pcdev/dokskan/central/model/UserDocumentCrossModel.java

@@ -0,0 +1,122 @@
+package it.pcdev.dokskan.central.model;
+
+
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "user_document_cross")
+public class UserDocumentCrossModel extends PanacheEntityBase {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    @Column(name = "id")
+    public Integer id;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "document_id", referencedColumnName = "id")
+    private DocumentModel document;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "user_id", referencedColumnName = "username")
+    private UserModel user;
+
+    @Column(name = "owner_association")
+    private Boolean ownerAssociation;
+
+    public UserDocumentCrossModel(Integer id, DocumentModel document, UserModel user, Boolean ownerAssociation) {
+        this.id = id;
+        this.document = document;
+        this.user = user;
+        this.ownerAssociation = ownerAssociation;
+    }
+
+    private UserDocumentCrossModel(UserDocumentCrossModel.builder builder) {
+        this.id = builder.id;
+        this.document = builder.document;
+        this.user = builder.user;
+        this.ownerAssociation = builder.ownerAssociation;
+    }
+
+    public UserDocumentCrossModel() {
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public DocumentModel getDocument() {
+        return document;
+    }
+
+    public void setDocument(DocumentModel document) {
+        this.document = document;
+    }
+
+    public UserModel getUser() {
+        return user;
+    }
+
+    public void setUser(UserModel user) {
+        this.user = user;
+    }
+
+    public Boolean getOwnerAssociation() {
+        return ownerAssociation;
+    }
+
+    public void setOwnerAssociation(Boolean ownerAssociation) {
+        this.ownerAssociation = ownerAssociation;
+    }
+
+    @PrePersist
+    public void prePersist() {
+        if (ownerAssociation == null) {
+            ownerAssociation = false;
+        }
+    }
+
+    //Builder class
+    public static class builder {
+        private Integer id;
+        private DocumentModel document;
+        private UserModel user;
+        private Boolean ownerAssociation;
+
+        public builder() {
+
+        }
+
+        public UserDocumentCrossModel.builder id(Integer id) {
+            this.id = id;
+            return this;
+        }
+
+        public UserDocumentCrossModel.builder document(DocumentModel document) {
+            this.document = document;
+            return this;
+        }
+
+        public UserDocumentCrossModel.builder user(UserModel user) {
+            this.user = user;
+            return this;
+        }
+
+        public UserDocumentCrossModel.builder ownerAssociation(Boolean ownerAssociation) {
+            this.ownerAssociation = ownerAssociation;
+            return this;
+        }
+
+        public UserDocumentCrossModel build() {
+            return new UserDocumentCrossModel(this);
+        }
+
+
+    }
+
+
+}

+ 77 - 0
src/main/java/it/pcdev/dokskan/central/model/UserModel.java

@@ -0,0 +1,77 @@
+package it.pcdev.dokskan.central.model;
+
+import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
+import jakarta.persistence.*;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "users")
+public class UserModel extends PanacheEntityBase {
+
+    @Id
+    @Column(name = "username")
+    private String username;
+
+    @Column(name = "password")
+    private byte[] password;
+
+    @Column(name = "creation_tms")
+    private LocalDateTime creationTms;
+
+    public UserModel(String username, byte[] password, LocalDateTime creationTms) {
+        this.username = username;
+        this.password = password;
+        this.creationTms = creationTms;
+    }
+
+    public UserModel() {
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public byte[] getPassword() {
+        return password;
+    }
+
+    public void setPassword(byte[] password) {
+        this.password = password;
+    }
+
+    public LocalDateTime getCreationTms() {
+        return creationTms;
+    }
+
+    public void setCreationTms(LocalDateTime creationTms) {
+        this.creationTms = creationTms;
+    }
+
+    @PrePersist
+    public void prePersist() {
+        this.creationTms = LocalDateTime.now();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (!(o instanceof UserModel))
+            return false;
+
+        UserModel userModel = (UserModel) o;
+
+        return username != null ? username.equals(userModel.username) : userModel.username == null;
+    }
+
+    @Override
+    public int hashCode() {
+        return username != null ? username.hashCode() : 0;
+    }
+
+}

+ 40 - 0
src/main/java/it/pcdev/dokskan/central/repository/ConfDocumentCategoryRepository.java

@@ -0,0 +1,40 @@
+package it.pcdev.dokskan.central.repository;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import it.pcdev.dokskan.central.model.ConfDocumentCategoryModel;
+import it.pcdev.dokskan.central.model.DocumentCategoryCrossModel;
+import it.pcdev.dokskan.central.repository.abs.GenericAbstractRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Root;
+import jakarta.persistence.criteria.Subquery;
+
+@ApplicationScoped
+public class ConfDocumentCategoryRepository extends GenericAbstractRepository
+        implements PanacheRepositoryBase<ConfDocumentCategoryModel, String> {
+
+    public Set<ConfDocumentCategoryModel> findDeletableCategories() {
+
+        CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();
+        CriteriaQuery<ConfDocumentCategoryModel> cq = cb.createQuery(ConfDocumentCategoryModel.class);
+
+        Root<ConfDocumentCategoryModel> category = cq.from(ConfDocumentCategoryModel.class);
+
+        Subquery<Integer> sq = cq.subquery(Integer.class);
+        Root<DocumentCategoryCrossModel> cross = sq.from(DocumentCategoryCrossModel.class);
+
+        sq.select(cb.literal(1))
+                .where(cb.equal(cross.get("category"), category));
+
+        cq.select(category)
+                .where(cb.not(cb.exists(sq)));
+
+        return this.getEntityManager().createQuery(cq).getResultStream().collect(Collectors.toSet());
+
+    }
+
+}

+ 38 - 0
src/main/java/it/pcdev/dokskan/central/repository/DocumentCategoryCrossRepository.java

@@ -0,0 +1,38 @@
+package it.pcdev.dokskan.central.repository;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import it.pcdev.dokskan.central.model.DocumentCategoryCrossModel;
+import it.pcdev.dokskan.central.repository.abs.GenericAbstractRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.TypedQuery;
+import jakarta.transaction.Transactional;
+
+import java.util.List;
+
+@ApplicationScoped
+public class DocumentCategoryCrossRepository extends GenericAbstractRepository implements PanacheRepositoryBase<DocumentCategoryCrossModel, Integer> {
+
+
+    /*
+     * Query personalizzata in PanacheRepositoryBase in JPQL
+     * E' piu' manuale rispetto a Spring.
+     * Tuttavia, rende piu' chiaro il susseguirsi delle operazioni.
+     */
+    @Transactional
+    public List<DocumentCategoryCrossModel> findByDocumentId(Integer id){
+        String jpqlQuery = """
+                SELECT cross FROM DocumentCategoryCrossModel cross
+                    WHERE cross.document.id = :id
+                """;
+        TypedQuery<DocumentCategoryCrossModel> query = getEntityManager().createQuery(jpqlQuery, DocumentCategoryCrossModel.class);
+        query.setParameter("id", id);
+        return query.getResultList();
+    }
+
+
+
+
+
+
+
+}

+ 22 - 0
src/main/java/it/pcdev/dokskan/central/repository/DocumentRepository.java

@@ -0,0 +1,22 @@
+package it.pcdev.dokskan.central.repository;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import it.pcdev.dokskan.central.model.DocumentModel;
+import it.pcdev.dokskan.central.repository.abs.GenericAbstractRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.transaction.Transactional;
+
+@ApplicationScoped
+public class DocumentRepository extends GenericAbstractRepository implements PanacheRepositoryBase<DocumentModel,Integer> {
+
+    /*
+     * Questa versione della persist e' piu' fedele rispetto a quella di spring.
+     * In questo caso il metodo ritorna l'oggetto cosi' come e' stato salvato.
+     */
+    @Transactional
+    public DocumentModel save(DocumentModel cross) {
+        getEntityManager().persist(cross);
+        return cross;
+    }
+
+}

+ 74 - 0
src/main/java/it/pcdev/dokskan/central/repository/UserDocumentCrossRepository.java

@@ -0,0 +1,74 @@
+package it.pcdev.dokskan.central.repository;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import it.pcdev.dokskan.central.model.UserDocumentCrossModel;
+import it.pcdev.dokskan.central.repository.abs.GenericAbstractRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.TypedQuery;
+
+import java.util.List;
+import java.util.Optional;
+
+@ApplicationScoped
+public class UserDocumentCrossRepository extends GenericAbstractRepository implements PanacheRepositoryBase<UserDocumentCrossModel, Integer> {
+
+
+    public List<UserDocumentCrossModel> getUserAssociationByUser(String username) {
+
+        final String jpqlQuery = "SELECT u FROM UserDocumentCrossModel u WHERE u.user.username = :username";
+
+        TypedQuery<UserDocumentCrossModel> query = getEntityManager().createQuery(jpqlQuery,
+                UserDocumentCrossModel.class);
+        query.setParameter("username", username);
+
+        return query.getResultList();
+    }
+
+    public Optional<UserDocumentCrossModel> findByUsernameAndDocumentId(Integer documentId, String username) {
+
+        final String jpqlQuery = "SELECT u FROM UserDocumentCrossModel u WHERE u.user.username = :username AND u.document.id = :documentId";
+
+        TypedQuery<UserDocumentCrossModel> query = getEntityManager().createQuery(jpqlQuery,
+                UserDocumentCrossModel.class);
+        query.setParameter("username", username);
+        query.setParameter("documentId", documentId);
+
+        return super.getSingleResultOptional(query);
+
+    }
+
+    public List<UserDocumentCrossModel> findByDocumentId(Integer documentId) {
+
+        final String jpqlQuery = "SELECT u FROM UserDocumentCrossModel u WHERE u.document.id = :documentId";
+
+        TypedQuery<UserDocumentCrossModel> query = getEntityManager().createQuery(jpqlQuery,
+                UserDocumentCrossModel.class);
+        query.setParameter("documentId", documentId);
+
+        return query.getResultList();
+    }
+
+    public Optional<UserDocumentCrossModel> findOwnerTupleByDocumentId(Integer documentId) {
+
+        final String jpqlQuery = "SELECT u FROM UserDocumentCrossModel u WHERE u.document.id = :documentId and u.ownerAssociation = true";
+
+        TypedQuery<UserDocumentCrossModel> query = getEntityManager().createQuery(jpqlQuery,
+                UserDocumentCrossModel.class);
+        query.setParameter("documentId", documentId);
+
+        return super.getSingleResultOptional(query);
+
+    }
+
+    public List<UserDocumentCrossModel> findUsersAccessReadOnly(Integer documentId) {
+
+        final String jpqlQuery = "SELECT u FROM UserDocumentCrossModel u WHERE u.document.id = :documentId and u.ownerAssociation = false";
+
+        TypedQuery<UserDocumentCrossModel> query = getEntityManager().createQuery(jpqlQuery,
+                UserDocumentCrossModel.class);
+        query.setParameter("documentId", documentId);
+
+        return query.getResultList();
+    }
+
+}

+ 32 - 0
src/main/java/it/pcdev/dokskan/central/repository/UserRepository.java

@@ -0,0 +1,32 @@
+package it.pcdev.dokskan.central.repository;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import it.pcdev.dokskan.central.model.UserModel;
+import it.pcdev.dokskan.central.repository.abs.GenericAbstractRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.TypedQuery;
+
+@ApplicationScoped
+public class UserRepository extends GenericAbstractRepository implements PanacheRepositoryBase<UserModel, String> {
+
+
+    
+    public Set<UserModel> findAllExcludeExceptInput(String... usrs) {
+
+        List<String> usernames = Arrays.asList(usrs);
+
+        final String jpqlQuery = "SELECT u FROM UserModel u WHERE u.username NOT IN :usernames";
+
+        TypedQuery<UserModel> query = getEntityManager().createQuery(jpqlQuery, UserModel.class);
+        query.setParameter("usernames", usernames);
+
+        return query.getResultStream().collect(Collectors.toSet());
+
+    }
+
+}

+ 18 - 0
src/main/java/it/pcdev/dokskan/central/repository/abs/GenericAbstractRepository.java

@@ -0,0 +1,18 @@
+package it.pcdev.dokskan.central.repository.abs;
+
+import java.util.Optional;
+
+import jakarta.persistence.NoResultException;
+import jakarta.persistence.TypedQuery;
+
+public abstract class GenericAbstractRepository {
+
+    protected <T> Optional<T> getSingleResultOptional(TypedQuery<T> query) {
+        try {
+            return Optional.ofNullable(query.getSingleResult());
+        } catch (NoResultException exc) {
+            return Optional.empty();
+        }
+    }
+
+}

+ 9 - 0
src/main/java/it/pcdev/dokskan/central/service/IAuthenticationService.java

@@ -0,0 +1,9 @@
+package it.pcdev.dokskan.central.service;
+
+import it.pcdev.dokskan.central.dto.JwtDto;
+
+import java.security.NoSuchAlgorithmException;
+
+public interface IAuthenticationService {
+    JwtDto authenticate(String username, String password) throws NoSuchAlgorithmException;
+}

+ 15 - 0
src/main/java/it/pcdev/dokskan/central/service/IConfDocumentCategoryService.java

@@ -0,0 +1,15 @@
+package it.pcdev.dokskan.central.service;
+
+import jakarta.transaction.Transactional;
+
+import java.util.List;
+
+import it.pcdev.dokskan.central.dto.CategoryDTO;
+
+public interface IConfDocumentCategoryService {
+
+    @Transactional
+    void persist(String newCategory);
+
+    List<CategoryDTO> getAllCategories();
+}

+ 9 - 0
src/main/java/it/pcdev/dokskan/central/service/IDocumentSecurityService.java

@@ -0,0 +1,9 @@
+package it.pcdev.dokskan.central.service;
+
+public interface IDocumentSecurityService {
+
+
+    public void securityValidation(byte [] input);
+    public byte[] getBase64Bytes(String base64);
+    
+}

+ 19 - 0
src/main/java/it/pcdev/dokskan/central/service/IDocumentUploadService.java

@@ -0,0 +1,19 @@
+package it.pcdev.dokskan.central.service;
+
+import it.pcdev.dokskan.central.dto.DocumentDto;
+import it.pcdev.dokskan.central.exception.UploadException;
+import jakarta.transaction.Transactional;
+
+import java.util.List;
+
+public interface IDocumentUploadService {
+    DocumentDto uploadDocument(DocumentDto documentDto,String requestingUserUsername) throws UploadException;
+
+    List<DocumentDto> getDocumentsByUser(String username);
+
+    @Transactional
+    void deleteDocument(Integer documentId);
+
+    @Transactional
+    void deleteDocumentDataByUser(String username);
+}

+ 15 - 0
src/main/java/it/pcdev/dokskan/central/service/IUserService.java

@@ -0,0 +1,15 @@
+package it.pcdev.dokskan.central.service;
+
+import it.pcdev.dokskan.central.dto.UserDto;
+import it.pcdev.dokskan.central.model.UserModel;
+import jakarta.transaction.Transactional;
+
+public interface IUserService {
+    @Transactional
+    void persist(UserDto user);
+
+    UserModel getUserByUsername(String username);
+
+    @Transactional
+    void deleteByUsername(String username);
+}

+ 57 - 0
src/main/java/it/pcdev/dokskan/central/service/impl/AuthenticationServiceImpl.java

@@ -0,0 +1,57 @@
+package it.pcdev.dokskan.central.service.impl;
+
+import io.smallrye.jwt.build.Jwt;
+import it.pcdev.dokskan.central.dto.JwtDto;
+import it.pcdev.dokskan.central.service.IAuthenticationService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.persistence.PersistenceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.Set;
+
+@ApplicationScoped
+public class AuthenticationServiceImpl implements IAuthenticationService {
+
+    Logger log = LoggerFactory.getLogger(getClass());
+
+    @Inject
+    UserServiceImpl userService;
+
+    private byte[] hash(String clearText) throws NoSuchAlgorithmException {
+        log.debug("Hashing cleared text");
+        return MessageDigest.getInstance("SHA-256").digest(clearText.getBytes());
+    }
+
+    private Boolean validate(String clearText, byte[] hashed) throws NoSuchAlgorithmException {
+        log.debug("Validating hashed text");
+        return Arrays.equals(hash(clearText), hashed);
+    }
+
+
+    private String generateToken(String username){
+        log.info("Generating token");
+        return Jwt.issuer("dokskan")
+                .subject(username)
+                .upn(username)
+                .groups(Set.of("user"))
+                .expiresIn(3600 * 24)
+                .sign();
+    }
+
+    @Override
+    public JwtDto authenticate(String username, String password) throws NoSuchAlgorithmException {
+        log.info("Authenticating user {}", username);
+        final var userData = Optional.ofNullable(userService.getUserByUsername(username)).orElseThrow(PersistenceException::new);
+        return validate(password,userData.getPassword())
+                ? new JwtDto(generateToken(username),true)
+                : new JwtDto(null,false);
+
+    }
+
+}

+ 50 - 0
src/main/java/it/pcdev/dokskan/central/service/impl/ConfDocumentCategoryServiceImpl.java

@@ -0,0 +1,50 @@
+package it.pcdev.dokskan.central.service.impl;
+
+import it.pcdev.dokskan.central.dto.CategoryDTO;
+import it.pcdev.dokskan.central.model.ConfDocumentCategoryModel;
+import it.pcdev.dokskan.central.repository.ConfDocumentCategoryRepository;
+import it.pcdev.dokskan.central.service.IConfDocumentCategoryService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@ApplicationScoped
+public class ConfDocumentCategoryServiceImpl implements IConfDocumentCategoryService {
+
+    private static final Logger log = LoggerFactory.getLogger(ConfDocumentCategoryServiceImpl.class);
+    @Inject
+    ConfDocumentCategoryRepository confDocumentCategoryRepository;
+
+    @Transactional
+    @Override
+    public void persist(String newCategory) {
+        log.info("Persisting new document category: " + newCategory);
+        confDocumentCategoryRepository.persist(new ConfDocumentCategoryModel(newCategory));
+    }
+
+    @Override
+    public List<CategoryDTO> getAllCategories() {
+        log.info("Retrieving all document categories");
+
+        Set<String> deletableCategories = confDocumentCategoryRepository.findDeletableCategories()
+                .stream()
+                .map(ConfDocumentCategoryModel::getName)
+                .collect(Collectors.toSet());
+
+        return confDocumentCategoryRepository.streamAll()
+                .map(cat -> CategoryDTO
+                        .builder()
+                        .isDeletable(deletableCategories.contains(cat.getName()))
+                        .label(cat.getName())
+                        .build())
+                .toList();
+    }
+
+
+}

+ 47 - 0
src/main/java/it/pcdev/dokskan/central/service/impl/UserServiceImpl.java

@@ -0,0 +1,47 @@
+package it.pcdev.dokskan.central.service.impl;
+
+import it.pcdev.dokskan.central.dto.UserDto;
+import it.pcdev.dokskan.central.mapper.UserMapper;
+import it.pcdev.dokskan.central.model.UserModel;
+import it.pcdev.dokskan.central.repository.UserRepository;
+import it.pcdev.dokskan.central.service.IUserService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ApplicationScoped
+public class UserServiceImpl implements IUserService {
+
+    Logger log = LoggerFactory.getLogger(getClass());
+
+    @Inject
+    UserRepository userRepository;
+
+    UserMapper mapper = UserMapper.INSTANCE;
+
+    @Transactional
+    @Override
+    public void persist(UserDto user) {
+        log.info("Persisting user {}", user.getUsername());
+        final var model = mapper.toModel(user);
+        userRepository.persist(model);
+    }
+
+    @Override
+    public UserModel getUserByUsername(String username) {
+        log.info("Getting user by username {}", username);
+        return userRepository.findById(username);
+    }
+
+    @Transactional
+    @Override
+    public void deleteByUsername(String username) {
+        userRepository.deleteById(username);
+    }
+
+
+
+
+}

+ 60 - 0
src/main/java/it/pcdev/dokskan/central/service/impl/strategy/docupload/DocumentUploadServiceAiImpl.java

@@ -0,0 +1,60 @@
+package it.pcdev.dokskan.central.service.impl.strategy.docupload;
+
+import it.pcdev.dokskan.central.client.AiClient;
+import it.pcdev.dokskan.central.dto.AiClientDtos;
+import it.pcdev.dokskan.central.dto.CategoryDTO;
+import it.pcdev.dokskan.central.dto.DocumentDto;
+import it.pcdev.dokskan.central.exception.UploadException;
+import it.pcdev.dokskan.central.service.IDocumentUploadService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.core.Response;
+import org.apache.http.HttpStatus;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+@ApplicationScoped
+public class DocumentUploadServiceAiImpl extends DocumentUploadServiceCommon implements IDocumentUploadService {
+
+    Logger log = LoggerFactory.getLogger(getClass());
+
+    @RestClient
+    AiClient aiClient;
+
+    @Override
+    public DocumentDto uploadDocument(DocumentDto documentDto, String requestingUserUsername) throws UploadException {
+        log.info("Uploading document");
+        log.info("Ai Processing enabled, sending request to AI MS");
+
+        AiClientDtos.ResponseDto responseData = new AiClientDtos.ResponseDto();
+
+        /*
+         * Chiamata API REST verso AI MS
+         */
+        try (Response classificationResponse = aiClient.processData(
+                new AiClientDtos.RequestDto
+                        .builder()
+                        .documentContent(documentDto.getBaseData().getBase64Content())
+                        .categories(IConfDocumentCategoryService.getAllCategories().stream().map(CategoryDTO::getLabel).toList())
+                        .build()
+        )) {
+            if (classificationResponse.getStatus() == HttpStatus.SC_OK) {
+                responseData = classificationResponse.readEntity(AiClientDtos.ResponseDto.class);
+                var additionalData = Optional.ofNullable(documentDto.getAdditionalData()).orElse(new DocumentDto.AdditionalData());
+                additionalData.setDescription(responseData.getSummary());
+                additionalData.setCategories(Arrays.asList(new String[]{responseData.getCategory()}));
+                documentDto.setAdditionalData(additionalData);
+                return documentDto;
+            }
+            return documentDto;
+        } catch (Exception e) {
+            return documentDto;
+        }
+
+    }
+
+
+}

+ 201 - 0
src/main/java/it/pcdev/dokskan/central/service/impl/strategy/docupload/DocumentUploadServiceCommon.java

@@ -0,0 +1,201 @@
+package it.pcdev.dokskan.central.service.impl.strategy.docupload;
+
+import it.pcdev.dokskan.central.dto.DocumentDto;
+import it.pcdev.dokskan.central.dto.DocumentShareDTO;
+import it.pcdev.dokskan.central.dto.OwnershipDTO;
+import it.pcdev.dokskan.central.exception.SharePermissionException;
+import it.pcdev.dokskan.central.mapper.DocumentMapper;
+import it.pcdev.dokskan.central.model.UserDocumentCrossModel;
+import it.pcdev.dokskan.central.model.UserModel;
+import it.pcdev.dokskan.central.repository.*;
+import it.pcdev.dokskan.central.service.IConfDocumentCategoryService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.persistence.PersistenceException;
+import jakarta.transaction.Transactional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@ApplicationScoped
+public class DocumentUploadServiceCommon {
+
+    private static final Logger log = LoggerFactory.getLogger(DocumentUploadServiceCommon.class);
+    protected DocumentMapper documentMapper = DocumentMapper.INSTANCE;
+
+    @Inject
+    protected DocumentCategoryCrossRepository documentCategoryCrossRepository;
+
+    @Inject
+    protected ConfDocumentCategoryRepository confDocumentCategoryRepository;
+
+    @Inject
+    protected DocumentRepository documentRepository;
+
+    @Inject
+    protected UserRepository userRepository;
+
+    @Inject
+    protected UserDocumentCrossRepository userDocumentCrossRepository;
+
+    @Inject
+    protected IConfDocumentCategoryService IConfDocumentCategoryService;
+
+    public List<DocumentDto> getDocumentsByUser(String username) {
+        log.info("Getting documents by user {}", username);
+        return userDocumentCrossRepository.getUserAssociationByUser(username)
+                .stream()
+                .map(el -> {
+                    return documentMapper.toDto(el.getDocument(), documentCategoryCrossRepository);
+                })
+                .toList();
+    }
+
+    @Transactional
+    public void deleteDocument(Integer documentId) {
+        log.info("Deleting document: {}", documentId);
+        documentRepository.deleteById(documentId);
+    }
+
+    @Transactional
+    public void disassociateUserOrDeleteIfOwner(Integer documentId, String username) {
+
+        log.info("Disassociate user {} from document {}", username, documentId);
+
+        log.info("Checking if {} is the owner of document {}", username, documentId);
+
+        final Boolean isOwner = this.isOwner(documentId, username).isOwner();
+
+        if (isOwner == null)
+            throw new PersistenceException();
+
+        log.info("{} {} the owner of document {}", username, isOwner ? "is" : "is not", documentId);
+
+        if (isOwner)
+            this.deleteDocumentAndAllCrosses(documentId);
+        else
+            userDocumentCrossRepository.findByUsernameAndDocumentId(documentId, username)
+                    .ifPresent(document -> userDocumentCrossRepository.delete(document));
+
+    }
+
+    @Transactional
+    public void deleteDocumentAndAllCrosses(Integer documentId) {
+
+        documentCategoryCrossRepository.findByDocumentId(documentId)
+                .forEach(documentCategoryCrossRepository::delete);
+
+        userDocumentCrossRepository.findByDocumentId(documentId)
+                .forEach(userDocumentCrossRepository::delete);
+
+        documentCategoryCrossRepository.flush();
+        userDocumentCrossRepository.flush();
+
+        this.deleteDocument(documentId);
+    }
+
+    @Transactional
+    public void deleteDocumentDataByUser(String username) {
+        log.info("Deleting document data by username {}", username);
+
+        List<UserDocumentCrossModel> documentCross = userDocumentCrossRepository.getUserAssociationByUser(username);
+
+        List<Integer> documentIds = new ArrayList<>();
+
+        documentCross
+                .stream()
+                .map(UserDocumentCrossModel::getDocument)
+                .forEach(document -> {
+                    documentCategoryCrossRepository.findByDocumentId(document.getId())
+                            .forEach(documentCategoryCrossRepository::delete);
+                    documentIds.add(document.getId());
+                });
+
+        documentCross.forEach(userDocumentCrossRepository::delete);
+        documentIds.forEach(this::deleteDocument);
+    }
+
+    public OwnershipDTO isOwner(Integer documentId, String username) {
+
+        return OwnershipDTO
+                .builder()
+                .withIsOwner(userDocumentCrossRepository.findByUsernameAndDocumentId(documentId,
+                        username)
+                        .map(UserDocumentCrossModel::getOwnerAssociation)
+                        .orElse(null))
+                .withOwnerName(userDocumentCrossRepository.findOwnerTupleByDocumentId(documentId)
+                        .map(el -> el.getUser().getUsername())
+                        .orElse(null))
+                .build();
+
+    }
+
+    public List<DocumentShareDTO> getDocumentShareDetails(Integer documentId, String actualUsername) {
+        List<UserDocumentCrossModel> udcm = userDocumentCrossRepository.findUsersAccessReadOnly(documentId);
+
+        Set<UserModel> users = userRepository.findAllExcludeExceptInput(actualUsername);
+
+        Map<UserModel, UserDocumentCrossModel> crossModelMap = udcm.stream()
+                .collect(Collectors.toMap(
+                        UserDocumentCrossModel::getUser,
+                        Function.identity()));
+
+        return users.stream()
+                .map(user -> {
+                    UserDocumentCrossModel existingCross = crossModelMap.get(user);
+                    boolean active = existingCross != null;
+
+                    UserDocumentCrossModel crossModel = active
+                            ? existingCross
+                            : new UserDocumentCrossModel.builder()
+                                    .user(user)
+                                    .build();
+
+                    return DocumentShareDTO.mapToDocumentShareDTO(crossModel, active);
+                })
+                .toList();
+    }
+
+    @Transactional
+    public void saveOrUpdateShareInfo(List<DocumentShareDTO> dataInfo, Integer documentId, String requestUser) {
+
+        if (!this.isOwner(documentId, requestUser).isOwner())
+            throw new SharePermissionException();
+
+        List<DocumentShareDTO> actualDatabaseInformation = this.getDocumentShareDetails(documentId, requestUser);
+        dataInfo.stream()
+                .filter(data -> actualDatabaseInformation.contains(data) && !data.isEnabled())
+                .forEach(data -> {
+
+                    Optional<UserDocumentCrossModel> udcm = userDocumentCrossRepository
+                            .findByUsernameAndDocumentId(data.getDocumentId(), data.getUsername());
+
+                    if (udcm.isPresent()) {
+                        userDocumentCrossRepository.delete(udcm.get());
+                    }
+
+                });
+
+        dataInfo.stream()
+                .filter(data -> !actualDatabaseInformation.contains(data) && data.isEnabled())
+                .forEach(data -> {
+
+                    UserDocumentCrossModel toSave = new UserDocumentCrossModel.builder()
+                            .document(documentRepository.findById(documentId))
+                            .user(userRepository.findById(data.getUsername()))
+                            .build();
+
+                    userDocumentCrossRepository.persist(toSave);
+
+                });
+
+    }
+
+}

+ 69 - 0
src/main/java/it/pcdev/dokskan/central/service/impl/strategy/docupload/DocumentUploadServiceManualImpl.java

@@ -0,0 +1,69 @@
+package it.pcdev.dokskan.central.service.impl.strategy.docupload;
+
+import it.pcdev.dokskan.central.dto.DocumentDto;
+import it.pcdev.dokskan.central.exception.UploadException;
+import it.pcdev.dokskan.central.model.DocumentCategoryCrossModel;
+import it.pcdev.dokskan.central.model.DocumentModel;
+import it.pcdev.dokskan.central.model.UserDocumentCrossModel;
+import it.pcdev.dokskan.central.service.IDocumentUploadService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.PersistenceException;
+import jakarta.transaction.Transactional;
+
+@ApplicationScoped
+public class DocumentUploadServiceManualImpl extends DocumentUploadServiceCommon implements IDocumentUploadService {
+
+    @Override
+    @Transactional
+    public DocumentDto uploadDocument(DocumentDto documentDto, String requestingUserUsername) throws UploadException {
+
+        DocumentModel documentModel = documentMapper.toModel(documentDto, documentCategoryCrossRepository);
+
+        // Restituisce l'oggetto cosi' come e' stato salvato
+        DocumentModel savedEntity = documentRepository.save(documentModel);
+
+        /*
+         * Associazione dei documenti alle categorie
+         */
+        documentDto.getAdditionalData().getCategories().forEach(category -> {
+            final var categoryObj = confDocumentCategoryRepository.findByIdOptional(category)
+                    .orElseThrow(PersistenceException::new);
+
+            documentCategoryCrossRepository.persist(new DocumentCategoryCrossModel
+                    .builder()
+                    .document(savedEntity)
+                    .category(categoryObj)
+                    .build());
+        });
+
+        /*
+         * Associazione degli utenti ai documenti
+         */
+        if(documentDto.getAdditionalData().getAssociatedUsers() != null)
+            documentDto.getAdditionalData().getAssociatedUsers().forEach(user -> {
+                final var userObj = userRepository.findByIdOptional(user)
+                        .orElseThrow(PersistenceException::new);
+
+                userDocumentCrossRepository.persist(new UserDocumentCrossModel
+                        .builder()
+                        .document(savedEntity)
+                        .user(userObj)
+                        .build());
+
+            });
+
+        //Associazione dell'utente owner al documento
+        final var requestingUser = userRepository.findByIdOptional(requestingUserUsername)
+                .orElseThrow(PersistenceException::new);
+
+        userDocumentCrossRepository.persist(new UserDocumentCrossModel
+                .builder()
+                .document(savedEntity)
+                .user(requestingUser)
+                .ownerAssociation(true)
+                .build());
+
+        return documentMapper.toDto(savedEntity, documentCategoryCrossRepository);
+    }
+}
+

+ 13 - 0
src/main/resources/application.properties

@@ -0,0 +1,13 @@
+mp.jwt.sign.key-location=classpath:privateKey.pem
+smallrye.jwt.sign.key.location=classpath:privateKey.pem
+mp.jwt.verify.publickey.location=classpath:publicKey.pem
+quarkus.datasource.db-kind= postgresql
+quarkus.datasource.username= ${DB_USERNAME:postgres}
+quarkus.datasource.password= ${DB_PASSWORD:devpass}
+quarkus.datasource.jdbc.url=jdbc:postgresql://${DB_HOSTNAME:localhost}:${DB_PORT:5432}/dokskan
+quarkus.hibernate-orm.database.generation=update
+quarkus.banner.path=banner.txt
+
+
+quarkus.rest-client."it.pcdev.dokskan.central.client.AiClient".url=http://${AI_MS_HOSTNAME:localhost:8000}
+quarkus.rest-client."it.pcdev.dokskan.central.client.AiClient".scope=jakarta.inject.Singleton

+ 13 - 0
src/main/resources/banner.txt

@@ -0,0 +1,13 @@
+
+
+..............................................................
+..............................................................
+..............................................................
+....%%%%%....%%%%...%%..%%...%%%%...%%..%%...%%%%...%%..%%....
+....%%..%%..%%..%%..%%.%%...%%......%%.%%...%%..%%..%%%.%%....
+....%%..%%..%%..%%..%%%%.....%%%%...%%%%....%%%%%%..%%.%%%....
+....%%..%%..%%..%%..%%.%%.......%%..%%.%%...%%..%%..%%..%%....
+....%%%%%....%%%%...%%..%%...%%%%...%%..%%..%%..%%..%%..%%....
+..............................................................
+.........................Central MS...........................
+..............................................................

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно