From ff9e9689c84997f195859781f0f8b5308ce5afe7 Mon Sep 17 00:00:00 2001 From: junjinyun Date: Mon, 28 Jul 2025 13:10:44 +0900 Subject: [PATCH 01/10] =?UTF-8?q?Feat=20:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8(=EB=84=A4=EC=9D=B4=EB=B2=84,=EC=B9=B4?= =?UTF-8?q?=EC=B9=B4=EC=98=A4,=EA=B5=AC=EA=B8=80)=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9C=A0=EC=A0=80=EA=B8=B0=EB=8A=A5=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20&&=20jwt=20?= =?UTF-8?q?=EC=95=A1=EC=84=B8=EC=8A=A4,=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gradle/8.14.2/checksums/checksums.lock | Bin 0 -> 17 bytes .gradle/8.14.2/checksums/md5-checksums.bin | Bin 0 -> 20997 bytes .gradle/8.14.2/checksums/sha1-checksums.bin | Bin 0 -> 24437 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .gradle/8.14.2/fileChanges/last-build.bin | Bin 0 -> 1 bytes .gradle/8.14.2/fileHashes/fileHashes.bin | Bin 0 -> 18697 bytes .gradle/8.14.2/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .gradle/8.14.2/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .gradle/buildOutputCleanup/cache.properties | 2 + .gradle/vcs-1/gc.properties | 0 .idea/.name | 1 + .idea/HeyDoctor_Backend-feat-6.iml | 9 + .idea/compiler.xml | 19 ++ .idea/dataSources.local.xml | 15 ++ .idea/dataSources.xml | 28 +++ .idea/misc.xml | 5 +- .idea/workspace.xml | 169 +++++++++++++++ build.gradle | 28 ++- .../HeyDoctorBackendApplication.class | Bin 0 -> 791 bytes .../domain/User/entity/User$UserBuilder.class | Bin 0 -> 3134 bytes .../domain/User/entity/User.class | Bin 0 -> 2732 bytes .../Handler/OAuthLoginFailureHandler.class | Bin 0 -> 1652 bytes .../Handler/OAuthLoginSuccessHandler.class | Bin 0 -> 7510 bytes .../User/oauth/UserInfo/GoogleUserInfo.class | Bin 0 -> 1211 bytes .../User/oauth/UserInfo/KakaoUserInfo.class | Bin 0 -> 1257 bytes .../User/oauth/UserInfo/NaverUserInfo.class | Bin 0 -> 1206 bytes .../User/oauth/UserInfo/OAuth2UserInfo.class | Bin 0 -> 251 bytes .../User/repository/UserRepository.class | Bin 0 -> 931 bytes .../global/config/AppConfig.class | Bin 0 -> 433 bytes .../global/config/WebConfig.class | Bin 0 -> 1621 bytes .../global/exception/CustomException.class | Bin 0 -> 1129 bytes .../global/exception/ErrorCode.class | Bin 0 -> 4115 bytes .../exception/GlobalExceptionHandler.class | Bin 0 -> 2428 bytes .../global/exception/SuccessCode.class | Bin 0 -> 3440 bytes .../global/exception/dto/BaseCode.class | Bin 0 -> 261 bytes .../dto/ResultDto$ResultDtoBuilder.class | Bin 0 -> 2521 bytes .../global/exception/dto/ResultDto.class | Bin 0 -> 1487 bytes .../exception/responce/ApiResponse.class | Bin 0 -> 3588 bytes .../security/config/SecurityConfig.class | Bin 0 -> 6727 bytes .../RefreshToken$RefreshTokenBuilder.class | Bin 0 -> 2488 bytes .../RefreshToken/entity/RefreshToken.class | Bin 0 -> 1947 bytes .../repository/RefreshTokenRepository.class | Bin 0 -> 1065 bytes .../TokenResponse$TokenResponseBuilder.class | Bin 0 -> 1826 bytes .../security/jwt/Token/TokenResponse.class | Bin 0 -> 1128 bytes .../security/jwt/Token/TokenService.class | Bin 0 -> 3398 bytes .../global/security/jwt/util/JwtUtil.class | Bin 0 -> 4533 bytes out/production/resources/application.yml | 96 +++++++++ .../users/model/signup/EnvFileLoadTest.class | Bin 0 -> 4670 bytes .../model/signup/SignupApplicationTests.class | Bin 0 -> 611 bytes .../User/SignupApplication.java | 13 -- .../domain/controller/UserController.java | 65 ------ .../User/domain/dto/CommonResponse.java | 13 -- .../User/domain/dto/LoginRequest.java | 13 -- .../User/domain/dto/LoginResponse.java | 14 -- .../User/domain/dto/UserRegisterRequest.java | 14 -- .../User/domain/entity/User.java | 42 ---- .../domain/repository/UserRepository.java | 13 -- .../User/domain/service/UserService.java | 73 ------- .../User/global/util/JwtUtil.java | 30 --- .../domain/User/entity/User.java | 42 ++++ .../Handler/OAuthLoginFailureHandler.java | 24 +++ .../Handler/OAuthLoginSuccessHandler.java | 111 ++++++++++ .../User/oauth/UserInfo/GoogleUserInfo.java | 26 +++ .../User/oauth/UserInfo/KakaoUserInfo.java | 28 +++ .../User/oauth/UserInfo/NaverUserInfo.java | 26 +++ .../User/oauth/UserInfo/OAuth2UserInfo.java | 7 + .../User/repository/UserRepository.java | 16 ++ .../{User => }/global/config/WebConfig.java | 2 +- .../global/exception/CustomException.java | 34 +-- .../global/exception/ErrorCode.java | 44 +++- .../exception/GlobalExceptionHandler.java | 25 ++- .../global/exception/SuccessCode.java | 40 ++++ .../global/exception/dto/BaseCode.java | 6 + .../global/exception/dto/ResultDto.java | 14 ++ .../exception/responce/ApiResponse.java | 42 ++++ .../global/security/DummySecurityConfig.java | 5 - .../security/config/SecurityConfig.java | 55 +++++ .../jwt/RefreshToken/entity/RefreshToken.java | 25 +++ .../repository/RefreshTokenRepository.java | 21 ++ .../security/jwt/Token/TokenResponse.java | 12 ++ .../security/jwt/Token/TokenService.java | 43 ++++ .../global/security/jwt/util/JwtUtil.java | 93 +++++++++ src/main/resources/application.properties | 29 --- src/main/resources/application.yml | 96 +++++++++ .../users/model/signup/EnvFileLoadTest.java | 108 ++++++++++ .../model/signup/SignupApplicationTests.java | 2 +- .../model/signup/service/UserServiceTest.java | 197 ------------------ 88 files changed, 1289 insertions(+), 546 deletions(-) create mode 100644 .gradle/8.14.2/checksums/checksums.lock create mode 100644 .gradle/8.14.2/checksums/md5-checksums.bin create mode 100644 .gradle/8.14.2/checksums/sha1-checksums.bin create mode 100644 .gradle/8.14.2/executionHistory/executionHistory.lock create mode 100644 .gradle/8.14.2/fileChanges/last-build.bin create mode 100644 .gradle/8.14.2/fileHashes/fileHashes.bin create mode 100644 .gradle/8.14.2/fileHashes/fileHashes.lock create mode 100644 .gradle/8.14.2/gc.properties create mode 100644 .gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 .gradle/buildOutputCleanup/cache.properties create mode 100644 .gradle/vcs-1/gc.properties create mode 100644 .idea/.name create mode 100644 .idea/HeyDoctor_Backend-feat-6.iml create mode 100644 .idea/compiler.xml create mode 100644 .idea/dataSources.local.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/workspace.xml create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/HeyDoctorBackendApplication.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/entity/User$UserBuilder.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/entity/User.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginFailureHandler.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/OAuth2UserInfo.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/repository/UserRepository.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/AppConfig.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/ErrorCode.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/GlobalExceptionHandler.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto$ResultDtoBuilder.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken$RefreshTokenBuilder.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse$TokenResponseBuilder.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenService.class create mode 100644 out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/util/JwtUtil.class create mode 100644 out/production/resources/application.yml create mode 100644 out/test/classes/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.class create mode 100644 out/test/classes/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/SignupApplicationTests.class delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/SignupApplication.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/controller/UserController.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/CommonResponse.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/LoginRequest.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/LoginResponse.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/UserRegisterRequest.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/entity/User.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/repository/UserRepository.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/service/UserService.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/User/global/util/JwtUtil.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/entity/User.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginFailureHandler.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/OAuth2UserInfo.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/repository/UserRepository.java rename src/main/java/HeyDoctor/HeyDoctor_Backend/{User => }/global/config/WebConfig.java (92%) create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/DummySecurityConfig.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenService.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/util/JwtUtil.java delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.java delete mode 100644 src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/service/UserServiceTest.java diff --git a/.gradle/8.14.2/checksums/checksums.lock b/.gradle/8.14.2/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..a434231b10446e194a820f2af622cdd0968f925b GIT binary patch literal 17 UcmZQpiavU$CgAgK1_;;&05`G)QUCw| literal 0 HcmV?d00001 diff --git a/.gradle/8.14.2/checksums/md5-checksums.bin b/.gradle/8.14.2/checksums/md5-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..f1b5645fde5d8f5f2b6894d4634442619e938c24 GIT binary patch literal 20997 zcmeI3`9Ia!AIC3AxWK4OMHbBzzN_4Z~{01oB&P$Cx8>c3E%{90yqJj08RiWfD^z8-~@02I02l% z|4RZaVj&V>F$(&^Yf*g-Z0S1^{PjB7Qw=z0UF{UT{847UIAB`>cKK z?h9RDyptK?oa-H`v>>NIz}+_>-gfYgRA_7RamZy6@0oPF(WIzS0Qe?4;?Kw}Bl<1i&gT%HOw25ps$j-LzGZA6!;fID_9@cuWQhpp_l1MZT6xa6L2-=IcWI8WE= z1wK)rlOQSI3C6q0BQB+9sV&Ah=mj`)3~~7+R**!b8Qj*c%7|+w7kx9g-bxLOcUpwF zu0%b37u%W#`CY^fme~5Qt5>%M+}RLu)2bZV182^=!0|bVf3ZqIM9VA{uGcMLfj665 zdO9i_!|`*7TVD8yv5NgeCEyO17WjO@dbRQVG{9ZnAa1Wa-I=zv5zR9cac2BbdQOJ- zA7H#|GU6UPwu;v`-);ol%@J{*id}Lm*{1n`JBA~^IaOS*)2*}|aQ7<u> z6Tk`J1aJa40h|C%04IPGzzN_4Z~{01oB&P$Cx8>c3E%{90yqJj08RiWfD^z8-~@02 zI02jhP5>u>6ZoekU_vZdhj^)zFPW0?c3<&XwZb}=Q?FVg={+=>P_|KWR1!#qzJ+*S z{l5msBv$+E{NteJ6hCj(g!UR}{0A5e52~?E-=INCm@%^A828M!%hM6Sh+YZ|F;}W# zJyR1hxM%uPi>~eduR`g>_pr%XMXLcrVmZ~AaDKW>*w7&TOiB*xUS^vDG<1O>{ef!m zdPI`Xgu~C`$4miKY24H+rPc;Un%ud&evM-Kyu@h^)zxo0ig}{*EPz~Ot z*W-pEzN5D$yG^?GPrio+JSQO*LNzu&&M8qRGY*oxr{_5D6)y z&o>Uu%*I@P?R&@u8jJV>P3}*s5hgiNzII>gOoY;h-R2GVTc80?o5+*3ksluB5D`esnLa0Z#$w5lINvSi!ab^|Jbl zw-b9yidJ+S`wkjpMnqF^Bszgvai=Z()~sT$=$WE}vVD7EpX~hkZ*?aD@bvd6CJCNL0Y$v`z6FWyWI_pn2 z_x^RF`LoWeg`>&^Ql_!zENh zBk}2CUm?a2x$2Hy<6g2r87$dJ25U{-fs^pA{%rVERVPu}(oj3rjgUk5x9BH2YX zZmOA@538*iTYaeYug~d=`Rgz)YzYK4=qIU$^dH6y<5>2Ue8se#E+1?*A~Qa?RvPib&dS{VO+>@<_olC52?ofv2rN`rpWMaDKCRi&4CV3g9B?Q zFQXb^v9{jZmM+N+zGXjPs>K?Cv+@Ns3mxX)OWheH->=02%{P&xceoal&CpxeyOJxSM7He7_A$DAznc> zw3-ghsVK&jtDC3$8J^OIHCo~Qu+X6z*W!i(s@gSvGjS8Pjw&7c71n?!&6e(?8nvR_ zmZ5wfTkRzSVM(re@P4$CsS?fT3DF7ck~CI)*^>@`)-|4zqG#mLX;=fMtI@&d{veVn5-5cGet8H43rSO(0LE{=X8biC=}nBIcI#Y?>+u{uIu;D@0z);#@*{aulsZ6J#)^CI=420h%B0e8}i>x@V_sq zS5yL20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20{>qUutX1p z9UP3v8hY#nk3WK}<3wM0hAEC{++=K%0&kni5&wS>zPsqTM`e&K;HEVMk7Hciov>0w z4{+PR37#-|_@vgWnM}ZUZzp)7Pz|r!gAW3L+g%{|=^G6a0~wP6fSWTAJY`?%iS3h- zR{^&PC3xDf-G!pMyPAOOI}$vdWwE&4pt%v?dW#8u;f@euDaEo6aMNW3zo_}pZvE9i z#ef^$A$U%K@2lIV1gGHVpCfo~-9T{Ah73Ev9cKuBU8P++H*m8U;3hir{PMTx8(A?K zfZJauc!BK2>7{IY9RN4JOz_(~SyR)GT#WA&1Zra%giR~-Zl&d zd}jf{@A8yCYLs*82i#DL;P+y4au=5}nF4MkO7Ie0zXy%w!*2lBs~~u(j&rBpE?G9n zjR{`%>XYp2rOCE{TdNVg{I$rSXWWzm;7*wYuLyaT@$I7FOTdji2wpAx!{xh_?GnHZ z))Tx|{j6@O`=#fQ_Yk~pS+_)LuF@RfmX`B8YjxO*%r`lJ8&wg!(LDV~kZ(aH+`6TIETpx-P?DG_k1ee?X{tw?RzQ69h@3<%!U z8<;Yp?AZjkK7r@n$o-DY&-Y1!pPxXy(XTUo#OTovcwWrC2tJ^eop8^>>lSElv4h}) zjIF{Z3PN&e1x~* zcn{nDK)_9c2|l(_Yqp?jY#M%!Ey2H8`!WYB9D5A7As4~N|4MZHXy9Z6xaA6h|8R{K zVao4L0Nm(1!G9%3D!jkheg$xSX@XC&*?%~CLVp%;i(!IKS4RxFXxF&`Zr4fh+2uRz zDkV8!ZaW9(xx*^v{Lf32L3;;5f-^Qc3)~Yfj|O}<+dQ8#6EIrM{2Oq~ID#{kzho?M zE&BktnKQvzoOY?p3AdaF+@^%!oZ~q%!JNX+AlD-}x2#ZW_}AD}!1YcNoY#DE6{DHQ zX27kz=Xr+HqGPIS4+C!bli>W>HC>%s)XxEK!b5NY`rxbO+5DFQcTyy{ki79jhr05& zfE#|8=PF5BQfV!4oZ5=d^B7n9qlF&ZLHnH=1Yi7EN#$S+8w23>TM52omH(cEUuWU< zX8xMs%cQ!?1HVM>g6)sabEku>cM$I``1$?>U%@B6yt-Ls5OC{Wf=i?fYIP$1@Vass zndi4vye7i-qW3dwv)fN_Nk%E8Oimxp4-O9qzH)cgD!sy9Ezn-?8^NU$gtvuMBXIm$ z))8DTJu|9#t@9$#-pP&NYl6?-Ihm{j=V8+cf-6+ktF|(vWr6l)sRUQ-U@n6#Un1bkLC}6z3&FQPwqdrZQt1QS zn2zAuHhm?N4byPk*-jB$=US*z%Dc~lJW2R^+i0a07ROo5*u6VfeMb8E{i`f?GT}FCr{g1lrrP5Pa_yy3O2y8kYb!DI)lR<~-k6`QOZd z@7hi9Lt_T4Ok=#}fZOmB{IJi+<*&Xql7QPO5Zp`DqEW_28;&pImjw5IYyX$v$+&LN z-b$X}0h$KonXyS0uYtGC{O3~knyY#dnvJ?w1@Dg?I56vTm97Ttivm%K~b(1PD~;K5cM)70YSy-Bz4A<|N*x%}BD6Mf@?{+ zu(g*omWbZ7!OivQX9gbW-xp)}sG{Fs+Pjt2(bcuM1y{AQVXG5qdKkHkYe=)@ zlZI1#SJKSZEeApKiX=GX2S#(mY2 zK})~~S}MR2#;y^X9`>_VzmqqrzOZ<%CW6ktPh+I^4Emh|ycS$z?T6K*vDWHWdKRpS zG<1_3Ix_TUwnd{SsSa9OK`rYAu6fZI6@fpJo}(&kJ=SAujx{jvQAEF|g4Y7;r|AN) zb>lR(KD5<8Rg3DmsO2Rz`;^su^{%L-{iu~%BQjM6YAwD_w*FnMrB`ItO!d|ysp9GR z{Ewr$5*cm@!XCmEw2a5dwWi+|<+Zin7%e~GQ~0^{i+KIXRCI*k$29_aD5pWTx~i<4 zYeWr=JZhcfc%O)_6K$A**SzmfUlF&Fe=l8Hco)`!YoPJ( z9MNRRu6gg4hq`-Pt@(&+VM&y<{0`w-Xu(>n@#I?EX&m94d%rXO9!=ZE8KtJjQ?I2C zEI)CyHM((pM|U)}SenP${{x09yLEgs`3?TU-NQ4x1HUShIWE zz)^t@_3qpu?Dfzx1Qr*zcAchHWyjg{^@IAEi5wTTi=-bb{k!@~6A=)8_)7%By7@hxy%!c%UH`G54*9u_*A>y#A0gBtcI5cMt4LbJp+BU^MwQ!8F~Aee2q)=DfYq03obWyze@TXZJFjw>t_msrkZlP%dR zdS_(biPjcP?khKywPf{3)<$O;+`>i(x=CUqhNhOzCnI5P`fU?8vyU27C$lk1FblOq zD;byA%|4SYn|1V0f=bfnbhnSYZJw;^TdT;5uItBZ)!-J}igjd5^3V$ZY*?*eh zGe9^KZqYGgHchQVL6N>f$=(;@CikrHyL;@+{uRrYgIeL1xWyAWM7Gvkn~pZyDOWq4 z*rmK<-z)9l)NuF=5RQ#u^aEaUlPzNoFW1r^UP2aE5_>L2>p<=dtz;ZR?E9iK_t@D_KAh)nFO=FF}J6rg4)oqy*(p=vW zhw}dY>O5l5D#aXx$>Srsr?E!;d~AGN%?u>0YJYe7CgeX(4_^$eT1-NafA6@&a#k2M{up$I- zi)k$v*)mgGT;aXlJ)o+(?V5?(p~AWgEaT8Z&l|e2OsJAAr22z-jqot<nh%u~u6dwQvs@R}s>4emHcI4L z<@cjoU2QvlJlnEV(a}*oA>G--0a{bQ;>#ji_jAov-C>xP-LNk*%K(O0Iv@I{)o> z$RU1D``-rXN3A??OXM3oUrr0$9p={aak*AjbvVQ&mjttxb@2%Ptp$7N0%{5Il54Gs z5zOJ^=;6H|@1OssarV~p&P01$2|qedEAK3cG7bs6q#V1#>%Bq%8)~65 zK!X9W1QwWWnpWop<)&-u?ug$t-Ql$^^JMTkH)z4}wG5jVX)^qGwO;>fa%`(+weVlH zg`ZbJ^kGnq$u(FDJOd!?A>>--8;7b(c(1xzi@eCGd}|Z%fPVsi-awBFUQrwi%o`66 z?VmVY#m&C*Z=C>Jflx-{CkE(U0Z-!(!I{$swG{vJ;68y4*EGwlohg+aE z2rR)vWXoq{b}#a*>D(SQ_U2KixcJ9!($H%HtMv$-gRzb64!Wbsu-lFPkQJkU(!}I4 z<1OWzFFrlPCJHThzEow%7IU$OK}CE*Pg2LStzo)_KSKGpCqipKRtZ7)e9#?Dt(pz? zZ#OE(28{)-zI$!v_PDK11V6N}#L;bCFS?_#s#-tKRP?&eTD|F0dplGXbaG28`qYIV zS0OGTf+}Ro;GLA=?YOAMMNNhFTk?eh%zlLZf)+eq66kL`w6)~>9b3#Aqs*JP-EBH) z$zPV781frhe}h_*qhw3BH2>s%c4;>@`zT&#H5==YlxNs1iv7ME11!E5Wb5-@Kc;45 zy~ZKmn;oZ(ofNWbkxo#n%NttgyieQ1@$17T_5zL)SHyPca{e_FfsD7{PhU#8UD(-0 zx3Z1sjwZu^D85*UPT7cEhObq)HcQ-!fAkT3+QKsI0{0ZUw@1m=NPXFghpJMKB)G#u z&<75OWuuE2fb|^okX~|u^VJhqcJ!F!sjc$8wf1QT7+m&jGi-zw`iz5abUElV9BmI} zPIL$fFpbz`0n$3kk>%by!&ir)uo9mK5#uin(gZEZ&?LlVb|4?BH2 zF>ZQw`yxeXg#k+r`dYKUo^aFOenbgN`fwxDtRRw!qA0y2DuZ zbaBLJ>Z{65+s5hf9jCve=LffPaEsgAiCioA^al1BxhQV7k1F1LVO)H5aF3`glVz|ZmS)FVh;qjgu>*f@yiXY;=Zj};pon`44Xj$SG$9qq*WuHG7+dFM_ zim71h<9Q&slu|`)gOD{Gx54SkJk=A0D*rAdZ$s$D*``x0Fkrb&;F|nyjM2DnK5p_zfiKKIu z(VyeUc1VXrvrDH)hjm%c&u@?U*|A&R56|;_eV^xZcwX0MOvLXkSB6waYqg|8009IL zKmY**5I_I{1Q0*~0R#|0009ILKmdXNA~3BU(r7=%-{Z21=7dO}M-}y_1G&zVpY?tF zIFi)=2kAZ2{pH&TRr;g4|2B6MEc?bwmHwLUuS|_hWY0`}|5^9-uI|U|3@ZW%Ab7DvKpIH*k&w+7#nX=C|eH_)ANc?yn1TOws8 FegQy?cP{_{ literal 0 HcmV?d00001 diff --git a/.gradle/8.14.2/fileHashes/fileHashes.lock b/.gradle/8.14.2/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..a2be29061ac59e56d55da12c0ab19524dc9afaa9 GIT binary patch literal 17 UcmZP;y0`Gt1}lwu3=qHp05xI+KL7v# literal 0 HcmV?d00001 diff --git a/.gradle/8.14.2/gc.properties b/.gradle/8.14.2/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000000000000000000000000000000000000..496432e99a44d9083d14c12687327cfd4d30ea46 GIT binary patch literal 17 UcmZR+pLf4|k + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..89a0b9c --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml new file mode 100644 index 0000000..58af63d --- /dev/null +++ b/.idea/dataSources.local.xml @@ -0,0 +1,15 @@ + + + + + + postgres.hoxnroonysjvuokheyzo + + + + + postgres.hoxnroonysjvuokheyzo + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..840958a --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,28 @@ + + + + + postgresql + true + true + $PROJECT_DIR$/src/main/resources/application.properties + org.postgresql.Driver + jdbc:postgresql://aws-0-ap-northeast-2.pooler.supabase.com:5432/postgres?sslmode=require + + + + + + $ProjectFileDir$ + + + postgresql + true + true + $PROJECT_DIR$/src/main/resources/application.properties + org.postgresql.Driver + jdbc:postgresql://aws-0-ap-northeast-2.pooler.supabase.com:5432/postgres?sslmode=require + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 5cd9a10..2758df8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,10 @@ - + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..790db93 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + { + "associatedIndex": 4 +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1753160427004 + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6c082ec..bfc64ad 100644 --- a/build.gradle +++ b/build.gradle @@ -26,37 +26,61 @@ repositories { dependencies { // WebClient (비동기 HTTP 통신) implementation 'org.springframework.boot:spring-boot-starter-webflux' + // Web (동기 HTTP 서버 및 Controller 등) implementation 'org.springframework.boot:spring-boot-starter-web' + // JPA implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // Security implementation 'org.springframework.boot:spring-boot-starter-security' + + // OAuth2 Client (소셜 로그인용) + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.12.2' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.2' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.2' + + // Thymeleaf + Spring Security 연동 — [추가] + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' + // Lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + // DevTools developmentOnly 'org.springframework.boot:spring-boot-devtools' + // DB Driver runtimeOnly 'org.postgresql:postgresql' + // Swagger (API 문서 자동화) implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + // PostGIS (공간 처리 라이브러리) implementation 'org.locationtech.jts:jts-core:1.18.2' implementation 'org.hibernate:hibernate-spatial' + // AI - Gemini API 연동 implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'com.google.code.gson:gson:2.8.9' + + // dotenv (환경변수 .env 자동 로드) + implementation 'io.github.cdimascio:dotenv-java:3.0.0' + // 테스트 testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' - testImplementation 'org.mockito:mockito-inline:5.2.0' // static, final mocking + testImplementation 'org.mockito:mockito-inline:5.2.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() jvmArgs += [ - "-javaagent:${classpath.find { it.name.contains('byte-buddy-agent') || it.name.contains('mockito-inline') }.absolutePath}" + "-javaagent:${classpath.find { it.name.contains('byte-buddy-agent') || it.name.contains('mockito-inline') }.absolutePath}" ] } diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/HeyDoctorBackendApplication.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/HeyDoctorBackendApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..1be8ee68ae1f9fcf4f7aa6b1b6468c3bc2590c3d GIT binary patch literal 791 zcma)4%TC)s6g?9X971@7@}^SRg)H!{Rh0?}f)ERrB7(xAi)!o)X26+=#^XXiixpKD z{Qy3y>K!*qEHw)(`QDjxpL6bg`Sa^HfFr!Ep@bO^uWmvxB-?^V~75iVpI}uri z*-uhQd&n@;-0yj)GJH4_KTdRHweiQNZ^t~k7pgx&M!?ZYHj^)4i+M*xXBbl*bF8Ob`qYZ?XLx-YhAMTelRU_q?Hwq z*6(O_Bje2QFcF0k1Uqw^q1@4Z!LSraCC;-!D9n}fWoQIC;z^Gi>Fy)2Y;R@C@G%%u zF`?i!q_~0uF6nXW&1rAxk)4~EN~}&X)5Jatd#n2w+)-$SA;(%*XC@M-(lz~V+}HmD zIdEh$Y+Yu`%7N&~R60_W(l#F$uwx16#kssUOH{IN!v6DX_~Mt*`#!U0tL2d$%Qlv+oUuXgs6m^9n-iwJF}Yc zM#?krho3wKNJT|O;sJQ5isSJlJIRcyZIvG$+sDV>IX?b;&iwcK)xQ9iuw6tS`g0ht zFo+?B(sO>mZI^q`?9ILBqM;au?nzIo`wabamE8jJDCAJIa1Iv3gLQGZ>Nk`h*vF%v zmwBTpJjZtY7MGsA6NuPnBbnTs;+G?aVVqp}Q7zRAv5)2~=q%9T580(6KF|Czv2mXO1!`i^Og$XSz9tmrX z7Qbg<3fCA4jX-cEoF$6ExPeu&CG5K2;O;7|Y|||A;kt#HXh)~*m>oI1&v55BB)tcx zh*{jwcX5;9*SW-DqOh2GHvozC_^`c8GR|T)XDn29iCBYIwh$#Bjhkn~# z8E!L7KIssfTcYM2$WZRNV#)J-#g!zlH3MD0wdXhOhr$zqrbCt3x#By-&f&WB#73vJ zCxUHG8@0A((=HFBp2xxgwJ!;2ss0WGO1>^Kyp;-HO4DRXl#&CD;ot&k6%ParrVO=7SZhDM^Kv`?c?*t98 zCN+%;I;Y;&TC{aTglgRnDQ%UOeP4wt;B9kZVIFG?Pb1|-7M_#NBD;0nP_7+)XcnIr7)bMQ%-|5F)XDD~Qm)?Qj6t+2pdGBjnmybh+-;;M8 zD;l@gx#zedEZ2R%*=bkP(MU*6Y}`IOY~rPuIqjj4EMD zMs+VGqq3KhQO!%qsH&Zk70EXJD3JX-G_q-xY03OcusXS#HIN}TRM?h5qj1D`$@E3CPe~@Y)PD5QWDlwFfjET#a+JHtYox&f+S6TSyd&W00 zj`8*6r2nI+X5nA#8Q>1y1GRB`u;m>5_Pxp*(_#NZVr0^GUpD3q^zJWr-HY3g3#L*Py37Bb8Y(qF%R mUCSrPyb{S~u}XcOTzE+B8$6=f2hbW*7H^8J@vc z@F8@@OlNe)2k@af{?6Scgb+=q%p~`mdw=J5&plt^pVxo>O+;(-W0D30J$@$7i%!jV zT)izFt{b%zY1j3-(=<$5-}9uaOWQa7b0yF7#(H2{b?K%_P)wsZB?KiNnzrdb5;VB5 zSWVLq#gdc~lzC~K8MmmRyY z%G5VB8l!jNP5?Jcm#DxuEq!mVR8*)5jV9?mIBy$GDQK*NY1ennh%2knG^2)2UFXci zd-;N!8r|Z}^u=bW&fD*6G)r@WQZ-i^zO1kLg0dm4Z#Jb~c4~%I#7PC3^5FxG=9NWo zT5ntI1c-D~R!z@5vgDd=JHFwYj_nDW0^5n<`i6cgUC;D%~?|Jl8+R|m_ zs$m5Zup!T9y>Z^h0{2zUPnAWfxUp3!RrUql?0HAQv4W<}+#GN3kn@rb#UM~gswqTK z{>0@0W&}MvNd&g}b07y%j{^2~U|9y<%sR>{VM)8;AF~$cue6}yi-_YE-nu7frR=y3 z{n$K`u8o)J#$~|t0w;o>f_Qqq(L9Bh=_osg2GX23JZ(n zwTEwj+RYZZUs#|r^2xil#2?YWS{jYiLtWfG>@DUy`5@sjj2+r8m`F{ z5l_rNHc>+l-o{sO{<093vg%{iYoUJGRUPM12fg_YSA184gvLD%7OQCdP3a#y^=-pN z_xdP1LGgz4x!cE(j;^VIp2Q(ftt)^_*99d3*iw^V{EB__5|kbQ+tCmZG}56lrv_bB zTiEuw3yvP{hv2Zf)Qm{)I)Pi0PfX5sioLwc{{L6MZKy$vjO6gl>(fAG8>eyAg7A9_ zrN1e;pb#XjWMd)}EW`68)M36Yn&dQx88(Eb!tVkPW^~{kXwx)EA7cFo`huiI%F`0W zNspF+!=R0D6Ihpk#lZ^Q^+>0W59kv>FlDbpCh6V*-KS4W^Z*cy*z1t#HsmuvFmSI! zK5s+500hG~f{a7^5c4s|ul>@{rP$qOa)@wjX1A7AyY^{Y7H` z6%GCdS}S6Vcl&&FCbmCwdol*?xmEt<(^lP$X4ecy<>Zztr5?gL|# zU6we1{M2QYCLXEFDjP8+anR#R;@BpX#4$|LQ^>cHAj*Iu1LF+LGRRVfX7QQFr}JTg zo>A#qg#Ms32evap6MZ5i=sA7cgDBHYG}V`=Oq)H3#=D7feTlYu5>0gz&GaRD(UU0G zO*Ge+Xq&$4@wAz4qC0(wc6t)s?j~C7LlmQ3mFJ3@DBOE{(5vVJbm#Z<1C*HRn|(<9 LA0;Le9Z>3DlqoZL literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginFailureHandler.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginFailureHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..917300e7b61044ef72ae34b9dbffecbb5d81daf7 GIT binary patch literal 1652 zcmb_cZBrCS5PpUO_BanzL9^^Ay8r#}pZ5Uf@pB)#kO|P8M-O@hR+eb* zp^2QavctW2prRd0Dzai~N+)tXB`b|;xGlM)l1j|d>O4y&Q`O0$(s9F5`$8ZSo2tNI z$=Iq)jNjNwIJJ?(Fh&A=lgB8=1lB*9 z{!1!LU&j6H<(jV5V_LWIhp#$Z)e(~%t03^FPjp9Fr?^aZHzp^yoU6+v{;jnRp3$!j zN?mvY#k6jcl)_f`KBo)ql3dJV9N!A`&gn$EUl&_@T|d@sL^t=t5aQ$X+BAeYeFRTc#KW0YhO2{fl zl~AC2!BiM2r%O7al}4>h_PJuiCVC?kZz!w1+&=7fTbeyREp_JPt4s{%5!hCP1UzI< z)=Pn`rB=5DiYG8@cb1Dw`xEmejpljl%w8JcuE4qGylEqsS35TWeiSGiepMTo9W5Tk zC*-zF<*D*R@5pkcvvX10Mo(_dG;BnR+IMICW6S53k1sIVM#9E1I4B;EYz|1q?bS&x zm>Q1&4`_%9-ju_@;+rl^@_&H!f%8m<`9FdwmWnM)rqe9358T8Id-vQeNVfFcceu^AcbMkDPBQv?CvUT^9G)5wAhknrLbP geS=-^aOp1y?@TnG({eVz5%2R^7iJMM^`~>d|5;eh6951J literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..3a4ec7d7c95e351d1c600c1c45fd89abeded0a7c GIT binary patch literal 7510 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 T3W+sB9!w&Y592;D89Dz080|`? literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.class new file mode 100644 index 0000000000000000000000000000000000000000..9c4ca7498fcd51cb13cab9dbea2319b4b57443c7 GIT binary patch literal 1211 zcmbtTYi|-k6g>m9%c}*UV(X)dPoTDIOiVO1P4IWW=Z<5G|?FS zVEpWlGTw!C3pFN2KkUrhyZ4?s=giFaA78!#sNr4$5kxJ-(pbPE!?PFS*i^uSdeQHTO(^vfNc~wO!Y`HxT|GKV(R9V_ezsj2JOw8i#z$ZO_Po zy~mHMal{#-Bd?P}5-AJmG?uW;uryajB|TvnZkML*1Fm}ZLFZ6(O||@c%0iYQJ3*LK zwrGFVizCOd*qgA6V}&86_)sKKzy%AdX{_PmT-OwzTDGNp}^a8{v?QWJ`N)S3H-#bNTs;zUALH6zL5WK;ZO6{C7n26?$F~tRqJ2tGGsE zkur}c$7s$}K7i${&qze@4Vib7;_Ec}t-vyF(3qZ<;wElUL=Pc^*`&y?i{{RqZOzZL zM#I)SVe34D{GS-C&cI|sm^GA0P?2_Sl8^|^$;v03uWWn-d++nXsRnC;P?%N)2vi@~ jQ>N;vK0mWgYATdjU=(Bn?>2CkQobRZG&5|`7=is0`1Km5 literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.class new file mode 100644 index 0000000000000000000000000000000000000000..710147d02d9dddcd3db7f39bea22e5543d2c04b7 GIT binary patch literal 1257 zcmbVLT~8B16g|@xx0GUQttg=42Vz@nAwdu&mXC3~gtc?JNoZN)tb# z55{MIl=04XDO-F|UuNdsx%bRDXJ&r>`t}{bDi(*4K+-`fiy@q2Sbrgo*LB@!-|es7 zKIQeMP@e1QHkZnM(-pp}d0-C0<)(V4-Iu({wcW50LxvmU%UWPWmmya@njs;*5o9ppAe+S~&NGY-R52tQ!Z6H~dfhFq8tzW*P}I#*`Oj1ad0cQ%$l@Z3 z4EYc%*29oCdXG4%21BtFN9bjV)fmH2)9Og$3Q6erx+8oeMK_HJ>Q}PfRJ<)5Tpc)| z*KY}2&teKU7|tzACCv&+D3$je++ujvH}l_Y-B~4<7fyX0-jfZ*P2dZL#X(z_W2Qus zVwuRfl_m0-eBJf+k@V=cJciVo_6ROll}c;}?V9jk^ID5Cg{rRe);{;8jiY4B97wv; zjp~0Y8h9wgW+cXj!J%lIJf_2=d+)!R@;<}ZAfw~X39I~;FbCS(^;fjkcAx zQKB&$t;?uT#4y{3nWM$k2&48}tSmqP|WkSUButI4x~6EVQ? zKHwDQNSc+jOMnTQGnFs6R+;?-_Q5j4sTwCl80%F<2vi>#HBZ$&r8YB7x?IW((SIdR VdoJF`14>zI7HDR8NMizzegi0AB-sD} literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.class new file mode 100644 index 0000000000000000000000000000000000000000..9457326cf805262c654ee557e0e499d04136d303 GIT binary patch literal 1206 zcmbVLU2hUW6g{&*7bq=AE4F@A>nBj#H6|t+nkLm++l016lvgvbBTNf3&32Zg|4I{$ z(FfzRKgxI))-79OqCPMmmwV4W=gi*kKfZhgu!#p5SV-7N=8(b^!`6;C-O?SSJtsQ7 zdCEISLb;Bs2V5%W^@;Et&3)4w9e34R?dr8$*^G| zd;GYTMw-Er?liKPwvo$W2D1z^u`Gr}R~Ux7<#BbNtFCjE+d1S`F*>bQ+ zE!O&gk^`7puaaFg@)yxJSiCc*OAWIe@>kEogJ)rLT+}h+Q?5Cz7;pDy0oKQY!*3 z*86(U)<@0@p@heca7jkCseP{_w&lpUn8>x@W5Y6yPYHxgKPwm^+ON@MSy)1n&ew5+ z)@6!3p%|k*U;O}9v_B(b;T!VrM#(p64N8Go+@du%PQ`89p&Ly>1hY!FATLq8aK3bI zqBIee-iu1(4CemCU||9#AHgi5OoEoFbAyCfv}dcIaIw1b5$t`y2WJ}W5khI46(Uf6 lXitT*$NGZE5~-9U5Jm6!st*J~KSDPK+`3X!6vPVRT4Eb(wI&eLg1_d%5AdVJM8wBt=3eG7 zbLalJ-T(|x^N=Hqr{XeFfl-=$)Mm(oqmUsB)rrTFtG2Ts+tyHNIhtPt-vb+)gc=*8ZkMb?aLwWn+lW(>?u865;vZlgA37FKM*OEp#AU?mQYyn- zl~@oB<`w0;BrndpV=fx@2L5LUX%>OMaf z5o%X+YRy(`*Xs)${Kb#x}!8s0BX&>OIsMQvqKWdv;lJOdE{BC`Mh literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/AppConfig.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/AppConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..38fd95425675c302cf0f75998e9b02b390ca30f8 GIT binary patch literal 433 zcma)2yH3L}6g}=M5K3FRF|q*z9uO==l|m6>h(H3A6*;aO+@^NrIy^p$35kIZ;G+<> zL7B0obI*NP$LD^1e|!Q+FpSYaGeWD2HaY}@IeTKXWU8Py`CM>I&^ePz+A%?M{~(L7 zL2!H}UN1Da+R&OkoHM=>ZAFOIxDg5BFnS-BLMTuUdJD5Wj4 zQY)u;nrc(f^~y+9%nVzKXKfbVjukJKvU-&M`l?KLIKmzxw@QDAyKb2dH(f?U4zv&7 fLEc=Uf!LvMd)#u^4ZQ8*cIfv4_Av+(1UtYF9EEUj literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..92e3db95bbe131fc9c9f85b022dff59fab02018d GIT binary patch literal 1621 zcmcIl+foxj5IrLayM{G{h)CtS-B$*$I~)@jraQ zDy#GZ{3y$w1cOxhUL>g_wgldKpsh#fIR%2pYUp zeJ^X8q4%K|c=|CzPbOQoVKZ1eZSQv6}5~mnu4xR%;Quuzj zDeEQWHM}5VsAS&$C%iV!Q0?XlD>lwC#Fk4gS;I87{F01|RN&b{v9MCGahXb6T3(qi zEx88sRR`A$;9t2eO1&1=BO5mu7!`jISqCG(?|3dnU8=||-Ewdnqjb@2pm`8s9s_=iY`<1Nb+dQ{$|DVW04i-0R9UmHb!#1nF2 z4u2#^N2p)8nYMOtH1-w!m7er~yMyC)$3E>=V(GJPC7wR-R(jJH+=`XHMC(^}_g*JE z6WhVfLGT@nz1quQ7#4Z%LqEmmB-=S8DQ-UsNMRlW?aYQc(RVYWJn)qM6vSmZ8D}&w PX!GrMmJW7cJp+CM;bG67 literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.class new file mode 100644 index 0000000000000000000000000000000000000000..19042edd3fce17aa1ca27d65588af691ff974aff GIT binary patch literal 1129 zcmbVL+invv5Is(F-E7jbO$#(!3cMsKV&4!VRi#qOO$nkDgbGS0Hhb zc;Ey2Q5EAPY*7mmjrPGav&UyV$H&JfKYjw(#vKnCEEJISkwc#0{xk9Mv5u@ZVH&-C z#G^x@24S4&K2JjNArkMc)GFLHMw=Zy5DZ={Y*&m%Jf@`9Xm;N5BOWGP#o@j+QpIf# zMU)Ege3aoctepa0kCl}}vHP2sA-^e=v|9`-jq^m!UI|q!7O>>w5(0+J^r-)H=$-M% z>fs;FO5${k2b~LXr2@1W+KqYlHhT=&sf>#qsl?0iurJIT-cLx&pra$6^th2OpB1zA zK#mx;F4pnP50@y%C&;G5F-3R7-}3OXCHdL=XG=FnlVMFx-j($>8nlv5CYJ=>)Hxk?Ec@DsGYG ziu7cN!I_St1&Yg9ruS2$>;SaEg213qz7=&NFtveoGh zT*ej39L6y-T%|b2C~@t|SRd$Px;F;exg7f*NDz_ zDrF{}>J&0u$BkLHkTOQKTp6&GK{TKVyT>gcfJ(brM%Iz{7tb9{qC*Du5c)j z7>FJ%?QE0Lz&qRS?sWC^1mp24!}n$EU;xRmK!=o~uK#K%xGNWrMIo|3GpHj{${*0oH z`lBPK)TGhw#4bE#L#qrgeB@b`A*kiY$e*)HCa*?MNI1}1Bi)lxa=G>zAZ}#S+Gu;N z1y(@%pT-^=+GO~#SHcVHs&LBC{V_F{&lmwiCtpVL=}bz^N{CV@53Z@DqO-fCki2{b z&)TqG#sNGh;pjuuO%0aL#q-Ien#)P(Z$18SJ?GvzB;$F!K<6t-2Tg)|Wm!1B4(57M zf>K+@O4%M_#Ns&qP{v{Q$u>rDE2AoV(9PJ3{1uQ9L@%|bsHlX_|Er$DV4sW-UXtKE zX&7hXhGOI?-A{(~?5IC?hI{iwRvA-Y*R!Yn%;E2=u!-u2S^Ss;qjgOp54VVX2%|Fk z@v?-C9ce9XbV}I1N}WR9Av>%N3}DcPA-+y2;wRP+qa8nzU^%dNuN}uFgv^QAVqwDk z&%qy48!e7j}36EKrDc=0jvphXhoVx5OUi{RYp7fYA*NZo1?ktRZiVL&m+jGkw zPnuJ=ix(z~7p}KCsr`P;p8rHbVl{`?l+Be5yAt8Z9US>fJea)O$GN-PuBDTYhUr5-uni!RH`SL_@e$u=!yNYBJ z2lZX%jLxjD#~IX_-?o*Uu3jCb%ZzhUChp!3(> z>V4o>%!c>q5nT7Z)UD)H9ysVE-SwibwB8adQ9jG`e;qxQ?mDNj=rC@GWGhSxSy$R5fC%lG8Q1x{Z@*g0@zm>h(mk z2%)v>r|7m^!FRwt~_iD=5vag3_caD9xpU()cMT z4Vg5tyZJ#Bl%@kg=|u)yJ9*NiwUhKt;a#$JfmV+bU9f1}uQ z5Wg4iV}sz|Pn0E`ZppWVC%=Wn{2T7!{~aN6i3=Bn50?nBO1L=Asy{$4{b%E^l$A9u zVYg)w`+T=>a1rf9Iv3Hkh@PtMogy|5>S)N_L|(gT$ZbFijouzq?KR>bzf3^LHrPz^^=|fWQ*J!WwimE9KI|fBI}ySfcZ{^h-LJTZ zzs9Ry!6LAqlC6YWaVG_46PQYtPqc9wi( z>~(={71$f>F@rxB;C2E2lKuacd))n&0O>GIe1&GNMJb$Jbbx_+Y{O=>(je18$?V5g kqzE}jhvt^!^OP76_-3`b% zei1)~+L=oI!w=wxay+{UA%)ZdopCbReedPHbMHO(-23w9U%&qWU=Ghxh@nG6Tt@<( z0=--2j%hfiTQOEjTha;yI&-#b2eSelnThojx{%b6($NiFAh#g*?kOu!p3xjV%bV77 z>6VR(qe`Y@$nPy#4Q%Bag_<9z?c%{)K(EN4=y}R3sInB8$xJLZVNL?rJey77EP6Eb z>gYpS;MTu`5jaDS7J{I<7MMZJ7q~)~lvgqQs%N{E4bR+`yUKfRY;uorxG|eVKhA3y z&@qU&1PZ6Je?KCs$;*Q2mL2Jla7R@H(sc)%jhkD>qN-G+m+iu^z~~V=$CPE&JWrNq z#(wxQuPWDCDft+{F-HvtQ4-n(D1&F4{%+e?~t>+QjRo9hPGomHMz1OFpzoD z%4%Z=({xiHAwz+8;}$;Ba9hV6%ur?#TrIF2W67)v#1v5;x03rtDvy(Ue5K*c0Y3Aa z!(9!tIzGlH0yj=bek(haK#5!l+->9KiTP%o=26gaPe&2+0)t1DHgAT_gl3WyZC&jG zms(909Mks?GTfrkEIq8Zk)B1{mCLp5lJp*#C5NJC30bDIZhCe&Z!E@xO`8>7JYCCa zU#m2JelsfDBSk4-XHI9}2*5$4?fTE_TBZ+zvCjTCb7awqn(^(^;T*PN=IbPdN_Tsg zWU#5q56!Tx2GV0Tl52LwW$X1AiSvIkmz$iWH$$s6ww!IbZu>S>GUvLAorm`p-I{3| z(XK2RC7U;$+1zU!?cxMZM14Y_D`z>4t2DKyYMv$M?NFT~?KO2R>=9u;+f{2j)09I{ zP;THUhu6nkdB?US&Fc?bS9(#|NQTng((*9QYb(Y-0s_O~#l=wv$NVS2=Y7r$z6IZj z$=@J;;UI8kS7O})w^i;<_rqegV)qQ;U3fUNb=Bc`+%SduOK%_yW=-?1P!~Be4lo!ecUM3@K z&_j_YlXYtiPiPm*SfO_bOyeOwCt?a2o>*m&YeY>5ZU7!d#=gKh-)Fh~OMbt43SseZ w7aljc7>@!4lIi|^%)P+5kPp$qM;~#YL|{)Nu#ly%Isb;=6vsJw{B39QZyZC%B>(^b literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.class new file mode 100644 index 0000000000000000000000000000000000000000..af10f020b4520223eb8beb109b71baddd384c26d GIT binary patch literal 3440 zcmb_f-BS}+6#rd9$cIZ!S5f093TlYe6{{5uS|LQGM6d+0XlvXgOIS&EX|h4>$4oo5 zt<{&l)V{P%JL6*?6bmx#wBvLClp6kpPS4qmAquUaz-(^r*>ivA`<$D<{`u`s0A2Xh zgF1nZ)7teOGi{j#rTQ(|t)|B{Bco(E zu^t;d2uj$9O{B;?L(C}UNuxpNWFxf_7=ZA%V5OGY(c9BJ0u`3=7-0m$~){sI_sdhHV8cwhLjugm%`+ zlc!Qsvl?sgCe!xuw?h)%!eL5HJE8)|Un!opz#|flA|l`)v#g1PrCOz;z|Oc?$STDN z&dt$+n%AzHg>i*Bl+#rR`6B4zvZh?kQ-Ow%4WZfch>TMhL z!tFy0Cp|bN;WS&^vIH9i>IV9}c!%2Df&KfvhzkVDHy-@`aJqc=%O|&QKDm950wd7R zS_}vz7ZbiD&n!&7g%q~J$5sqVNU#Z>O1JdloULKI7v}}Gl)w0qlq`S$ZL_26+?|`{ zThkSFy^y@PAkbd^?&tE2+48M>&2wMe$%mY|KJNH`E*%n9#EZHI4$=A75G@SeV-j zDQ&}aALvZyDs}A&G2!bPJ|2{v%bRDlfjk~r-dUciPxFo(=ueoXLRveibB+aSfcNoT zKtSp>$lLb7*C+^q=ZEKsb}Fa!^d#Xq5#+lRKr3B??3HdlbW@YLIom5uDto2r00cJj zy@&RG;#YBrP*SwpK`a0Hr_eZq*B?W^PdkA$eKy$dsDIFfC4)Mf{~)b=!Y_;PEY>}S zVE(#i_%)(P4i`pk9kN7m3yglo&|^4EpDg@*1uQs=&Gj?b5&jjS8N5M@GJ^v%I5>~J z(Y9>^>Jh{$(nrQ7G+{F~(Eq%=Aat8g;7V0Usv@L$UP#9bI{!!3R(MFc?Iddl$!Z~4 ztt53PN!x{OM4y+}T$MMD9LZs$6f%xITp{0MJ{_fDfD9IT&4C>(T8LwP_vwW;8< zvBPv46ecG${EZXTWz#rCowuB%Zg8SBxlsrJMS6}>#)j!>r^m9vOQ>;Iz|WI~oaXqQ zfq?Z5e?W}5rr=79)K9^a7;#Tg<+0t*rnMK7xLP@!wL=a>f2Xr@luq}^y>j0poOuYB zO*>0i@5)2+uub#WwD$;^X(KkxKZ}bG(a2741j)8({L`QjA+n$oUNYOU;~K6LUmZTe P$F%a8Y@zG>C-D6X1!;)D literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.class new file mode 100644 index 0000000000000000000000000000000000000000..5f378ee192380d1077ab4c22da251bcda02c9fa7 GIT binary patch literal 261 zcma)%O$x$54256(s|tePC3NGgMG&kaE(AdjAmcO?b!enbMX%<<19&L0UD&m&7ZUjL zz4zz&0x(B6K#ee2^J6JaB{kJ_2(7zt5}DY`(R#yo$5%Z`Vj>k2+Kd+xaexM)f3`Pk zyiH=WJ)YSqp%b(2*k+OtX2a2@!v6bU$64-msWK%DD%e`}x>u`tM(FM(PaUsLo(a95 c(71GI5eW67AW$p(nrNZ)49YAn?Q)OK7qQ?=asU7T literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto$ResultDtoBuilder.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto$ResultDtoBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..68d2467d981861f5d5f69c6491562331fdeabaa5 GIT binary patch literal 2521 zcmb_e>vGdZ6#h0hIj$ha0pc_iCMNptzOwuM!gEKh5U?vRV2eXzJM_AT+q*Wkq z(#I$>VPIf}2k1j}de#z-6L}c7rVX$)c5z=(-aj4@ailPAAZ_9Ym<$ifVtZNIO8QoJ_i~BbP2tt8 zhAV5_wZvOnw3Q=0tFEN=L{xTJ6_|J z&s*Y+^qZE}u*zMvqK@aVi3wyF#+{%V+O`PDnr-MWSrd~u$}nQfx?spwcC)K04cxIm zZemJn87(s94PjuK;chpd|D0JGIn3yKo?uwZ50u76GP0zku%5y)F=n=&Q$Wj~3nmC8^3{x%M6ea1|Ts?EtChY>nJw@ew&FYfGEE>)dzrdn_DLn+_qZRsKJTiT0!*)L>*54%lsGO{h&-f6VnMP{OCo z9_Kd>+6(VP@j?3-t?8rAu#o>AWMU$v2!DK=odX_^cqi+c~+Ph!+aVG z_|3rY`j{;;%?I^tj6y7PukMOqwjyOSY!{R1_ek~_ zKHYAMKI$)e^!rST7^1{ox9TWC)iGB+I+`Vy2Z5k&%&q zcTkinjlC4zSHK88=>h`71$rB!HBal1z6TOAx&ab0n)mx;2HDdMNEij$S@e}@$=nC9 zcOB?CdTK|Y5H8YFJJ%+caGBQLFc^h)1;ZVnA_3}vjk$~O>G2nBhp->&c%%|Tk#d}y zr5$Lr%s%`HTutE5yvLz|@uND$`1)mU{FAtrz^C|+42&PuJjS0);7|3(KQ=Ia#9xd* zmB9Z6*QunM$fcOG+-KyT&+QNeJ5;6}N@WMfKH$gxghbrt`f_^%H)HG)wW02TX^s04 zKlMU?!dc*3;M0A;NAYW?KW^=z<90`u!yQ^v-$c8<7Tw;GxgZy6XOHCSQj0b6aQ(55P)z~L`kHTQw;#V@KnhW>3HQmncf8ildx(+Ji)I}AB(^WErpig7R^&IbG`-LoN6IwF0?RYPG~KR zex=hs+@>`JMr#^(Xnoqw!d*<$j`HKG>bw}&RlO9|A4GMOr)q1|B`~B>Xg&@aCOby& zefq4xACS#p2D4O9H8)LGJ?S49e|w7J7xLMKR?cTKJj7fl)1o-lOw#-YU8S98kc}A_ z?SKx0i$H@YT>+H)d7k( KHulnZjLaW;Z92{X literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..eb16b440fa941b8c7c8f4a667d1587133d6de9ad GIT binary patch literal 3588 zcmcImYg5!#7=8{5tU%OdD^#!-FR06cq*iNN7pZ_3u*;=_wAzbivxhaBO?Hy3b^k%X z^uM$-MQuCNubuHnb^4qnyJQiaMcNNZPTuogp7UPM%YXj(3&1Qk)94bIU674yt{J$V z-df$CGt9@*DeD#6Eg7~hpPI53Sgxac(yzIWDfQW!bvs=9vLD^(NuyUo8V3Xh9~n;! z-8P(xKIgi&G#r8S6T_~{l}&-++`aMQD;$M>^kX0mO~XMP61d!g(g~rFoL=-aF$42ucG@kg>lm}s`5wId#pbs zkG{=F@W=388t-fP03TB9wKC1%Tz3L1Xz)mWZ0SL+6GO^>g*QNIS3frAmK$?*%Pvc= zFn|*{nZ_v%V>m5vp+ksC9Rl4YY81GV^ncTlugBkxs?f)fp4E`UxImxfuhmVHv1XVP z@rqM4cu#T*i*oN9cOdbgTNN;Rn)tTDA2@ zEL5L$X|$O1Tl`=3+?w=)MnRw_EIm44&8j#|Hjj0YT3_eRRA;&t9n-Ee8w4)xNnmVS z@WArQ^8MvI#Uf$2PAd+LT=DJeypV{_o->0njylBQqUFftdbK3Ib%V}Ufpbm6-Y`5% z{l~;!FKjOY)B8b8t7RiF1P)Lg+o(@-^LtYm3AXoNMrODjbfeY;zOaAw(@d5b+Q=-) z;GtVyHB@tA`Y{_{POo2nH4HQR3M;L3+;{dEjoq9Volc#PnR&yo?65#8`#M6$_-Xa; z|G|YF+y77fr*Wh$Za3MXw%OZkvn+w&p~ciW7B{&?=gBT@E?czSYRP@9-;|E@j6jy@ zDmAQEoBg$Yw8ssA-7YsC_O`ac(KMb&)z=Pgm458Ox!uE((9km3zvk9GQ{J#t_sAsf zMN_K53uucD&oFNrzAt(3fyAs#qa<*#qmCXo(`HjmgEMAwFOnQiu@25>_HteUyOCAH zk$;!@H^6U!&$LfsR!u?@vuf6bU(Ppl;R@GXmSl-uVTav zA`XQW;#2~$AmU<-cw$$?CyDqJm5mYcX(Gynh?`g<#7&&MR1$!eKqo?UJt9OGIruiW z*RW1J%9peJ6a4n%f5Vw=Z}o=6!=Wvuj|{j&PoQNXqAZcJ!AJ3b%`b1C^x3Pb^E*`! zzJ+RDt9Ma7wNrKWEmVJlyK$(J4mrP5^-xEuBR}8U;TYcjSry9XKk*bQ8AtQa@kxGS z3up3^Tgd0nZDA@u(!GWAzj8N9+b9qNq1}s-R2&G(#}%els-#qgjfO>~s$*1L8) literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..76961ba093fb8ed5c83dc0016a28432fcbea24e2 GIT binary patch literal 6727 zcmcgwiF+GI75|N6du?SCE3%U`4y0*u6Iph=xhMgZ6kAQ|Byw%y#3l)}tfaBL@p{+Y zT{%f9Pzv;*q)_g1L%HulqXd_ha6>8AKg7qIJ*2g~mQ^bs-zR-DJI6bJ^WK|z?Y~Q} z0vN`B64bQF*nn1so+5WLre$YLV@4~MEY;CWgW*WBKWkb=#a^&9qc~%!^Zb%&%_*07 zPRY}9WyPLJvt;FYIst}u84@}eI`bvlG3T{MIK#nJ2--YfvNY!kQ56CxnP7{Rjvhu$f^rN}k&7s$rOptAi_QuB7P&PU4O_&cdY6iUgab&k@zu@(hnABNeSB9Fi;A zGPN5Tk%X+(g1E5O7KTzXJIb%zXXYK#QYyWNM%4TqHwsEoH*>14)UpYb^8A->Otz3R z!pPk+dW3~rg=Z6li*_-(UB+$RsBO(8(CUrul<_7px>YSXv!+G96{bDSA1-m*q0McG zi_N#o*e#ZAG{w4uS+l4aV*E}SZ$TeJo9h{ZAHm|Hx~M9;Y7~|6+(n*u(h~MC9Ioqb zfx|%cJJK7D@C=NXZz$AVa*CG-2}|&Cr~q3>%XDQ=J%sB4NLb131VqT(SCk z?ay$VIZO(>`c9Ved2ZXIs!`Cn#V}FVzXfxi6j09E+)`YlDgH)fd=-jmA^u?*Z^hlq z_#Rg^y=3uQ$agtKI+#cBb_ws0k;Xj?xw@Y6&vngl@J2~WI-4Ful)SES!|}BB0OMg= zEMnMf*I4uV8s}CZL|~6-YI!dr2s>6km%FFIlT$~H;hgPQYTjAS#RsP!UAc&AM{rce zy?7TXHB;lNrj@GkRAb?wd8H(bEIVzJjz}9FxR1`6l1jyJ9QR8Ym2mpEeWQ=2iQrH5w<~3(n*BP!hm|N@l`IecK06v7%GA2cJv0Y@8z%#Bp zyfPt%vzU@FE#o~n$1v8U6~o1d2o~KzcnPs&crrR-tXa}DeTbHeN5HwjFchWo>Y~JO z9{nA7kb%)@dOuVNIT?8rC=X=4$qgaXO_1Tg=YOFkn)W zs!|-cG%5)uREJWxkalDx;mEpdbrX^755tmR%WzO4@vA3x#58rT8Vu5+s+aiq48ykM z`S6+cSGf9Ll5rUn8oC2%GZrt1yrJqO=(eyKgKhm&R6S+P0u{PlStVH#pQ)iM%LbOxEPmj#;J7bzhH5{F-Ql#sDT2uY;i3-!zOb(C{;q0k7u+T>55-05q4!dQ&ZIg!=P z`J6eY+|Lb8S3h1L#d>sgK37otLYV41#86xps)9h=V4as12dT|12pI~I9$5o3Zax}v;&6?&G$`)9MT8H(Z(elryTXPihV) zH_Ai(yy&r4;8pXWaRJBO#vesFx5=Vbn_Wr|T!ZL_b2k{nRpQ+m}T13+lDt z1LE5q?Zu&V8F$>oT~%JYT$%O|&%LxciS6je?MP8i!5~R3WZ35M@ns;6_y`^+Sw2d0 zLYI%>3F@^m8U~)Ezfj;QY8gFtK25K-LfkPTvVq#J)OGAl4P3*%)ZjH7dXa|0T5oYV z?)H{eICi;q6t?PeE!pC7>};XoI1R+{37QgIp8?`6;)qgTaGfCH!bm*>sh6;?jJMsy z5$YW(2uVDDxhD2tv zjAJ)(s*JG^dC#ANA%AYgXYg59RElFhv7^4Iv4K<>?=Iua3JS@PV6lo|F?<1EbOoc# z;?r96X$`%M^XIPPz1e}p`)=T&GI-F@f{s2t(0T(4Wh@Rp47Jxc6zZ3 zeT0ia><-B=gfHVOw5|ht@Kt<`c(>x~^xcAQxG;!O@0;}fE&6|({zr-GO)~Wh_zwL= z@B8>6;oj^064xqYWxsa=FZ;dU;J5e-(deaJ$MILZMsl5^_V2{?b^H_m#*Y61%qxY4 literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken$RefreshTokenBuilder.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken$RefreshTokenBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..d4bcc2c5e8940f28ece0671ea5a190bbf4d6c439 GIT binary patch literal 2488 zcmcguYj4v?6g`tRaheS@eFTzcTErN}8M4~MWf)CREoom8nT2BvProH`KPkkZ?gSE}wP&`>>xR{I(m9JLq zCmp5ApDW@#E-)N#^G#7#UW@A&Qm@k}(A?7j!$OiQA-A(PVtM`CDd8ASJ`1VRZL#QW z%Rn|=ao_Wl=2}uTQw&$Nn`+a3C_LeFEu0#|U|l(aVYneZ@i=TZg}=<{L4#H;?yhiO znt8l9sMjSitu_w2erem0)cxJ(P4%!smmUeduAHab=WU^dPigO}>29P$Q}a*Kh}}JV zSXN}4rav$&R}Ma3Dso<{-B)a!B6OQioF_wJNriq(%uBN|N0OcEnn4rAV-e^D6;Kx~ z%_^k>?endufI%L&7;g7Gl$^x9uOgjkcI9l;-OQ-d!KPVtIEOzAsNs%EX*=Y_GOxufQ31R)8B{E$q{9Xb99U$Vm*{FpK|v~XHG#|{ zRZP)68Ilp=GRZMSH6k?Ptf|ZY)8!2fGuTUYEP}*T1dj8#Lg=hvEA8PQ#?>_bBm3r$ zJc;?uq*wnKuBGvh?3+LGH|8Jf1IW~ zkEi;61HZ()Go;_}mUlVd<0nJ=0cU~l5tmblv-p*$GWe~>rkkC$GJdCfo`T(`-yfKv U*(hQ2^Z{`XL-eIq#{-=B3J?c=ZU6uP literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.class new file mode 100644 index 0000000000000000000000000000000000000000..2b2c19ba25753baefa1db6812e4dfd060da139ae GIT binary patch literal 1947 zcmb_d*=`d_6g_1--Y~>54uoNc0Et ziI8R_K7fxxoNBk6jxowh@{p?Ab?&*_sq)uv|9t_lfQ1}V0^MhFYsu?q&$ouVwMTYm zQ>w1jbG^3hT7m3@zN5FSjZfNIk?(vNtT#O(EUC2lp8PhOG#-VH+m(J10+|vfkQK<@ zb(Ev;3#8}fTSerN$)O-n-mpK~mTRk?^`gBYJGy`aDCKalghQAVcsa~&pYjw)IbFtW z>;!Ijs&~i4ohqS%!^|}dq+j1bh1zkg)z$h^gqkkl2#yL&XwwUUN`mT@_8pQ599s$5 zqrPl8fzx*7f>NHgwd1KkV1^Exwy$mLz4QYo&{B1zRqxVmcI?qpsibdf*=^ZwC<)64 z+St9VJveIPOw)>{FTq+E z8Y3Gj=R+vd6qR5{c{3L@7Yh(lm~ZoaYK;~ghMQ`Dl@ zWl@t2N6F=|-N_Y95bm(!}a=YZ_^r1fLxV^*RkD}?K_4d=E>-FhofED z_ktMkx4DGEBC!ta9a$#MV+GE)IFsj6uY28>w$F)bPL7)GNo|B(Ve3aOfIxwVtFiKj zfSn+e9Rbk;AW+>!FqD1ql^6OQ`NT23ok~6l*G#ydiSKdrp0L#8%<9Lh5%rj-hjwdH z;J~sB^mA$E$>K_N#e1^o+Cd;`9!`djUnHF6HP2IrX{5{pLp{#W`i1tfXk`i#CvXy{ zcyv0z8RGaR#JFj$r-reM0oVAYGyuTDby}lD zH<-Ce;VnHgT&sqtc6l4c+LdjTYge~X9px=XytM&7=A~!cJF_$0F(p-_CZutb`-5o$ z$~-FbV|iC10=Mw%4;B53qG?33qRJi>@m}Ee7^3nh(e$1~zvGWFMAcEEnLUUy_%o{E huSipqq`X7>E@wh9+{1kunP`R&xHG?v6fhp5@E!fd#Vh~- literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..bf1935f35967847965f8605635dbfd72678fdd90 GIT binary patch literal 1065 zcmb_bO>Yx15Sa=0=)7KrIPElnn zmwLn{vzU4#T*)S)~{CR zFo0)NwkbDMZfCf%Jgo?`!Q#v%k$Jf^{hrRmxTI!QuewBi5d9_bmA=RocixcrZ)Z)d zd8V^8lS_Vi^Z(6IqZVt@`;HXf>}zjQ+!d0@Kdtv_Vco6DrvYsKQHUi^p>}0~d=@3aE(@zMz%qt)P@_-fEBtm=~LQn%@$1sf$msXCCpt|yNX$-H1`O5pVm3H3O%4} S10KSo+5UKzu0^Sy0Q?36pile& literal 0 HcmV?d00001 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse$TokenResponseBuilder.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse$TokenResponseBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..82611a559fa5115696f01271b9360040d9c8f48c GIT binary patch literal 1826 zcmcIl>rN9v6#k|_x0E6kQN%l1u?1zl->N23gJ|j{KRNQ?zg5V`!o= z(ZmPvp^Rs`6Wf&<8J4k0Rf(*m+CDFR4JflK)hgw_U-jwCwJ(DS>}Qy)^DR+Q(&OftZ#Ib1hD)OvX5w`F;TR4t zH>L6G;(^@uwOwdLLyQiR-yT9IL}q-ug;tn5qixLL1?aFvsy zl~x`PDm?VMV(o9r&YsS_pv8%Qu8Sdu` z|017cC}mVs!weGy+E>{O=@r!sJ+bJwKXFfk%v*87cr3J8QkohxyP%ZOCghDMLpp`C z3>SK}dTyO2%XhWdZB`gdVm=KQ=N(+I#eR_?x9giPw<#ZPPF8IJu~ltyM0E~aT(CTMmr+G$?Wd4O(7Iw$EIq(wuQ-VPGw z5|>%d>}RkKL?kdpyA=i;9HQL5Mdo%=-Fdm{Ux4n!gy zC~F|{?-)rBS+p#U6FEW_PtcmyIQg5wQ^*m9HFy;RZO1ZSG5TWmBb?6|i?Xz3WVA)F!o1oAyT7uvj0%+i@63+HIf;S$}(Nj61)fy)@i K6fRM?V{ zk6C0GmiH5FRaZ7tteT;$YHh5riYPMhdSJTzgNUt+K6OLhA&IfkT#SIpHs*mXWZsMW=YpK|2}V zSJp0r*@c%yh34AQR>vo%O!dyq41<4Qk|>ZbRNBXhn}6ccY4CPsYVnfAxd^pA4-?p=VOafG5X$?e|W?z8Xvyr2KR{NpbGakxP=p;6|v@pO1xb56)oq zB`xa;!z#t1uP;eS+ElblF(l4{!$qnnfjmUX;%OBf=wvu6_?qC(y7wW&E4E`0o!bnn zM>|a@nbb@!o=%OgWReq^WOj08 ze)d6fhT-mElx~1X4si)l6@BPuIC0Og40n_v7Wuz$(Od|F7*e3A_!z?sbBECMK(?qS zUTa_shLgKN%Fq>Q7>KuUTfrwP?%-2~@TH4($;G);CO)5H`zj`pVn`g7YELmgF=x0_u`Net z=6uyKy}b93$bAQpy|paoUohsQlixi+TEVo68O$;)zORcITJ?Hnt$K^^n(zIp`IU+c zvJ5RAvkdp&4X;Gu0&)tzmeIR-z|Vb8W@wH?a}3>?D&@2?&l!%9H+kH$Y*&{_iUQzv z+7>0vsmP+XCiF6YVv9{J&vnbu)^u6qw>5q5fR@wEDyOJu*^(26tG(Y-As_M?9&}}) zAL24nJp4g<>km{71veb0deFs}-j^Xrynvr9+tY@{XR75q7xOx4PLibUf^Oz?VaWH| zV!&NDh>G#!ctNePp}RZvs=ICr<2#Rn-5#Rj^?rd$nW!t?l$EMp$G~u!KQ$b;J`plx zjx1gM`9_J9WIdI2sc)OsD>Wv<)0|{4&gs$<{|bnx>v z2TxgB4P3kuQwmaG46k0`=3)G*XoS!xFu<(zs0jZyY?N){vS*TI`U0HjPd6 z;afbSaRp#)2zaZNzTd$5cc4tm$-oXy&Cnz~_yS$O<6IL)0^Nas(Do-TE&Yls&k^~% zJFtTR+IaIvwAIesrD@#`xher@ytjidM_OKByt`!wQ#+XZ*%Nw|o;RV=vl~I2BN=;0 zr3=)}T*NTVCUFH>Ttg8zU?2h$QP?!s>BOinH}WXZ8&IKLoOBDKgf*Hq5!xe~b0X(5 zX0c8P0oo%=11Byv=u8;RFEOm(miHSx8^R{7wov~^1EU0J(qWLmi^gVBP=Z!G TOG?b z{qDWry>sWo|Gx7p08RLpA3l_5n5Ux@^93r0%)_P;HRBOuXJ|-bMBv64xSgc`*j=S+Gfn_%bM*RJrDFSP13It{R5~#g# zO2aY%-+^X7mg8Ox_vyGF)dKg<)sch0Lypo-3kG9mI5=qXZL96jTQvuoGt!S3VP|yM zwT++*JJRI>&s!^Xd>X45kflqvBqE(a8P*8YrN>@QpO{RazEnN^ z%*E;B6Y29;tEVQ;O}}tz>e=!1izm~k-<~>ia_Y>bh6>c6RzsbRdaNZ0<_Z*;XT=9> zfyIT6$a=K4&yNNe8XnNG9uEpEnrknELo>Bd(u$H80{7L_-arVhY0i8ndQxV7G9jJz zem^#1lZMSYK8J?|?(pL2MZCASy;Y#hwKFiNmQss^yAh9Q*rMZ6JXR#!d9?zi;izfF z1e%K&Ss*zV{~~72n4qC~7T8wPm42pMNd+3wreiC%5xbR0BxQdSbF3k&(z8r0CcSPU z-j8;4XxO2n6J4{CHaMDaC2hEi3F64O+hfIKmlcg#3;^@DLxI?-a(x$LTGLV6rvUBI z5mbQu^2o5|n672V{Y=VU9evnKenzAlv?6h)4;e@&W*uFGol4q19s6;B<|VjiujLL3 ztk0TPNc>w{s04jc$Cp&lwXhueb8B&!3pOcrtzJ^2ZX}j}XEd`gKSQ z%yyJl4g|^*$xtFYUR={&ghGnlgE}k>k(I-yLtYB3Do){D-sc$>R3M6&hPVzJ!vfVs zB06gWBXC$c*1%}SYmFJN6>)P>a?BFNIv0hbBLT9j)XO{;qFaIHj>8zya74!_o)B1i zQ?z84=LW?I+25j)hU_v5v!-Rxbb|8nq>P7)Q#nC(U%`_azN+IX91~c0qdE%plX|<% z(Wq_q&rR@b8fy5Oz>>L}GoUJP94B-5Ipzt^TQ3-en~npFTT2J@!VnhH5y?(mfXl>r2~_upCx7rQw?bEAu6)JsOn}Guq@t zk};-gZe1C^O@dL`^h>W#kBz5aJ64?@JDom%Y=-U^o-U}{&+7QDD%$f#OouAI_sLFB zIRnaQNhVx7mQ!AiF}$GRMIGZfE3h$l2H*TN&dX|Z9NTH(>Cd9k-rd*K+1`4vXXlQ# zZh_66wFX?y%ud+&nuj?hLRW+mnpDEcxy6!&bryMP*YwZo6JZ zXEs%JM(tS0K4ff@amhL_`?+Olc1vH84I478#jxs}1{^acM{MVi5t3#+VGLw&8fH#| z(Pu`Jk|IkFs{!4->gdpj+rX;N=H*FJG((L$JFU3vPR2sg=`o4XYhl7>w9j-b^`4!a z=MJh@JuuLR+RLh!ot`{ZMRs=eV90NQ( zGL<5~j8pj&bLNy49h~lx?x5YzicFumUN5|8M)e*!;K;;aR(BPH>_BmF@lU_zi7V4s1;(jj7drS2=vpwo{J&aakIGVk0_=9%R3CpdP zqKM8)+RF~VK_Ovg7G)-TA4l5m=&&?e*fZF1mel}Ou$S@iw&TMx)i*=nr+k~w=g+)Z zUOPDcImZ=zPVo0;0#|s^p-a4FZ}F4CH^FCV-F1jJy?s~tucUxiinsZ%XBOZZu6y6v zJaI;C?xJpmVDS}bbyu+@IAz4Z5!P|?U&a33x{d8OnUAGadu?1_;!g+k0iqLSJ{g(BH?e zvtAGIsbZzoS8CT_<{g*e!={4pX*uU;_>IRj<&sjt7v+xM=4AexzEs}qo_3730XfG3zW@LL literal 0 HcmV?d00001 diff --git a/out/production/resources/application.yml b/out/production/resources/application.yml new file mode 100644 index 0000000..52af950 --- /dev/null +++ b/out/production/resources/application.yml @@ -0,0 +1,96 @@ + +server: + port: 8080 + +spring: + config: + import: optional:file:.env[.properties] + datasource: + url: ${SPRING_DATASOURCE_URL} + username: ${SPRING_DATASOURCE_USERNAME} + password: ${SPRING_DATASOURCE_PASSWORD} + driver-class-name: org.postgresql.Driver + + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + + security: + user: + name: admin + password: admin123 + oauth2: + client: + registration: + google: + client-id: ${OAUTH_GOOGLE_CLIENT_ID} + client-secret: ${OAUTH_GOOGLE_CLIENT_SECRET} + scope: + - email + - profile + redirect-uri: ${OAUTH_GOOGLE_REDIRECT_URI} + kakao: + client-id: ${OAUTH_KAKAO_CLIENT_ID} + client-secret: ${OAUTH_KAKAO_CLIENT_SECRET} + scope: + - profile_nickname + authorization-grant-type: authorization_code + redirect-uri: ${OAUTH_KAKAO_REDIRECT_URI} + client-name: Kakao + client-authentication-method: client_secret_post + naver: + client-id: ${OAUTH_NAVER_CLIENT_ID} + client-secret: ${OAUTH_NAVER_CLIENT_SECRET} + scope: + - name + authorization-grant-type: authorization_code + redirect-uri: ${OAUTH_NAVER_REDIRECT_URI} + client-name: Naver + + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id + naver: + authorization-uri: https://nid.naver.com/oauth2.0/authorize + token-uri: https://nid.naver.com/oauth2.0/token + user-info-uri: https://openapi.naver.com/v1/nid/me + user-name-attribute: response + +logging: + level: + org: + hibernate: + SQL: DEBUG + type: + descriptor: + sql: TRACE + +jwt: + secret: ${JWT_SECRET} + redirect: ${JWT_REDIRECT_URI} + access-token: + expiration-time: ${ACCESS_TOKEN_EXPIRATION_TIME} + refresh-token: + expiration-time: ${REFRESH_TOKEN_EXPIRATION_TIME} + +springdoc: + swagger-ui: + path: /swagger-ui.html + +api: + public: + hospital-key: Wjn354cR5FyaLyi8ljwEFz3haoZfNnwwkPqzKkSorTG/ekqLLvQ7zUv1aZdZ4axe4y0UDDq7mjEnNJPYyC6CUw== + kakao: + rest-key: 63e456faac2c82ccf10db8911595efc4 + +ai: + api: + key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx + url: https://api.openai.com/v1/chat/completions diff --git a/out/test/classes/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.class b/out/test/classes/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.class new file mode 100644 index 0000000000000000000000000000000000000000..bd5d1ca207bc856a40619aa6778493482e1f2967 GIT binary patch literal 4670 zcmbtX`F9i775<(qBUu)kSd3W`0VbGr0Gl+}KoGWU3(Jy`gjv!=9m`{TY8NlEGYL;nt3)NdE-dz936?S}$*8c-jj;HZla;q|s(IMGS@cQSzvm8kCeCp+_AOWkty7lBNqoYLAo_bh%H-3=2VJ zq(x3kVO8u&NLLIkQVi02GiqGe z;>9!A%22z|jSQ82lF_ND!;)relk(Do@0)?%9%+qsQhvVN3ggWP#L zz1W4_#9!>`VOV89EE^7u+s<&4?eStS_EBu4)xJK7-091zn(nP15W?nMqZ{=MyKGAO zM?_5)yVH_?>$&q={j*bJvmafb8=rhI{i%QM(&WR-*DCQWzT(1jUK~IJdCRnd?M#Mp zNz-`vY_(If^t5T7BirT1L595xC2`@#gIjkR{O8VdB8RBiHD{8{n@F3X-m zrf;K-vzI34u3Yv%oEU#_^WN;O*XPE@XWyf#+y2?H3$s`5&Au}}_x{-2#XGZ?#%OJW ze~a$HF|@l7_M!uk$5G7#F)TeR8~rV!obHfxUF?$>I!oB;g%V~dtwf+(H#9M2_L3J#e2u}O_A<2DO=g8|vBipK>=o3E3=gGdw-+f; z@-9jBOR2#wE9EGvRul&kX-V*FAF-o!AK$*YIY+{=%r-wU{frZa6@6JE|AB9&7oMZ zDM5Y+xiMTseqRB3Din={dqeIj?tzg1cM_CR|eYNt;1-J(_?$}xYLVU2xf zA@Ns=h}RVm|3(q<`W*3yI3j6JB>w6=@kpR67_$?9eV%xMC`bI8^Te~_LgE*Sh}Rbo z|2EZ~LW}Qj@E<#wcp~93DtojpnWJ%$%0GWEgul$NKG4(@jK`CSXnQb{44&!?#R7>? zG?Gk&cRPA>sv6zs6?DI6Z&ZdYcD_w%x+bj4K(}3zjpW~0Mls|esY}*nx^DxDq8g%+ zyY-zuP1N*I%@50p6v+&AOIm_o=jQF764SX_ zV@*1ZewoTnYk0ns`hR*Vf-fHt(-{-Z>!AFVNo~@Qtn1W= zwVMl8hgCb_(P7CVUGvD!gEm)Ncf(<#vN&!~Q`jL6Ticd(NJhWf(<$;Uk9t|+P!Xrz zP&6_cG0t@>^MpzsyNxcRDuepR{56&{DV?|8^xHs&dU0VOGt4__fj17k;Uq>I9wUxw zJ(AW~M$JB>#O9|*MJnFe+C6_fmpd$UQ*M^W0&$sLY!;ycv6HOMPd|3nD3~v&uK!>v zZGA#`;^ytln`MTT)Rx)X=H1+_49}JhP*pVq4nQrFVo;ke#Ld3?D9u|Y*!ma~aBk`cu*rdj^6K)xp|ZMs3bhkhx06rraNy)k2);W0vcC+UU^#zntaetH&)|>~pZxp! z&S?aEt6shQ^Po|C7gH1R@qZZWQ1bsZEP>Wvd zfPtNOg+6A^W4EbcJsD&V>Tnf5qtRv@!N)Xma?LEQ`DhY9Crh|7f?v?cK@_=#xWT#X z+Pv%<&Ha*Q*P;ANEOnvMh2>vjmkVp@uj(II{7+i&D_TI|KS_^Y(|-r9)9-J{nhw(~ zrwE;UU=ue&Yz7Gjw);-{PWxV%#*0%B`LtwC=Y8fg8P~ysdQ{Oz(nd7c{I|){gonaX zLisH{e@7TsL->@2H%deISrMf8B!CeHq`` zJoIQu=yrk*6ZCO{jx7xOHbLJh4SmjnKJRcLEoK+ zK0`6beO<$y`lbc_9p78NOTM?K@jc7i-!BQCBJdtmV}PP|&<0-fcyCWr2+t6BHvf5b z;QuOo8>aDtTM&3WFcIa3LPgbY!)`@7wi&M>C;fjCIDP!m<#iz)Z&?#X=^k%yHyRKhIMcaVxbN zi^3KRYNu4ipc64&12Hv zxGI&EnV85z(&K%lwDnC>ZY$6x!D&frAP-0r8uxw>D()(;{r$`d register(@RequestBody UserRegisterRequest request) { - try { - User user = userService.register(request); - return new CommonResponse<>(false, user); - } catch (IllegalStateException e) { - return new CommonResponse<>(true, e.getMessage()); - } catch (Exception e) { - return new CommonResponse<>(true, "회원가입 처리 중 오류가 발생했습니다: " + e.getMessage()); - } - } - - @PostMapping("/login") - public CommonResponse login(@RequestBody LoginRequest request) { - try { - User user = userService.login(request.getEmail(), request.getPassword()); - - if (user == null) { - return new CommonResponse<>(true, "이메일 또는 비밀번호가 일치하지 않습니다."); - } - - String token = JwtUtil.generateToken(user.getUsername()); - LoginResponse response = new LoginResponse(user, token); - - return new CommonResponse<>(false, response); - - } catch (Exception e) { - return new CommonResponse<>(true, "로그인 처리 중 오류가 발생했습니다: " + e.getMessage()); - } - } - @PutMapping("/update") - public CommonResponse updateUserInfo( - @RequestHeader("Authorization") String authHeader, - @RequestBody Map updates) { - try { - String token = authHeader.startsWith("Bearer ") ? authHeader.substring(7) : authHeader; - String username = JwtUtil.extractUsername(token); - - User updatedUser = userService.updateUserInfoByMap(username, updates); - - return new CommonResponse<>(false, updatedUser); - } catch (IllegalStateException e) { - return new CommonResponse<>(true, e.getMessage()); - } catch (Exception e) { - return new CommonResponse<>(true, "유저 정보 수정 중 오류가 발생했습니다: " + e.getMessage()); - } - } -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/CommonResponse.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/CommonResponse.java deleted file mode 100644 index dccc4b0..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/CommonResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.signup.dto; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class CommonResponse { - private boolean error; - private T data; -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/LoginRequest.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/LoginRequest.java deleted file mode 100644 index 4b3c43b..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/LoginRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.signup.dto; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class LoginRequest { - private String email; - private String password; -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/LoginResponse.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/LoginResponse.java deleted file mode 100644 index d5e5dad..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/LoginResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.signup.dto; - -import com.example.signup.entity.User; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class LoginResponse { - private User user; - private String jwt; -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/UserRegisterRequest.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/UserRegisterRequest.java deleted file mode 100644 index 1fcf8d0..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/dto/UserRegisterRequest.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.signup.dto; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor // ✅ 모든 필드 생성자 자동 생성 -public class UserRegisterRequest { - private String username; - private String email; - private String password; - private String intro; -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/entity/User.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/entity/User.java deleted file mode 100644 index 031cda6..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/entity/User.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.example.signup.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.CreationTimestamp; -import com.fasterxml.jackson.annotation.JsonFormat; - -import java.time.LocalDate; - -@Entity -@Table(name = "users") -@Data -@AllArgsConstructor -@NoArgsConstructor -public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long uid; - - @Column(unique = true) - private String username; - - private String email; - private String password; - - @Column(length = 500) - private String intro; - - @CreationTimestamp - @Column(updatable = false) - @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") // JSON 응답 형식 지정 - private LocalDate createdAt; - - public User(String username, String email, String password, String intro) { - this.username = username; - this.email = email; - this.password = password; - this.intro = intro; - } -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/repository/UserRepository.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/repository/UserRepository.java deleted file mode 100644 index 0efb334..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/repository/UserRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.signup.repository; - -import com.example.signup.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface UserRepository extends JpaRepository { - Optional findByEmailAndPassword(String email, String password); - Optional findByUsername(String username); - boolean existsByUsername(String username); - boolean existsByEmailAndPassword(String email, String password); -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/service/UserService.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/service/UserService.java deleted file mode 100644 index 21b4b9d..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/domain/service/UserService.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.example.signup.service; - -import com.example.signup.dto.UserRegisterRequest; -import com.example.signup.entity.User; -import com.example.signup.repository.UserRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Map; - -@Service -public class UserService { - private final UserRepository repository; - - public UserService(UserRepository repository) { - this.repository = repository; - } - - @Transactional - public User register(UserRegisterRequest request) { - if (repository.existsByUsername(request.getUsername())) { - throw new IllegalStateException("이미 존재하는 사용자명입니다."); - } - - if (repository.existsByEmailAndPassword(request.getEmail(), request.getPassword())) { - throw new IllegalStateException("이미 존재하는 이메일과 비밀번호 조합입니다."); - } - - // 생성일자(createdAt)는 자동 생성되므로 포함하지 않음 - User user = new User( - request.getUsername(), - request.getEmail(), - request.getPassword(), - request.getIntro() - ); - - return repository.save(user); - } - - @Transactional(readOnly = true) - public User login(String email, String password) { - return repository.findByEmailAndPassword(email, password) - .orElse(null); - } - @Transactional - public User updateUserInfoByMap(String username, Map updates) { - User user = repository.findByUsername(username) - .orElseThrow(() -> new IllegalStateException("사용자를 찾을 수 없습니다.")); - - for (Map.Entry entry : updates.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - - switch (key) { - case "username": - throw new IllegalArgumentException("username은 수정할 수 없습니다."); - case "email": - user.setEmail((String) value); - break; - case "password": - user.setPassword((String) value); - break; - case "intro": - user.setIntro((String) value); - break; - default: - throw new IllegalArgumentException("수정할 수 없는 필드: " + key); - } - } - - return repository.save(user); - } -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/global/util/JwtUtil.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/User/global/util/JwtUtil.java deleted file mode 100644 index 7e85db4..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/global/util/JwtUtil.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.signup.util; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; - -import java.security.Key; -import java.util.Date; - -public class JwtUtil { - private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); - private static final long EXPIRATION_TIME = 86400000L; // 1일 - - public static String generateToken(String username) { - return Jwts.builder() - .setSubject(username) - .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) - .signWith(key) - .compact(); - } - - public static String extractUsername(String token) { - return Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody() - .getSubject(); - } -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/entity/User.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/entity/User.java new file mode 100644 index 0000000..389f59c --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/entity/User.java @@ -0,0 +1,42 @@ +package HeyDoctor.HeyDoctor_Backend.domain.User.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@Getter +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "users_id") + private Long id; + + @Column(name = "users_uuid", columnDefinition = "uuid", unique = true) + private UUID userId; + + @Column(name = "name", nullable = false, length = 5) + private String name; + + @Column(name = "provider", nullable = false, length = 10) + private String provider; + + @Column(name = "provider_id", nullable = false, length = 50) + private String providerId; + + @CreationTimestamp + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginFailureHandler.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginFailureHandler.java new file mode 100644 index 0000000..872585a --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginFailureHandler.java @@ -0,0 +1,24 @@ +package HeyDoctor.HeyDoctor_Backend.domain.User.oauth.Handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class OAuthLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + log.error("LOGIN FAILED : {}", exception.getMessage()); + super.onAuthenticationFailure(request, response, exception); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.java new file mode 100644 index 0000000..d5206af --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.java @@ -0,0 +1,111 @@ +package HeyDoctor.HeyDoctor_Backend.domain.User.oauth.Handler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import HeyDoctor.HeyDoctor_Backend.global.security.jwt.RefreshToken.repository.RefreshTokenRepository; +import HeyDoctor.HeyDoctor_Backend.global.security.jwt.RefreshToken.entity.RefreshToken; +import HeyDoctor.HeyDoctor_Backend.domain.User.repository.UserRepository; +import HeyDoctor.HeyDoctor_Backend.domain.User.entity.User; +import HeyDoctor.HeyDoctor_Backend.domain.User.oauth.UserInfo.*; +import HeyDoctor.HeyDoctor_Backend.global.security.jwt.util.JwtUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.Map; +import java.util.UUID; + +@Slf4j +@Component +@RequiredArgsConstructor +public class OAuthLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + @Value("${jwt.redirect}") + private String REDIRECT_URI; // 프론트엔드로 Jwt 토큰을 리다이렉트할 URI + + @Value("${jwt.access-token.expiration-time}") + private long ACCESS_TOKEN_EXPIRATION_TIME; // 액세스 토큰 유효기간 + + @Value("${jwt.refresh-token.expiration-time}") + private long REFRESH_TOKEN_EXPIRATION_TIME; // 리프레쉬 토큰 유효기간 + + private OAuth2UserInfo oAuth2UserInfo = null; + + private final JwtUtil jwtUtil; + private final UserRepository userRepository; + private final RefreshTokenRepository refreshTokenRepository; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; // 토큰 + final String provider = token.getAuthorizedClientRegistrationId(); // provider 추출 + + // 구글 || 카카오 || 네이버 로그인 요청 + switch (provider) { + case "google" -> { + log.info("구글 로그인 요청"); + oAuth2UserInfo = new GoogleUserInfo(token.getPrincipal().getAttributes()); + } + case "kakao" -> { + log.info("카카오 로그인 요청"); + oAuth2UserInfo = new KakaoUserInfo(token.getPrincipal().getAttributes()); + } + case "naver" -> { + log.info("네이버 로그인 요청"); + oAuth2UserInfo = new NaverUserInfo((Map) token.getPrincipal().getAttributes().get("response")); + } + } + + // 정보 추출 + String providerId = oAuth2UserInfo.getProviderId(); + String name = oAuth2UserInfo.getName(); + + User existUser = userRepository.findByProviderId(providerId); + User user; + + if (existUser == null) { + // 신규 유저인 경우 + log.info("신규 유저입니다. 등록을 진행합니다."); + + user = User.builder() + .userId(UUID.randomUUID()) + .name(name) + .provider(provider) + .providerId(providerId) + .build(); + userRepository.save(user); + } else { + // 기존 유저인 경우 + log.info("기존 유저입니다."); + refreshTokenRepository.deleteByUserId(existUser.getUserId()); + user = existUser; + } + + log.info("유저 이름 : {}", name); + log.info("PROVIDER : {}", provider); + log.info("PROVIDER_ID : {}", providerId); + + // 리프레쉬 토큰 발급 후 저장 + String refreshToken = jwtUtil.generateRefreshToken(user.getUserId(), REFRESH_TOKEN_EXPIRATION_TIME); + + RefreshToken newRefreshToken = RefreshToken.builder() + .userId(user.getUserId()) + .token(refreshToken) + .build(); + refreshTokenRepository.save(newRefreshToken); + + // 액세스 토큰 발급 + String accessToken = jwtUtil.generateAccessToken(user.getUserId(), ACCESS_TOKEN_EXPIRATION_TIME); + + // 이름, 액세스 토큰, 리프레쉬 토큰을 담아 리다이렉트 + String encodedName = URLEncoder.encode(name, "UTF-8"); + String redirectUri = String.format(REDIRECT_URI, encodedName, accessToken, refreshToken); + getRedirectStrategy().sendRedirect(request, response, redirectUri); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.java new file mode 100644 index 0000000..d6c6bd5 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.java @@ -0,0 +1,26 @@ +package HeyDoctor.HeyDoctor_Backend.domain.User.oauth.UserInfo; + +import lombok.AllArgsConstructor; + +import java.util.Map; + +@AllArgsConstructor +public class GoogleUserInfo implements OAuth2UserInfo { + + private Map attributes; + + @Override + public String getProviderId() { + return (String) attributes.get("sub"); + } + + @Override + public String getProvider() { + return "google"; + } + + @Override + public String getName() { + return (String) attributes.get("name"); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.java new file mode 100644 index 0000000..550f0b3 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.java @@ -0,0 +1,28 @@ +package HeyDoctor.HeyDoctor_Backend.domain.User.oauth.UserInfo; + +import lombok.AllArgsConstructor; + +import java.util.Map; + +@AllArgsConstructor +public class KakaoUserInfo implements OAuth2UserInfo { + + private Map attributes; + + @Override + public String getProviderId() { + // Long 타입이기 때문에 toString으로 변환 + return attributes.get("id").toString(); + } + + @Override + public String getProvider() { + return "kakao"; + } + + @Override + public String getName() { + // kakao_account라는 Map에서 추출 + return (String) ((Map) attributes.get("properties")).get("nickname"); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.java new file mode 100644 index 0000000..ffd1a9e --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.java @@ -0,0 +1,26 @@ +package HeyDoctor.HeyDoctor_Backend.domain.User.oauth.UserInfo; + +import lombok.AllArgsConstructor; + +import java.util.Map; + +@AllArgsConstructor +public class NaverUserInfo implements OAuth2UserInfo { + + private Map attributes; + + @Override + public String getProviderId() { + return (String) attributes.get("id"); + } + + @Override + public String getProvider() { + return "naver"; + } + + @Override + public String getName() { + return (String) attributes.get("name"); + } +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/OAuth2UserInfo.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/OAuth2UserInfo.java new file mode 100644 index 0000000..d5185ee --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/OAuth2UserInfo.java @@ -0,0 +1,7 @@ +package HeyDoctor.HeyDoctor_Backend.domain.User.oauth.UserInfo; + +public interface OAuth2UserInfo { + String getProviderId(); + String getProvider(); + String getName(); +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/repository/UserRepository.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/repository/UserRepository.java new file mode 100644 index 0000000..0197df4 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/repository/UserRepository.java @@ -0,0 +1,16 @@ +package HeyDoctor.HeyDoctor_Backend.domain.User.repository; +import HeyDoctor.HeyDoctor_Backend.domain.User.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface UserRepository extends JpaRepository { + @Query("SELECT u FROM User u WHERE u.userId = :userId") + Optional findByUserId(UUID userId); + + User findByProviderId(String providerId); +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/global/config/WebConfig.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.java similarity index 92% rename from src/main/java/HeyDoctor/HeyDoctor_Backend/User/global/config/WebConfig.java rename to src/main/java/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.java index 18709d6..5d02844 100644 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/User/global/config/WebConfig.java +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.java @@ -1,4 +1,4 @@ -package com.example.signup.config; +package HeyDoctor.HeyDoctor_Backend.global.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.java index 63cfc5d..1e49bb7 100644 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.java +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.java @@ -1,13 +1,21 @@ -package HeyDoctor.HeyDoctor_Backend.global.exception; - -import lombok.Getter; - -@Getter -public class CustomException extends RuntimeException { - private final ErrorCode errorCode; - - public CustomException(ErrorCode errorCode) { - super(errorCode.getMessage()); - this.errorCode = errorCode; - } -} +package HeyDoctor.HeyDoctor_Backend.global.exception; + +import HeyDoctor.HeyDoctor_Backend.global.exception.dto.ResultDto; +import lombok.Getter; + +@Getter +public class CustomException extends RuntimeException { + private final ErrorCode errorCode; + + // 기본 ErrorCode 메시지를 사용하는 생성자 + public CustomException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + // 커스텀 메시지를 사용하는 생성자 + public CustomException(ErrorCode errorCode, String customMessage) { + super(customMessage); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/ErrorCode.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/ErrorCode.java index cd79e18..193dd20 100644 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/ErrorCode.java +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/ErrorCode.java @@ -1,12 +1,48 @@ package HeyDoctor.HeyDoctor_Backend.global.exception; +import HeyDoctor.HeyDoctor_Backend.global.exception.dto.BaseCode; +import HeyDoctor.HeyDoctor_Backend.global.exception.dto.ResultDto; +import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; @Getter -@RequiredArgsConstructor -public enum ErrorCode { - MEMBER_NOT_FOUND("Member not found"); +@AllArgsConstructor +public enum ErrorCode implements BaseCode { + // 전역 에러 + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500", "서버에서 요청을 처리 하는 동안 오류가 발생했습니다."), + BAD_REQUEST(HttpStatus.BAD_REQUEST, "400", "입력 값이 잘못된 요청 입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "401", "인증이 필요 합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, "403", "금지된 요청 입니다."), + // 토큰 에러 + INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "401", "유효하지 않은 토큰입니다."), + INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "401", "유효하지 않은 액세스 토큰입니다."), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "401", "유효하지 않은 리프레시 토큰입니다."), + + // 유저 에러 + NOT_FOUND_USER(HttpStatus.NOT_FOUND, "404", "존재하지 않는 유저입니다."); + + private final HttpStatus httpStatus; + private final String code; private final String message; + + @Override + public ResultDto getReason() { + return ResultDto.builder() + .isSuccess(false) + .code(code) + .message(message) + .build(); + } + + @Override + public ResultDto getReasonHttpStatus() { + return ResultDto.builder() + .isSuccess(false) + .httpStatus(httpStatus) + .code(code) + .message(message) + .build(); + } } diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/GlobalExceptionHandler.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/GlobalExceptionHandler.java index 546e313..84168c7 100644 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/GlobalExceptionHandler.java @@ -1,13 +1,32 @@ package HeyDoctor.HeyDoctor_Backend.global.exception; +import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; +import HeyDoctor.HeyDoctor_Backend.global.exception.CustomException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.Map; + @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(RuntimeException.class) - public String handleRuntimeException(RuntimeException e) { - return e.getMessage(); + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + // 사용자 정의 예외 처리 + @ExceptionHandler(CustomException.class) + protected ResponseEntity handleCustomException(CustomException e) { + ErrorCode code = e.getErrorCode(); + HttpStatus status = code.getHttpStatus(); + log.warn("CustomException occurred: {}", code.getMessage(), e); + return ResponseEntity + .status(status) + .body(Map.of( + "success", false, + "error", code.getMessage() + )); } } diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.java new file mode 100644 index 0000000..bccff6f --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.java @@ -0,0 +1,40 @@ +package HeyDoctor.HeyDoctor_Backend.global.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import HeyDoctor.HeyDoctor_Backend.global.exception.dto.*; + +@Getter +@AllArgsConstructor +public enum SuccessCode implements BaseCode { + // 전역 응답 코드 + _OK(HttpStatus.OK, "200", "성공입니다."), + _CREATED(HttpStatus.CREATED, "201", "생성에 성공했습니다."), + + // 커스텀 응답 코드 + _CREATED_ACCESS_TOKEN(HttpStatus.CREATED, "201", "액세스 토큰 재발행에 성공했습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ResultDto getReason() { + return ResultDto.builder() + .isSuccess(true) + .code(code) + .message(message) + .build(); + } + + @Override + public ResultDto getReasonHttpStatus() { + return ResultDto.builder() + .isSuccess(true) + .httpStatus(httpStatus) + .code(code) + .message(message) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.java new file mode 100644 index 0000000..5ae3028 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.java @@ -0,0 +1,6 @@ +package HeyDoctor.HeyDoctor_Backend.global.exception.dto; + +public interface BaseCode { + ResultDto getReason(); + ResultDto getReasonHttpStatus(); +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto.java new file mode 100644 index 0000000..10b8bdf --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto.java @@ -0,0 +1,14 @@ +package HeyDoctor.HeyDoctor_Backend.global.exception.dto; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ResultDto { + private HttpStatus httpStatus; + private final boolean isSuccess; + private final String code; + private final String message; +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.java new file mode 100644 index 0000000..b70a3f2 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.java @@ -0,0 +1,42 @@ +package HeyDoctor.HeyDoctor_Backend.global.exception.responce; + +import HeyDoctor.HeyDoctor_Backend.global.exception.dto.ResultDto; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; + +@Getter +@RequiredArgsConstructor +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + + private final String code; + private final String message; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private final T payload; + + public static ResponseEntity> onSuccess(ResultDto result, T data) { + ApiResponse response = new ApiResponse<>( + true, + result.getCode(), + result.getMessage(), + data + ); + return ResponseEntity.status(result.getHttpStatus()).body(response); + } + + public static ResponseEntity> onFailure(ResultDto result) { + ApiResponse response = new ApiResponse<>( + false, + result.getCode(), + result.getMessage(), + null + ); + return ResponseEntity.status(result.getHttpStatus()).body(response); + } +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/DummySecurityConfig.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/DummySecurityConfig.java deleted file mode 100644 index 9ff75bb..0000000 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/DummySecurityConfig.java +++ /dev/null @@ -1,5 +0,0 @@ -package HeyDoctor.HeyDoctor_Backend.global.security; - -public class DummySecurityConfig { - // Security 설정 클래스 -} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.java new file mode 100644 index 0000000..fd08bd6 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.java @@ -0,0 +1,55 @@ +package HeyDoctor.HeyDoctor_Backend.global.security.config; + +import HeyDoctor.HeyDoctor_Backend.domain.User.oauth.Handler.*; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; + +import java.util.Collections; + +@RequiredArgsConstructor +@Configuration +@EnableWebSecurity +public class SecurityConfig { + private final OAuthLoginSuccessHandler oAuthLoginSuccessHandler; + private final OAuthLoginFailureHandler oAuthLoginFailureHandler; + + // CORS 설정 + CorsConfigurationSource corsConfigurationSource() { + return request -> { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedHeaders(Collections.singletonList("*")); + config.setAllowedMethods(Collections.singletonList("*")); + config.setAllowedOriginPatterns(Collections.singletonList("*")); // 허용할 origin + config.setAllowCredentials(true); + return config; + }; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + httpSecurity. + httpBasic(HttpBasicConfigurer::disable) + .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) // CORS 설정 추가 + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(authorize -> + authorize + .requestMatchers("/**").permitAll() + ) + + .oauth2Login(oauth -> // OAuth2 로그인 기능에 대한 여러 설정의 진입점 + oauth + .successHandler(oAuthLoginSuccessHandler) // 로그인 성공 시 핸들러 + .failureHandler(oAuthLoginFailureHandler) // 로그인 실패 시 핸들러 + ); + + return httpSecurity.build(); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.java new file mode 100644 index 0000000..fe56f9c --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.java @@ -0,0 +1,25 @@ +package HeyDoctor.HeyDoctor_Backend.global.security.jwt.RefreshToken.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.util.UUID; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@Getter +@Table(name = "refresh_tokens") +public class RefreshToken { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "refresh_tokens_id") + private Long id; + + @Column(name = "users_uuid", unique = true, columnDefinition = "uuid") + private UUID userId; + + @Column(name = "token", nullable = false) + private String token; +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..2282588 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.java @@ -0,0 +1,21 @@ +package HeyDoctor.HeyDoctor_Backend.global.security.jwt.RefreshToken.repository; + +import jakarta.transaction.Transactional; +import HeyDoctor.HeyDoctor_Backend.global.security.jwt.RefreshToken.entity.RefreshToken; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface RefreshTokenRepository extends JpaRepository { + @Query("SELECT u FROM RefreshToken u WHERE u.userId = :userId") + RefreshToken findByUserId(UUID userId); + + @Transactional + @Modifying + @Query("DELETE FROM RefreshToken u WHERE u.userId = :userId") + void deleteByUserId(UUID userId); +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse.java new file mode 100644 index 0000000..f5b0a5b --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse.java @@ -0,0 +1,12 @@ +package HeyDoctor.HeyDoctor_Backend.global.security.jwt.Token; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class TokenResponse { + @JsonProperty("access_token") + private String accessToken; +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenService.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenService.java new file mode 100644 index 0000000..ee1f3e1 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenService.java @@ -0,0 +1,43 @@ +package HeyDoctor.HeyDoctor_Backend.global.security.jwt.Token; + +import HeyDoctor.HeyDoctor_Backend.global.security.jwt.RefreshToken.entity.RefreshToken; +import HeyDoctor.HeyDoctor_Backend.global.security.jwt.RefreshToken.repository.RefreshTokenRepository; +import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; +import HeyDoctor.HeyDoctor_Backend.global.exception.CustomException; +import HeyDoctor.HeyDoctor_Backend.global.security.jwt.util.JwtUtil; +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class TokenService { + @Value("${jwt.access-token.expiration-time}") + private long ACCESS_TOKEN_EXPIRATION_TIME; + + private final RefreshTokenRepository refreshTokenRepository; + private final JwtUtil jwtUtil; + + public TokenResponse reissueAccessToken(String authorizationHeader) { + String refreshToken = jwtUtil.getTokenFromHeader(authorizationHeader); + String userId = jwtUtil.getUserIdFromToken(refreshToken); + + RefreshToken existRefreshToken = refreshTokenRepository.findByUserId(UUID.fromString(userId)); + if (existRefreshToken == null) { + throw new CustomException(ErrorCode.INVALID_REFRESH_TOKEN); + } + + if (!existRefreshToken.getToken().equals(refreshToken) || jwtUtil.isTokenExpired(refreshToken)) { + throw new CustomException(ErrorCode.INVALID_REFRESH_TOKEN); + } + + String accessToken = jwtUtil.generateAccessToken(UUID.fromString(userId), ACCESS_TOKEN_EXPIRATION_TIME); + + return TokenResponse.builder() + .accessToken(accessToken) + .build(); + } +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/util/JwtUtil.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/util/JwtUtil.java new file mode 100644 index 0000000..7e1ff2a --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/global/security/jwt/util/JwtUtil.java @@ -0,0 +1,93 @@ +package HeyDoctor.HeyDoctor_Backend.global.security.jwt.util; + +import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; +import HeyDoctor.HeyDoctor_Backend.global.exception.CustomException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.util.Date; +import java.util.UUID; + +@Slf4j +@Component +public class JwtUtil { + @Value("${jwt.secret}") + private String SECRET_KEY; + + private SecretKey getSigningKey() { + byte[] keyBytes = Decoders.BASE64.decode(this.SECRET_KEY); + return Keys.hmacShaKeyFor(keyBytes); + } + + // 액세스 토큰을 발급하는 메서드 + public String generateAccessToken(UUID userId, long expirationMillis) { + log.info("액세스 토큰이 발행되었습니다."); + + return Jwts.builder() + .claim("userId", userId.toString()) // 클레임에 userId 추가 + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + expirationMillis)) + .signWith(this.getSigningKey()) + .compact(); + } + + // 리프레쉬 토큰을 발급하는 메서드 + public String generateRefreshToken(UUID userId, long expirationMillis) { + log.info("리프레쉬 토큰이 발행되었습니다."); + + return Jwts.builder() + .claim("userId", userId.toString()) // 클레임에 userId 추가 + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + expirationMillis)) + .signWith(this.getSigningKey()) + .compact(); + } + + // 응답 헤더에서 액세스 토큰을 반환하는 메서드 + public String getTokenFromHeader(String authorizationHeader) { + return authorizationHeader.substring(7); + } + + // 토큰에서 유저 id를 반환하는 메서드 + public String getUserIdFromToken(String token) { + try { + String userId = Jwts.parser() + .verifyWith(this.getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload() + .get("userId", String.class); + log.info("유저 id를 반환합니다."); + return userId; + } catch (JwtException | IllegalArgumentException e) { + // 토큰이 유효하지 않은 경우 + log.warn("유효하지 않은 토큰입니다."); + throw new CustomException(ErrorCode.INVALID_TOKEN); + } + } + + // Jwt 토큰의 유효기간을 확인하는 메서드 + public boolean isTokenExpired(String token) { + try { + Date expirationDate = Jwts.parser() + .verifyWith(this.getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload() + .getExpiration(); + log.info("토큰의 유효기간을 확인합니다."); + return expirationDate.before(new Date()); + } catch (JwtException | IllegalArgumentException e) { + // 토큰이 유효하지 않은 경우 + log.warn("유효하지 않은 토큰입니다."); + throw new CustomException(ErrorCode.INVALID_TOKEN); + } + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 58c0210..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,29 +0,0 @@ -# Server -server.port=8080 - -# Database (PostgreSQL + PostGIS) -spring.datasource.url=jdbc:postgresql://aws-0-ap-northeast-2.pooler.supabase.com:5432/postgres?sslmode=require -spring.datasource.username=postgres.hoxnroonysjvuokheyzo -spring.datasource.password=zCxhzvfMCWdVgdu1 -spring.datasource.driver-class-name=org.postgresql.Driver - -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect - - -# Swagger -# springdoc.swagger-ui.path=/swagger-ui.html - -# Spring Security (JWT) -spring.security.user.name=admin -spring.security.user.password=admin123 - -# Logging -logging.level.org.hibernate.SQL=DEBUG -logging.level.org.hibernate.type.descriptor.sql=TRACE - -# Gemini AI API (OpenAI) - -# ai.api.key=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx -# ai.api.url=https://api.openai.com/v1/chat/completions diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..52af950 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,96 @@ + +server: + port: 8080 + +spring: + config: + import: optional:file:.env[.properties] + datasource: + url: ${SPRING_DATASOURCE_URL} + username: ${SPRING_DATASOURCE_USERNAME} + password: ${SPRING_DATASOURCE_PASSWORD} + driver-class-name: org.postgresql.Driver + + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + + security: + user: + name: admin + password: admin123 + oauth2: + client: + registration: + google: + client-id: ${OAUTH_GOOGLE_CLIENT_ID} + client-secret: ${OAUTH_GOOGLE_CLIENT_SECRET} + scope: + - email + - profile + redirect-uri: ${OAUTH_GOOGLE_REDIRECT_URI} + kakao: + client-id: ${OAUTH_KAKAO_CLIENT_ID} + client-secret: ${OAUTH_KAKAO_CLIENT_SECRET} + scope: + - profile_nickname + authorization-grant-type: authorization_code + redirect-uri: ${OAUTH_KAKAO_REDIRECT_URI} + client-name: Kakao + client-authentication-method: client_secret_post + naver: + client-id: ${OAUTH_NAVER_CLIENT_ID} + client-secret: ${OAUTH_NAVER_CLIENT_SECRET} + scope: + - name + authorization-grant-type: authorization_code + redirect-uri: ${OAUTH_NAVER_REDIRECT_URI} + client-name: Naver + + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id + naver: + authorization-uri: https://nid.naver.com/oauth2.0/authorize + token-uri: https://nid.naver.com/oauth2.0/token + user-info-uri: https://openapi.naver.com/v1/nid/me + user-name-attribute: response + +logging: + level: + org: + hibernate: + SQL: DEBUG + type: + descriptor: + sql: TRACE + +jwt: + secret: ${JWT_SECRET} + redirect: ${JWT_REDIRECT_URI} + access-token: + expiration-time: ${ACCESS_TOKEN_EXPIRATION_TIME} + refresh-token: + expiration-time: ${REFRESH_TOKEN_EXPIRATION_TIME} + +springdoc: + swagger-ui: + path: /swagger-ui.html + +api: + public: + hospital-key: Wjn354cR5FyaLyi8ljwEFz3haoZfNnwwkPqzKkSorTG/ekqLLvQ7zUv1aZdZ4axe4y0UDDq7mjEnNJPYyC6CUw== + kakao: + rest-key: 63e456faac2c82ccf10db8911595efc4 + +ai: + api: + key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx + url: https://api.openai.com/v1/chat/completions diff --git a/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.java b/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.java new file mode 100644 index 0000000..17f62ec --- /dev/null +++ b/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.java @@ -0,0 +1,108 @@ +package HeyDoctor.HeyDoctor_Backend.domain.users.model.signup; + +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@TestMethodOrder(OrderAnnotation.class) +public class EnvFileLoadTest { + + private static final Logger logger = LoggerFactory.getLogger(EnvFileLoadTest.class); + + @Autowired + private Environment env; + + private void checkVariables(List keys) { + List missingKeys = new ArrayList<>(); + + for (String key : keys) { + String value = env.getProperty(key); + if (value == null || value.isBlank()) { + missingKeys.add(key); + logger.error("Environment variable '{}' 불러오기 실패", key); + } else { + logger.info("Environment variable '{}' 불러오기 성공: {}", key, value); + } + } + + // AssertJ 이용해서 누락된 키가 없음을 검증 + assertThat(missingKeys) + .withFailMessage("다음 환경변수들이 누락되었습니다: %s", missingKeys) + .isEmpty(); + } + + @Test + @Order(1) + void testDatasourceVariables() { + logger.info("=== 테스트 1: Datasource 환경변수 체크 시작 ==="); + checkVariables(List.of( + "SPRING_DATASOURCE_URL", + "SPRING_DATASOURCE_USERNAME", + "SPRING_DATASOURCE_PASSWORD" + )); + logger.info("=== 테스트 1 완료 ==="); + } + + @Test + @Order(2) + void testOauthGoogleVariables() { + logger.info("=== 테스트 2: OAuth Google 환경변수 체크 시작 ==="); + checkVariables(List.of( + "OAUTH_GOOGLE_CLIENT_ID", + "OAUTH_GOOGLE_CLIENT_SECRET", + "OAUTH_GOOGLE_REDIRECT_URI" + )); + logger.info("=== 테스트 2 완료 ==="); + } + + @Test + @Order(3) + void testOauthKakaoVariables() { + logger.info("=== 테스트 3: OAuth Kakao 환경변수 체크 시작 ==="); + checkVariables(List.of( + "OAUTH_KAKAO_CLIENT_ID", + "OAUTH_KAKAO_CLIENT_SECRET", + "OAUTH_KAKAO_REDIRECT_URI" + )); + logger.info("=== 테스트 3 완료 ==="); + } + + @Test + @Order(4) + void testOauthNaverVariables() { + logger.info("=== 테스트 4: OAuth Naver 환경변수 체크 시작 ==="); + checkVariables(List.of( + "OAUTH_NAVER_CLIENT_ID", + "OAUTH_NAVER_CLIENT_SECRET", + "OAUTH_NAVER_REDIRECT_URI" + )); + logger.info("=== 테스트 4 완료 ==="); + } + + @Test + @Order(5) + void testJwtVariables() { + logger.info("=== 테스트 5: JWT 환경변수 체크 시작 ==="); + checkVariables(List.of( + "JWT_SECRET", + "JWT_REDIRECT_URI", + "ACCESS_TOKEN_EXPIRATION_TIME", + "REFRESH_TOKEN_EXPIRATION_TIME" + )); + logger.info("=== 테스트 5 완료 ==="); + } +} diff --git a/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/SignupApplicationTests.java b/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/SignupApplicationTests.java index cef2df2..cf42fd9 100644 --- a/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/SignupApplicationTests.java +++ b/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/SignupApplicationTests.java @@ -1,4 +1,4 @@ -package com.example.signup; +package HeyDoctor.HeyDoctor_Backend.domain.users.model.signup; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/service/UserServiceTest.java b/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/service/UserServiceTest.java deleted file mode 100644 index a8cf2be..0000000 --- a/src/test/java/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/service/UserServiceTest.java +++ /dev/null @@ -1,197 +0,0 @@ -package com.example.signup.service; - -import com.example.signup.dto.UserRegisterRequest; -import com.example.signup.entity.User; -import com.example.signup.repository.UserRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -class UserServiceTest { - - private UserRepository repository; - private UserService userService; - - @BeforeEach - void setUp() { - repository = mock(UserRepository.class); - userService = new UserService(repository); - } - - @Nested - @DisplayName("회원가입 테스트") - class RegisterTest { - - @Test - @DisplayName("성공") - void register_success() { - UserRegisterRequest request = new UserRegisterRequest("user1", "user@example.com", "pass", "hello"); - - when(repository.existsByUsername("user1")).thenReturn(false); - when(repository.existsByEmailAndPassword("user@example.com", "pass")).thenReturn(false); - when(repository.save(any(User.class))).thenAnswer(i -> i.getArguments()[0]); - - User user = userService.register(request); - - System.out.println("✅ 회원가입 성공 - username: " + user.getUsername()); - - assertThat(user.getUsername()).isEqualTo("user1"); - } - - @Test - @DisplayName("이미 존재하는 사용자명") - void register_duplicateUsername() { - UserRegisterRequest request = new UserRegisterRequest("user1", "user@example.com", "pass", "hello"); - when(repository.existsByUsername("user1")).thenReturn(true); - - System.out.println("❌ 사용자명 중복 발생 테스트 시작"); - - assertThatThrownBy(() -> userService.register(request)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("이미 존재하는 사용자명입니다.") - .satisfies(e -> { - System.out.println("▶ 예외 메시지: " + e.getMessage()); - System.out.println("✅ 예외가 정상적으로 처리되었습니다."); - }); - } - - @Test - @DisplayName("이미 존재하는 이메일+비밀번호") - void register_duplicateEmailAndPassword() { - UserRegisterRequest request = new UserRegisterRequest("user1", "user@example.com", "pass", "hello"); - - when(repository.existsByUsername("user1")).thenReturn(false); - when(repository.existsByEmailAndPassword("user@example.com", "pass")).thenReturn(true); - - System.out.println("❌ 이메일+비밀번호 중복 발생 테스트 시작"); - - assertThatThrownBy(() -> userService.register(request)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("이미 존재하는 이메일과 비밀번호 조합입니다.") - .satisfies(e -> { - System.out.println("▶ 예외 메시지: " + e.getMessage()); - System.out.println("✅ 예외가 정상적으로 처리되었습니다."); - }); - } - } - - @Nested - @DisplayName("로그인 테스트") - class LoginTest { - - @Test - @DisplayName("성공") - void login_success() { - User user = new User("user1", "user@example.com", "pass", "hi"); - - when(repository.findByEmailAndPassword("user@example.com", "pass")) - .thenReturn(Optional.of(user)); - - User result = userService.login("user@example.com", "pass"); - - System.out.println("✅ 로그인 성공 - username: " + result.getUsername()); - - assertThat(result).isNotNull(); - assertThat(result.getUsername()).isEqualTo("user1"); - } - - @Test - @DisplayName("이메일/비밀번호 불일치") - void login_invalid() { - when(repository.findByEmailAndPassword("user@example.com", "wrongpass")) - .thenReturn(Optional.empty()); - - User result = userService.login("user@example.com", "wrongpass"); - - System.out.println("❌ 로그인 실패 - 유저 없음 (결과가 null)"); - - assertThat(result).isNull(); - } - } - - @Nested - @DisplayName("유저 정보 수정 테스트") - class UpdateUserInfoTest { - - @Test - @DisplayName("성공") - void update_success() { - User user = new User("user1", "user@example.com", "pass", "hi"); - - when(repository.findByUsername("user1")).thenReturn(Optional.of(user)); - when(repository.save(any(User.class))).thenAnswer(i -> i.getArguments()[0]); - - Map updates = Map.of("email", "new@example.com", "intro", "updated intro"); - - User result = userService.updateUserInfoByMap("user1", updates); - - System.out.println("✅ 유저 정보 수정 성공 - email: " + result.getEmail() + ", intro: " + result.getIntro()); - - assertThat(result.getEmail()).isEqualTo("new@example.com"); - assertThat(result.getIntro()).isEqualTo("updated intro"); - } - - @Test - @DisplayName("username 수정 시도") - void update_usernameChange() { - User user = new User("user1", "user@example.com", "pass", "hi"); - when(repository.findByUsername("user1")).thenReturn(Optional.of(user)); - - Map updates = Map.of("username", "newUser"); - - System.out.println("❌ username 수정 시도 테스트 시작"); - - assertThatThrownBy(() -> userService.updateUserInfoByMap("user1", updates)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("username은 수정할 수 없습니다.") - .satisfies(e -> { - System.out.println("▶ 예외 메시지: " + e.getMessage()); - System.out.println("✅ 예외가 정상적으로 처리되었습니다."); - }); - } - - @Test - @DisplayName("허용되지 않은 필드 수정 시도") - void update_invalidField() { - User user = new User("user1", "user@example.com", "pass", "hi"); - when(repository.findByUsername("user1")).thenReturn(Optional.of(user)); - - Map updates = Map.of("role", "admin"); - - System.out.println("❌ 허용되지 않은 필드 수정 테스트 시작"); - - assertThatThrownBy(() -> userService.updateUserInfoByMap("user1", updates)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("수정할 수 없는 필드: role") - .satisfies(e -> { - System.out.println("▶ 예외 메시지: " + e.getMessage()); - System.out.println("✅ 예외가 정상적으로 처리되었습니다."); - }); - } - - @Test - @DisplayName("사용자 없음") - void update_userNotFound() { - when(repository.findByUsername("unknown")).thenReturn(Optional.empty()); - - Map updates = Map.of("intro", "intro"); - - System.out.println("❌ 사용자 없음 테스트 시작"); - - assertThatThrownBy(() -> userService.updateUserInfoByMap("unknown", updates)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("사용자를 찾을 수 없습니다.") - .satisfies(e -> { - System.out.println("▶ 예외 메시지: " + e.getMessage()); - System.out.println("✅ 예외가 정상적으로 처리되었습니다."); - }); - } - } -} From 68d79c26027dd2906afbda53142ac051e8357e33 Mon Sep 17 00:00:00 2001 From: yjj Date: Fri, 8 Aug 2025 18:10:38 +0900 Subject: [PATCH 02/10] =?UTF-8?q?refactor:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EC=9D=84=20=EC=9B=B9=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EC=95=B1=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=B2=98=EB=A6=AC=EB=A5=BC=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EA=B0=80=20=EC=95=84=EB=8B=8C=20=ED=94=84=EB=A1=A0?= =?UTF-8?q?=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=B2=98=EB=A6=AC=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20=ED=86=A0=ED=81=B0=EC=9D=84=20=EB=B0=9B=EC=95=84?= =?UTF-8?q?=EC=84=9C=20=EC=9D=B4=EB=A5=BC=20=EC=9D=B8=EC=A6=9D=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=B2=98=EB=A6=AC=20=ED=95=98=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 97 +++++++++++---- .../Controller/SocialLoginController.java | 26 ++++ .../domain/User/Dto/SocialLoginRequest.java | 12 ++ .../User/Service/SocialLoginService.java | 113 ++++++++++++++++++ .../Handler/OAuthLoginFailureHandler.java | 24 ---- .../Handler/OAuthLoginSuccessHandler.java | 111 ----------------- .../User/oauth/UserInfo/GoogleUserInfo.java | 52 ++++---- .../User/oauth/UserInfo/KakaoUserInfo.java | 57 ++++----- .../User/oauth/UserInfo/NaverUserInfo.java | 53 ++++---- .../User/oauth/UserInfo/OAuth2UserInfo.java | 15 +-- .../global/config/WebConfig.java | 18 --- .../global/exception/ErrorCode.java | 3 + .../global/exception/SuccessCode.java | 12 +- .../exception/responce/ApiResponse.java | 7 +- .../security/config/SecurityConfig.java | 41 ++----- src/main/resources/application.yml | 52 +------- 16 files changed, 344 insertions(+), 349 deletions(-) create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/Controller/SocialLoginController.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/Dto/SocialLoginRequest.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/Service/SocialLoginService.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginFailureHandler.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.java delete mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.java diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 790db93..1bfb537 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,7 +4,25 @@ hO2{fl zl~AC2!BiM2r%O7al}4>h_PJuiCVC?kZz!w1+&=7fTbeyREp_JPt4s{%5!hCP1UzI< z)=Pn`rB=5DiYG8@cb1Dw`xEmejpljl%w8JcuE4qGylEqsS35TWeiSGiepMTo9W5Tk zC*-zF<*D*R@5pkcvvX10Mo(_dG;BnR+IMICW6S53k1sIVM#9E1I4B;EYz|1q?bS&x zm>Q1&4`_%9-ju_@;+rl^@_&H!f%8m<`9FdwmWnM)rqe9358T8Id-vQeNVfFcceu^AcbMkDPBQv?CvUT^9G)5wAhknrLbP geS=-^aOp1y?@TnG({eVz5%2R^7iJMM^`~>d|5;eh6951J diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/Handler/OAuthLoginSuccessHandler.class deleted file mode 100644 index 3a4ec7d7c95e351d1c600c1c45fd89abeded0a7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7510 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 T3W+sB9!w&Y592;D89Dz080|`? diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/GoogleUserInfo.class deleted file mode 100644 index 9c4ca7498fcd51cb13cab9dbea2319b4b57443c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1211 zcmbtTYi|-k6g>m9%c}*UV(X)dPoTDIOiVO1P4IWW=Z<5G|?FS zVEpWlGTw!C3pFN2KkUrhyZ4?s=giFaA78!#sNr4$5kxJ-(pbPE!?PFS*i^uSdeQHTO(^vfNc~wO!Y`HxT|GKV(R9V_ezsj2JOw8i#z$ZO_Po zy~mHMal{#-Bd?P}5-AJmG?uW;uryajB|TvnZkML*1Fm}ZLFZ6(O||@c%0iYQJ3*LK zwrGFVizCOd*qgA6V}&86_)sKKzy%AdX{_PmT-OwzTDGNp}^a8{v?QWJ`N)S3H-#bNTs;zUALH6zL5WK;ZO6{C7n26?$F~tRqJ2tGGsE zkur}c$7s$}K7i${&qze@4Vib7;_Ec}t-vyF(3qZ<;wElUL=Pc^*`&y?i{{RqZOzZL zM#I)SVe34D{GS-C&cI|sm^GA0P?2_Sl8^|^$;v03uWWn-d++nXsRnC;P?%N)2vi@~ jQ>N;vK0mWgYATdjU=(Bn?>2CkQobRZG&5|`7=is0`1Km5 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/KakaoUserInfo.class deleted file mode 100644 index 710147d02d9dddcd3db7f39bea22e5543d2c04b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1257 zcmbVLT~8B16g|@xx0GUQttg=42Vz@nAwdu&mXC3~gtc?JNoZN)tb# z55{MIl=04XDO-F|UuNdsx%bRDXJ&r>`t}{bDi(*4K+-`fiy@q2Sbrgo*LB@!-|es7 zKIQeMP@e1QHkZnM(-pp}d0-C0<)(V4-Iu({wcW50LxvmU%UWPWmmya@njs;*5o9ppAe+S~&NGY-R52tQ!Z6H~dfhFq8tzW*P}I#*`Oj1ad0cQ%$l@Z3 z4EYc%*29oCdXG4%21BtFN9bjV)fmH2)9Og$3Q6erx+8oeMK_HJ>Q}PfRJ<)5Tpc)| z*KY}2&teKU7|tzACCv&+D3$je++ujvH}l_Y-B~4<7fyX0-jfZ*P2dZL#X(z_W2Qus zVwuRfl_m0-eBJf+k@V=cJciVo_6ROll}c;}?V9jk^ID5Cg{rRe);{;8jiY4B97wv; zjp~0Y8h9wgW+cXj!J%lIJf_2=d+)!R@;<}ZAfw~X39I~;FbCS(^;fjkcAx zQKB&$t;?uT#4y{3nWM$k2&48}tSmqP|WkSUButI4x~6EVQ? zKHwDQNSc+jOMnTQGnFs6R+;?-_Q5j4sTwCl80%F<2vi>#HBZ$&r8YB7x?IW((SIdR VdoJF`14>zI7HDR8NMizzegi0AB-sD} diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/domain/User/oauth/UserInfo/NaverUserInfo.class deleted file mode 100644 index 9457326cf805262c654ee557e0e499d04136d303..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1206 zcmbVLU2hUW6g{&*7bq=AE4F@A>nBj#H6|t+nkLm++l016lvgvbBTNf3&32Zg|4I{$ z(FfzRKgxI))-79OqCPMmmwV4W=gi*kKfZhgu!#p5SV-7N=8(b^!`6;C-O?SSJtsQ7 zdCEISLb;Bs2V5%W^@;Et&3)4w9e34R?dr8$*^G| zd;GYTMw-Er?liKPwvo$W2D1z^u`Gr}R~Ux7<#BbNtFCjE+d1S`F*>bQ+ zE!O&gk^`7puaaFg@)yxJSiCc*OAWIe@>kEogJ)rLT+}h+Q?5Cz7;pDy0oKQY!*3 z*86(U)<@0@p@heca7jkCseP{_w&lpUn8>x@W5Y6yPYHxgKPwm^+ON@MSy)1n&ew5+ z)@6!3p%|k*U;O}9v_B(b;T!VrM#(p64N8Go+@du%PQ`89p&Ly>1hY!FATLq8aK3bI zqBIee-iu1(4CemCU||9#AHgi5OoEoFbAyCfv}dcIaIw1b5$t`y2WJ}W5khI46(Uf6 lXitT*$NGZE5~-9U5Jm6!st*J~KSDPK+`3X!6vPVRT4Eb(wI&eLg1_d%5AdVJM8wBt=3eG7 zbLalJ-T(|x^N=Hqr{XeFfl-=$)Mm(oqmUsB)rrTFtG2Ts+tyHNIhtPt-vb+)gc=*8ZkMb?aLwWn+lW(>?u865;vZlgA37FKM*OEp#AU?mQYyn- zl~@oB<`w0;BrndpV=fx@2L5LUX%>OMaf z5o%X+YRy(`*Xs)${Kb#x}!8s0BX&>OIsMQvqKWdv;lJOdE{BC`Mh diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/AppConfig.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/AppConfig.class deleted file mode 100644 index 38fd95425675c302cf0f75998e9b02b390ca30f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 433 zcma)2yH3L}6g}=M5K3FRF|q*z9uO==l|m6>h(H3A6*;aO+@^NrIy^p$35kIZ;G+<> zL7B0obI*NP$LD^1e|!Q+FpSYaGeWD2HaY}@IeTKXWU8Py`CM>I&^ePz+A%?M{~(L7 zL2!H}UN1Da+R&OkoHM=>ZAFOIxDg5BFnS-BLMTuUdJD5Wj4 zQY)u;nrc(f^~y+9%nVzKXKfbVjukJKvU-&M`l?KLIKmzxw@QDAyKb2dH(f?U4zv&7 fLEc=Uf!LvMd)#u^4ZQ8*cIfv4_Av+(1UtYF9EEUj diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/config/WebConfig.class deleted file mode 100644 index 92e3db95bbe131fc9c9f85b022dff59fab02018d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1621 zcmcIl+foxj5IrLayM{G{h)CtS-B$*$I~)@jraQ zDy#GZ{3y$w1cOxhUL>g_wgldKpsh#fIR%2pYUp zeJ^X8q4%K|c=|CzPbOQoVKZ1eZSQv6}5~mnu4xR%;Quuzj zDeEQWHM}5VsAS&$C%iV!Q0?XlD>lwC#Fk4gS;I87{F01|RN&b{v9MCGahXb6T3(qi zEx88sRR`A$;9t2eO1&1=BO5mu7!`jISqCG(?|3dnU8=||-Ewdnqjb@2pm`8s9s_=iY`<1Nb+dQ{$|DVW04i-0R9UmHb!#1nF2 z4u2#^N2p)8nYMOtH1-w!m7er~yMyC)$3E>=V(GJPC7wR-R(jJH+=`XHMC(^}_g*JE z6WhVfLGT@nz1quQ7#4Z%LqEmmB-=S8DQ-UsNMRlW?aYQc(RVYWJn)qM6vSmZ8D}&w PX!GrMmJW7cJp+CM;bG67 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/CustomException.class deleted file mode 100644 index 19042edd3fce17aa1ca27d65588af691ff974aff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1129 zcmbVL+invv5Is(F-E7jbO$#(!3cMsKV&4!VRi#qOO$nkDgbGS0Hhb zc;Ey2Q5EAPY*7mmjrPGav&UyV$H&JfKYjw(#vKnCEEJISkwc#0{xk9Mv5u@ZVH&-C z#G^x@24S4&K2JjNArkMc)GFLHMw=Zy5DZ={Y*&m%Jf@`9Xm;N5BOWGP#o@j+QpIf# zMU)Ege3aoctepa0kCl}}vHP2sA-^e=v|9`-jq^m!UI|q!7O>>w5(0+J^r-)H=$-M% z>fs;FO5${k2b~LXr2@1W+KqYlHhT=&sf>#qsl?0iurJIT-cLx&pra$6^th2OpB1zA zK#mx;F4pnP50@y%C&;G5F-3R7-}3OXCHdL=XG=FnlVMFx-j($>8nlv5CYJ=>)Hxk?Ec@DsGYG ziu7cN!I_St1&Yg9ruS2$>;SaEg213qz7=&NFtveoGh zT*ej39L6y-T%|b2C~@t|SRd$Px;F;exg7f*NDz_ zDrF{}>J&0u$BkLHkTOQKTp6&GK{TKVyT>gcfJ(brM%Iz{7tb9{qC*Du5c)j z7>FJ%?QE0Lz&qRS?sWC^1mp24!}n$EU;xRmK!=o~uK#K%xGNWrMIo|3GpHj{${*0oH z`lBPK)TGhw#4bE#L#qrgeB@b`A*kiY$e*)HCa*?MNI1}1Bi)lxa=G>zAZ}#S+Gu;N z1y(@%pT-^=+GO~#SHcVHs&LBC{V_F{&lmwiCtpVL=}bz^N{CV@53Z@DqO-fCki2{b z&)TqG#sNGh;pjuuO%0aL#q-Ien#)P(Z$18SJ?GvzB;$F!K<6t-2Tg)|Wm!1B4(57M zf>K+@O4%M_#Ns&qP{v{Q$u>rDE2AoV(9PJ3{1uQ9L@%|bsHlX_|Er$DV4sW-UXtKE zX&7hXhGOI?-A{(~?5IC?hI{iwRvA-Y*R!Yn%;E2=u!-u2S^Ss;qjgOp54VVX2%|Fk z@v?-C9ce9XbV}I1N}WR9Av>%N3}DcPA-+y2;wRP+qa8nzU^%dNuN}uFgv^QAVqwDk z&%qy48!e7j}36EKrDc=0jvphXhoVx5OUi{RYp7fYA*NZo1?ktRZiVL&m+jGkw zPnuJ=ix(z~7p}KCsr`P;p8rHbVl{`?l+Be5yAt8Z9US>fJea)O$GN-PuBDTYhUr5-uni!RH`SL_@e$u=!yNYBJ z2lZX%jLxjD#~IX_-?o*Uu3jCb%ZzhUChp!3(> z>V4o>%!c>q5nT7Z)UD)H9ysVE-SwibwB8adQ9jG`e;qxQ?mDNj=rC@GWGhSxSy$R5fC%lG8Q1x{Z@*g0@zm>h(mk z2%)v>r|7m^!FRwt~_iD=5vag3_caD9xpU()cMT z4Vg5tyZJ#Bl%@kg=|u)yJ9*NiwUhKt;a#$JfmV+bU9f1}uQ z5Wg4iV}sz|Pn0E`ZppWVC%=Wn{2T7!{~aN6i3=Bn50?nBO1L=Asy{$4{b%E^l$A9u zVYg)w`+T=>a1rf9Iv3Hkh@PtMogy|5>S)N_L|(gT$ZbFijouzq?KR>bzf3^LHrPz^^=|fWQ*J!WwimE9KI|fBI}ySfcZ{^h-LJTZ zzs9Ry!6LAqlC6YWaVG_46PQYtPqc9wi( z>~(={71$f>F@rxB;C2E2lKuacd))n&0O>GIe1&GNMJb$Jbbx_+Y{O=>(je18$?V5g kqzE}jhvt^!^OP76_-3`b% zei1)~+L=oI!w=wxay+{UA%)ZdopCbReedPHbMHO(-23w9U%&qWU=Ghxh@nG6Tt@<( z0=--2j%hfiTQOEjTha;yI&-#b2eSelnThojx{%b6($NiFAh#g*?kOu!p3xjV%bV77 z>6VR(qe`Y@$nPy#4Q%Bag_<9z?c%{)K(EN4=y}R3sInB8$xJLZVNL?rJey77EP6Eb z>gYpS;MTu`5jaDS7J{I<7MMZJ7q~)~lvgqQs%N{E4bR+`yUKfRY;uorxG|eVKhA3y z&@qU&1PZ6Je?KCs$;*Q2mL2Jla7R@H(sc)%jhkD>qN-G+m+iu^z~~V=$CPE&JWrNq z#(wxQuPWDCDft+{F-HvtQ4-n(D1&F4{%+e?~t>+QjRo9hPGomHMz1OFpzoD z%4%Z=({xiHAwz+8;}$;Ba9hV6%ur?#TrIF2W67)v#1v5;x03rtDvy(Ue5K*c0Y3Aa z!(9!tIzGlH0yj=bek(haK#5!l+->9KiTP%o=26gaPe&2+0)t1DHgAT_gl3WyZC&jG zms(909Mks?GTfrkEIq8Zk)B1{mCLp5lJp*#C5NJC30bDIZhCe&Z!E@xO`8>7JYCCa zU#m2JelsfDBSk4-XHI9}2*5$4?fTE_TBZ+zvCjTCb7awqn(^(^;T*PN=IbPdN_Tsg zWU#5q56!Tx2GV0Tl52LwW$X1AiSvIkmz$iWH$$s6ww!IbZu>S>GUvLAorm`p-I{3| z(XK2RC7U;$+1zU!?cxMZM14Y_D`z>4t2DKyYMv$M?NFT~?KO2R>=9u;+f{2j)09I{ zP;THUhu6nkdB?US&Fc?bS9(#|NQTng((*9QYb(Y-0s_O~#l=wv$NVS2=Y7r$z6IZj z$=@J;;UI8kS7O})w^i;<_rqegV)qQ;U3fUNb=Bc`+%SduOK%_yW=-?1P!~Be4lo!ecUM3@K z&_j_YlXYtiPiPm*SfO_bOyeOwCt?a2o>*m&YeY>5ZU7!d#=gKh-)Fh~OMbt43SseZ w7aljc7>@!4lIi|^%)P+5kPp$qM;~#YL|{)Nu#ly%Isb;=6vsJw{B39QZyZC%B>(^b diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/SuccessCode.class deleted file mode 100644 index af10f020b4520223eb8beb109b71baddd384c26d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3440 zcmb_f-BS}+6#rd9$cIZ!S5f093TlYe6{{5uS|LQGM6d+0XlvXgOIS&EX|h4>$4oo5 zt<{&l)V{P%JL6*?6bmx#wBvLClp6kpPS4qmAquUaz-(^r*>ivA`<$D<{`u`s0A2Xh zgF1nZ)7teOGi{j#rTQ(|t)|B{Bco(E zu^t;d2uj$9O{B;?L(C}UNuxpNWFxf_7=ZA%V5OGY(c9BJ0u`3=7-0m$~){sI_sdhHV8cwhLjugm%`+ zlc!Qsvl?sgCe!xuw?h)%!eL5HJE8)|Un!opz#|flA|l`)v#g1PrCOz;z|Oc?$STDN z&dt$+n%AzHg>i*Bl+#rR`6B4zvZh?kQ-Ow%4WZfch>TMhL z!tFy0Cp|bN;WS&^vIH9i>IV9}c!%2Df&KfvhzkVDHy-@`aJqc=%O|&QKDm950wd7R zS_}vz7ZbiD&n!&7g%q~J$5sqVNU#Z>O1JdloULKI7v}}Gl)w0qlq`S$ZL_26+?|`{ zThkSFy^y@PAkbd^?&tE2+48M>&2wMe$%mY|KJNH`E*%n9#EZHI4$=A75G@SeV-j zDQ&}aALvZyDs}A&G2!bPJ|2{v%bRDlfjk~r-dUciPxFo(=ueoXLRveibB+aSfcNoT zKtSp>$lLb7*C+^q=ZEKsb}Fa!^d#Xq5#+lRKr3B??3HdlbW@YLIom5uDto2r00cJj zy@&RG;#YBrP*SwpK`a0Hr_eZq*B?W^PdkA$eKy$dsDIFfC4)Mf{~)b=!Y_;PEY>}S zVE(#i_%)(P4i`pk9kN7m3yglo&|^4EpDg@*1uQs=&Gj?b5&jjS8N5M@GJ^v%I5>~J z(Y9>^>Jh{$(nrQ7G+{F~(Eq%=Aat8g;7V0Usv@L$UP#9bI{!!3R(MFc?Iddl$!Z~4 ztt53PN!x{OM4y+}T$MMD9LZs$6f%xITp{0MJ{_fDfD9IT&4C>(T8LwP_vwW;8< zvBPv46ecG${EZXTWz#rCowuB%Zg8SBxlsrJMS6}>#)j!>r^m9vOQ>;Iz|WI~oaXqQ zfq?Z5e?W}5rr=79)K9^a7;#Tg<+0t*rnMK7xLP@!wL=a>f2Xr@luq}^y>j0poOuYB zO*>0i@5)2+uub#WwD$;^X(KkxKZ}bG(a2741j)8({L`QjA+n$oUNYOU;~K6LUmZTe P$F%a8Y@zG>C-D6X1!;)D diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/BaseCode.class deleted file mode 100644 index 5f378ee192380d1077ab4c22da251bcda02c9fa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 261 zcma)%O$x$54256(s|tePC3NGgMG&kaE(AdjAmcO?b!enbMX%<<19&L0UD&m&7ZUjL zz4zz&0x(B6K#ee2^J6JaB{kJ_2(7zt5}DY`(R#yo$5%Z`Vj>k2+Kd+xaexM)f3`Pk zyiH=WJ)YSqp%b(2*k+OtX2a2@!v6bU$64-msWK%DD%e`}x>u`tM(FM(PaUsLo(a95 c(71GI5eW67AW$p(nrNZ)49YAn?Q)OK7qQ?=asU7T diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto$ResultDtoBuilder.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/dto/ResultDto$ResultDtoBuilder.class deleted file mode 100644 index 68d2467d981861f5d5f69c6491562331fdeabaa5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2521 zcmb_e>vGdZ6#h0hIj$ha0pc_iCMNptzOwuM!gEKh5U?vRV2eXzJM_AT+q*Wkq z(#I$>VPIf}2k1j}de#z-6L}c7rVX$)c5z=(-aj4@ailPAAZ_9Ym<$ifVtZNIO8QoJ_i~BbP2tt8 zhAV5_wZvOnw3Q=0tFEN=L{xTJ6_|J z&s*Y+^qZE}u*zMvqK@aVi3wyF#+{%V+O`PDnr-MWSrd~u$}nQfx?spwcC)K04cxIm zZemJn87(s94PjuK;chpd|D0JGIn3yKo?uwZ50u76GP0zku%5y)F=n=&Q$Wj~3nmC8^3{x%M6ea1|Ts?EtChY>nJw@ew&FYfGEE>)dzrdn_DLn+_qZRsKJTiT0!*)L>*54%lsGO{h&-f6VnMP{OCo z9_Kd>+6(VP@j?3-t?8rAu#o>AWMU$v2!DK=odX_^cqi+c~+Ph!+aVG z_|3rY`j{;;%?I^tj6y7PukMOqwjyOSY!{R1_ek~_ zKHYAMKI$)e^!rST7^1{ox9TWC)iGB+I+`Vy2Z5k&%&q zcTkinjlC4zSHK88=>h`71$rB!HBal1z6TOAx&ab0n)mx;2HDdMNEij$S@e}@$=nC9 zcOB?CdTK|Y5H8YFJJ%+caGBQLFc^h)1;ZVnA_3}vjk$~O>G2nBhp->&c%%|Tk#d}y zr5$Lr%s%`HTutE5yvLz|@uND$`1)mU{FAtrz^C|+42&PuJjS0);7|3(KQ=Ia#9xd* zmB9Z6*QunM$fcOG+-KyT&+QNeJ5;6}N@WMfKH$gxghbrt`f_^%H)HG)wW02TX^s04 zKlMU?!dc*3;M0A;NAYW?KW^=z<90`u!yQ^v-$c8<7Tw;GxgZy6XOHCSQj0b6aQ(55P)z~L`kHTQw;#V@KnhW>3HQmncf8ildx(+Ji)I}AB(^WErpig7R^&IbG`-LoN6IwF0?RYPG~KR zex=hs+@>`JMr#^(Xnoqw!d*<$j`HKG>bw}&RlO9|A4GMOr)q1|B`~B>Xg&@aCOby& zefq4xACS#p2D4O9H8)LGJ?S49e|w7J7xLMKR?cTKJj7fl)1o-lOw#-YU8S98kc}A_ z?SKx0i$H@YT>+H)d7k( KHulnZjLaW;Z92{X diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/exception/responce/ApiResponse.class deleted file mode 100644 index eb16b440fa941b8c7c8f4a667d1587133d6de9ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3588 zcmcImYg5!#7=8{5tU%OdD^#!-FR06cq*iNN7pZ_3u*;=_wAzbivxhaBO?Hy3b^k%X z^uM$-MQuCNubuHnb^4qnyJQiaMcNNZPTuogp7UPM%YXj(3&1Qk)94bIU674yt{J$V z-df$CGt9@*DeD#6Eg7~hpPI53Sgxac(yzIWDfQW!bvs=9vLD^(NuyUo8V3Xh9~n;! z-8P(xKIgi&G#r8S6T_~{l}&-++`aMQD;$M>^kX0mO~XMP61d!g(g~rFoL=-aF$42ucG@kg>lm}s`5wId#pbs zkG{=F@W=388t-fP03TB9wKC1%Tz3L1Xz)mWZ0SL+6GO^>g*QNIS3frAmK$?*%Pvc= zFn|*{nZ_v%V>m5vp+ksC9Rl4YY81GV^ncTlugBkxs?f)fp4E`UxImxfuhmVHv1XVP z@rqM4cu#T*i*oN9cOdbgTNN;Rn)tTDA2@ zEL5L$X|$O1Tl`=3+?w=)MnRw_EIm44&8j#|Hjj0YT3_eRRA;&t9n-Ee8w4)xNnmVS z@WArQ^8MvI#Uf$2PAd+LT=DJeypV{_o->0njylBQqUFftdbK3Ib%V}Ufpbm6-Y`5% z{l~;!FKjOY)B8b8t7RiF1P)Lg+o(@-^LtYm3AXoNMrODjbfeY;zOaAw(@d5b+Q=-) z;GtVyHB@tA`Y{_{POo2nH4HQR3M;L3+;{dEjoq9Volc#PnR&yo?65#8`#M6$_-Xa; z|G|YF+y77fr*Wh$Za3MXw%OZkvn+w&p~ciW7B{&?=gBT@E?czSYRP@9-;|E@j6jy@ zDmAQEoBg$Yw8ssA-7YsC_O`ac(KMb&)z=Pgm458Ox!uE((9km3zvk9GQ{J#t_sAsf zMN_K53uucD&oFNrzAt(3fyAs#qa<*#qmCXo(`HjmgEMAwFOnQiu@25>_HteUyOCAH zk$;!@H^6U!&$LfsR!u?@vuf6bU(Ppl;R@GXmSl-uVTav zA`XQW;#2~$AmU<-cw$$?CyDqJm5mYcX(Gynh?`g<#7&&MR1$!eKqo?UJt9OGIruiW z*RW1J%9peJ6a4n%f5Vw=Z}o=6!=Wvuj|{j&PoQNXqAZcJ!AJ3b%`b1C^x3Pb^E*`! zzJ+RDt9Ma7wNrKWEmVJlyK$(J4mrP5^-xEuBR}8U;TYcjSry9XKk*bQ8AtQa@kxGS z3up3^Tgd0nZDA@u(!GWAzj8N9+b9qNq1}s-R2&G(#}%els-#qgjfO>~s$*1L8) diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/config/SecurityConfig.class deleted file mode 100644 index 76961ba093fb8ed5c83dc0016a28432fcbea24e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6727 zcmcgwiF+GI75|N6du?SCE3%U`4y0*u6Iph=xhMgZ6kAQ|Byw%y#3l)}tfaBL@p{+Y zT{%f9Pzv;*q)_g1L%HulqXd_ha6>8AKg7qIJ*2g~mQ^bs-zR-DJI6bJ^WK|z?Y~Q} z0vN`B64bQF*nn1so+5WLre$YLV@4~MEY;CWgW*WBKWkb=#a^&9qc~%!^Zb%&%_*07 zPRY}9WyPLJvt;FYIst}u84@}eI`bvlG3T{MIK#nJ2--YfvNY!kQ56CxnP7{Rjvhu$f^rN}k&7s$rOptAi_QuB7P&PU4O_&cdY6iUgab&k@zu@(hnABNeSB9Fi;A zGPN5Tk%X+(g1E5O7KTzXJIb%zXXYK#QYyWNM%4TqHwsEoH*>14)UpYb^8A->Otz3R z!pPk+dW3~rg=Z6li*_-(UB+$RsBO(8(CUrul<_7px>YSXv!+G96{bDSA1-m*q0McG zi_N#o*e#ZAG{w4uS+l4aV*E}SZ$TeJo9h{ZAHm|Hx~M9;Y7~|6+(n*u(h~MC9Ioqb zfx|%cJJK7D@C=NXZz$AVa*CG-2}|&Cr~q3>%XDQ=J%sB4NLb131VqT(SCk z?ay$VIZO(>`c9Ved2ZXIs!`Cn#V}FVzXfxi6j09E+)`YlDgH)fd=-jmA^u?*Z^hlq z_#Rg^y=3uQ$agtKI+#cBb_ws0k;Xj?xw@Y6&vngl@J2~WI-4Ful)SES!|}BB0OMg= zEMnMf*I4uV8s}CZL|~6-YI!dr2s>6km%FFIlT$~H;hgPQYTjAS#RsP!UAc&AM{rce zy?7TXHB;lNrj@GkRAb?wd8H(bEIVzJjz}9FxR1`6l1jyJ9QR8Ym2mpEeWQ=2iQrH5w<~3(n*BP!hm|N@l`IecK06v7%GA2cJv0Y@8z%#Bp zyfPt%vzU@FE#o~n$1v8U6~o1d2o~KzcnPs&crrR-tXa}DeTbHeN5HwjFchWo>Y~JO z9{nA7kb%)@dOuVNIT?8rC=X=4$qgaXO_1Tg=YOFkn)W zs!|-cG%5)uREJWxkalDx;mEpdbrX^755tmR%WzO4@vA3x#58rT8Vu5+s+aiq48ykM z`S6+cSGf9Ll5rUn8oC2%GZrt1yrJqO=(eyKgKhm&R6S+P0u{PlStVH#pQ)iM%LbOxEPmj#;J7bzhH5{F-Ql#sDT2uY;i3-!zOb(C{;q0k7u+T>55-05q4!dQ&ZIg!=P z`J6eY+|Lb8S3h1L#d>sgK37otLYV41#86xps)9h=V4as12dT|12pI~I9$5o3Zax}v;&6?&G$`)9MT8H(Z(elryTXPihV) zH_Ai(yy&r4;8pXWaRJBO#vesFx5=Vbn_Wr|T!ZL_b2k{nRpQ+m}T13+lDt z1LE5q?Zu&V8F$>oT~%JYT$%O|&%LxciS6je?MP8i!5~R3WZ35M@ns;6_y`^+Sw2d0 zLYI%>3F@^m8U~)Ezfj;QY8gFtK25K-LfkPTvVq#J)OGAl4P3*%)ZjH7dXa|0T5oYV z?)H{eICi;q6t?PeE!pC7>};XoI1R+{37QgIp8?`6;)qgTaGfCH!bm*>sh6;?jJMsy z5$YW(2uVDDxhD2tv zjAJ)(s*JG^dC#ANA%AYgXYg59RElFhv7^4Iv4K<>?=Iua3JS@PV6lo|F?<1EbOoc# z;?r96X$`%M^XIPPz1e}p`)=T&GI-F@f{s2t(0T(4Wh@Rp47Jxc6zZ3 zeT0ia><-B=gfHVOw5|ht@Kt<`c(>x~^xcAQxG;!O@0;}fE&6|({zr-GO)~Wh_zwL= z@B8>6;oj^064xqYWxsa=FZ;dU;J5e-(deaJ$MILZMsl5^_V2{?b^H_m#*Y61%qxY4 diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken$RefreshTokenBuilder.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken$RefreshTokenBuilder.class deleted file mode 100644 index d4bcc2c5e8940f28ece0671ea5a190bbf4d6c439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2488 zcmcguYj4v?6g`tRaheS@eFTzcTErN}8M4~MWf)CREoom8nT2BvProH`KPkkZ?gSE}wP&`>>xR{I(m9JLq zCmp5ApDW@#E-)N#^G#7#UW@A&Qm@k}(A?7j!$OiQA-A(PVtM`CDd8ASJ`1VRZL#QW z%Rn|=ao_Wl=2}uTQw&$Nn`+a3C_LeFEu0#|U|l(aVYneZ@i=TZg}=<{L4#H;?yhiO znt8l9sMjSitu_w2erem0)cxJ(P4%!smmUeduAHab=WU^dPigO}>29P$Q}a*Kh}}JV zSXN}4rav$&R}Ma3Dso<{-B)a!B6OQioF_wJNriq(%uBN|N0OcEnn4rAV-e^D6;Kx~ z%_^k>?endufI%L&7;g7Gl$^x9uOgjkcI9l;-OQ-d!KPVtIEOzAsNs%EX*=Y_GOxufQ31R)8B{E$q{9Xb99U$Vm*{FpK|v~XHG#|{ zRZP)68Ilp=GRZMSH6k?Ptf|ZY)8!2fGuTUYEP}*T1dj8#Lg=hvEA8PQ#?>_bBm3r$ zJc;?uq*wnKuBGvh?3+LGH|8Jf1IW~ zkEi;61HZ()Go;_}mUlVd<0nJ=0cU~l5tmblv-p*$GWe~>rkkC$GJdCfo`T(`-yfKv U*(hQ2^Z{`XL-eIq#{-=B3J?c=ZU6uP diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/entity/RefreshToken.class deleted file mode 100644 index 2b2c19ba25753baefa1db6812e4dfd060da139ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1947 zcmb_d*=`d_6g_1--Y~>54uoNc0Et ziI8R_K7fxxoNBk6jxowh@{p?Ab?&*_sq)uv|9t_lfQ1}V0^MhFYsu?q&$ouVwMTYm zQ>w1jbG^3hT7m3@zN5FSjZfNIk?(vNtT#O(EUC2lp8PhOG#-VH+m(J10+|vfkQK<@ zb(Ev;3#8}fTSerN$)O-n-mpK~mTRk?^`gBYJGy`aDCKalghQAVcsa~&pYjw)IbFtW z>;!Ijs&~i4ohqS%!^|}dq+j1bh1zkg)z$h^gqkkl2#yL&XwwUUN`mT@_8pQ599s$5 zqrPl8fzx*7f>NHgwd1KkV1^Exwy$mLz4QYo&{B1zRqxVmcI?qpsibdf*=^ZwC<)64 z+St9VJveIPOw)>{FTq+E z8Y3Gj=R+vd6qR5{c{3L@7Yh(lm~ZoaYK;~ghMQ`Dl@ zWl@t2N6F=|-N_Y95bm(!}a=YZ_^r1fLxV^*RkD}?K_4d=E>-FhofED z_ktMkx4DGEBC!ta9a$#MV+GE)IFsj6uY28>w$F)bPL7)GNo|B(Ve3aOfIxwVtFiKj zfSn+e9Rbk;AW+>!FqD1ql^6OQ`NT23ok~6l*G#ydiSKdrp0L#8%<9Lh5%rj-hjwdH z;J~sB^mA$E$>K_N#e1^o+Cd;`9!`djUnHF6HP2IrX{5{pLp{#W`i1tfXk`i#CvXy{ zcyv0z8RGaR#JFj$r-reM0oVAYGyuTDby}lD zH<-Ce;VnHgT&sqtc6l4c+LdjTYge~X9px=XytM&7=A~!cJF_$0F(p-_CZutb`-5o$ z$~-FbV|iC10=Mw%4;B53qG?33qRJi>@m}Ee7^3nh(e$1~zvGWFMAcEEnLUUy_%o{E huSipqq`X7>E@wh9+{1kunP`R&xHG?v6fhp5@E!fd#Vh~- diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/RefreshToken/repository/RefreshTokenRepository.class deleted file mode 100644 index bf1935f35967847965f8605635dbfd72678fdd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1065 zcmb_bO>Yx15Sa=0=)7KrIPElnn zmwLn{vzU4#T*)S)~{CR zFo0)NwkbDMZfCf%Jgo?`!Q#v%k$Jf^{hrRmxTI!QuewBi5d9_bmA=RocixcrZ)Z)d zd8V^8lS_Vi^Z(6IqZVt@`;HXf>}zjQ+!d0@Kdtv_Vco6DrvYsKQHUi^p>}0~d=@3aE(@zMz%qt)P@_-fEBtm=~LQn%@$1sf$msXCCpt|yNX$-H1`O5pVm3H3O%4} S10KSo+5UKzu0^Sy0Q?36pile& diff --git a/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse$TokenResponseBuilder.class b/out/production/classes/HeyDoctor/HeyDoctor_Backend/global/security/jwt/Token/TokenResponse$TokenResponseBuilder.class deleted file mode 100644 index 82611a559fa5115696f01271b9360040d9c8f48c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1826 zcmcIl>rN9v6#k|_x0E6kQN%l1u?1zl->N23gJ|j{KRNQ?zg5V`!o= z(ZmPvp^Rs`6Wf&<8J4k0Rf(*m+CDFR4JflK)hgw_U-jwCwJ(DS>}Qy)^DR+Q(&OftZ#Ib1hD)OvX5w`F;TR4t zH>L6G;(^@uwOwdLLyQiR-yT9IL}q-ug;tn5qixLL1?aFvsy zl~x`PDm?VMV(o9r&YsS_pv8%Qu8Sdu` z|017cC}mVs!weGy+E>{O=@r!sJ+bJwKXFfk%v*87cr3J8QkohxyP%ZOCghDMLpp`C z3>SK}dTyO2%XhWdZB`gdVm=KQ=N(+I#eR_?x9giPw<#ZPPF8IJu~ltyM0E~aT(CTMmr+G$?Wd4O(7Iw$EIq(wuQ-VPGw z5|>%d>}RkKL?kdpyA=i;9HQL5Mdo%=-Fdm{Ux4n!gy zC~F|{?-)rBS+p#U6FEW_PtcmyIQg5wQ^*m9HFy;RZO1ZSG5TWmBb?6|i?Xz3WVA)F!o1oAyT7uvj0%+i@63+HIf;S$}(Nj61)fy)@i K6fRM?V{ zk6C0GmiH5FRaZ7tteT;$YHh5riYPMhdSJTzgNUt+K6OLhA&IfkT#SIpHs*mXWZsMW=YpK|2}V zSJp0r*@c%yh34AQR>vo%O!dyq41<4Qk|>ZbRNBXhn}6ccY4CPsYVnfAxd^pA4-?p=VOafG5X$?e|W?z8Xvyr2KR{NpbGakxP=p;6|v@pO1xb56)oq zB`xa;!z#t1uP;eS+ElblF(l4{!$qnnfjmUX;%OBf=wvu6_?qC(y7wW&E4E`0o!bnn zM>|a@nbb@!o=%OgWReq^WOj08 ze)d6fhT-mElx~1X4si)l6@BPuIC0Og40n_v7Wuz$(Od|F7*e3A_!z?sbBECMK(?qS zUTa_shLgKN%Fq>Q7>KuUTfrwP?%-2~@TH4($;G);CO)5H`zj`pVn`g7YELmgF=x0_u`Net z=6uyKy}b93$bAQpy|paoUohsQlixi+TEVo68O$;)zORcITJ?Hnt$K^^n(zIp`IU+c zvJ5RAvkdp&4X;Gu0&)tzmeIR-z|Vb8W@wH?a}3>?D&@2?&l!%9H+kH$Y*&{_iUQzv z+7>0vsmP+XCiF6YVv9{J&vnbu)^u6qw>5q5fR@wEDyOJu*^(26tG(Y-As_M?9&}}) zAL24nJp4g<>km{71veb0deFs}-j^Xrynvr9+tY@{XR75q7xOx4PLibUf^Oz?VaWH| zV!&NDh>G#!ctNePp}RZvs=ICr<2#Rn-5#Rj^?rd$nW!t?l$EMp$G~u!KQ$b;J`plx zjx1gM`9_J9WIdI2sc)OsD>Wv<)0|{4&gs$<{|bnx>v z2TxgB4P3kuQwmaG46k0`=3)G*XoS!xFu<(zs0jZyY?N){vS*TI`U0HjPd6 z;afbSaRp#)2zaZNzTd$5cc4tm$-oXy&Cnz~_yS$O<6IL)0^Nas(Do-TE&Yls&k^~% zJFtTR+IaIvwAIesrD@#`xher@ytjidM_OKByt`!wQ#+XZ*%Nw|o;RV=vl~I2BN=;0 zr3=)}T*NTVCUFH>Ttg8zU?2h$QP?!s>BOinH}WXZ8&IKLoOBDKgf*Hq5!xe~b0X(5 zX0c8P0oo%=11Byv=u8;RFEOm(miHSx8^R{7wov~^1EU0J(qWLmi^gVBP=Z!G TOG?b z{qDWry>sWo|Gx7p08RLpA3l_5n5Ux@^93r0%)_P;HRBOuXJ|-bMBv64xSgc`*j=S+Gfn_%bM*RJrDFSP13It{R5~#g# zO2aY%-+^X7mg8Ox_vyGF)dKg<)sch0Lypo-3kG9mI5=qXZL96jTQvuoGt!S3VP|yM zwT++*JJRI>&s!^Xd>X45kflqvBqE(a8P*8YrN>@QpO{RazEnN^ z%*E;B6Y29;tEVQ;O}}tz>e=!1izm~k-<~>ia_Y>bh6>c6RzsbRdaNZ0<_Z*;XT=9> zfyIT6$a=K4&yNNe8XnNG9uEpEnrknELo>Bd(u$H80{7L_-arVhY0i8ndQxV7G9jJz zem^#1lZMSYK8J?|?(pL2MZCASy;Y#hwKFiNmQss^yAh9Q*rMZ6JXR#!d9?zi;izfF z1e%K&Ss*zV{~~72n4qC~7T8wPm42pMNd+3wreiC%5xbR0BxQdSbF3k&(z8r0CcSPU z-j8;4XxO2n6J4{CHaMDaC2hEi3F64O+hfIKmlcg#3;^@DLxI?-a(x$LTGLV6rvUBI z5mbQu^2o5|n672V{Y=VU9evnKenzAlv?6h)4;e@&W*uFGol4q19s6;B<|VjiujLL3 ztk0TPNc>w{s04jc$Cp&lwXhueb8B&!3pOcrtzJ^2ZX}j}XEd`gKSQ z%yyJl4g|^*$xtFYUR={&ghGnlgE}k>k(I-yLtYB3Do){D-sc$>R3M6&hPVzJ!vfVs zB06gWBXC$c*1%}SYmFJN6>)P>a?BFNIv0hbBLT9j)XO{;qFaIHj>8zya74!_o)B1i zQ?z84=LW?I+25j)hU_v5v!-Rxbb|8nq>P7)Q#nC(U%`_azN+IX91~c0qdE%plX|<% z(Wq_q&rR@b8fy5Oz>>L}GoUJP94B-5Ipzt^TQ3-en~npFTT2J@!VnhH5y?(mfXl>r2~_upCx7rQw?bEAu6)JsOn}Guq@t zk};-gZe1C^O@dL`^h>W#kBz5aJ64?@JDom%Y=-U^o-U}{&+7QDD%$f#OouAI_sLFB zIRnaQNhVx7mQ!AiF}$GRMIGZfE3h$l2H*TN&dX|Z9NTH(>Cd9k-rd*K+1`4vXXlQ# zZh_66wFX?y%ud+&nuj?hLRW+mnpDEcxy6!&bryMP*YwZo6JZ zXEs%JM(tS0K4ff@amhL_`?+Olc1vH84I478#jxs}1{^acM{MVi5t3#+VGLw&8fH#| z(Pu`Jk|IkFs{!4->gdpj+rX;N=H*FJG((L$JFU3vPR2sg=`o4XYhl7>w9j-b^`4!a z=MJh@JuuLR+RLh!ot`{ZMRs=eV90NQ( zGL<5~j8pj&bLNy49h~lx?x5YzicFumUN5|8M)e*!;K;;aR(BPH>_BmF@lU_zi7V4s1;(jj7drS2=vpwo{J&aakIGVk0_=9%R3CpdP zqKM8)+RF~VK_Ovg7G)-TA4l5m=&&?e*fZF1mel}Ou$S@iw&TMx)i*=nr+k~w=g+)Z zUOPDcImZ=zPVo0;0#|s^p-a4FZ}F4CH^FCV-F1jJy?s~tucUxiinsZ%XBOZZu6y6v zJaI;C?xJpmVDS}bbyu+@IAz4Z5!P|?U&a33x{d8OnUAGadu?1_;!g+k0iqLSJ{g(BH?e zvtAGIsbZzoS8CT_<{g*e!={4pX*uU;_>IRj<&sjt7v+xM=4AexzEs}qo_3730XfG3zW@LL diff --git a/out/production/resources/application.yml b/out/production/resources/application.yml deleted file mode 100644 index 52af950..0000000 --- a/out/production/resources/application.yml +++ /dev/null @@ -1,96 +0,0 @@ - -server: - port: 8080 - -spring: - config: - import: optional:file:.env[.properties] - datasource: - url: ${SPRING_DATASOURCE_URL} - username: ${SPRING_DATASOURCE_USERNAME} - password: ${SPRING_DATASOURCE_PASSWORD} - driver-class-name: org.postgresql.Driver - - jpa: - hibernate: - ddl-auto: update - show-sql: true - properties: - hibernate: - dialect: org.hibernate.dialect.PostgreSQLDialect - - security: - user: - name: admin - password: admin123 - oauth2: - client: - registration: - google: - client-id: ${OAUTH_GOOGLE_CLIENT_ID} - client-secret: ${OAUTH_GOOGLE_CLIENT_SECRET} - scope: - - email - - profile - redirect-uri: ${OAUTH_GOOGLE_REDIRECT_URI} - kakao: - client-id: ${OAUTH_KAKAO_CLIENT_ID} - client-secret: ${OAUTH_KAKAO_CLIENT_SECRET} - scope: - - profile_nickname - authorization-grant-type: authorization_code - redirect-uri: ${OAUTH_KAKAO_REDIRECT_URI} - client-name: Kakao - client-authentication-method: client_secret_post - naver: - client-id: ${OAUTH_NAVER_CLIENT_ID} - client-secret: ${OAUTH_NAVER_CLIENT_SECRET} - scope: - - name - authorization-grant-type: authorization_code - redirect-uri: ${OAUTH_NAVER_REDIRECT_URI} - client-name: Naver - - provider: - kakao: - authorization-uri: https://kauth.kakao.com/oauth/authorize - token-uri: https://kauth.kakao.com/oauth/token - user-info-uri: https://kapi.kakao.com/v2/user/me - user-name-attribute: id - naver: - authorization-uri: https://nid.naver.com/oauth2.0/authorize - token-uri: https://nid.naver.com/oauth2.0/token - user-info-uri: https://openapi.naver.com/v1/nid/me - user-name-attribute: response - -logging: - level: - org: - hibernate: - SQL: DEBUG - type: - descriptor: - sql: TRACE - -jwt: - secret: ${JWT_SECRET} - redirect: ${JWT_REDIRECT_URI} - access-token: - expiration-time: ${ACCESS_TOKEN_EXPIRATION_TIME} - refresh-token: - expiration-time: ${REFRESH_TOKEN_EXPIRATION_TIME} - -springdoc: - swagger-ui: - path: /swagger-ui.html - -api: - public: - hospital-key: Wjn354cR5FyaLyi8ljwEFz3haoZfNnwwkPqzKkSorTG/ekqLLvQ7zUv1aZdZ4axe4y0UDDq7mjEnNJPYyC6CUw== - kakao: - rest-key: 63e456faac2c82ccf10db8911595efc4 - -ai: - api: - key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx - url: https://api.openai.com/v1/chat/completions diff --git a/out/test/classes/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.class b/out/test/classes/HeyDoctor/HeyDoctor_Backend/domain/users/model/signup/EnvFileLoadTest.class deleted file mode 100644 index bd5d1ca207bc856a40619aa6778493482e1f2967..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4670 zcmbtX`F9i775<(qBUu)kSd3W`0VbGr0Gl+}KoGWU3(Jy`gjv!=9m`{TY8NlEGYL;nt3)NdE-dz936?S}$*8c-jj;HZla;q|s(IMGS@cQSzvm8kCeCp+_AOWkty7lBNqoYLAo_bh%H-3=2VJ zq(x3kVO8u&NLLIkQVi02GiqGe z;>9!A%22z|jSQ82lF_ND!;)relk(Do@0)?%9%+qsQhvVN3ggWP#L zz1W4_#9!>`VOV89EE^7u+s<&4?eStS_EBu4)xJK7-091zn(nP15W?nMqZ{=MyKGAO zM?_5)yVH_?>$&q={j*bJvmafb8=rhI{i%QM(&WR-*DCQWzT(1jUK~IJdCRnd?M#Mp zNz-`vY_(If^t5T7BirT1L595xC2`@#gIjkR{O8VdB8RBiHD{8{n@F3X-m zrf;K-vzI34u3Yv%oEU#_^WN;O*XPE@XWyf#+y2?H3$s`5&Au}}_x{-2#XGZ?#%OJW ze~a$HF|@l7_M!uk$5G7#F)TeR8~rV!obHfxUF?$>I!oB;g%V~dtwf+(H#9M2_L3J#e2u}O_A<2DO=g8|vBipK>=o3E3=gGdw-+f; z@-9jBOR2#wE9EGvRul&kX-V*FAF-o!AK$*YIY+{=%r-wU{frZa6@6JE|AB9&7oMZ zDM5Y+xiMTseqRB3Din={dqeIj?tzg1cM_CR|eYNt;1-J(_?$}xYLVU2xf zA@Ns=h}RVm|3(q<`W*3yI3j6JB>w6=@kpR67_$?9eV%xMC`bI8^Te~_LgE*Sh}Rbo z|2EZ~LW}Qj@E<#wcp~93DtojpnWJ%$%0GWEgul$NKG4(@jK`CSXnQb{44&!?#R7>? zG?Gk&cRPA>sv6zs6?DI6Z&ZdYcD_w%x+bj4K(}3zjpW~0Mls|esY}*nx^DxDq8g%+ zyY-zuP1N*I%@50p6v+&AOIm_o=jQF764SX_ zV@*1ZewoTnYk0ns`hR*Vf-fHt(-{-Z>!AFVNo~@Qtn1W= zwVMl8hgCb_(P7CVUGvD!gEm)Ncf(<#vN&!~Q`jL6Ticd(NJhWf(<$;Uk9t|+P!Xrz zP&6_cG0t@>^MpzsyNxcRDuepR{56&{DV?|8^xHs&dU0VOGt4__fj17k;Uq>I9wUxw zJ(AW~M$JB>#O9|*MJnFe+C6_fmpd$UQ*M^W0&$sLY!;ycv6HOMPd|3nD3~v&uK!>v zZGA#`;^ytln`MTT)Rx)X=H1+_49}JhP*pVq4nQrFVo;ke#Ld3?D9u|Y*!ma~aBk`cu*rdj^6K)xp|ZMs3bhkhx06rraNy)k2);W0vcC+UU^#zntaetH&)|>~pZxp! z&S?aEt6shQ^Po|C7gH1R@qZZWQ1bsZEP>Wvd zfPtNOg+6A^W4EbcJsD&V>Tnf5qtRv@!N)Xma?LEQ`DhY9Crh|7f?v?cK@_=#xWT#X z+Pv%<&Ha*Q*P;ANEOnvMh2>vjmkVp@uj(II{7+i&D_TI|KS_^Y(|-r9)9-J{nhw(~ zrwE;UU=ue&Yz7Gjw);-{PWxV%#*0%B`LtwC=Y8fg8P~ysdQ{Oz(nd7c{I|){gonaX zLisH{e@7TsL->@2H%deISrMf8B!CeHq`` zJoIQu=yrk*6ZCO{jx7xOHbLJh4SmjnKJRcLEoK+ zK0`6beO<$y`lbc_9p78NOTM?K@jc7i-!BQCBJdtmV}PP|&<0-fcyCWr2+t6BHvf5b z;QuOo8>aDtTM&3WFcIa3LPgbY!)`@7wi&M>C;fjCIDP!m<#iz)Z&?#X=^k%yHyRKhIMcaVxbN zi^3KRYNu4ipc64&12Hv zxGI&EnV85z(&K%lwDnC>ZY$6x!D&frAP-0r8uxw>D()(;{r$`d Date: Fri, 12 Sep 2025 01:44:05 +0900 Subject: [PATCH 04/10] =?UTF-8?q?chore:=20hibernate=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bfc64ad..c6a82b9 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ dependencies { // PostGIS (공간 처리 라이브러리) implementation 'org.locationtech.jts:jts-core:1.18.2' - implementation 'org.hibernate:hibernate-spatial' + implementation 'org.hibernate:hibernate-spatial:6.6.18.Final' // AI - Gemini API 연동 implementation 'com.squareup.okhttp3:okhttp:4.9.3' From ada42a2feb0aadf92f44fc1b9b9580051a24e136 Mon Sep 17 00:00:00 2001 From: junjinyun Date: Fri, 12 Sep 2025 20:03:41 +0900 Subject: [PATCH 05/10] =?UTF-8?q?chore:=20json=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A3=BC=EC=9E=85=20=EB=B0=8F=20api=20=ED=82=A4=20?= =?UTF-8?q?=EA=B0=92=EB=93=A4=20yml=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 9 ++++++--- src/main/resources/application.yml | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index c6a82b9..4cdf2d3 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,9 @@ dependencies { // Web (동기 HTTP 서버 및 Controller 등) implementation 'org.springframework.boot:spring-boot-starter-web' + // JSON 파싱 및 생성 라이브러리 추가 + implementation 'org.json:json:20231013' + // JPA implementation 'org.springframework.boot:spring-boot-starter-data-jpa' @@ -44,7 +47,7 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.2' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.2' - // Thymeleaf + Spring Security 연동 — [추가] + // Thymeleaf + Spring Security 연동 implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' // Lombok @@ -68,7 +71,7 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'com.google.code.gson:gson:2.8.9' - // dotenv (환경변수 .env 자동 로드) + // dotenv (환경변수 ..env 자동 로드) implementation 'io.github.cdimascio:dotenv-java:3.0.0' // 테스트 @@ -83,4 +86,4 @@ tasks.named('test') { jvmArgs += [ "-javaagent:${classpath.find { it.name.contains('byte-buddy-agent') || it.name.contains('mockito-inline') }.absolutePath}" ] -} +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f42a521..649051a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -39,10 +39,13 @@ springdoc: path: /swagger-ui.html api: - public: - hospital-key: Wjn354cR5FyaLyi8ljwEFz3haoZfNnwwkPqzKkSorTG/ekqLLvQ7zUv1aZdZ4axe4y0UDDq7mjEnNJPYyC6CUw== kakao: - rest-key: 63e456faac2c82ccf10db8911595efc4 + rest-key: ${KAKAO_API_KEY} + weather: + key: ${WEATHER_API_KEY} + alert: + key: ${ALERT_API_KEY} + gemini: - api-key: ${GEMINI_API_KEY} \ No newline at end of file + api-key: ${GEMINI_API_KEY} From ab139007f1e93b7e68bd3b8ffed20f41ca1947be Mon Sep 17 00:00:00 2001 From: junjinyun Date: Fri, 12 Sep 2025 20:05:55 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=EC=A2=8C=ED=91=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=B9=B4=EC=B9=B4=EC=98=A4api=20=EB=A1=9C=20=ED=96=89=EC=A0=95?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=EB=A1=9C=20=EB=B3=80=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../location/dto/LocationRequestDto.java | 20 ++++++ .../location/dto/LocationResponseDto.java | 23 +++++++ .../location/service/LocationService.java | 65 +++++++++++++++++++ .../domain/location/util/RegionMatcher.java | 37 +++++++++++ 4 files changed, 145 insertions(+) create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/dto/LocationRequestDto.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/dto/LocationResponseDto.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/service/LocationService.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/util/RegionMatcher.java diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/dto/LocationRequestDto.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/dto/LocationRequestDto.java new file mode 100644 index 0000000..3322af2 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/dto/LocationRequestDto.java @@ -0,0 +1,20 @@ +package HeyDoctor.HeyDoctor_Backend.domain.location.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LocationRequestDto { + private double latitude; + private double longitude; + + // 좌표를 바로 받을 수 있는 생성자 추가 + public LocationRequestDto(double latitude, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + // 기본 생성자 (롬복 또는 수동) + public LocationRequestDto() {} +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/dto/LocationResponseDto.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/dto/LocationResponseDto.java new file mode 100644 index 0000000..cdcc344 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/dto/LocationResponseDto.java @@ -0,0 +1,23 @@ +package HeyDoctor.HeyDoctor_Backend.domain.location.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LocationResponseDto { + private String sido; + private String sigungu; + private String eupmyeondong; + + public LocationResponseDto() {} + + public LocationResponseDto(String sido, String sigungu, String eupmyeondong) { + this.sido = sido; + this.sigungu = sigungu; + this.eupmyeondong = eupmyeondong; + } + + +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/service/LocationService.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/service/LocationService.java new file mode 100644 index 0000000..66825e0 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/service/LocationService.java @@ -0,0 +1,65 @@ +package HeyDoctor.HeyDoctor_Backend.domain.location.service; + +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationRequestDto; +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationResponseDto; +import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.List; + +@Service +public class LocationService { + + private final WebClient webClient; + + @Value("${api.kakao.rest-key}") + private String KAKAO_API_KEY; + + private final String KAKAO_LOCAL_URL = "https://dapi.kakao.com/v2/local/geo/coord2regioncode.json"; + + public LocationService(WebClient.Builder webClientBuilder) { + this.webClient = webClientBuilder.baseUrl(KAKAO_LOCAL_URL).build(); + } + + /** + * 좌표를 받아서 행정구역 정보 반환 (비동기, 논블로킹) + */ + public Mono getAdministrativeRegion(LocationRequestDto request) { + String query = String.format("x=%s&y=%s", + URLEncoder.encode(String.valueOf(request.getLongitude()), StandardCharsets.UTF_8), + URLEncoder.encode(String.valueOf(request.getLatitude()), StandardCharsets.UTF_8) + ); + + return webClient.get() + .uri("?" + query) + .header("Authorization", "KakaoAK " + KAKAO_API_KEY) + .retrieve() + // API 응답 코드가 200이 아닌 경우 에러 처리 + .onStatus(status -> status.is4xxClientError() || status.is5xxServerError(), + clientResponse -> Mono.error(new RuntimeException(ErrorCode.INTERNAL_SERVER_ERROR.getMessage()))) + .bodyToMono(Map.class) + .map(response -> { + // JSON 응답에서 문서(documents) 목록을 추출 + @SuppressWarnings("unchecked") + List> documents = (List>) response.get("documents"); + + if (documents.isEmpty()) { + return null; + } + + // 첫 번째 문서에서 행정구역 정보 추출 + Map regionInfo = documents.get(0); + String sido = (String) regionInfo.get("region_1depth_name"); + String sigungu = (String) regionInfo.get("region_2depth_name"); + String eupmyeondong = (String) regionInfo.get("region_3depth_name"); + + return new LocationResponseDto(sido, sigungu, eupmyeondong); + }); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/util/RegionMatcher.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/util/RegionMatcher.java new file mode 100644 index 0000000..b7931b9 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/util/RegionMatcher.java @@ -0,0 +1,37 @@ +package HeyDoctor.HeyDoctor_Backend.domain.location.util; + +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationResponseDto; + +public class RegionMatcher { + + /** + * 재난문자/날씨 지역 문자열이 사용자의 행정구역과 매칭되는지 확인 + * + * @param regionString API에서 내려온 지역 문자열 (예: "서울특별시 종로구 청운효자동") + * @param userLocation 사용자 행정구역 정보 + * @return true: 해당 지역 포함, false: 미포함 + */ + public static boolean matchRegion(String regionString, LocationResponseDto userLocation) { + if (regionString == null || userLocation == null) { + return false; + } + + // 시/도 비교 + if (!regionString.contains(userLocation.getSido())) { + return false; + } + + // 시/군/구 비교 + if (!regionString.contains(userLocation.getSigungu())) { + return false; + } + + // 읍/면/동 정보가 존재하면 포함 여부 확인 + String eupmyeondong = userLocation.getEupmyeondong(); + if (eupmyeondong != null && !eupmyeondong.isEmpty()) { + return regionString.contains(eupmyeondong); + } + + return true; + } +} From 16ec4fe0d10065f6c0de2598cd3b2df984ee1aa3 Mon Sep 17 00:00:00 2001 From: junjinyun Date: Fri, 12 Sep 2025 20:07:07 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EC=A2=8C=ED=91=9C=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=84=9C=20=ED=95=B4=EB=8B=B9=20=ED=96=89?= =?UTF-8?q?=EC=A0=95=EA=B5=AC=EC=97=AD=EC=9D=98=20=EC=9E=AC=EB=82=9C?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DisasterAlertController.java | 32 ++++++ .../dto/DisasterAlertResponseDto.java | 24 +++++ .../service/DisasterAlertService.java | 97 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/controller/DisasterAlertController.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/dto/DisasterAlertResponseDto.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/service/DisasterAlertService.java diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/controller/DisasterAlertController.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/controller/DisasterAlertController.java new file mode 100644 index 0000000..f86f2d3 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/controller/DisasterAlertController.java @@ -0,0 +1,32 @@ +package HeyDoctor.HeyDoctor_Backend.domain.DisasterAlert.controller; + +import HeyDoctor.HeyDoctor_Backend.domain.DisasterAlert.dto.DisasterAlertResponseDto; +import HeyDoctor.HeyDoctor_Backend.domain.DisasterAlert.service.DisasterAlertService; +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationRequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/disaster-alert") +public class DisasterAlertController { + + private final DisasterAlertService disasterAlertService; + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public Mono> getAlerts( + @RequestParam("lat") double latitude, + @RequestParam("lon") double longitude) { + + LocationRequestDto request = new LocationRequestDto(latitude, longitude); + return disasterAlertService.getAlertsByCoords(request); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/dto/DisasterAlertResponseDto.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/dto/DisasterAlertResponseDto.java new file mode 100644 index 0000000..a138127 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/dto/DisasterAlertResponseDto.java @@ -0,0 +1,24 @@ +package HeyDoctor.HeyDoctor_Backend.domain.DisasterAlert.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class DisasterAlertResponseDto { + private String title; // 재난문자 제목 + private String message; // 재난문자 내용 + private String location; // 재난문자 발생 지역 + private String sendTime; // 발송 시간 + + public static DisasterAlertResponseDto fromApiData( + String title, + String message, + String location, + String sendTime + ) { + return new DisasterAlertResponseDto(title, message, location, sendTime); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/service/DisasterAlertService.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/service/DisasterAlertService.java new file mode 100644 index 0000000..24f69a5 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/service/DisasterAlertService.java @@ -0,0 +1,97 @@ +package HeyDoctor.HeyDoctor_Backend.domain.DisasterAlert.service; + +import HeyDoctor.HeyDoctor_Backend.domain.DisasterAlert.dto.DisasterAlertResponseDto; +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationRequestDto; +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationResponseDto; +import HeyDoctor.HeyDoctor_Backend.domain.location.service.LocationService; +import HeyDoctor.HeyDoctor_Backend.domain.location.util.RegionMatcher; +import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +@Service +public class DisasterAlertService { + + private final LocationService locationService; + private final WebClient webClient; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Value("${api.alert.key}") + private String alertApiKey; + + public DisasterAlertService(LocationService locationService, WebClient.Builder webClientBuilder) { + this.locationService = locationService; + this.webClient = webClientBuilder.baseUrl("https://www.safetydata.go.kr/V2/api").build(); + } + + /** + * 좌표 기반 재난문자 조회 (전체 로직을 비동기적으로 처리) + */ + public Mono> getAlertsByCoords(LocationRequestDto request) { + return locationService.getAdministrativeRegion(request) + .flatMap(userLocation -> { + if (userLocation == null) { + return Mono.error(new RuntimeException(ErrorCode.BAD_REQUEST.getMessage())); + } + return fetchDisasterAlertsFromApi(userLocation) + .map(alerts -> { + List matchedAlerts = new ArrayList<>(); + for (DisasterAlertResponseDto alert : alerts) { + if (RegionMatcher.matchRegion(alert.getLocation(), userLocation)) { + matchedAlerts.add(alert); + } + } + return matchedAlerts; + }); + }) + // locationService가 빈 Mono를 반환할 경우 에러 처리 + .switchIfEmpty(Mono.error(new RuntimeException(ErrorCode.BAD_REQUEST.getMessage()))); + } + + /** + * 실제 재난문자 API 호출 후 DTO 변환 (비동기) + */ + private Mono> fetchDisasterAlertsFromApi(LocationResponseDto location) { + String rgnNm = URLEncoder.encode(location.getSido() + " " + location.getSigungu(), StandardCharsets.UTF_8); + + return webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/DSSP-IF-00247") + .queryParam("serviceKey", alertApiKey) + .queryParam("pageNo", "1") + .queryParam("numOfRows", "50") + .queryParam("returnType", "json") + .queryParam("rgnNm", rgnNm) + .build()) + .retrieve() + .bodyToMono(String.class) + .handle((rawJson, sink) -> { + List alerts = new ArrayList<>(); + try { + JsonNode root = objectMapper.readTree(rawJson); + JsonNode items = root.path("response").path("body").path("items"); + if (items.isArray()) { + for (JsonNode item : items) { + String title = item.path("EMRG_STEP_NM").asText(); + String message = item.path("MSG_CN").asText(); + String loc = item.path("RCPTN_RGN_NM").asText(); + String sendTime = item.path("CRT_DT").asText(); + alerts.add(DisasterAlertResponseDto.fromApiData(title, message, loc, sendTime)); + } + } + sink.next(alerts); + } catch (Exception e) { + sink.error(new RuntimeException(ErrorCode.INTERNAL_SERVER_ERROR.getMessage())); + } + }); + } +} \ No newline at end of file From 7ced0783b5cbf45f19bc0e72a9a0de628f4704ff Mon Sep 17 00:00:00 2001 From: junjinyun Date: Fri, 12 Sep 2025 20:07:22 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=EC=A2=8C=ED=91=9C=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=84=9C=20=ED=95=B4=EB=8B=B9=20=ED=96=89?= =?UTF-8?q?=EC=A0=95=EA=B5=AC=EC=97=AD=EC=9D=98=20=EB=82=A0=EC=94=A8?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weather/controller/WeatherController.java | 31 +++++++ .../dto/OpenWeatherApiResponseDto.java | 55 ++++++++++++ .../weather/dto/WeatherResponseDto.java | 53 +++++++++++ .../weather/service/WeatherService.java | 89 +++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/controller/WeatherController.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/dto/OpenWeatherApiResponseDto.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/dto/WeatherResponseDto.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/service/WeatherService.java diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/controller/WeatherController.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/controller/WeatherController.java new file mode 100644 index 0000000..97a71c0 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/controller/WeatherController.java @@ -0,0 +1,31 @@ +package HeyDoctor.HeyDoctor_Backend.domain.weather.controller; + +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationRequestDto; +import HeyDoctor.HeyDoctor_Backend.domain.weather.dto.WeatherResponseDto; +import HeyDoctor.HeyDoctor_Backend.domain.weather.service.WeatherService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/weather") +public class WeatherController { + + private final WeatherService weatherService; + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public Mono getWeatherByCoords( + @RequestParam("lat") double latitude, + @RequestParam("lon") double longitude) { + + LocationRequestDto request = new LocationRequestDto(latitude, longitude); + return weatherService.getWeatherByCoords(request); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/dto/OpenWeatherApiResponseDto.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/dto/OpenWeatherApiResponseDto.java new file mode 100644 index 0000000..43806cd --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/dto/OpenWeatherApiResponseDto.java @@ -0,0 +1,55 @@ +package HeyDoctor.HeyDoctor_Backend.domain.weather.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +// OpenWeather API 응답의 최상위 객체 +@Getter +@Setter +@NoArgsConstructor +public class OpenWeatherApiResponseDto { + private Main main; + private Wind wind; + private List weather; + private Rain rain; + private Snow snow; + + @Getter + @Setter + public static class Main { + private double temp; + private double feels_like; + private int humidity; + } + + @Getter + @Setter + public static class Wind { + private double speed; + } + + @Getter + @Setter + public static class Weather { + private String main; + private String description; + } + + @Getter + @Setter + public static class Rain { + @JsonProperty("1h") + private double oneHour; + } + + @Getter + @Setter + public static class Snow { + @JsonProperty("1h") + private double oneHour; + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/dto/WeatherResponseDto.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/dto/WeatherResponseDto.java new file mode 100644 index 0000000..d65b753 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/dto/WeatherResponseDto.java @@ -0,0 +1,53 @@ +package HeyDoctor.HeyDoctor_Backend.domain.weather.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class WeatherResponseDto { + private String sido; + private String sigungu; + private String eupmyeondong; + private double temperature; // 실제 온도 + private double feelsLikeTemperature; // 체감 온도 + private int humidity; // % + private String precipitation; // "없음", "강수", "강설" + private double windSpeed; // m/s + private String weatherDescription; // 예: "맑음", "흐림", "비" + + /** + * OpenWeatherMap API JSON 데이터를 기반으로 DTO 생성 + */ + public static WeatherResponseDto fromOpenWeatherData( + String sido, + String sigungu, + String eupmyeondong, + double temp, + double feelsLike, + int humidity, + double windSpeed, + String weatherMain, + String weatherDescription, + double rainVolume, + double snowVolume + ) { + String precipitationType = "없음"; + if (rainVolume > 0) precipitationType = "강수"; + else if (snowVolume > 0) precipitationType = "강설"; + + return new WeatherResponseDto( + sido, + sigungu, + eupmyeondong, + temp, + feelsLike, + humidity, + precipitationType, + windSpeed, + weatherDescription + ); + } +} diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/service/WeatherService.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/service/WeatherService.java new file mode 100644 index 0000000..cdb737c --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/weather/service/WeatherService.java @@ -0,0 +1,89 @@ +package HeyDoctor.HeyDoctor_Backend.domain.weather.service; + +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationRequestDto; +import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationResponseDto; +import HeyDoctor.HeyDoctor_Backend.domain.location.service.LocationService; +import HeyDoctor.HeyDoctor_Backend.domain.weather.dto.OpenWeatherApiResponseDto; +import HeyDoctor.HeyDoctor_Backend.domain.weather.dto.WeatherResponseDto; +import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@Service +public class WeatherService { + + private final LocationService locationService; + private final WebClient webClient; + + @Value("${api.weather.key}") + private String openWeatherApiKey; + + private static final String OPENWEATHER_URL = "https://api.openweathermap.org/data/2.5/weather"; + + public WeatherService(LocationService locationService, WebClient.Builder webClientBuilder) { + this.locationService = locationService; + this.webClient = webClientBuilder.baseUrl(OPENWEATHER_URL).build(); + } + + /** + * 좌표를 받아서 날씨 정보 반환 (전체 로직을 비동기적으로 처리) + */ + public Mono getWeatherByCoords(LocationRequestDto request) { + // locationService 호출을 리액티브 체인의 시작점으로 변경 + return locationService.getAdministrativeRegion(request) + .flatMap(location -> { + // location이 null일 경우 BAD_REQUEST 에러 반환 + if (location == null) { + return Mono.error(new RuntimeException(ErrorCode.BAD_REQUEST.getMessage())); + } + + // WebClient를 사용하여 OpenWeatherMap API 호출 + return webClient.get() + .uri(uriBuilder -> uriBuilder + .queryParam("lat", request.getLatitude()) + .queryParam("lon", request.getLongitude()) + .queryParam("appid", openWeatherApiKey) + .queryParam("units", "metric") + .build()) + .retrieve() + .bodyToMono(OpenWeatherApiResponseDto.class) + .map(openWeatherResponse -> { + // DTO에서 필요한 데이터 추출 및 가공 + double temp = openWeatherResponse.getMain().getTemp(); + double feelsLike = openWeatherResponse.getMain().getFeels_like(); + int humidity = openWeatherResponse.getMain().getHumidity(); + double windSpeed = openWeatherResponse.getWind().getSpeed(); + String weatherMain = openWeatherResponse.getWeather().getFirst().getMain(); + String weatherDescription = openWeatherResponse.getWeather().getFirst().getDescription(); + + double rainVolume = 0; + if (openWeatherResponse.getRain() != null) { + rainVolume = openWeatherResponse.getRain().getOneHour(); + } + double snowVolume = 0; + if (openWeatherResponse.getSnow() != null) { + snowVolume = openWeatherResponse.getSnow().getOneHour(); + } + + // 최종 WeatherResponseDto로 변환하여 반환 + return WeatherResponseDto.fromOpenWeatherData( + location.getSido(), + location.getSigungu(), + location.getEupmyeondong(), + temp, + feelsLike, + humidity, + windSpeed, + weatherMain, + weatherDescription, + rainVolume, + snowVolume + ); + }); + }) + // locationService에서 빈 Mono가 반환될 경우 에러 처리 + .switchIfEmpty(Mono.error(new RuntimeException(ErrorCode.BAD_REQUEST.getMessage()))); + } +} \ No newline at end of file From 0eaf5910bf42dba80704a489013743a190f14d01 Mon Sep 17 00:00:00 2001 From: junjinyun Date: Fri, 12 Sep 2025 20:08:53 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=EC=A0=9C=EB=AF=B8=EB=82=98?= =?UTF-8?q?=EC=9D=B4=20=EC=97=B0=EB=8F=99=ED=95=B4=EC=84=9C=20=EA=B0=84?= =?UTF-8?q?=EB=8B=A8=ED=95=9C=20=EB=8B=B5=EB=B3=80=EC=9D=84=20=EB=B0=9B?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gemini/controller/GeminiController.java | 30 +++++++++ .../domain/gemini/service/GeminiService.java | 63 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/gemini/controller/GeminiController.java create mode 100644 src/main/java/HeyDoctor/HeyDoctor_Backend/domain/gemini/service/GeminiService.java diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/gemini/controller/GeminiController.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/gemini/controller/GeminiController.java new file mode 100644 index 0000000..54548de --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/gemini/controller/GeminiController.java @@ -0,0 +1,30 @@ +package HeyDoctor.HeyDoctor_Backend.domain.gemini.controller; + +import HeyDoctor.HeyDoctor_Backend.domain.gemini.service.GeminiService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/gemini") +public class GeminiController { + + private final GeminiService geminiService; + + @PostMapping("/simple") + public Mono> callGemini(@RequestBody String input) { + return geminiService.generateSimpleResponse(input) + .map(ResponseEntity::ok) + .defaultIfEmpty(ResponseEntity.status(HttpStatus.NO_CONTENT).body("No response from Gemini")) + .onErrorResume(e -> { + System.err.println("Error calling Gemini API: " + e.getMessage()); + return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error processing request: " + e.getMessage())); + }); + } +} \ No newline at end of file diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/gemini/service/GeminiService.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/gemini/service/GeminiService.java new file mode 100644 index 0000000..b477da6 --- /dev/null +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/gemini/service/GeminiService.java @@ -0,0 +1,63 @@ +package HeyDoctor.HeyDoctor_Backend.domain.gemini.service; + +import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import java.util.List; +import java.util.Map; + +@Service +public class GeminiService { + + private static final Logger log = LoggerFactory.getLogger(GeminiService.class); + + private final WebClient webClient; + private final String apiKey; + private final ObjectMapper objectMapper; + + public GeminiService(@Value("${gemini.api-key}") String apiKey) { + this.apiKey = apiKey; + this.webClient = WebClient.create("https://generativelanguage.googleapis.com"); + this.objectMapper = new ObjectMapper(); + } + + public Mono generateSimpleResponse(String input) { + Map body = Map.of( + "contents", List.of(Map.of( + "parts", List.of(Map.of("text", "간단히 대답해줘:\n\n" + input)) + )) + ); + + return webClient.post() + .uri(uriBuilder -> uriBuilder + .path("/v1beta/models/gemini-1.5-flash-latest:generateContent") + .queryParam("key", apiKey) + .build()) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .retrieve() + .bodyToMono(String.class) + .flatMap(rawJson -> { + log.info("Gemini raw response: {}", rawJson); + + try { + JsonNode root = objectMapper.readTree(rawJson); + JsonNode textNode = root + .path("candidates").get(0) + .path("content").path("parts").get(0) + .path("text"); + return Mono.just(textNode.asText("")); + } catch (Exception e) { + log.error("Failed to parse Gemini response", e); + return Mono.error(new RuntimeException(ErrorCode.INTERNAL_SERVER_ERROR.getMessage())); + } + }); + } +} \ No newline at end of file From a87688be4bcdef963e1a1fbc907aee73e3014d3f Mon Sep 17 00:00:00 2001 From: junjinyun Date: Fri, 12 Sep 2025 21:37:53 +0900 Subject: [PATCH 10/10] =?UTF-8?q?update:=20=EC=A2=8C=ED=91=9C=20=EB=B3=80?= =?UTF-8?q?=ED=99=98=20=EC=84=9C=EB=B9=84=EC=8A=A4=EC=99=80,=20=ED=96=89?= =?UTF-8?q?=EC=A0=95=EA=B5=AC=EC=97=AD=20=EB=B9=84=EA=B5=90=EA=B8=B0,=20?= =?UTF-8?q?=EA=B8=B4=EA=B8=89=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EC=9D=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B9=84?= =?UTF-8?q?=EA=B5=90=20=ED=9B=84=20=EB=B0=98=ED=99=98=EB=B6=80=EB=B6=84?= =?UTF-8?q?=EC=9D=B4=20=EC=9E=91=EB=8F=99=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DisasterAlertService.java | 44 ++++++++++++++----- .../location/service/LocationService.java | 19 ++++---- .../domain/location/util/RegionMatcher.java | 36 +++++++-------- 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/service/DisasterAlertService.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/service/DisasterAlertService.java index 24f69a5..05ca6cc 100644 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/service/DisasterAlertService.java +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/DisasterAlert/service/DisasterAlertService.java @@ -8,6 +8,8 @@ import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; @@ -15,12 +17,17 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; @Service public class DisasterAlertService { + private static final Logger logger = LoggerFactory.getLogger(DisasterAlertService.class); + private final LocationService locationService; private final WebClient webClient; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -33,9 +40,6 @@ public DisasterAlertService(LocationService locationService, WebClient.Builder w this.webClient = webClientBuilder.baseUrl("https://www.safetydata.go.kr/V2/api").build(); } - /** - * 좌표 기반 재난문자 조회 (전체 로직을 비동기적으로 처리) - */ public Mono> getAlertsByCoords(LocationRequestDto request) { return locationService.getAdministrativeRegion(request) .flatMap(userLocation -> { @@ -44,32 +48,50 @@ public Mono> getAlertsByCoords(LocationRequestDto } return fetchDisasterAlertsFromApi(userLocation) .map(alerts -> { + // 1. 필터링된 재난문자 리스트 생성 List matchedAlerts = new ArrayList<>(); for (DisasterAlertResponseDto alert : alerts) { if (RegionMatcher.matchRegion(alert.getLocation(), userLocation)) { matchedAlerts.add(alert); } } - return matchedAlerts; + + // 2. 최종 결과를 담을 새로운 리스트 생성 + List finalResponse = new ArrayList<>(); + + // 3. 사용자 위치 정보로 가상의 DTO를 생성 + String userLocationString = (userLocation.getSido() + " " + userLocation.getSigungu() + " " + userLocation.getEupmyeondong()).trim(); + DisasterAlertResponseDto locationInfo = new DisasterAlertResponseDto( + "현재 위치", + "요청하신 좌표의 행정구역 정보입니다.", + userLocationString, + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")) + ); + + // 4. 최종 리스트의 맨 앞에 위치 정보를 추가 + finalResponse.add(locationInfo); + + // 5. 그 뒤에 필터링된 실제 재난문자들을 추가 + finalResponse.addAll(matchedAlerts); + + return finalResponse; }); }) - // locationService가 빈 Mono를 반환할 경우 에러 처리 .switchIfEmpty(Mono.error(new RuntimeException(ErrorCode.BAD_REQUEST.getMessage()))); } - /** - * 실제 재난문자 API 호출 후 DTO 변환 (비동기) - */ private Mono> fetchDisasterAlertsFromApi(LocationResponseDto location) { String rgnNm = URLEncoder.encode(location.getSido() + " " + location.getSigungu(), StandardCharsets.UTF_8); + String startDate = LocalDate.now().minusDays(7).format(DateTimeFormatter.ofPattern("yyyyMMdd")); return webClient.get() .uri(uriBuilder -> uriBuilder .path("/DSSP-IF-00247") .queryParam("serviceKey", alertApiKey) .queryParam("pageNo", "1") - .queryParam("numOfRows", "50") + .queryParam("numOfRows", "100") .queryParam("returnType", "json") + .queryParam("crtDt", startDate) .queryParam("rgnNm", rgnNm) .build()) .retrieve() @@ -78,7 +100,8 @@ private Mono> fetchDisasterAlertsFromApi(Location List alerts = new ArrayList<>(); try { JsonNode root = objectMapper.readTree(rawJson); - JsonNode items = root.path("response").path("body").path("items"); + JsonNode items = root.path("body"); + if (items.isArray()) { for (JsonNode item : items) { String title = item.path("EMRG_STEP_NM").asText(); @@ -90,6 +113,7 @@ private Mono> fetchDisasterAlertsFromApi(Location } sink.next(alerts); } catch (Exception e) { + logger.error("Failed to parse disaster alert JSON", e); sink.error(new RuntimeException(ErrorCode.INTERNAL_SERVER_ERROR.getMessage())); } }); diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/service/LocationService.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/service/LocationService.java index 66825e0..12c4530 100644 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/service/LocationService.java +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/service/LocationService.java @@ -3,6 +3,8 @@ import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationRequestDto; import HeyDoctor.HeyDoctor_Backend.domain.location.dto.LocationResponseDto; import HeyDoctor.HeyDoctor_Backend.global.exception.ErrorCode; +import org.slf4j.Logger; // 로거 import +import org.slf4j.LoggerFactory; // 로거 import import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; @@ -16,6 +18,9 @@ @Service public class LocationService { + // 1. 로거 필드 추가 + private static final Logger logger = LoggerFactory.getLogger(LocationService.class); + private final WebClient webClient; @Value("${api.kakao.rest-key}") @@ -27,9 +32,6 @@ public LocationService(WebClient.Builder webClientBuilder) { this.webClient = webClientBuilder.baseUrl(KAKAO_LOCAL_URL).build(); } - /** - * 좌표를 받아서 행정구역 정보 반환 (비동기, 논블로킹) - */ public Mono getAdministrativeRegion(LocationRequestDto request) { String query = String.format("x=%s&y=%s", URLEncoder.encode(String.valueOf(request.getLongitude()), StandardCharsets.UTF_8), @@ -40,21 +42,20 @@ public Mono getAdministrativeRegion(LocationRequestDto requ .uri("?" + query) .header("Authorization", "KakaoAK " + KAKAO_API_KEY) .retrieve() - // API 응답 코드가 200이 아닌 경우 에러 처리 .onStatus(status -> status.is4xxClientError() || status.is5xxServerError(), clientResponse -> Mono.error(new RuntimeException(ErrorCode.INTERNAL_SERVER_ERROR.getMessage()))) .bodyToMono(Map.class) - .map(response -> { - // JSON 응답에서 문서(documents) 목록을 추출 + // 2. bodyToMono 다음에 doOnNext를 사용하여 응답 데이터를 로그로 출력 + .doOnNext(responseMap -> logger.info("Response from Kakao API: {}", responseMap)) + .mapNotNull(response -> { @SuppressWarnings("unchecked") List> documents = (List>) response.get("documents"); - if (documents.isEmpty()) { + if (documents == null || documents.isEmpty()) { return null; } - // 첫 번째 문서에서 행정구역 정보 추출 - Map regionInfo = documents.get(0); + Map regionInfo = documents.getFirst(); String sido = (String) regionInfo.get("region_1depth_name"); String sigungu = (String) regionInfo.get("region_2depth_name"); String eupmyeondong = (String) regionInfo.get("region_3depth_name"); diff --git a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/util/RegionMatcher.java b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/util/RegionMatcher.java index b7931b9..a3b7697 100644 --- a/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/util/RegionMatcher.java +++ b/src/main/java/HeyDoctor/HeyDoctor_Backend/domain/location/util/RegionMatcher.java @@ -5,33 +5,27 @@ public class RegionMatcher { /** - * 재난문자/날씨 지역 문자열이 사용자의 행정구역과 매칭되는지 확인 + * 재난문자 API의 지역 문자열이 사용자의 행정구역을 포함하는지 확인합니다. + * 공백, 쉼표 등 불규칙한 형식을 처리하여 안정성을 높였습니다. * - * @param regionString API에서 내려온 지역 문자열 (예: "서울특별시 종로구 청운효자동") - * @param userLocation 사용자 행정구역 정보 + * @param alertRegionString API에서 받은 지역 문자열 (예: "서울특별시 강서구 , 서울특별시 양천구 ") + * @param userLocation 사용자 행정구역 정보 (예: sido="서울특별시", sigungu="강서구") * @return true: 해당 지역 포함, false: 미포함 */ - public static boolean matchRegion(String regionString, LocationResponseDto userLocation) { - if (regionString == null || userLocation == null) { + public static boolean matchRegion(String alertRegionString, LocationResponseDto userLocation) { + if (alertRegionString == null || userLocation == null || userLocation.getSido() == null || userLocation.getSigungu() == null) { return false; } - // 시/도 비교 - if (!regionString.contains(userLocation.getSido())) { - return false; - } - - // 시/군/구 비교 - if (!regionString.contains(userLocation.getSigungu())) { - return false; - } + // 1. 사용자 위치를 "시도시군구" 형태의 공백 없는 문자열로 만듭니다. (예: "서울특별시강서구") + String userFullLocation = userLocation.getSido() + userLocation.getSigungu(); - // 읍/면/동 정보가 존재하면 포함 여부 확인 - String eupmyeondong = userLocation.getEupmyeondong(); - if (eupmyeondong != null && !eupmyeondong.isEmpty()) { - return regionString.contains(eupmyeondong); - } + // 2. 재난문자 수신 지역(alertRegionString)에서 모든 공백을 제거합니다. + // 예: " 서울특별시 강서구 , 서울특별시 양천구 " -> "서울특별시강서구,서울특별시양천구" + String cleanedAlertRegion = alertRegionString.replaceAll("\\s+", ""); - return true; + // 3. 공백이 제거된 재난문자 수신 지역에, 공백 없는 사용자 위치가 포함되어 있는지 확인합니다. + // 예: "서울특별시강서구,서울특별시양천구".contains("서울특별시강서구") -> true + return cleanedAlertRegion.contains(userFullLocation); } -} +} \ No newline at end of file