Mohamed Abdel-Moneim commited on
Commit
2361e31
·
1 Parent(s): 6f2fb0a

Deploying Spring Boot

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. .mvn/wrapper/maven-wrapper.properties +3 -0
  3. Dockerfile +17 -0
  4. mvnw +295 -0
  5. mvnw.cmd +189 -0
  6. pom.xml +167 -0
  7. src/main/java/edu/alexu/fitfinder/FitFinderApplication.java +12 -0
  8. src/main/java/edu/alexu/fitfinder/component/JobRegistry.java +26 -0
  9. src/main/java/edu/alexu/fitfinder/configs/CloudinaryConfig.java +18 -0
  10. src/main/java/edu/alexu/fitfinder/configs/JwtAuthenticationFilter.java +60 -0
  11. src/main/java/edu/alexu/fitfinder/configs/SecurityConfig.java +85 -0
  12. src/main/java/edu/alexu/fitfinder/configs/WebSocketConfig.java +17 -0
  13. src/main/java/edu/alexu/fitfinder/controller/FavoriteController.java +88 -0
  14. src/main/java/edu/alexu/fitfinder/controller/GlobalExceptionHandler.java +62 -0
  15. src/main/java/edu/alexu/fitfinder/controller/SearchController.java +58 -0
  16. src/main/java/edu/alexu/fitfinder/controller/SegmentationController.java +65 -0
  17. src/main/java/edu/alexu/fitfinder/controller/UserController.java +118 -0
  18. src/main/java/edu/alexu/fitfinder/dto/ImageMasksDTO.java +14 -0
  19. src/main/java/edu/alexu/fitfinder/dto/ItemDTO.java +22 -0
  20. src/main/java/edu/alexu/fitfinder/dto/ResegmentImageDTO.java +17 -0
  21. src/main/java/edu/alexu/fitfinder/dto/SearchRequestDTO.java +16 -0
  22. src/main/java/edu/alexu/fitfinder/dto/SearchResponseDTO.java +23 -0
  23. src/main/java/edu/alexu/fitfinder/dto/UserDTO.java +18 -0
  24. src/main/java/edu/alexu/fitfinder/entity/Favorite.java +30 -0
  25. src/main/java/edu/alexu/fitfinder/entity/ItemVector.java +26 -0
  26. src/main/java/edu/alexu/fitfinder/entity/StoredItem.java +61 -0
  27. src/main/java/edu/alexu/fitfinder/entity/UploadedImage.java +37 -0
  28. src/main/java/edu/alexu/fitfinder/entity/User.java +41 -0
  29. src/main/java/edu/alexu/fitfinder/exception/FavoriteNotFoundException.java +7 -0
  30. src/main/java/edu/alexu/fitfinder/exception/ImageNotFoundException.java +7 -0
  31. src/main/java/edu/alexu/fitfinder/exception/InvalidInputException.java +7 -0
  32. src/main/java/edu/alexu/fitfinder/exception/ItemNotFoundException.java +7 -0
  33. src/main/java/edu/alexu/fitfinder/exception/LogInException.java +7 -0
  34. src/main/java/edu/alexu/fitfinder/exception/SocketException.java +7 -0
  35. src/main/java/edu/alexu/fitfinder/exception/UnauthorizedException.java +7 -0
  36. src/main/java/edu/alexu/fitfinder/exception/UserAlreadyExistsException.java +7 -0
  37. src/main/java/edu/alexu/fitfinder/exception/UserNotFoundException.java +7 -0
  38. src/main/java/edu/alexu/fitfinder/exception/ValidatorException.java +7 -0
  39. src/main/java/edu/alexu/fitfinder/handler/MyWebSocketHandler.java +26 -0
  40. src/main/java/edu/alexu/fitfinder/mapper/ItemMapper.java +42 -0
  41. src/main/java/edu/alexu/fitfinder/mapper/UserMapper.java +35 -0
  42. src/main/java/edu/alexu/fitfinder/repository/FavoriteRepo.java +25 -0
  43. src/main/java/edu/alexu/fitfinder/repository/ImageRepo.java +9 -0
  44. src/main/java/edu/alexu/fitfinder/repository/ItemRepo.java +22 -0
  45. src/main/java/edu/alexu/fitfinder/repository/UserRepo.java +10 -0
  46. src/main/java/edu/alexu/fitfinder/service/AIService.java +74 -0
  47. src/main/java/edu/alexu/fitfinder/service/FavoriteService.java +88 -0
  48. src/main/java/edu/alexu/fitfinder/service/ImageService.java +74 -0
  49. src/main/java/edu/alexu/fitfinder/service/JwtService.java +88 -0
  50. src/main/java/edu/alexu/fitfinder/service/SearchService.java +74 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ /mvnw text eol=lf
