oZdlH(Y%N7`Q
z$tJQ{7&y_+s7g)E&Jh({721M{ps2~O(9SBcraCmcZ0}dc5$rEJ!v9Pbl&6ubxH@S&
ztYob|2_`2;c^Oa>H*AXv!H4p7jIMDi7;0~m>)a$fmh^tqSUKkGutJV0J%@winXVE}
z1%Efz)uZZ}4@jH2eb^k(9K)`8{RrURx2bPm4BcAoetOQG1Yd9lGtN|#HSUjX16N>h
zgp&z_RHqL2#CB%Ab+D{k$HbPfS>)o3Tge}(!1u2$?BrpEgXExq>_cGo??dcNzwR(V
z`2az=)m9(}T9VsMQ)TcvTmoO*co=y?Ehmv68vM8`XAYc}We
zjk&~={oCs$W&`ksP}g8;6e0#Qzfi1(I;sI<8?wAN#=S{q>b48Z8FtBqMe3Lo?t!EY
z^itX@b~44Vwu5KIb~f1^NSYKTZoKLnZZe6uiSTR9JbuYG=>r+hd$|$O8?Z9?6eW!k
zTvcHux%(;faiU}^r84lESQ4bMI=%MtQE>xOs(mCe>RrTGIvDfQnE0D5LQjK%wz@pq
z{80dAMVzvl{BgUGwK)lIPb$1`LijJNSCwa+)WkhJcWqqlj9V`-C$fYU5EheRA
zYafq_r_hB0^C}Z2UoB0XSs!8%AUq)yVUO)
zwX6RI_&)zfJ?O}QN})B
zszeLFN+26+QHH@RthaWS#8B>Gj$1KjY3qnj(efg95O48)}Hn;x28!H&jZ`_1+LeOo1{$L
zw1a-o%V@mzgD3f2q79xeeEC1aKOyC7B61gS*S?_Zh`&^p>&?}@RO{q0!(DW^ec6;M
zYT#36iu`t^u4YK394UnkPHrG6(vS#2#W7^a)DseTl(SK{_mRx$SSO(;R_bGn<;tZ{
z)`77$`ig8YMyqtHF!Oe^VW=Tk_L10)5Fg6Lmp5r4<(4)Vuimrx8er5B(n2pC(7r5?
z#p<4o`2yc+!ZWADaFv&@35Yi_ve!%T@*JOz%$|SD0Vg&dWx_ie8OD<1#3l8(_F|Jo
zCmXF1Uv%5xfF-Fk3?4k)4sbvl&!T!idJn0sbY#s!A+COh21I8hGu6fXK(MHhwc<^7
zjk#}tUy&wBpV8PzVY|f#+K#Y!YbCTm*g~AP
zgs!E>RURoH8CYZ1E6;(H%K|7or+2N9^-bbqr-9b9nv)Xdd--LXSApu89O>+r&{j(e
zsoCK3=YM5>U@;s1%m%t8n8Ez6Tl$-szkla^0A(mQvov>gGWtbU4d3`(1<+GX_por*
zJEnKK!ZAfXWakj?oanK>w98Y9u$CH^O}GD3ny%d#s%lo*wAAtBn7P_V4@?f6B`EFdP27|nUbv{J6fxz
z&di#|ozz#*%c7NKR-|Rr$zJ`G^W7UZb$KrG$#u0iQ!4Pom1;dBDrR`K5>p%fuIim|
z)uO7-JkL@}EF$p2sMc%(@TkgyPCk7K`eakofj`y_h6>Tv{FFOv?|n8K1nWY~c$J7O
zo$OnJ8VwVPt8`m#*V2+6*PL2&p-b36MazIZ^`hSGmUdct9ltF~lGm8yY_CPrcVPqF
zbm=0sw{Pc%=v4NPkOWx#dk#Lxd4?Z0s9pr?U_k))RlmZg8}zO3szcme$P5m32;ToK?74f|_(j%4_CBhdvdOZ
zAAS*wBz1AnzmDxfU@^OsTn#5a;%Jrku_al3e{1bvi{DS7E@q1{$_8->K{_OWv2
zCZTgG2Pr3n8|ec9kIu&uC|d?k4-cQ4#}Z`qDX5Y2mhC(jR1Ms;UG4Ho$DE|+SeJ@{
zJQQhAXj|<)*t3KiOWTuh{Wd^mS{u{&ERV)OpZwiQ%#1->r9p
zSK_^*U~=?ywH~4IUxb}{0J!SmL!z2Tzq_PpetoC^_az1JFg0=gMcQADuOP%3=H1hH
zH_=dG(PD;d*037Ov5G1924U#Zns?~fs+eh1%-bWqa%ssm3=nio1r3J<4G0IBETtr?
zycs~0JIOn;MecYG=~OQsYHIrf?~A5>_ob%8+uOrVA+VCJw}{lygrBBdY1k<8B^wf6
zl|<%N$7)fOZX$%y>4ueco_Gb1H@B%XrKVwrn6hUOecnc^PU0rFuCB5=*2;|u-`o(@
zL*tr4bnQzXYLc4XqFbv5sK0}A)`}`8iM8ehtj#Oc5DrE;0VxbPmL@BUa_BQwa$EW~sU#-LP0?sGmqfUGhGWcciGZ*4(}u3z=@b>Ow9DQe7lcO3K}BG3j(t&
zH10>sK!&4Q5-=gN@Nxj6{|*nuyqw7KZJ1?p)NUJ?U0bOigGdsOk}Iz&9PmN_5=W*Z9M
zy^pA`&dX0oo6?CSuhE~(pYbLuTPp1a1Fa@e3Lu&mmgd$;D}&g-i=D-{sv?J9kIr9r
zrX&Z)aFGK^kNY{LxrotP0}k*;uN12i_2a_JJhKwh
zBt{D-JRxC$8U+-`u1xD>gJ^H4lbW;7spI-=H506i=ncdK;xq*L6f7jVz$XGMg5aQk
zHRJY&$@g}i_SP##iC?lR?ltnWUTT-UDlq(*BTQaYNkg
zNG#sNoo{WmP+Vl}U~?+T?g25b$E-7iwhu=VVgw3JdFXm~ba+LC4p>CP3~rNTiNBl7
zL{RfLLepNPEtZj}yL_#R{(^MqIlG)c0Va}>U|9Pl&B_3tV;Ps{r)WqBznD7FcTlP4
z`JQe2DvGhmeeHGGX39zGyOOxZ3tq~Dft(BQ;mDXwwJi?sBtxo$Gf1SS2w*eQ0p&RVMNVi@d
zY8v4J0(n}%6*Rw(g~l@sUuxpiJ*Y}7TzBQyU+>-qWm*InUeGt@)T9g^0J#z4){Lw*
zT;69if~U9DXBR9fgVPlYy7aDhJU)gDC?_GHQtwa6QXNaah7-CzA|Fx-lH7d@N9>38
zX(F&fd3w7AkZ+ha8-gKfX%@_~<#HDs?kBg5zW>V3%Xw5jwPs6uni{7r
zd`EfPYrA*SU;xDtm@E>5TrJKlg5o=h;NSXk)pt4K)GbpP0xkUg>2o|oG=`UnX7^Un
zb&@8d6Fj1cBWW^c(K#Csc8xEBa4KfHY>8Lp^77-lhzgWr9kR9_p+g|-9r?VSv?qA%^1O;cqgke)%AqHlR$B{!Y1Mq
zj|)Ecg?{_!>kGDAwGa7%cwSUb{BcayJihkv$}ql+yu=O}jVvAFdC{Hjh$4}u+$mx%
z5V$sUiGCX%D3A>bKwY8HR)Gv*lisI4q^3vJ*nDwj|mtr!0r!~+Qoe2cw^jPCXkT7tI*01|w@
z&gPC`?O1w7hQ%=&bcHi7(fqhY3${~JepA7y@^aLwHpew^Yk$;R4v{ASHjXjXtaTc_
zuz5*nXB&PrcyWx#gQ%?HyxawmS+Wu(7ssvB1UMh!1$to&o(mv_f=9~!9@VsJCGxpu
z`>g5Sp=xDhpsiCy^y>=fI0DON$&pb7o7^d{@@&hj3!6PUd=vA;G;#7&8ChamsE{`^
zY8pDra8Jntp62Ivi)Y`*XbpM60s06v@Rz^-g)TW_F@B!~y7!4AJ>37mAuz!(!C+xQ
zSR61?u!{N|qHWOeR%$RXRL~vpN0SGri7-klNHEJuivbi=0qSbdV4&ghf4i|7?$>z(
zI{qH?i}`~a7GyB6|8pZRq982+P*r1+m-t&(%U5#ZWFQd-(CXKLHeN@y(c
z;wqq1hzE@q1b$GG0VQ_)`{MeylBlVfy%UHR=;Z98>T3M&;{0i?+0T-Bck?I)AUQrz
zeF**_iGu$JlCpLnFv`D9?q6R51jKPM{Rd6!0FF#KP=O|b3iQX*TqXSjO?gXaXAmLr
zU#g&%@+XpjVArlGkfaPKk^PUSnMLsjlK<9nH*zxl^V2-jGC$4+HGE%?F3%4|y9>HN
z|FJgz*HW$VwU8$RNtuBf(2vdZhW3x;R6%eoJM(|2zvKebxCh$s5J-*fhZ75B_yeUs
zFTrToFiB^SNH?gV2>l?G&h!UD>UP%uKh1L;Er59!q&NoZRe$VEf?5Ar^&iUad&2gQ
z&WE`E%lTg=_3XQT@gJOjkAi-Hbbqrl{(pA<>_GH4O8+xI^=IAhS#v+$vmgOK=>C!~_xFg-pLM>6kUfy=zL|u~KkNJ<
z$L?p*?;%(Ze6w%%M(zjE|4dH&5$)_}mG3z{KUQ6s!Y@_+kInPH;kAC&{T^5HKmqz@
z@+!aA{YNIy&r;uKTz=r6e6v>d-%9<%_4R!+-iN^8H#0N(rQbiu-u&}-|2`q@k1agM
zdHkW_1&%VDD_|I;NpK*OZfAjAb
z`Ttl8km0{|{F`kWKWltH$^Ech;G2y`{7&N^%H;d0$cGv7Z^oJNOSiwAFaP<=em}wX
z<8AA6<}bbeZc_7S=ii6PALi)3nOXL)o&Uj%-OnQ52M&L%(%ZaWiu^(R{b!Bu2WJl<
h$Zw`p^gE5e2}ml*LW4$nU|{5+pXG<~Ugg7I{||-5t(pJ;
literal 0
HcmV?d00001
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c61a118
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..739907d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# 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
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# 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"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..c4bdd3a
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,93 @@
+@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
+@rem SPDX-License-Identifier: Apache-2.0
+@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=.
+@rem This is normally unused
+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% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..9e919f7
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'chat'
diff --git a/src/main/java/com/messaging/chat/ChatApplication.java b/src/main/java/com/messaging/chat/ChatApplication.java
new file mode 100644
index 0000000..e70db4d
--- /dev/null
+++ b/src/main/java/com/messaging/chat/ChatApplication.java
@@ -0,0 +1,13 @@
+package com.messaging.chat;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ChatApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ChatApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/com/messaging/chat/logging/DPLogger.java b/src/main/java/com/messaging/chat/logging/DPLogger.java
new file mode 100644
index 0000000..2e71aa4
--- /dev/null
+++ b/src/main/java/com/messaging/chat/logging/DPLogger.java
@@ -0,0 +1,80 @@
+package com.messaging.chat.logging;
+
+import java.util.Arrays;
+import java.util.regex.MatchResult;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DPLogger {
+
+ private static final Pattern PHONE_PATTERN = Pattern.compile("(? clazz) {
+ this.logger = LoggerFactory.getLogger(clazz);
+ }
+
+ public static DPLogger getLogger(Class> clazz) {
+ return new DPLogger(clazz);
+ }
+
+ public void info(String msg, Object... args) {
+ logger.info(mask(msg), maskArgs(args));
+ }
+
+ public void debug(String msg, Object... args) {
+ logger.debug(mask(msg), maskArgs(args));
+ }
+
+ public void warn(String msg, Object... args) {
+ logger.warn(mask(msg), maskArgs(args));
+ }
+
+ public void error(String msg, Object... args) {
+ logger.error(mask(msg), maskArgs(args));
+ }
+
+ private String mask(String message) {
+ if (message == null) {
+ return null;
+ }
+ return PHONE_PATTERN.matcher(message).replaceAll(this::maskPhoneNumber);
+ }
+
+ private Object[] maskArgs(Object[] args) {
+ if (args == null) {
+ return null;
+ }
+
+ return Arrays.stream(args)
+ .map(arg -> arg instanceof String text ? mask(text) : arg)
+ .toArray();
+ }
+
+ private String maskPhoneNumber(MatchResult matchResult) {
+ String original = matchResult.group(1);
+ String digitsOnly = original.replaceAll("\\D", "");
+
+ if (digitsOnly.length() <= 4) {
+ return original;
+ }
+
+ int digitsToMask = digitsOnly.length() - 4;
+ int maskedDigits = 0;
+ StringBuilder result = new StringBuilder(original.length());
+
+ for (char currentChar : original.toCharArray()) {
+ if (Character.isDigit(currentChar) && maskedDigits < digitsToMask) {
+ result.append('*');
+ maskedDigits++;
+ } else {
+ result.append(currentChar);
+ }
+ }
+
+ return result.toString();
+ }
+}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
new file mode 100644
index 0000000..c9061cb
--- /dev/null
+++ b/src/main/resources/application.yaml
@@ -0,0 +1,22 @@
+spring:
+ application:
+ name: chat
+ datasource:
+ url: jdbc:postgresql://localhost:5432/chat
+ username: postgres
+ password: postgres
+ driver-class-name: org.postgresql.Driver
+ jpa:
+ hibernate:
+ ddl-auto: none
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ dialect: org.hibernate.dialect.PostgreSQLDialect
+logging:
+ file:
+ path: logs
+ level:
+ root: INFO
+ com.messaging.chat: DEBUG
diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000..8b10dc1
--- /dev/null
+++ b/src/main/resources/logback-spring.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/com/messaging/chat/ChatApplicationTests.java b/src/test/java/com/messaging/chat/ChatApplicationTests.java
new file mode 100644
index 0000000..d3f87b9
--- /dev/null
+++ b/src/test/java/com/messaging/chat/ChatApplicationTests.java
@@ -0,0 +1,13 @@
+package com.messaging.chat;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ChatApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
From e816dc7a072ace368a02ed031145166e5f6c2dea Mon Sep 17 00:00:00 2001
From: ferid333 <135500346+ferid333@users.noreply.github.com>
Date: Wed, 15 Apr 2026 11:31:52 +1000
Subject: [PATCH 02/13] DVLP-002: Add entities and create tables
---
build.gradle | 2 +
.../messaging/chat/dao/entity/Attachment.java | 50 +++++++++++++++++++
.../messaging/chat/dao/entity/BaseEntity.java | 22 ++++++++
.../chat/dao/entity/Conversation.java | 25 ++++++++++
.../dao/entity/ConversationParticipant.java | 23 +++++++++
.../messaging/chat/dao/entity/Message.java | 41 +++++++++++++++
.../chat/model/constant/FileStatus.java | 7 +++
.../chat/model/constant/MessageType.java | 7 +++
.../db/changelog/db.changelog-master.yaml | 1 +
9 files changed, 178 insertions(+)
create mode 100644 src/main/java/com/messaging/chat/dao/entity/Attachment.java
create mode 100644 src/main/java/com/messaging/chat/dao/entity/BaseEntity.java
create mode 100644 src/main/java/com/messaging/chat/dao/entity/Conversation.java
create mode 100644 src/main/java/com/messaging/chat/dao/entity/ConversationParticipant.java
create mode 100644 src/main/java/com/messaging/chat/dao/entity/Message.java
create mode 100644 src/main/java/com/messaging/chat/model/constant/FileStatus.java
create mode 100644 src/main/java/com/messaging/chat/model/constant/MessageType.java
create mode 100644 src/main/resources/db/changelog/db.changelog-master.yaml
diff --git a/build.gradle b/build.gradle
index 898d0bc..b3c92e3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -20,9 +20,11 @@ repositories {
dependencies {
implementation 'ch.qos.logback:logback-classic'
+ implementation 'org.liquibase:liquibase-core'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.16'
runtimeOnly 'org.postgresql:postgresql'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
diff --git a/src/main/java/com/messaging/chat/dao/entity/Attachment.java b/src/main/java/com/messaging/chat/dao/entity/Attachment.java
new file mode 100644
index 0000000..5c3d0b9
--- /dev/null
+++ b/src/main/java/com/messaging/chat/dao/entity/Attachment.java
@@ -0,0 +1,50 @@
+package com.messaging.chat.dao.entity;
+
+import com.messaging.chat.model.constant.FileStatus;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+@Entity
+@Table(name = "attachments")
+@Getter
+@Setter
+public class Attachment extends BaseEntity{
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "message_id", nullable = false)
+ private Message message;
+
+ @Column(name = "uploader_id")
+ private Long uploaderId;
+
+ @Column(name = "storage_key")
+ private String storageKey;
+
+ @Column(name = "original_file_name")
+ private String originalFileName;
+
+ @Column(name = "content_type")
+ private String contentType;
+
+ @Column(name = "file_size")
+ private Long fileSize;
+
+ @Column(name = "checksum")
+ private String checksum;
+
+ @Column(name = "thumbnail_storage_key")
+ private String thumbnailStorageKey;
+
+ @Column(name = "public_url")
+ private String publicUrl;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status")
+ private FileStatus status;
+
+}
diff --git a/src/main/java/com/messaging/chat/dao/entity/BaseEntity.java b/src/main/java/com/messaging/chat/dao/entity/BaseEntity.java
new file mode 100644
index 0000000..59f77ff
--- /dev/null
+++ b/src/main/java/com/messaging/chat/dao/entity/BaseEntity.java
@@ -0,0 +1,22 @@
+package com.messaging.chat.dao.entity;
+
+
+import jakarta.persistence.MappedSuperclass;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@MappedSuperclass
+public class BaseEntity {
+
+ @CreationTimestamp
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/messaging/chat/dao/entity/Conversation.java b/src/main/java/com/messaging/chat/dao/entity/Conversation.java
new file mode 100644
index 0000000..76666e1
--- /dev/null
+++ b/src/main/java/com/messaging/chat/dao/entity/Conversation.java
@@ -0,0 +1,25 @@
+package com.messaging.chat.dao.entity;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Table(name = "conversations")
+@Getter
+@Setter
+public class Conversation extends BaseEntity{
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @OneToMany(mappedBy = "conversation", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ private List conversationParticipants = new ArrayList<>();
+
+ @OneToMany(mappedBy = "conversation", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ private List messages = new ArrayList<>();
+}
diff --git a/src/main/java/com/messaging/chat/dao/entity/ConversationParticipant.java b/src/main/java/com/messaging/chat/dao/entity/ConversationParticipant.java
new file mode 100644
index 0000000..06dd7e0
--- /dev/null
+++ b/src/main/java/com/messaging/chat/dao/entity/ConversationParticipant.java
@@ -0,0 +1,23 @@
+package com.messaging.chat.dao.entity;
+
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+@Entity
+@Table(name = "conversation_participants", uniqueConstraints = @UniqueConstraint(columnNames = {"conversation_id", "user_id"}))
+@Getter
+@Setter
+public class ConversationParticipant extends BaseEntity{
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "conversation_id")
+ private Conversation conversation;
+
+ @Column(name = "user_id")
+ private Long userId;
+}
diff --git a/src/main/java/com/messaging/chat/dao/entity/Message.java b/src/main/java/com/messaging/chat/dao/entity/Message.java
new file mode 100644
index 0000000..960ba99
--- /dev/null
+++ b/src/main/java/com/messaging/chat/dao/entity/Message.java
@@ -0,0 +1,41 @@
+package com.messaging.chat.dao.entity;
+
+import com.messaging.chat.model.constant.MessageType;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Table(name = "messages")
+@Getter
+@Setter
+public class Message extends BaseEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "conversation_id", nullable = false)
+ private Conversation conversation;
+
+ @Column(name = "sender_id")
+ private Long senderId;
+
+ @Column(name = "client_message_id")
+ private String clientMessageId;
+
+ @Column(name = "type")
+ @Enumerated(EnumType.STRING)
+ private MessageType type;
+
+ @Column(name = "text_content")
+ private String textContent;
+
+ @OneToMany(mappedBy = "message", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ private List attachments = new ArrayList<>();
+
+}
diff --git a/src/main/java/com/messaging/chat/model/constant/FileStatus.java b/src/main/java/com/messaging/chat/model/constant/FileStatus.java
new file mode 100644
index 0000000..a2f2356
--- /dev/null
+++ b/src/main/java/com/messaging/chat/model/constant/FileStatus.java
@@ -0,0 +1,7 @@
+package com.messaging.chat.model.constant;
+
+public enum FileStatus {
+ PENDING,
+ UPLOADED,
+ FAILED
+}
diff --git a/src/main/java/com/messaging/chat/model/constant/MessageType.java b/src/main/java/com/messaging/chat/model/constant/MessageType.java
new file mode 100644
index 0000000..dbefc64
--- /dev/null
+++ b/src/main/java/com/messaging/chat/model/constant/MessageType.java
@@ -0,0 +1,7 @@
+package com.messaging.chat.model.constant;
+
+public enum MessageType {
+ TEXT,
+ IMAGE,
+ FILE
+}
diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml
new file mode 100644
index 0000000..daf9f00
--- /dev/null
+++ b/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -0,0 +1 @@
+databaseChangeLog: []
From 8cb725383dbe3030941e9a2dd2dbeb8fce38680b Mon Sep 17 00:00:00 2001
From: ferid333 <135500346+ferid333@users.noreply.github.com>
Date: Thu, 16 Apr 2026 19:53:21 +1000
Subject: [PATCH 03/13] DVLP-003: Add liquibase and exception handler
---
build.gradle | 3 +-
.../controller/ConversationController.java | 23 +++++++++++++
.../chat/controller/ErrorHandler.java | 32 +++++++++++++++++++
.../repository/ConversationRepository.java | 9 ++++++
.../model/exceptions/ResourceNotFound.java | 8 +++++
.../chat/service/ConversationService.java | 25 +++++++++++++++
src/main/resources/application.yaml | 4 +--
.../db/changelog/db.changelog-master.yaml | 4 ++-
8 files changed, 103 insertions(+), 5 deletions(-)
create mode 100644 src/main/java/com/messaging/chat/controller/ConversationController.java
create mode 100644 src/main/java/com/messaging/chat/controller/ErrorHandler.java
create mode 100644 src/main/java/com/messaging/chat/dao/repository/ConversationRepository.java
create mode 100644 src/main/java/com/messaging/chat/model/exceptions/ResourceNotFound.java
create mode 100644 src/main/java/com/messaging/chat/service/ConversationService.java
diff --git a/build.gradle b/build.gradle
index b3c92e3..9edc6f8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -21,14 +21,13 @@ repositories {
dependencies {
implementation 'ch.qos.logback:logback-classic'
implementation 'org.liquibase:liquibase-core'
+ implementation 'org.springframework.boot:spring-boot-liquibase'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
- implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.16'
runtimeOnly 'org.postgresql:postgresql'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
- testImplementation 'org.springframework.boot:spring-boot-starter-websocket-test'
testCompileOnly 'org.projectlombok:lombok'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testAnnotationProcessor 'org.projectlombok:lombok'
diff --git a/src/main/java/com/messaging/chat/controller/ConversationController.java b/src/main/java/com/messaging/chat/controller/ConversationController.java
new file mode 100644
index 0000000..a54441d
--- /dev/null
+++ b/src/main/java/com/messaging/chat/controller/ConversationController.java
@@ -0,0 +1,23 @@
+package com.messaging.chat.controller;
+
+
+import com.messaging.chat.model.dto.ConversationResponse;
+import com.messaging.chat.model.dto.CreateConversationRequest;
+import com.messaging.chat.service.ConversationService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/ms-chat/conversations")
+public class ConversationController {
+
+ private final ConversationService conversationService;
+
+}
diff --git a/src/main/java/com/messaging/chat/controller/ErrorHandler.java b/src/main/java/com/messaging/chat/controller/ErrorHandler.java
new file mode 100644
index 0000000..77ec54f
--- /dev/null
+++ b/src/main/java/com/messaging/chat/controller/ErrorHandler.java
@@ -0,0 +1,32 @@
+package com.messaging.chat.controller;
+
+import com.messaging.chat.model.exceptions.ResourceNotFound;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.Map;
+
+@RestControllerAdvice
+public class ErrorHandler {
+
+ @ExceptionHandler(ResourceNotFound.class)
+ public ResponseEntity