37
+ *.cmd text eol=crlf
.mvn/wrapper/maven-wrapper.properties ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ wrapperVersion=3.3.4
2
+ distributionType=only-script
3
+ distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM maven:3.9.6-eclipse-temurin-17 AS build
2
+ WORKDIR /app
3
+ COPY pom.xml .
4
+ COPY src ./src
5
+ RUN mvn clean package -DskipTests
6
+
7
+
8
+ FROM eclipse-temurin:17-jre-alpine
9
+ WORKDIR /app
10
+
11
+
12
+ COPY --from=build /app/target/*.jar app.jar
13
+
14
+ EXPOSE 7860
15
+
16
+ # Run the jar
17
+ ENTRYPOINT ["java", "-jar", "app.jar"]
mvnw ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ # ----------------------------------------------------------------------------
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+ # ----------------------------------------------------------------------------
20
+
21
+ # ----------------------------------------------------------------------------
22
+ # Apache Maven Wrapper startup batch script, version 3.3.4
23
+ #
24
+ # Optional ENV vars
25
+ # -----------------
26
+ # JAVA_HOME - location of a JDK home dir, required when download maven via java source
27
+ # MVNW_REPOURL - repo url base for downloading maven distribution
28
+ # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
29
+ # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
30
+ # ----------------------------------------------------------------------------
31
+
32
+ set -euf
33
+ [ "${MVNW_VERBOSE-}" != debug ] || set -x
34
+
35
+ # OS specific support.
36
+ native_path() { printf %s\\n "$1"; }
37
+ case "$(uname)" in
38
+ CYGWIN* | MINGW*)
39
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
40
+ native_path() { cygpath --path --windows "$1"; }
41
+ ;;
42
+ esac
43
+
44
+ # set JAVACMD and JAVACCMD
45
+ set_java_home() {
46
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
47
+ if [ -n "${JAVA_HOME-}" ]; then
48
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
49
+ # IBM's JDK on AIX uses strange locations for the executables
50
+ JAVACMD="$JAVA_HOME/jre/sh/java"
51
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
52
+ else
53
+ JAVACMD="$JAVA_HOME/bin/java"
54
+ JAVACCMD="$JAVA_HOME/bin/javac"
55
+
56
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
57
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
58
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
59
+ return 1
60
+ fi
61
+ fi
62
+ else
63
+ JAVACMD="$(
64
+ 'set' +e
65
+ 'unset' -f command 2>/dev/null
66
+ 'command' -v java
67
+ )" || :
68
+ JAVACCMD="$(
69
+ 'set' +e
70
+ 'unset' -f command 2>/dev/null
71
+ 'command' -v javac
72
+ )" || :
73
+
74
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
75
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
76
+ return 1
77
+ fi
78
+ fi
79
+ }
80
+
81
+ # hash string like Java String::hashCode
82
+ hash_string() {
83
+ str="${1:-}" h=0
84
+ while [ -n "$str" ]; do
85
+ char="${str%"${str#?}"}"
86
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
87
+ str="${str#?}"
88
+ done
89
+ printf %x\\n $h
90
+ }
91
+
92
+ verbose() { :; }
93
+ [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
94
+
95
+ die() {
96
+ printf %s\\n "$1" >&2
97
+ exit 1
98
+ }
99
+
100
+ trim() {
101
+ # MWRAPPER-139:
102
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
103
+ # Needed for removing poorly interpreted newline sequences when running in more
104
+ # exotic environments such as mingw bash on Windows.
105
+ printf "%s" "${1}" | tr -d '[:space:]'
106
+ }
107
+
108
+ scriptDir="$(dirname "$0")"
109
+ scriptName="$(basename "$0")"
110
+
111
+ # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
112
+ while IFS="=" read -r key value; do
113
+ case "${key-}" in
114
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
115
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
116
+ esac
117
+ done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
118
+ [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
119
+
120
+ case "${distributionUrl##*/}" in
121
+ maven-mvnd-*bin.*)
122
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
123
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
124
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
125
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
126
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
127
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
128
+ *)
129
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
130
+ distributionPlatform=linux-amd64
131
+ ;;
132
+ esac
133
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
134
+ ;;
135
+ maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
136
+ *) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
137
+ esac
138
+
139
+ # apply MVNW_REPOURL and calculate MAVEN_HOME
140
+ # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
141
+ [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
142
+ distributionUrlName="${distributionUrl##*/}"
143
+ distributionUrlNameMain="${distributionUrlName%.*}"
144
+ distributionUrlNameMain="${distributionUrlNameMain%-bin}"
145
+ MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
146
+ MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
147
+
148
+ exec_maven() {
149
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
150
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
151
+ }
152
+
153
+ if [ -d "$MAVEN_HOME" ]; then
154
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
155
+ exec_maven "$@"
156
+ fi
157
+
158
+ case "${distributionUrl-}" in
159
+ *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
160
+ *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
161
+ esac
162
+
163
+ # prepare tmp dir
164
+ if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
165
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
166
+ trap clean HUP INT TERM EXIT
167
+ else
168
+ die "cannot create temp dir"
169
+ fi
170
+
171
+ mkdir -p -- "${MAVEN_HOME%/*}"
172
+
173
+ # Download and Install Apache Maven
174
+ verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
175
+ verbose "Downloading from: $distributionUrl"
176
+ verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
177
+
178
+ # select .zip or .tar.gz
179
+ if ! command -v unzip >/dev/null; then
180
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
181
+ distributionUrlName="${distributionUrl##*/}"
182
+ fi
183
+
184
+ # verbose opt
185
+ __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
186
+ [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
187
+
188
+ # normalize http auth
189
+ case "${MVNW_PASSWORD:+has-password}" in
190
+ '') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
191
+ has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
192
+ esac
193
+
194
+ if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
195
+ verbose "Found wget ... using wget"
196
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
197
+ elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
198
+ verbose "Found curl ... using curl"
199
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
200
+ elif set_java_home; then
201
+ verbose "Falling back to use Java to download"
202
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
203
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
204
+ cat >"$javaSource" <<-END
205
+ public class Downloader extends java.net.Authenticator
206
+ {
207
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
208
+ {
209
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
210
+ }
211
+ public static void main( String[] args ) throws Exception
212
+ {
213
+ setDefault( new Downloader() );
214
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
215
+ }
216
+ }
217
+ END
218
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
219
+ verbose " - Compiling Downloader.java ..."
220
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
221
+ verbose " - Running Downloader.java ..."
222
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
223
+ fi
224
+
225
+ # If specified, validate the SHA-256 sum of the Maven distribution zip file
226
+ if [ -n "${distributionSha256Sum-}" ]; then
227
+ distributionSha256Result=false
228
+ if [ "$MVN_CMD" = mvnd.sh ]; then
229
+ echo "Checksum validation is not supported for maven-mvnd." >&2
230
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
231
+ exit 1
232
+ elif command -v sha256sum >/dev/null; then
233
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
234
+ distributionSha256Result=true
235
+ fi
236
+ elif command -v shasum >/dev/null; then
237
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
238
+ distributionSha256Result=true
239
+ fi
240
+ else
241
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
242
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
243
+ exit 1
244
+ fi
245
+ if [ $distributionSha256Result = false ]; then
246
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
247
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
248
+ exit 1
249
+ fi
250
+ fi
251
+
252
+ # unzip and move
253
+ if command -v unzip >/dev/null; then
254
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
255
+ else
256
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
257
+ fi
258
+
259
+ # Find the actual extracted directory name (handles snapshots where filename != directory name)
260
+ actualDistributionDir=""
261
+
262
+ # First try the expected directory name (for regular distributions)
263
+ if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
264
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
265
+ actualDistributionDir="$distributionUrlNameMain"
266
+ fi
267
+ fi
268
+
269
+ # If not found, search for any directory with the Maven executable (for snapshots)
270
+ if [ -z "$actualDistributionDir" ]; then
271
+ # enable globbing to iterate over items
272
+ set +f
273
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
274
+ if [ -d "$dir" ]; then
275
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
276
+ actualDistributionDir="$(basename "$dir")"
277
+ break
278
+ fi
279
+ fi
280
+ done
281
+ set -f
282
+ fi
283
+
284
+ if [ -z "$actualDistributionDir" ]; then
285
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
286
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
287
+ die "Could not find Maven distribution directory in extracted archive"
288
+ fi
289
+
290
+ verbose "Found extracted Maven distribution directory: $actualDistributionDir"
291
+ printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
292
+ mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
293
+
294
+ clean || :
295
+ exec_maven "$@"
mvnw.cmd ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <# : batch portion
2
+ @REM ----------------------------------------------------------------------------
3
+ @REM Licensed to the Apache Software Foundation (ASF) under one
4
+ @REM or more contributor license agreements. See the NOTICE file
5
+ @REM distributed with this work for additional information
6
+ @REM regarding copyright ownership. The ASF licenses this file
7
+ @REM to you under the Apache License, Version 2.0 (the
8
+ @REM "License"); you may not use this file except in compliance
9
+ @REM with the License. You may obtain a copy of the License at
10
+ @REM
11
+ @REM http://www.apache.org/licenses/LICENSE-2.0
12
+ @REM
13
+ @REM Unless required by applicable law or agreed to in writing,
14
+ @REM software distributed under the License is distributed on an
15
+ @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ @REM KIND, either express or implied. See the License for the
17
+ @REM specific language governing permissions and limitations
18
+ @REM under the License.
19
+ @REM ----------------------------------------------------------------------------
20
+
21
+ @REM ----------------------------------------------------------------------------
22
+ @REM Apache Maven Wrapper startup batch script, version 3.3.4
23
+ @REM
24
+ @REM Optional ENV vars
25
+ @REM MVNW_REPOURL - repo url base for downloading maven distribution
26
+ @REM MVNW_USERNAME/MVNW_PASSWORD - userDTO and password for downloading maven
27
+ @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
28
+ @REM ----------------------------------------------------------------------------
29
+
30
+ @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
31
+ @SET __MVNW_CMD__=
32
+ @SET __MVNW_ERROR__=
33
+ @SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
34
+ @SET PSModulePath=
35
+ @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
36
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
37
+ )
38
+ @SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
39
+ @SET __MVNW_PSMODULEP_SAVE=
40
+ @SET __MVNW_ARG0_NAME__=
41
+ @SET MVNW_USERNAME=
42
+ @SET MVNW_PASSWORD=
43
+ @IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
44
+ @echo Cannot start maven from wrapper >&2 && exit /b 1
45
+ @GOTO :EOF
46
+ : end batch / begin powershell #>
47
+
48
+ $ErrorActionPreference = "Stop"
49
+ if ($env:MVNW_VERBOSE -eq "true") {
50
+ $VerbosePreference = "Continue"
51
+ }
52
+
53
+ # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
54
+ $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
55
+ if (!$distributionUrl) {
56
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
57
+ }
58
+
59
+ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
60
+ "maven-mvnd-*" {
61
+ $USE_MVND = $true
62
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
63
+ $MVN_CMD = "mvnd.cmd"
64
+ break
65
+ }
66
+ default {
67
+ $USE_MVND = $false
68
+ $MVN_CMD = $script -replace '^mvnw','mvn'
69
+ break
70
+ }
71
+ }
72
+
73
+ # apply MVNW_REPOURL and calculate MAVEN_HOME
74
+ # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
75
+ if ($env:MVNW_REPOURL) {
76
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
77
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
78
+ }
79
+ $distributionUrlName = $distributionUrl -replace '^.*/',''
80
+ $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
81
+
82
+ $MAVEN_M2_PATH = "$HOME/.m2"
83
+ if ($env:MAVEN_USER_HOME) {
84
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
85
+ }
86
+
87
+ if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
88
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
89
+ }
90
+
91
+ $MAVEN_WRAPPER_DISTS = $null
92
+ if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
93
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
94
+ } else {
95
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
96
+ }
97
+
98
+ $MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
99
+ $MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
100
+ $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
101
+
102
+ if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
103
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
104
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
105
+ exit $?
106
+ }
107
+
108
+ if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
109
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
110
+ }
111
+
112
+ # prepare tmp dir
113
+ $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
114
+ $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
115
+ $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
116
+ trap {
117
+ if ($TMP_DOWNLOAD_DIR.Exists) {
118
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
119
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
120
+ }
121
+ }
122
+
123
+ New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
124
+
125
+ # Download and Install Apache Maven
126
+ Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
127
+ Write-Verbose "Downloading from: $distributionUrl"
128
+ Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
129
+
130
+ $webclient = New-Object System.Net.WebClient
131
+ if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
132
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
133
+ }
134
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
135
+ $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
136
+
137
+ # If specified, validate the SHA-256 sum of the Maven distribution zip file
138
+ $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
139
+ if ($distributionSha256Sum) {
140
+ if ($USE_MVND) {
141
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
142
+ }
143
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
144
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
145
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
146
+ }
147
+ }
148
+
149
+ # unzip and move
150
+ Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
151
+
152
+ # Find the actual extracted directory name (handles snapshots where filename != directory name)
153
+ $actualDistributionDir = ""
154
+
155
+ # First try the expected directory name (for regular distributions)
156
+ $expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
157
+ $expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
158
+ if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
159
+ $actualDistributionDir = $distributionUrlNameMain
160
+ }
161
+
162
+ # If not found, search for any directory with the Maven executable (for snapshots)
163
+ if (!$actualDistributionDir) {
164
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
165
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
166
+ if (Test-Path -Path $testPath -PathType Leaf) {
167
+ $actualDistributionDir = $_.Name
168
+ }
169
+ }
170
+ }
171
+
172
+ if (!$actualDistributionDir) {
173
+ Write-Error "Could not find Maven distribution directory in extracted archive"
174
+ }
175
+
176
+ Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
177
+ Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
178
+ try {
179
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
180
+ } catch {
181
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
182
+ Write-Error "fail to move MAVEN_HOME"
183
+ }
184
+ } finally {
185
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
186
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
187
+ }
188
+
189
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
pom.xml ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <parent>
6
+ <groupId>org.springframework.boot</groupId>
7
+ <artifactId>spring-boot-starter-parent</artifactId>
8
+ <version>3.5.6</version>
9
+ <relativePath/> <!-- lookup parent from repository -->
10
+ </parent>
11
+ <groupId>edu.alexu</groupId>
12
+ <artifactId>fitfinder</artifactId>
13
+ <version>0.0.1-SNAPSHOT</version>
14
+ <name>FItFinder</name>
15
+ <description>Demo project for Spring Boot</description>
16
+ <url/>
17
+ <licenses>
18
+ <license/>
19
+ </licenses>
20
+ <developers>
21
+ <developer/>
22
+ </developers>
23
+ <scm>
24
+ <connection/>
25
+ <developerConnection/>
26
+ <tag/>
27
+ <url/>
28
+ </scm>
29
+ <properties>
30
+ <maven.compiler.source>17</maven.compiler.source>
31
+ <maven.compiler.target>17</maven.compiler.target>
32
+ <maven.compiler.release>17</maven.compiler.release>
33
+ <java.version>21</java.version>
34
+ </properties>
35
+
36
+ <dependencies>
37
+
38
+ <dependency>
39
+ <groupId>org.springframework.boot</groupId>
40
+ <artifactId>spring-boot-starter-actuator</artifactId>
41
+ </dependency>
42
+
43
+ <dependency>
44
+ <groupId>org.springframework.boot</groupId>
45
+ <artifactId>spring-boot-starter-web</artifactId>
46
+ </dependency>
47
+ <dependency>
48
+ <groupId>org.springframework.boot</groupId>
49
+ <artifactId>spring-boot-devtools</artifactId>
50
+ <scope>runtime</scope>
51
+ <optional>true</optional>
52
+ </dependency>
53
+ <dependency>
54
+ <groupId>org.projectlombok</groupId>
55
+ <artifactId>lombok</artifactId>
56
+ <optional>true</optional>
57
+ </dependency>
58
+ <dependency>
59
+ <groupId>org.springframework.boot</groupId>
60
+ <artifactId>spring-boot-starter-test</artifactId>
61
+ <scope>test</scope>
62
+ </dependency>
63
+ <dependency>
64
+ <groupId>org.postgresql</groupId>
65
+ <artifactId>postgresql</artifactId>
66
+ <scope>runtime</scope>
67
+ </dependency>
68
+ <dependency>
69
+ <groupId>org.springframework.boot</groupId>
70
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
71
+ </dependency>
72
+ <dependency>
73
+ <groupId>me.paulschwarz</groupId>
74
+ <artifactId>spring-dotenv</artifactId>
75
+ <version>4.0.0</version>
76
+ </dependency>
77
+ <dependency>
78
+ <groupId>org.mindrot</groupId>
79
+ <artifactId>jbcrypt</artifactId>
80
+ <version>0.4</version>
81
+ </dependency>
82
+ <dependency>
83
+ <groupId>commons-validator</groupId>
84
+ <artifactId>commons-validator</artifactId>
85
+ <version>1.10.0</version>
86
+ </dependency>
87
+ <dependency>
88
+ <groupId>io.jsonwebtoken</groupId>
89
+ <artifactId>jjwt-api</artifactId>
90
+ <version>0.11.5</version>
91
+ </dependency>
92
+ <dependency>
93
+ <groupId>io.jsonwebtoken</groupId>
94
+ <artifactId>jjwt-impl</artifactId>
95
+ <version>0.11.5</version>
96
+ </dependency>
97
+ <dependency>
98
+ <groupId>io.jsonwebtoken</groupId>
99
+ <artifactId>jjwt-jackson</artifactId>
100
+ <version>0.11.5</version>
101
+ </dependency>
102
+ <dependency>
103
+ <groupId>org.springframework.boot</groupId>
104
+ <artifactId>spring-boot-starter-security</artifactId>
105
+ </dependency>
106
+ <dependency>
107
+ <groupId>com.cloudinary</groupId>
108
+ <artifactId>cloudinary-http5</artifactId>
109
+ <version>2.0.0</version>
110
+ </dependency>
111
+ <dependency>
112
+ <groupId>org.springframework.boot</groupId>
113
+ <artifactId>spring-boot-starter-webflux</artifactId>
114
+ </dependency>
115
+ <dependency>
116
+ <groupId>org.springframework.boot</groupId>
117
+ <artifactId>spring-boot-starter-websocket</artifactId>
118
+ </dependency>
119
+ </dependencies>
120
+
121
+ <build>
122
+ <plugins>
123
+ <plugin>
124
+ <groupId>org.apache.maven.plugins</groupId>
125
+ <artifactId>maven-compiler-plugin</artifactId>
126
+ <configuration>
127
+ <annotationProcessorPaths>
128
+ <path>
129
+ <groupId>org.projectlombok</groupId>
130
+ <artifactId>lombok</artifactId>
131
+ </path>
132
+ </annotationProcessorPaths>
133
+ </configuration>
134
+ </plugin>
135
+ <plugin>
136
+ <groupId>org.apache.maven.plugins</groupId>
137
+ <artifactId>maven-compiler-plugin</artifactId>
138
+ <version>3.14.0</version>
139
+ <configuration>
140
+ <source>17</source>
141
+ <target>17</target>
142
+ <release>17</release>
143
+ <annotationProcessorPaths>
144
+ <path>
145
+ <groupId>org.projectlombok</groupId>
146
+ <artifactId>lombok</artifactId>
147
+ <version>1.18.34</version>
148
+ </path>
149
+ </annotationProcessorPaths>
150
+ </configuration>
151
+ </plugin>
152
+ <plugin>
153
+ <groupId>org.springframework.boot</groupId>
154
+ <artifactId>spring-boot-maven-plugin</artifactId>
155
+ <configuration>
156
+ <excludes>
157
+ <exclude>
158
+ <groupId>org.projectlombok</groupId>
159
+ <artifactId>lombok</artifactId>
160
+ </exclude>
161
+ </excludes>
162
+ </configuration>
163
+ </plugin>
164
+ </plugins>
165
+ </build>
166
+
167
+ </project>
src/main/java/edu/alexu/fitfinder/FitFinderApplication.java ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+
6
+ @SpringBootApplication
7
+
8
+ public class FitFinderApplication {
9
+ public static void main(String[] args) {
10
+ SpringApplication.run(FitFinderApplication.class, args);
11
+ }
12
+ }
src/main/java/edu/alexu/fitfinder/component/JobRegistry.java ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.component;
2
+
3
+ import org.springframework.stereotype.Component;
4
+ import org.springframework.web.socket.WebSocketSession;
5
+ import java.util.concurrent.ConcurrentHashMap;
6
+
7
+ @Component
8
+ public class JobRegistry {
9
+
10
+ private final ConcurrentHashMap<String, WebSocketSession> jobToSession =
11
+ new ConcurrentHashMap<>();
12
+
13
+ public void registerJob(String jobId, WebSocketSession session) {
14
+ System.out.println("Job " + jobId + " has been registered with session " + session.getId());
15
+ jobToSession.put(jobId, session);
16
+ }
17
+
18
+ public WebSocketSession getSession(String jobId) {
19
+ return jobToSession.get(jobId);
20
+ }
21
+
22
+ public void removeJob(String jobId) {
23
+ System.out.println("The websocket session is no longer registered with job " + jobId);
24
+ jobToSession.remove(jobId);
25
+ }
26
+ }
src/main/java/edu/alexu/fitfinder/configs/CloudinaryConfig.java ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.configs;
2
+
3
+ import com.cloudinary.Cloudinary;
4
+ import org.springframework.beans.factory.annotation.Value;
5
+ import org.springframework.context.annotation.Bean;
6
+ import org.springframework.context.annotation.Configuration;
7
+
8
+ @Configuration
9
+ public class CloudinaryConfig {
10
+
11
+ @Value("${cloudinary.url}")
12
+ private String cloudinaryUrl;
13
+
14
+ @Bean
15
+ public Cloudinary cloudinary() {
16
+ return new Cloudinary(cloudinaryUrl);
17
+ }
18
+ }
src/main/java/edu/alexu/fitfinder/configs/JwtAuthenticationFilter.java ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.configs;
2
+
3
+ import jakarta.servlet.FilterChain;
4
+ import jakarta.servlet.ServletException;
5
+ import jakarta.servlet.http.HttpServletRequest;
6
+ import jakarta.servlet.http.HttpServletResponse;
7
+ import lombok.AllArgsConstructor;
8
+ import org.springframework.lang.NonNull;
9
+ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
10
+ import org.springframework.security.core.Authentication;
11
+ import org.springframework.security.core.authority.SimpleGrantedAuthority;
12
+ import org.springframework.security.core.context.SecurityContextHolder;
13
+ import org.springframework.security.core.userdetails.UserDetails;
14
+ import org.springframework.security.core.userdetails.User;
15
+ import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
16
+ import org.springframework.stereotype.Component;
17
+ import org.springframework.web.filter.OncePerRequestFilter;
18
+ import edu.alexu.fitfinder.service.JwtService;
19
+ import java.io.IOException;
20
+ import java.util.List;
21
+
22
+ @AllArgsConstructor
23
+ @Component
24
+ public class JwtAuthenticationFilter extends OncePerRequestFilter {
25
+
26
+ private final JwtService jwtService;
27
+
28
+ @Override
29
+ protected void doFilterInternal(
30
+ @NonNull HttpServletRequest request,
31
+ @NonNull HttpServletResponse response,
32
+ @NonNull FilterChain filterChain)
33
+ throws ServletException, IOException {
34
+
35
+ final String authHeader = request.getHeader("Authorization");
36
+
37
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
38
+ filterChain.doFilter(request, response);
39
+ return;
40
+ }
41
+
42
+ final String jwt = authHeader.substring(7);
43
+ final String userId = jwtService.extractUserId(jwt);
44
+
45
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
46
+ if (userId != null && authentication == null) {
47
+ UserDetails userDetails =
48
+ new User(userId, "", List.of(new SimpleGrantedAuthority("GENERAL")));
49
+
50
+ if (!jwtService.isTokenExpired(jwt)) {
51
+ UsernamePasswordAuthenticationToken authToken =
52
+ new UsernamePasswordAuthenticationToken(
53
+ userDetails, null, userDetails.getAuthorities());
54
+ authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
55
+ SecurityContextHolder.getContext().setAuthentication(authToken);
56
+ }
57
+ }
58
+ filterChain.doFilter(request, response);
59
+ }
60
+ }
src/main/java/edu/alexu/fitfinder/configs/SecurityConfig.java ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.configs;
2
+
3
+ import java.util.List;
4
+ import org.springframework.beans.factory.annotation.Autowired;
5
+ import org.springframework.context.annotation.Bean;
6
+ import org.springframework.context.annotation.Configuration;
7
+ import org.springframework.security.authentication.AuthenticationManager;
8
+ import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
9
+ import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
10
+ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
11
+ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
12
+ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
13
+ import org.springframework.security.config.http.SessionCreationPolicy;
14
+ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
15
+ import org.springframework.security.crypto.password.PasswordEncoder;
16
+ import org.springframework.security.web.SecurityFilterChain;
17
+ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
18
+
19
+ @Configuration
20
+ @EnableWebSecurity
21
+ @EnableMethodSecurity
22
+ public class SecurityConfig {
23
+
24
+ @Autowired private JwtAuthenticationFilter authFilter;
25
+
26
+ @Bean
27
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
28
+ http.csrf(AbstractHttpConfigurer::disable)
29
+ .cors(
30
+ cors ->
31
+ cors.configurationSource(
32
+ request -> {
33
+ var corsConfiguration = new org.springframework.web.cors.CorsConfiguration();
34
+ // Allow common frontend origins (GitHub Pages, local dev) and ngrok domain
35
+ corsConfiguration.setAllowedOriginPatterns(
36
+ List.of(
37
+ "https://*.github.io",
38
+ "https://*.githubusercontent.com",
39
+ "http://localhost:*",
40
+ "http://127.0.0.1:*",
41
+ "http://192.168.*.*",
42
+ "https://*.ngrok-free.dev",
43
+ "https://*.ngrok-free.app",
44
+ "https://telescopic-ungodlily-wilbert.ngrok-free.dev"
45
+ ));
46
+ corsConfiguration.setAllowCredentials(true); // allow cookies/authorization
47
+ corsConfiguration.setAllowedMethods(
48
+ List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
49
+ corsConfiguration.setAllowedHeaders(List.of("*"));
50
+ corsConfiguration.setExposedHeaders(List.of("*"));
51
+ return corsConfiguration;
52
+ }))
53
+ .authorizeHttpRequests(
54
+ auth ->
55
+ auth.requestMatchers(
56
+ "/api/v1/auth/**",
57
+ "/api/v1/items/**",
58
+ "/api/v1/favorites/**",
59
+ "/segment/upload",
60
+ "/re-segment",
61
+ "/segmentation/callback",
62
+ "/ws"
63
+ )
64
+ .permitAll()
65
+ .anyRequest()
66
+ .authenticated())
67
+ .sessionManagement(
68
+ session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // No sessions
69
+ )
70
+ .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class);
71
+
72
+ return http.build();
73
+ }
74
+
75
+ @Bean
76
+ public PasswordEncoder passwordEncoder() {
77
+ return new BCryptPasswordEncoder(); // Password encoding
78
+ }
79
+
80
+ @Bean
81
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
82
+ throws Exception {
83
+ return config.getAuthenticationManager();
84
+ }
85
+ }
src/main/java/edu/alexu/fitfinder/configs/WebSocketConfig.java ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.configs;
2
+
3
+ import edu.alexu.fitfinder.handler.MyWebSocketHandler;
4
+ import org.springframework.context.annotation.Configuration;
5
+ import org.springframework.context.annotation.Bean;
6
+ import org.springframework.web.socket.config.annotation.*;
7
+ import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
8
+
9
+ @Configuration
10
+ @EnableWebSocket
11
+ public class WebSocketConfig implements WebSocketConfigurer {
12
+
13
+ @Override
14
+ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
15
+ registry.addHandler(new MyWebSocketHandler(), "/ws").setAllowedOriginPatterns("*");
16
+ }
17
+ }
src/main/java/edu/alexu/fitfinder/controller/FavoriteController.java ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.controller;
2
+
3
+ import edu.alexu.fitfinder.dto.ItemDTO;
4
+ import edu.alexu.fitfinder.exception.FavoriteNotFoundException;
5
+ import edu.alexu.fitfinder.exception.ItemNotFoundException;
6
+ import edu.alexu.fitfinder.exception.UserNotFoundException;
7
+ import edu.alexu.fitfinder.service.FavoriteService;
8
+ import edu.alexu.fitfinder.service.JwtService;
9
+ import lombok.RequiredArgsConstructor;
10
+ import org.springframework.http.HttpStatus;
11
+ import org.springframework.http.ResponseEntity;
12
+ import org.springframework.web.bind.annotation.*;
13
+
14
+ import java.util.List;
15
+
16
+ @RestController
17
+ @RequestMapping("/api/v1/favorites")
18
+ @CrossOrigin
19
+ @RequiredArgsConstructor
20
+ public class FavoriteController {
21
+
22
+ private final JwtService jwtService;
23
+ private final FavoriteService favoriteService;
24
+
25
+
26
+ @PostMapping("/{itemId}")
27
+ public ResponseEntity<?> addToFavorites(
28
+ @PathVariable Long itemId,
29
+ @RequestHeader("Authorization") String token) {
30
+
31
+ try {
32
+ Long userId = jwtService.extractUserFromToken(token);
33
+ favoriteService.addFavorite(userId, itemId);
34
+ return ResponseEntity.ok("Item added to favorites");
35
+ } catch (IllegalArgumentException e) {
36
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
37
+ }
38
+ }
39
+
40
+ // FIX: Changed path to avoid conflict with /{itemId}
41
+ @DeleteMapping("/id/{favId}")
42
+ public ResponseEntity<?> deleteById(
43
+ @PathVariable Long favId,
44
+ @RequestHeader("Authorization") String token) {
45
+
46
+ try {
47
+ Long userId = jwtService.extractUserFromToken(token);
48
+ favoriteService.deleteFavoriteById(userId, favId);
49
+ return ResponseEntity.ok("Item removed from favorites");
50
+
51
+ } catch (IllegalArgumentException e) {
52
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
53
+ } catch (FavoriteNotFoundException e) {
54
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
55
+ } catch (RuntimeException e) {
56
+ // Catches the "Unauthorized: You do not own this favorite" from Service
57
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getMessage());
58
+ }
59
+ }
60
+
61
+ // This handles DELETE /favorites/5 (Assumes 5 is Item ID)
62
+ @DeleteMapping("/{itemId}")
63
+ public ResponseEntity<?> deleteByItem(
64
+ @PathVariable Long itemId,
65
+ @RequestHeader("Authorization") String token) {
66
+
67
+ try {
68
+ Long userId = jwtService.extractUserFromToken(token);
69
+ favoriteService.deleteFavoriteByItem(userId, itemId);
70
+ return ResponseEntity.ok("Item removed from favorites");
71
+
72
+ } catch (IllegalArgumentException e) {
73
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
74
+ } catch (FavoriteNotFoundException | UserNotFoundException | ItemNotFoundException e) {
75
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
76
+ }
77
+ }
78
+
79
+ @GetMapping
80
+ public ResponseEntity<List<ItemDTO>> getAllFavorites(@RequestHeader("Authorization") String token) {
81
+ try{
82
+ Long userId = jwtService.extractUserFromToken(token);
83
+ return ResponseEntity.ok(favoriteService.getUserFavorites(userId));
84
+ }catch (IllegalArgumentException e) {
85
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
86
+ }
87
+ }
88
+ }
src/main/java/edu/alexu/fitfinder/controller/GlobalExceptionHandler.java ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.controller;
2
+
3
+ import edu.alexu.fitfinder.exception.InvalidInputException;
4
+ import edu.alexu.fitfinder.exception.UnauthorizedException;
5
+ import edu.alexu.fitfinder.exception.UserAlreadyExistsException;
6
+ import edu.alexu.fitfinder.exception.SocketException;
7
+ import org.springframework.http.HttpStatus;
8
+ import org.springframework.http.ResponseEntity;
9
+ import org.springframework.web.bind.annotation.ExceptionHandler;
10
+ import org.springframework.web.bind.annotation.RestControllerAdvice;
11
+ import org.springframework.web.multipart.MaxUploadSizeExceededException;
12
+ import org.springframework.web.multipart.MultipartException;
13
+
14
+ import java.io.IOException;
15
+ import java.util.Map;
16
+
17
+ @RestControllerAdvice
18
+ public class GlobalExceptionHandler {
19
+ @ExceptionHandler(UserAlreadyExistsException.class)
20
+ public ResponseEntity<Map<String, String>> handleUserExists(UserAlreadyExistsException e) {
21
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of("error", e.getMessage()));
22
+ }
23
+
24
+ @ExceptionHandler(InvalidInputException.class)
25
+ public ResponseEntity<Map<String, String>> handleInvalidInput(InvalidInputException e) {
26
+ return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY)
27
+ .body(Map.of("error", e.getMessage()));
28
+ }
29
+
30
+ @ExceptionHandler(UnauthorizedException.class)
31
+ public ResponseEntity<Map<String, String>> handleUnauthorizedRequest(UnauthorizedException e) {
32
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", e.getMessage()));
33
+ }
34
+
35
+ @ExceptionHandler(MaxUploadSizeExceededException.class)
36
+ public ResponseEntity<?> handleMaxSizeException(MaxUploadSizeExceededException exc) {
37
+ return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
38
+ .body("File size shouldn't exceeds 10MB.");
39
+ }
40
+
41
+ @ExceptionHandler(MultipartException.class)
42
+ public ResponseEntity<?> handleMultipartException(MultipartException exc) {
43
+ return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE).body("Invalid file upload.");
44
+ }
45
+
46
+ @ExceptionHandler(SocketException.class)
47
+ public ResponseEntity<Map<String, String>> handleSocketException(SocketException e) {
48
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Map.of("error", e.getMessage()));
49
+ }
50
+
51
+ @ExceptionHandler(IOException.class)
52
+ public ResponseEntity<Map<String, String>> handleIOException(IOException e) {
53
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
54
+ .body(Map.of("error", e.getMessage()));
55
+ }
56
+
57
+ @ExceptionHandler(Exception.class)
58
+ public ResponseEntity<Map<String, String>> handleGeneric(Exception e) {
59
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
60
+ .body(Map.of("error", "Unexpected error occurred"));
61
+ }
62
+ }
src/main/java/edu/alexu/fitfinder/controller/SearchController.java ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.controller;
2
+
3
+ import edu.alexu.fitfinder.dto.ItemDTO;
4
+ import edu.alexu.fitfinder.dto.SearchRequestDTO;
5
+ import edu.alexu.fitfinder.repository.ItemRepo;
6
+ import edu.alexu.fitfinder.service.JwtService;
7
+ import edu.alexu.fitfinder.service.SearchService;
8
+ import edu.alexu.fitfinder.service.StoredItemService;
9
+ import lombok.RequiredArgsConstructor;
10
+ import org.springframework.beans.factory.annotation.Autowired;
11
+ import org.springframework.http.HttpStatus;
12
+ import org.springframework.http.ResponseEntity;
13
+ import org.springframework.web.bind.annotation.*;
14
+ import java.util.LinkedList;
15
+ import java.util.List;
16
+
17
+ @RestController
18
+ @RequestMapping("/api/v1/items")
19
+ @CrossOrigin
20
+ @RequiredArgsConstructor
21
+ public class SearchController {
22
+
23
+ private final JwtService jwtService;
24
+ private final ItemRepo findRandomEntities;
25
+ private final SearchService searchService;
26
+ private final StoredItemService storedItemService;
27
+
28
+ @PostMapping("/search")
29
+ public ResponseEntity<?> searchByImageMask(
30
+ @RequestBody SearchRequestDTO searchInfo,
31
+ @RequestHeader("Authorization") String token)
32
+ throws Exception {
33
+
34
+
35
+ try {
36
+ Long userId = jwtService.extractUserFromToken(token);
37
+ List<Long> vectorIds = searchService.getSimilarIndices(searchInfo);
38
+ if (vectorIds.isEmpty()) return ResponseEntity.ok().body(new LinkedList<>());
39
+ return ResponseEntity.ok().body(storedItemService.getProductsByVectorIds(vectorIds,userId));
40
+ } catch (IllegalArgumentException e) {
41
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
42
+ }
43
+ }
44
+
45
+ @GetMapping("/random")
46
+ public List<ItemDTO> getRandomItems() {
47
+ List<Long> randomIds = findRandomEntities.findRandomIds();
48
+ List<ItemDTO> items = storedItemService.getProducts(randomIds);
49
+ java.util.Collections.shuffle(items);
50
+ return items;
51
+ }
52
+
53
+ @PostMapping("/fav/:id")
54
+ private ResponseEntity<?> addFav(@RequestParam("id") Long item_id){
55
+
56
+ return ResponseEntity.status(HttpStatus.CREATED).build();
57
+ }
58
+ }
src/main/java/edu/alexu/fitfinder/controller/SegmentationController.java ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.controller;
2
+
3
+ import edu.alexu.fitfinder.component.JobRegistry;
4
+ import edu.alexu.fitfinder.dto.ImageMasksDTO;
5
+ import edu.alexu.fitfinder.dto.ResegmentImageDTO;
6
+ import edu.alexu.fitfinder.exception.InvalidInputException;
7
+ import edu.alexu.fitfinder.service.SegmentationService;
8
+ import org.springframework.beans.factory.annotation.Autowired;
9
+ import org.springframework.http.HttpStatus;
10
+ import org.springframework.http.ResponseEntity;
11
+ import org.springframework.web.bind.annotation.*;
12
+ import org.springframework.web.multipart.MultipartFile;
13
+ import org.springframework.web.socket.BinaryMessage;
14
+ import org.springframework.web.socket.TextMessage;
15
+ import org.springframework.web.socket.WebSocketSession;
16
+ import java.io.IOException;
17
+ import java.nio.charset.StandardCharsets;
18
+ import java.util.Map;
19
+
20
+ @RestController
21
+ public class SegmentationController {
22
+
23
+ @Autowired SegmentationService segmentationService;
24
+ @Autowired JobRegistry jobRegistry;
25
+
26
+ @PostMapping("/segment/upload")
27
+ public ResponseEntity<?> uploadImage(
28
+ @RequestParam MultipartFile image, @RequestParam String sessionId)
29
+ throws IOException, Exception {
30
+
31
+ Map<String, String> response = segmentationService.UploadImageAndSegment(image, sessionId);
32
+ return ResponseEntity.status(HttpStatus.OK).body(response);
33
+ }
34
+
35
+ @PostMapping("/re-segment")
36
+ public ResponseEntity<?> resegment(
37
+ @RequestParam String sessionId, @RequestBody ResegmentImageDTO resegmentInfo)
38
+ throws InvalidInputException, Exception {
39
+
40
+ segmentationService.Resegment(sessionId, resegmentInfo);
41
+ return new ResponseEntity<>(HttpStatus.OK);
42
+ }
43
+
44
+ @PutMapping("/segmentation/callback")
45
+ public void segmentationCallback(@RequestBody ImageMasksDTO masks) throws IOException {
46
+ System.out.println("mask has been received from hugging face!");
47
+ WebSocketSession session = jobRegistry.getSession(masks.getJob_id());
48
+ // make sure that session exists and still open
49
+ if (session != null && session.isOpen()) {
50
+ if (masks.getStatus().equals("re-segmented"))
51
+ session.sendMessage(new TextMessage(masks.getMasks().toString()));
52
+ else
53
+ session.sendMessage(
54
+ new TextMessage(
55
+ "{\"masks\": "
56
+ + masks.getMasks().toString()
57
+ + ", \"boxes\": "
58
+ + masks.getBoxes().toString()
59
+ + "}"));
60
+
61
+ System.out.println("mask has been sent to client!");
62
+ jobRegistry.removeJob(masks.getJob_id());
63
+ }
64
+ }
65
+ }
src/main/java/edu/alexu/fitfinder/controller/UserController.java ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.controller;
2
+
3
+ import edu.alexu.fitfinder.dto.UserDTO;
4
+ import edu.alexu.fitfinder.exception.UserNotFoundException;
5
+ import edu.alexu.fitfinder.service.ImageService;
6
+ import edu.alexu.fitfinder.service.UserService;
7
+ import jakarta.servlet.http.HttpServletRequest;
8
+ import jakarta.servlet.http.HttpServletResponse;
9
+ import lombok.RequiredArgsConstructor;
10
+ import org.springframework.http.HttpStatus;
11
+ import org.springframework.http.ResponseEntity;
12
+ import org.springframework.web.bind.annotation.*;
13
+ import org.springframework.web.multipart.MultipartFile;
14
+
15
+ import java.io.IOException;
16
+ import java.util.Map;
17
+
18
+ @RestController()
19
+ @RequestMapping("/api/v1/auth")
20
+ @RequiredArgsConstructor
21
+ public class UserController {
22
+
23
+ private final UserService userService;
24
+
25
+ @PostMapping("/signup")
26
+ public ResponseEntity<Map<String, String>> signup(
27
+ @RequestBody UserDTO user, HttpServletResponse response) {
28
+ Map<String, String> accessToken = userService.SignUP(user, response);
29
+ return ResponseEntity.status(HttpStatus.CREATED).body(accessToken);
30
+ }
31
+
32
+ @PostMapping("/login")
33
+ public ResponseEntity<Map<String, String>> login(
34
+ @RequestBody UserDTO user, HttpServletResponse response) {
35
+ Map<String, String> accessToken = userService.LogIn(user, response);
36
+ return ResponseEntity.status(HttpStatus.OK).body(accessToken);
37
+ }
38
+
39
+ @PostMapping("/refresh")
40
+ public ResponseEntity<Map<String, String>> refresh(HttpServletRequest request) {
41
+ Map<String, String> accessToken = userService.RefreshToken(request);
42
+ return ResponseEntity.status(HttpStatus.OK).body(accessToken);
43
+ }
44
+
45
+ @PostMapping("logout")
46
+ public ResponseEntity<?> logout(HttpServletResponse response) {
47
+ userService.LogOut(response);
48
+ return ResponseEntity.status(HttpStatus.OK).build();
49
+ }
50
+
51
+ @GetMapping("/profile")
52
+ public ResponseEntity<?> getProfile(@RequestHeader("Authorization") String token) {
53
+ UserDTO user;
54
+ try {
55
+ if (token != null && token.startsWith("Bearer ")) {
56
+ token = token.substring(7);
57
+ }
58
+ else {
59
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
60
+ }
61
+
62
+ user = userService.getUser(token);
63
+ } catch (UserNotFoundException e) {
64
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
65
+ }
66
+
67
+ return ResponseEntity.ok(user);
68
+ }
69
+
70
+ @PutMapping("/profile/photo")
71
+ public ResponseEntity<?> uploadImageProfile(
72
+ @RequestParam MultipartFile image,
73
+ @RequestHeader("Authorization") String token) {
74
+
75
+ UserDTO user;
76
+ try {
77
+ if (token != null && token.startsWith("Bearer ")) {
78
+ token = token.substring(7);
79
+ }
80
+ else {
81
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
82
+ }
83
+
84
+ user = userService.getUser(token);
85
+ userService.updateImageProfile(user.getId(), image);
86
+ return ResponseEntity.ok("Profile image Uploaded");
87
+ } catch (UserNotFoundException e) {
88
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
89
+ } catch (IOException e){
90
+ return ResponseEntity.internalServerError().body(e.getMessage());
91
+ }
92
+ }
93
+
94
+ @DeleteMapping("/profile/photo")
95
+ public ResponseEntity<?> deleteImageProfile(
96
+ @RequestParam MultipartFile image,
97
+ @RequestHeader("Authorization") String token) {
98
+
99
+ UserDTO user;
100
+ try {
101
+ if (token != null && token.startsWith("Bearer ")) {
102
+ token = token.substring(7);
103
+ }
104
+ else {
105
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
106
+ }
107
+
108
+ user = userService.getUser(token);
109
+
110
+ userService.deleteImageProfile(user.getId());
111
+ return ResponseEntity.ok("Profile image Uploaded");
112
+ } catch (UserNotFoundException e) {
113
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
114
+ } catch (IOException e){
115
+ return ResponseEntity.internalServerError().body(e.getMessage());
116
+ }
117
+ }
118
+ }
src/main/java/edu/alexu/fitfinder/dto/ImageMasksDTO.java ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.dto;
2
+
3
+ import lombok.AllArgsConstructor;
4
+ import lombok.Getter;
5
+ import java.util.List;
6
+
7
+ @AllArgsConstructor
8
+ @Getter
9
+ public class ImageMasksDTO {
10
+ String job_id;
11
+ String status;
12
+ List<List<Byte>> masks;
13
+ List<List<Integer>> boxes;
14
+ }
src/main/java/edu/alexu/fitfinder/dto/ItemDTO.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.dto;
2
+
3
+ import lombok.AllArgsConstructor;
4
+ import lombok.Getter;
5
+ import lombok.Setter;
6
+ import lombok.ToString;
7
+
8
+ @AllArgsConstructor
9
+ @Getter
10
+ @Setter
11
+ @ToString
12
+ public class ItemDTO {
13
+ private Long item_id;
14
+ private String category;
15
+ private String currency;
16
+ private String description;
17
+ private String imageURL;
18
+ private String itemWebURL;
19
+ private float price;
20
+ private String title;
21
+ private boolean isFavorite;
22
+ }
src/main/java/edu/alexu/fitfinder/dto/ResegmentImageDTO.java ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.dto;
2
+
3
+ import lombok.AllArgsConstructor;
4
+ import lombok.Getter;
5
+ import lombok.Setter;
6
+
7
+ @AllArgsConstructor
8
+ @Getter
9
+ @Setter
10
+ public class ResegmentImageDTO {
11
+ String job_id;
12
+ String image_url;
13
+ int[][] pos_points;
14
+ int[][] neg_points;
15
+ int[][] boxes;
16
+ String callback_url;
17
+ }
src/main/java/edu/alexu/fitfinder/dto/SearchRequestDTO.java ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.dto;
2
+
3
+ import java.util.List;
4
+ import lombok.AllArgsConstructor;
5
+ import lombok.Getter;
6
+ import lombok.Setter;
7
+
8
+ @AllArgsConstructor
9
+ @Getter
10
+ @Setter
11
+ public class SearchRequestDTO {
12
+ String job_id;
13
+ String image_url;
14
+ List<List<Integer>> mask_json;
15
+ String prompt;
16
+ }
src/main/java/edu/alexu/fitfinder/dto/SearchResponseDTO.java ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.dto;
2
+
3
+ import lombok.*;
4
+
5
+ @AllArgsConstructor
6
+ @NoArgsConstructor
7
+ @Getter
8
+ @Setter
9
+ @ToString
10
+ public class SearchResponseDTO {
11
+
12
+ @AllArgsConstructor
13
+ @Getter
14
+ @Setter
15
+ @ToString
16
+ public static class Result {
17
+ long faiss_id;
18
+ double distance;
19
+ }
20
+
21
+ String job_id;
22
+ Result[] results;
23
+ }
src/main/java/edu/alexu/fitfinder/dto/UserDTO.java ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.dto;
2
+
3
+ import lombok.AllArgsConstructor;
4
+ import lombok.Getter;
5
+ import lombok.NoArgsConstructor;
6
+ import lombok.Setter;
7
+
8
+ @NoArgsConstructor
9
+ @AllArgsConstructor
10
+ @Getter
11
+ @Setter
12
+ public class UserDTO {
13
+ private Long id;
14
+ private String profileImageURL;
15
+ private String userName;
16
+ private String password;
17
+ private String email;
18
+ }
src/main/java/edu/alexu/fitfinder/entity/Favorite.java ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.Data;
5
+ import lombok.Getter;
6
+ import lombok.NoArgsConstructor;
7
+ import lombok.Setter;
8
+
9
+ @Entity
10
+ @Data
11
+ @Table(name = "FAVORITES")
12
+ @NoArgsConstructor
13
+ @Getter
14
+ @Setter
15
+ public class Favorite {
16
+
17
+ @Id
18
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
19
+ @Column(name = "fav_id")
20
+ private Long id;
21
+
22
+ @ManyToOne
23
+ @JoinColumn(name = "user_id", nullable = false) // Defines the Foreign Key column
24
+ private User user;
25
+
26
+ @ManyToOne
27
+ @JoinColumn(name = "item_id", nullable = false) // Defines the Foreign Key column
28
+ private StoredItem item;
29
+
30
+ }
src/main/java/edu/alexu/fitfinder/entity/ItemVector.java ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.*;
5
+ import org.hibernate.annotations.CreationTimestamp;
6
+ import java.time.LocalDateTime;
7
+
8
+ @Entity
9
+ @Table(name = "ITEM_VECTOR")
10
+ @Data
11
+ @NoArgsConstructor
12
+ @Getter
13
+ @Setter
14
+ public class ItemVector {
15
+
16
+ @Id
17
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
18
+ private Long id;
19
+
20
+ @CreationTimestamp private LocalDateTime createdAt;
21
+ private Long vectorId;
22
+
23
+ @ManyToOne
24
+ @JoinColumn(name = "item_id", nullable = false)
25
+ private StoredItem item;
26
+ }
src/main/java/edu/alexu/fitfinder/entity/StoredItem.java ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.entity;
2
+
3
+ import java.time.LocalDateTime;
4
+
5
+ import org.hibernate.annotations.CreationTimestamp;
6
+
7
+ import jakarta.persistence.*;
8
+ import lombok.Data;
9
+ import lombok.Getter;
10
+ import lombok.NoArgsConstructor;
11
+ import lombok.Setter;
12
+
13
+ @Entity
14
+ @Data
15
+ @Table(name = "STORED_ITEMS")
16
+ @NoArgsConstructor
17
+ @Getter
18
+ @Setter
19
+ public class StoredItem {
20
+
21
+ @Id
22
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
23
+ private Long itemId;
24
+
25
+ private String category;
26
+ @CreationTimestamp private LocalDateTime createdAt;
27
+ private String currency;
28
+
29
+ @Column(columnDefinition = "TEXT", name = "description")
30
+ private String description;
31
+
32
+ private boolean embedded;
33
+ private String imageURL;
34
+ private String itemWebURL;
35
+ private float price;
36
+ private String source;
37
+ private String title;
38
+
39
+ public StoredItem(
40
+ String category,
41
+ LocalDateTime createdAt,
42
+ String currency,
43
+ String description,
44
+ boolean embedded,
45
+ String imageURL,
46
+ String itemWebURL,
47
+ float price,
48
+ String source,
49
+ String title) {
50
+ this.category = category;
51
+ this.createdAt = createdAt;
52
+ this.currency = currency;
53
+ this.description = description;
54
+ this.embedded = embedded;
55
+ this.imageURL = imageURL;
56
+ this.itemWebURL = itemWebURL;
57
+ this.price = price;
58
+ this.source = source;
59
+ this.title = title;
60
+ }
61
+ }
src/main/java/edu/alexu/fitfinder/entity/UploadedImage.java ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.Data;
5
+ import lombok.Getter;
6
+ import lombok.NoArgsConstructor;
7
+ import lombok.Setter;
8
+
9
+ @Entity
10
+ @Data
11
+ @Table(name = "UPLOADED_IMAGES")
12
+ @NoArgsConstructor
13
+ @Getter
14
+ @Setter
15
+ public class UploadedImage {
16
+ @Id
17
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
18
+ private Long uploadedImgId;
19
+
20
+ @ManyToOne
21
+ @JoinColumn(name = "user_id", nullable = false)
22
+ private User user;
23
+
24
+ private Long uploadedFAISSId;
25
+ private String imageURL;
26
+ private String croppedImageURL;
27
+ private String name;
28
+
29
+ // private boolean[][] mask;
30
+
31
+ public UploadedImage(User user, Long uploadedFAISSId, String imageURL /*, boolean[][] mask*/) {
32
+ this.user = user;
33
+ this.uploadedFAISSId = uploadedFAISSId;
34
+ this.imageURL = imageURL;
35
+ // this.mask = mask;
36
+ }
37
+ }
src/main/java/edu/alexu/fitfinder/entity/User.java ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.Data;
5
+ import lombok.Getter;
6
+ import lombok.NoArgsConstructor;
7
+ import lombok.Setter;
8
+
9
+ import java.util.List;
10
+
11
+ @Entity
12
+ @Data
13
+ @Table(name = "USERS")
14
+ @NoArgsConstructor
15
+ @Getter
16
+ @Setter
17
+ public class User {
18
+ @Id
19
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
20
+ @Column(name = "user_id")
21
+ private Long userId;
22
+
23
+ private String userName;
24
+ private String password;
25
+ private String email;
26
+
27
+ @Column(nullable = false)
28
+ private String profileImageURL = "";
29
+
30
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
31
+ private List<UploadedImage> images;
32
+
33
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
34
+ private List<Favorite> favorites;
35
+
36
+ public User(String userName, String password, String email) {
37
+ this.userName = userName;
38
+ this.password = password;
39
+ this.email = email;
40
+ }
41
+ }
src/main/java/edu/alexu/fitfinder/exception/FavoriteNotFoundException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class FavoriteNotFoundException extends RuntimeException {
4
+ public FavoriteNotFoundException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/ImageNotFoundException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class ImageNotFoundException extends RuntimeException {
4
+ public ImageNotFoundException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/InvalidInputException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class InvalidInputException extends RuntimeException {
4
+ public InvalidInputException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/ItemNotFoundException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class ItemNotFoundException extends RuntimeException {
4
+ public ItemNotFoundException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/LogInException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class LogInException extends Exception {
4
+ public LogInException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/SocketException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class SocketException extends RuntimeException {
4
+ public SocketException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/UnauthorizedException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class UnauthorizedException extends RuntimeException {
4
+ public UnauthorizedException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/UserAlreadyExistsException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class UserAlreadyExistsException extends RuntimeException {
4
+ public UserAlreadyExistsException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/UserNotFoundException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class UserNotFoundException extends RuntimeException {
4
+ public UserNotFoundException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/exception/ValidatorException.java ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.exception;
2
+
3
+ public class ValidatorException extends Exception {
4
+ public ValidatorException(String message) {
5
+ super(message);
6
+ }
7
+ }
src/main/java/edu/alexu/fitfinder/handler/MyWebSocketHandler.java ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.handler;
2
+
3
+ import org.springframework.web.socket.*;
4
+ import org.springframework.web.socket.handler.TextWebSocketHandler;
5
+ import java.util.concurrent.ConcurrentHashMap;
6
+
7
+ public class MyWebSocketHandler extends TextWebSocketHandler {
8
+
9
+ // store all active sessions
10
+ public static ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
11
+
12
+ @Override
13
+ public void afterConnectionEstablished(WebSocketSession session) throws Exception {
14
+ String sessionId = session.getId();
15
+ sessions.put(sessionId, session);
16
+ System.out.println("Websocket connected successfully with sessionId = " + sessionId);
17
+ session.sendMessage(new TextMessage("{\"sessionId\": \"" + sessionId + "\"}"));
18
+ }
19
+
20
+ @Override
21
+ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
22
+ String sessionId = session.getId();
23
+ sessions.remove(sessionId);
24
+ System.out.println("Websocket disconnected successfully with sessionId = " + sessionId);
25
+ }
26
+ }
src/main/java/edu/alexu/fitfinder/mapper/ItemMapper.java ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.mapper;
2
+
3
+ import edu.alexu.fitfinder.dto.ItemDTO;
4
+ import edu.alexu.fitfinder.entity.StoredItem;
5
+ import org.springframework.stereotype.Component;
6
+
7
+ import java.util.List;
8
+ import java.util.stream.Collectors;
9
+
10
+ @Component
11
+ public class ItemMapper {
12
+
13
+ public ItemDTO toDTO(StoredItem item) {
14
+ if (item == null) return null;
15
+
16
+ // Ensure these getters match your StoredItem Entity field names
17
+ return new ItemDTO(
18
+ item.getItemId(),
19
+ item.getCategory(),
20
+ item.getCurrency(),
21
+ item.getDescription(),
22
+ item.getImageURL(),
23
+ item.getItemWebURL(),
24
+ item.getPrice(),
25
+ item.getTitle(),
26
+ false
27
+ );
28
+ }
29
+
30
+ public ItemDTO toDTO(StoredItem item, boolean isFavorite) {
31
+ ItemDTO dto = toDTO(item); // Call your existing base method
32
+ dto.setFavorite(isFavorite); // Set the new field
33
+ return dto;
34
+ }
35
+
36
+ // Helper to map a whole list at once
37
+ public List<ItemDTO> toDTOList(List<StoredItem> items) {
38
+ return items.stream()
39
+ .map(this::toDTO)
40
+ .collect(Collectors.toList());
41
+ }
42
+ }
src/main/java/edu/alexu/fitfinder/mapper/UserMapper.java ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.mapper;
2
+
3
+ import edu.alexu.fitfinder.dto.UserDTO;
4
+ import edu.alexu.fitfinder.entity.User;
5
+ import org.springframework.stereotype.Component;
6
+
7
+ import java.util.List;
8
+ import java.util.stream.Collectors;
9
+
10
+ @Component
11
+ public class UserMapper {
12
+
13
+ public UserDTO toDTO(User user) {
14
+ if (user == null) {
15
+ return null;
16
+ }
17
+
18
+ UserDTO dto = new UserDTO();
19
+
20
+ // Mapping fields
21
+ dto.setId(user.getUserId()); // Note the name change: userId -> id
22
+ dto.setUserName(user.getUserName());
23
+ dto.setPassword("In Your Dreams 😛"); // Be careful sending passwords in DTOs!
24
+ dto.setEmail(user.getEmail());
25
+ dto.setProfileImageURL(user.getProfileImageURL());
26
+
27
+ return dto;
28
+ }
29
+
30
+ public List<UserDTO> toDTOList(List<User> users) {
31
+ return users.stream()
32
+ .map(this::toDTO)
33
+ .collect(Collectors.toList());
34
+ }
35
+ }
src/main/java/edu/alexu/fitfinder/repository/FavoriteRepo.java ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.repository;
2
+
3
+ import edu.alexu.fitfinder.entity.Favorite;
4
+ import edu.alexu.fitfinder.entity.StoredItem;
5
+ import edu.alexu.fitfinder.entity.User;
6
+ import org.springframework.data.jpa.repository.JpaRepository;
7
+ import org.springframework.data.jpa.repository.Query;
8
+ import org.springframework.data.repository.query.Param;
9
+ import org.springframework.stereotype.Repository;
10
+
11
+ import java.util.List;
12
+ import java.util.Optional;
13
+ import java.util.Set;
14
+
15
+ @Repository
16
+ public interface FavoriteRepo extends JpaRepository<Favorite, Long> {
17
+ boolean existsByUserAndItem(User user, StoredItem item);
18
+
19
+ Optional<Favorite> findByUserAndItem(User user, StoredItem item);
20
+
21
+ List<Favorite> findAllByUser(User user);
22
+
23
+ @Query("SELECT f.item.itemId FROM Favorite f WHERE f.user.userId = :userId")
24
+ Set<Long> findFavoriteItemIds(@Param("userId") Long userId);
25
+ }
src/main/java/edu/alexu/fitfinder/repository/ImageRepo.java ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.repository;
2
+
3
+ import edu.alexu.fitfinder.entity.UploadedImage;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.stereotype.Repository;
6
+
7
+ @Repository
8
+ public interface ImageRepo extends JpaRepository<UploadedImage, Long> {
9
+ }
src/main/java/edu/alexu/fitfinder/repository/ItemRepo.java ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.repository;
2
+
3
+ import edu.alexu.fitfinder.entity.StoredItem;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.data.jpa.repository.Query;
6
+ import org.springframework.data.repository.query.Param;
7
+ import org.springframework.stereotype.Repository;
8
+ import java.util.List;
9
+
10
+ @Repository
11
+ public interface ItemRepo extends JpaRepository<StoredItem, Long> {
12
+ @Query(value = "SELECT item_id FROM stored_items ORDER BY RANDOM() LIMIT 20", nativeQuery = true)
13
+ List<Long> findRandomIds();
14
+
15
+ @Query(value = """
16
+ SELECT DISTINCT si.* FROM STORED_ITEMS si
17
+ JOIN ITEM_VECTOR vi ON vi.item_id = si.item_id
18
+ WHERE vi.vector_id IN :vectorIds
19
+ """, nativeQuery = true)
20
+ List<StoredItem> findItemsByVectorIds(@Param("vectorIds") List<Long> vectorIds);
21
+
22
+ }
src/main/java/edu/alexu/fitfinder/repository/UserRepo.java ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.repository;
2
+
3
+ import edu.alexu.fitfinder.entity.User;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.stereotype.Repository;
6
+
7
+ @Repository
8
+ public interface UserRepo extends JpaRepository<User, Long> {
9
+ User findByEmail(String email);
10
+ }
src/main/java/edu/alexu/fitfinder/service/AIService.java ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.service;
2
+
3
+ import com.cloudinary.Cloudinary;
4
+ import com.cloudinary.utils.ObjectUtils;
5
+ import edu.alexu.fitfinder.dto.SearchRequestDTO;
6
+ import edu.alexu.fitfinder.exception.InvalidInputException;
7
+ import org.springframework.beans.factory.annotation.Value;
8
+ import org.springframework.http.MediaType;
9
+ import org.springframework.stereotype.Service;
10
+ import org.springframework.web.reactive.function.client.WebClient;
11
+ import edu.alexu.fitfinder.dto.SearchResponseDTO;
12
+ import java.util.LinkedList;
13
+ import java.util.List;
14
+
15
+ @Service
16
+ public class AIService {
17
+
18
+ @Value("${cloudinary.url}")
19
+ private Cloudinary cloudinary;
20
+
21
+ @Value("${forwarding.url}")
22
+ private String forwardingUrl;
23
+
24
+ private final WebClient webClient;
25
+
26
+ public AIService() {
27
+ this.webClient = WebClient.builder().baseUrl("https://fitfinder-ai-service.hf.space").build();
28
+ }
29
+
30
+ public List<Long> getSimilarIndices(SearchRequestDTO searchInfo)
31
+ throws InvalidInputException, Exception {
32
+
33
+ // Validate mask
34
+ if (searchInfo.getMask_json() == null) throw new InvalidInputException("Mask couldn't be null");
35
+
36
+ // Validate job_id
37
+ try {
38
+ searchInfo.setImage_url(
39
+ cloudinary
40
+ .api()
41
+ .resource(searchInfo.getJob_id(), ObjectUtils.emptyMap())
42
+ .get("secure_url")
43
+ .toString());
44
+ } catch (Exception e) {
45
+ throw new InvalidInputException("There is no image associated with job Id");
46
+ }
47
+ System.out.println(searchInfo.getImage_url());
48
+
49
+ // Call AI Service
50
+ SearchResponseDTO response =
51
+ webClient
52
+ .post()
53
+ .uri("/api/v1/job/search/")
54
+ .contentType(MediaType.APPLICATION_JSON)
55
+ .bodyValue(searchInfo)
56
+ .retrieve()
57
+ .bodyToMono(SearchResponseDTO.class)
58
+ .block();
59
+
60
+ // Validate response
61
+ System.out.println("response : " + response);
62
+ if (response == null) throw new Exception();
63
+
64
+ // No matched elements
65
+ LinkedList<Long> vectorIds = new LinkedList<>();
66
+ if (response.getResults() == null || response.getResults().length == 0) return vectorIds;
67
+
68
+ // Extract Vector Ids
69
+ for (SearchResponseDTO.Result result : response.getResults())
70
+ vectorIds.add(result.getFaiss_id());
71
+ System.out.println(vectorIds);
72
+ return vectorIds;
73
+ }
74
+ }
src/main/java/edu/alexu/fitfinder/service/FavoriteService.java ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.service;
2
+
3
+ import edu.alexu.fitfinder.dto.ItemDTO;
4
+ import edu.alexu.fitfinder.entity.Favorite;
5
+ import edu.alexu.fitfinder.entity.StoredItem;
6
+ import edu.alexu.fitfinder.entity.User;
7
+ import edu.alexu.fitfinder.exception.FavoriteNotFoundException;
8
+ import edu.alexu.fitfinder.exception.ItemNotFoundException;
9
+ import edu.alexu.fitfinder.exception.UserNotFoundException;
10
+ import edu.alexu.fitfinder.mapper.ItemMapper;
11
+ import edu.alexu.fitfinder.repository.FavoriteRepo;
12
+ import edu.alexu.fitfinder.repository.ItemRepo;
13
+ import edu.alexu.fitfinder.repository.UserRepo;
14
+ import jakarta.transaction.Transactional;
15
+ import lombok.RequiredArgsConstructor;
16
+ import org.springframework.stereotype.Service;
17
+
18
+ import java.util.List;
19
+ import java.util.stream.Collectors;
20
+
21
+ @Service
22
+ @RequiredArgsConstructor // Lombok for constructor injection
23
+ public class FavoriteService {
24
+
25
+ private final FavoriteRepo favoriteRepo;
26
+ private final UserRepo userRepo;
27
+ private final ItemRepo itemRepo;
28
+ private final ItemMapper itemMapper;
29
+
30
+ @Transactional
31
+ public Long addFavorite(Long userId, Long itemId) {
32
+ User user = userRepo.findById(userId)
33
+ .orElseThrow(() -> new UserNotFoundException("User not found"));
34
+
35
+ StoredItem item = itemRepo.findById(itemId)
36
+ .orElseThrow(() -> new ItemNotFoundException("Item not found"));
37
+
38
+ if (favoriteRepo.existsByUserAndItem(user, item)) {
39
+ throw new RuntimeException("Item is already in favorites");
40
+ }
41
+
42
+ Favorite favorite = new Favorite();
43
+ favorite.setUser(user);
44
+ favorite.setItem(item);
45
+
46
+ Favorite savedFav = favoriteRepo.save(favorite);
47
+
48
+ return savedFav.getId();
49
+ }
50
+
51
+ @Transactional
52
+ public void deleteFavoriteById(Long userId, Long favoriteId){
53
+ Favorite fav = favoriteRepo.findById(favoriteId)
54
+ .orElseThrow(() -> new FavoriteNotFoundException("Favorite not found"));
55
+
56
+ if (!fav.getUser().getUserId().equals(userId)) {
57
+ throw new RuntimeException("Unauthorized: You do not own this favorite");
58
+ }
59
+
60
+ favoriteRepo.delete(fav);
61
+ }
62
+
63
+ @Transactional
64
+ public void deleteFavoriteByItem(Long userId, Long itemId) {
65
+ User user = userRepo.findById(userId)
66
+ .orElseThrow(() -> new UserNotFoundException("User not found"));
67
+
68
+ StoredItem item = itemRepo.findById(itemId)
69
+ .orElseThrow(() -> new ItemNotFoundException("Item not found"));
70
+
71
+ Favorite fav = favoriteRepo.findByUserAndItem(user, item)
72
+ .orElseThrow(() -> new FavoriteNotFoundException("Favorite not found"));
73
+
74
+ favoriteRepo.delete(fav);
75
+ }
76
+
77
+ public List<ItemDTO> getUserFavorites(Long userId) {
78
+ User user = userRepo.findById(userId)
79
+ .orElseThrow(() -> new UserNotFoundException("User not found"));
80
+
81
+ List<Favorite> favorites = favoriteRepo.findAllByUser(user);
82
+
83
+ return favorites.stream()
84
+ .map(fav -> itemMapper.toDTO(fav.getItem(), true))
85
+ .collect(Collectors.toList());
86
+
87
+ }
88
+ }
src/main/java/edu/alexu/fitfinder/service/ImageService.java ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.service;
2
+
3
+ import com.cloudinary.Cloudinary;
4
+ import com.cloudinary.utils.ObjectUtils;
5
+ import edu.alexu.fitfinder.entity.UploadedImage;
6
+ import edu.alexu.fitfinder.entity.User;
7
+ import edu.alexu.fitfinder.exception.ImageNotFoundException;
8
+ import edu.alexu.fitfinder.exception.UserNotFoundException;
9
+ import edu.alexu.fitfinder.repository.ImageRepo;
10
+ import edu.alexu.fitfinder.repository.UserRepo;
11
+ import lombok.RequiredArgsConstructor;
12
+ import org.springframework.stereotype.Service;
13
+ import org.springframework.web.multipart.MultipartFile;
14
+ import org.springframework.transaction.annotation.Transactional;
15
+
16
+ import java.io.IOException;
17
+ import java.util.UUID;
18
+
19
+ @Service
20
+ @RequiredArgsConstructor
21
+ public class ImageService {
22
+
23
+ // FIX 1: Removed @Value. Spring now injects the Bean from CloudinaryConfig
24
+ private final Cloudinary cloudinary;
25
+
26
+ private final ImageRepo imageRepo;
27
+ private final UserRepo userRepo;
28
+
29
+ public String uploadImage(MultipartFile image, String imageName) throws IOException {
30
+ var params = ObjectUtils.asMap(
31
+ "public_id", imageName,
32
+ "overwrite", true
33
+ );
34
+ var uploadResult = cloudinary.uploader().upload(image.getBytes(), params);
35
+ return uploadResult.get("secure_url").toString();
36
+ }
37
+
38
+ @Transactional
39
+ public UploadedImage saveImage(MultipartFile image, User user) throws IOException {
40
+ UploadedImage img = new UploadedImage();
41
+ img.setUser(user);
42
+
43
+ // FIX 2: Use UUID for unique naming.
44
+ // Using .size() causes overwrites if images are deleted later.
45
+ String imageName = "user_" + user.getUserId() + "_" + UUID.randomUUID().toString();
46
+
47
+ String url = this.uploadImage(image, imageName);
48
+
49
+ img.setImageURL(url);
50
+ img.setName(imageName);
51
+
52
+ return imageRepo.save(img);
53
+ }
54
+
55
+ public void deleteImage(String publicId) throws IOException {
56
+ cloudinary.uploader().destroy(publicId, ObjectUtils.emptyMap());
57
+ }
58
+
59
+ @Transactional
60
+ public void deleteImageRow(Long imageId, Long userId) throws IOException {
61
+
62
+
63
+ UploadedImage img = imageRepo.findById(imageId)
64
+ .orElseThrow(() -> new ImageNotFoundException("Image not found"));
65
+
66
+ if (!img.getUser().getUserId().equals(userId)) {
67
+ throw new RuntimeException("Unauthorized: You do not own this Image");
68
+ }
69
+
70
+ this.deleteImage(img.getName());
71
+
72
+ imageRepo.delete(img);
73
+ }
74
+ }
src/main/java/edu/alexu/fitfinder/service/JwtService.java ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.service;
2
+
3
+ import io.jsonwebtoken.Claims;
4
+ import io.jsonwebtoken.Jwts;
5
+ import io.jsonwebtoken.SignatureAlgorithm;
6
+ import io.jsonwebtoken.io.Decoders;
7
+ import io.jsonwebtoken.security.Keys;
8
+ import java.security.Key;
9
+ import java.util.Date;
10
+ import java.util.HashMap;
11
+ import java.util.Map;
12
+ import java.util.function.Function;
13
+ import org.springframework.beans.factory.annotation.Value;
14
+ import org.springframework.stereotype.Service;
15
+
16
+ @Service
17
+ public class JwtService {
18
+ @Value("${security.jwt.secret-key}")
19
+ private String secretKey;
20
+
21
+ @Value("${security.jwt.access-expiration-time}")
22
+ private long accessExpiration;
23
+
24
+ @Value("${security.jwt.refresh-expiration-time}")
25
+ private long refreshExpiration;
26
+
27
+
28
+ private Claims extractAllClaims(String token) {
29
+ return Jwts.parserBuilder()
30
+ .setSigningKey(getSignInKey())
31
+ .build()
32
+ .parseClaimsJws(token)
33
+ .getBody();
34
+ }
35
+
36
+ private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
37
+ final Claims claims = extractAllClaims(token);
38
+ return claimsResolver.apply(claims);
39
+ }
40
+
41
+ public Date extractExpiration(String token) {
42
+ return extractClaim(token, Claims::getExpiration);
43
+ }
44
+
45
+ public String extractUserId(String token) {
46
+ return extractClaim(token, Claims::getSubject);
47
+ }
48
+
49
+ public String generateAccessToken(String id) {
50
+ return generateToken(new HashMap<>(), id, accessExpiration);
51
+ }
52
+
53
+ public String generateRefreshToken(String id) {
54
+ return generateToken(new HashMap<>(), id, refreshExpiration);
55
+ }
56
+
57
+ private String generateToken(Map<String, Object> extraClaims, String id, long expiration) {
58
+ return buildToken(extraClaims, id, expiration);
59
+ }
60
+
61
+ private String buildToken(Map<String, Object> extraClaims, String id, long expiration) {
62
+ return Jwts.builder()
63
+ .setClaims(extraClaims)
64
+ .setSubject(id)
65
+ .setIssuedAt(new Date(System.currentTimeMillis()))
66
+ .setExpiration(new Date(System.currentTimeMillis() + expiration))
67
+ .signWith(getSignInKey(), SignatureAlgorithm.HS256)
68
+ .compact();
69
+ }
70
+
71
+ public boolean isTokenExpired(String token) {
72
+ return extractExpiration(token).before(new Date());
73
+ }
74
+
75
+ public Long extractUserFromToken(String token) {
76
+ if (token == null || !token.startsWith("Bearer ")) {
77
+ throw new IllegalArgumentException("Invalid Token");
78
+ }
79
+ // Clean the token once
80
+ String cleanToken = token.substring(7);
81
+ return Long.parseLong(this.extractUserId(cleanToken));
82
+ }
83
+
84
+ private Key getSignInKey() {
85
+ byte[] keyBytes = Decoders.BASE64.decode(secretKey);
86
+ return Keys.hmacShaKeyFor(keyBytes);
87
+ }
88
+ }
src/main/java/edu/alexu/fitfinder/service/SearchService.java ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package edu.alexu.fitfinder.service;
2
+
3
+ import com.cloudinary.Cloudinary;
4
+ import com.cloudinary.utils.ObjectUtils;
5
+ import edu.alexu.fitfinder.dto.SearchRequestDTO;
6
+ import edu.alexu.fitfinder.exception.InvalidInputException;
7
+ import org.springframework.beans.factory.annotation.Value;
8
+ import org.springframework.http.MediaType;
9
+ import org.springframework.stereotype.Service;
10
+ import org.springframework.web.reactive.function.client.WebClient;
11
+ import edu.alexu.fitfinder.dto.SearchResponseDTO;
12
+ import java.util.LinkedList;
13
+ import java.util.List;
14
+
15
+ @Service
16
+ public class SearchService {
17
+
18
+ @Value("${cloudinary.url}")
19
+ private Cloudinary cloudinary;
20
+
21
+ @Value("${forwarding.url}")
22
+ private String forwardingUrl;
23
+
24
+ private final WebClient webClient;
25
+
26
+ public SearchService() {
27
+ this.webClient = WebClient.builder().baseUrl("https://fitfinder-ai-service.hf.space").build();
28
+ }
29
+
30
+ public List<Long> getSimilarIndices(SearchRequestDTO searchInfo)
31
+ throws InvalidInputException, Exception {
32
+
33
+ // Validate mask
34
+ if (searchInfo.getMask_json() == null) throw new InvalidInputException("Mask couldn't be null");
35
+
36
+ // Validate job_id
37
+ try {
38
+ searchInfo.setImage_url(
39
+ cloudinary
40
+ .api()
41
+ .resource(searchInfo.getJob_id(), ObjectUtils.emptyMap())
42
+ .get("secure_url")
43
+ .toString());
44
+ } catch (Exception e) {
45
+ throw new InvalidInputException("There is no image associated with job Id");
46
+ }
47
+ System.out.println(searchInfo.getImage_url());
48
+
49
+ // Call AI Service
50
+ SearchResponseDTO response =
51
+ webClient
52
+ .post()
53
+ .uri("/api/v1/job/search/")
54
+ .contentType(MediaType.APPLICATION_JSON)
55
+ .bodyValue(searchInfo)
56
+ .retrieve()
57
+ .bodyToMono(SearchResponseDTO.class)
58
+ .block();
59
+
60
+ // Validate response
61
+ System.out.println("response : " + response);
62
+ if (response == null) throw new Exception();
63
+
64
+ // No matched elements
65
+ LinkedList<Long> vectorIds = new LinkedList<>();
66
+ if (response.getResults() == null || response.getResults().length == 0) return vectorIds;
67
+
68
+ // Extract Vector Ids
69
+ for (SearchResponseDTO.Result result : response.getResults())
70
+ vectorIds.add(result.getFaiss_id());
71
+ System.out.println(vectorIds);
72
+ return vectorIds;
73
+ }
74
+ }