From f033f9991c5722d2ba7f2fd2c65704c243e3e87c Mon Sep 17 00:00:00 2001 From: SSung023 Date: Mon, 25 Dec 2023 20:41:23 +0900 Subject: [PATCH 001/234] =?UTF-8?q?Revert=20"init:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=B4=88=EA=B8=B0=20=EC=84=B8=ED=8C=85"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ac57c42d0c3241a46bf24c6757518d26b8f822e7. --- todoffin/.gitignore | 42 --- todoffin/build.gradle | 41 --- todoffin/gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - todoffin/gradlew | 249 ------------------ todoffin/gradlew.bat | 92 ------- todoffin/settings.gradle | 1 - .../genius/todoffin/TodoffinApplication.java | 13 - .../todoffin/TodoffinApplicationTests.java | 13 - 9 files changed, 458 deletions(-) delete mode 100644 todoffin/.gitignore delete mode 100644 todoffin/build.gradle delete mode 100644 todoffin/gradle/wrapper/gradle-wrapper.jar delete mode 100644 todoffin/gradle/wrapper/gradle-wrapper.properties delete mode 100755 todoffin/gradlew delete mode 100644 todoffin/gradlew.bat delete mode 100644 todoffin/settings.gradle delete mode 100644 todoffin/src/main/java/com/genius/todoffin/TodoffinApplication.java delete mode 100644 todoffin/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java diff --git a/todoffin/.gitignore b/todoffin/.gitignore deleted file mode 100644 index d98e3516..00000000 --- a/todoffin/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -HELP.md -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -*.yml - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### MAC ### -*.DS_Store \ No newline at end of file diff --git a/todoffin/build.gradle b/todoffin/build.gradle deleted file mode 100644 index 846cd36c..00000000 --- a/todoffin/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '3.2.1' - id 'io.spring.dependency-management' version '1.1.4' -} - -group = 'com.genius' -version = '0.0.1-SNAPSHOT' - -java { - sourceCompatibility = '17' -} - -configurations { - compileOnly { - extendsFrom annotationProcessor - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-websocket' - compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.security:spring-security-test' -} - -tasks.named('test') { - useJUnitPlatform() -} diff --git a/todoffin/gradle/wrapper/gradle-wrapper.jar b/todoffin/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd4917707c1f8861d8cb53dd15194d4248596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43462 zcma&NWl&^owk(X(xVyW%ySuwf;qI=D6|RlDJ2cR^yEKh!@I- zp9QeisK*rlxC>+~7Dk4IxIRsKBHqdR9b3+fyL=ynHmIDe&|>O*VlvO+%z5;9Z$|DJ zb4dO}-R=MKr^6EKJiOrJdLnCJn>np?~vU-1sSFgPu;pthGwf}bG z(1db%xwr#x)r+`4AGu$j7~u2MpVs3VpLp|mx&;>`0p0vH6kF+D2CY0fVdQOZ@h;A` z{infNyvmFUiu*XG}RNMNwXrbec_*a3N=2zJ|Wh5z* z5rAX$JJR{#zP>KY**>xHTuw?|-Rg|o24V)74HcfVT;WtQHXlE+_4iPE8QE#DUm%x0 zEKr75ur~W%w#-My3Tj`hH6EuEW+8K-^5P62$7Sc5OK+22qj&Pd1;)1#4tKihi=~8C zHiQSst0cpri6%OeaR`PY>HH_;CPaRNty%WTm4{wDK8V6gCZlG@U3$~JQZ;HPvDJcT1V{ z?>H@13MJcCNe#5z+MecYNi@VT5|&UiN1D4ATT+%M+h4c$t;C#UAs3O_q=GxK0}8%8 z8J(_M9bayxN}69ex4dzM_P3oh@ZGREjVvn%%r7=xjkqxJP4kj}5tlf;QosR=%4L5y zWhgejO=vao5oX%mOHbhJ8V+SG&K5dABn6!WiKl{|oPkq(9z8l&Mm%(=qGcFzI=eLu zWc_oCLyf;hVlB@dnwY98?75B20=n$>u3b|NB28H0u-6Rpl((%KWEBOfElVWJx+5yg z#SGqwza7f}$z;n~g%4HDU{;V{gXIhft*q2=4zSezGK~nBgu9-Q*rZ#2f=Q}i2|qOp z!!y4p)4o=LVUNhlkp#JL{tfkhXNbB=Ox>M=n6soptJw-IDI|_$is2w}(XY>a=H52d z3zE$tjPUhWWS+5h=KVH&uqQS=$v3nRs&p$%11b%5qtF}S2#Pc`IiyBIF4%A!;AVoI zXU8-Rpv!DQNcF~(qQnyyMy=-AN~U>#&X1j5BLDP{?K!%h!;hfJI>$mdLSvktEr*89 zdJHvby^$xEX0^l9g$xW-d?J;L0#(`UT~zpL&*cEh$L|HPAu=P8`OQZV!-}l`noSp_ zQ-1$q$R-gDL)?6YaM!=8H=QGW$NT2SeZlb8PKJdc=F-cT@j7Xags+Pr*jPtlHFnf- zh?q<6;)27IdPc^Wdy-mX%2s84C1xZq9Xms+==F4);O`VUASmu3(RlgE#0+#giLh-& zcxm3_e}n4{%|X zJp{G_j+%`j_q5}k{eW&TlP}J2wtZ2^<^E(O)4OQX8FDp6RJq!F{(6eHWSD3=f~(h} zJXCf7=r<16X{pHkm%yzYI_=VDP&9bmI1*)YXZeB}F? z(%QsB5fo*FUZxK$oX~X^69;x~j7ms8xlzpt-T15e9}$4T-pC z6PFg@;B-j|Ywajpe4~bk#S6(fO^|mm1hKOPfA%8-_iGCfICE|=P_~e;Wz6my&)h_~ zkv&_xSAw7AZ%ThYF(4jADW4vg=oEdJGVOs>FqamoL3Np8>?!W#!R-0%2Bg4h?kz5I zKV-rKN2n(vUL%D<4oj@|`eJ>0i#TmYBtYmfla;c!ATW%;xGQ0*TW@PTlGG><@dxUI zg>+3SiGdZ%?5N=8uoLA|$4isK$aJ%i{hECP$bK{J#0W2gQ3YEa zZQ50Stn6hqdfxJ*9#NuSLwKFCUGk@c=(igyVL;;2^wi4o30YXSIb2g_ud$ zgpCr@H0qWtk2hK8Q|&wx)}4+hTYlf;$a4#oUM=V@Cw#!$(nOFFpZ;0lc!qd=c$S}Z zGGI-0jg~S~cgVT=4Vo)b)|4phjStD49*EqC)IPwyeKBLcN;Wu@Aeph;emROAwJ-0< z_#>wVm$)ygH|qyxZaet&(Vf%pVdnvKWJn9`%DAxj3ot;v>S$I}jJ$FLBF*~iZ!ZXE zkvui&p}fI0Y=IDX)mm0@tAd|fEHl~J&K}ZX(Mm3cm1UAuwJ42+AO5@HwYfDH7ipIc zmI;1J;J@+aCNG1M`Btf>YT>~c&3j~Qi@Py5JT6;zjx$cvOQW@3oQ>|}GH?TW-E z1R;q^QFjm5W~7f}c3Ww|awg1BAJ^slEV~Pk`Kd`PS$7;SqJZNj->it4DW2l15}xP6 zoCl$kyEF%yJni0(L!Z&14m!1urXh6Btj_5JYt1{#+H8w?5QI%% zo-$KYWNMJVH?Hh@1n7OSu~QhSswL8x0=$<8QG_zepi_`y_79=nK=_ZP_`Em2UI*tyQoB+r{1QYZCpb?2OrgUw#oRH$?^Tj!Req>XiE#~B|~ z+%HB;=ic+R@px4Ld8mwpY;W^A%8%l8$@B@1m5n`TlKI6bz2mp*^^^1mK$COW$HOfp zUGTz-cN9?BGEp}5A!mDFjaiWa2_J2Iq8qj0mXzk; z66JBKRP{p%wN7XobR0YjhAuW9T1Gw3FDvR5dWJ8ElNYF94eF3ebu+QwKjtvVu4L zI9ip#mQ@4uqVdkl-TUQMb^XBJVLW(-$s;Nq;@5gr4`UfLgF$adIhd?rHOa%D);whv z=;krPp~@I+-Z|r#s3yCH+c1US?dnm+C*)r{m+86sTJusLdNu^sqLrfWed^ndHXH`m zd3#cOe3>w-ga(Dus_^ppG9AC>Iq{y%%CK+Cro_sqLCs{VLuK=dev>OL1dis4(PQ5R zcz)>DjEkfV+MO;~>VUlYF00SgfUo~@(&9$Iy2|G0T9BSP?&T22>K46D zL*~j#yJ?)^*%J3!16f)@Y2Z^kS*BzwfAQ7K96rFRIh>#$*$_Io;z>ux@}G98!fWR@ zGTFxv4r~v)Gsd|pF91*-eaZ3Qw1MH$K^7JhWIdX%o$2kCbvGDXy)a?@8T&1dY4`;L z4Kn+f%SSFWE_rpEpL9bnlmYq`D!6F%di<&Hh=+!VI~j)2mfil03T#jJ_s?}VV0_hp z7T9bWxc>Jm2Z0WMU?`Z$xE74Gu~%s{mW!d4uvKCx@WD+gPUQ zV0vQS(Ig++z=EHN)BR44*EDSWIyT~R4$FcF*VEY*8@l=218Q05D2$|fXKFhRgBIEE zdDFB}1dKkoO^7}{5crKX!p?dZWNz$m>1icsXG2N+((x0OIST9Zo^DW_tytvlwXGpn zs8?pJXjEG;T@qrZi%#h93?FP$!&P4JA(&H61tqQi=opRzNpm zkrG}$^t9&XduK*Qa1?355wd8G2CI6QEh@Ua>AsD;7oRUNLPb76m4HG3K?)wF~IyS3`fXuNM>${?wmB zpVz;?6_(Fiadfd{vUCBM*_kt$+F3J+IojI;9L(gc9n3{sEZyzR9o!_mOwFC#tQ{Q~ zP3-`#uK#tP3Q7~Q;4H|wjZHO8h7e4IuBxl&vz2w~D8)w=Wtg31zpZhz%+kzSzL*dV zwp@{WU4i;hJ7c2f1O;7Mz6qRKeASoIv0_bV=i@NMG*l<#+;INk-^`5w@}Dj~;k=|}qM1vq_P z|GpBGe_IKq|LNy9SJhKOQ$c=5L{Dv|Q_lZl=-ky*BFBJLW9&y_C|!vyM~rQx=!vun z?rZJQB5t}Dctmui5i31C_;_}CEn}_W%>oSXtt>@kE1=JW*4*v4tPp;O6 zmAk{)m!)}34pTWg8{i>($%NQ(Tl;QC@J@FfBoc%Gr&m560^kgSfodAFrIjF}aIw)X zoXZ`@IsMkc8_=w%-7`D6Y4e*CG8k%Ud=GXhsTR50jUnm+R*0A(O3UKFg0`K;qp1bl z7``HN=?39ic_kR|^R^~w-*pa?Vj#7|e9F1iRx{GN2?wK!xR1GW!qa=~pjJb-#u1K8 zeR?Y2i-pt}yJq;SCiVHODIvQJX|ZJaT8nO+(?HXbLefulKKgM^B(UIO1r+S=7;kLJ zcH}1J=Px2jsh3Tec&v8Jcbng8;V-`#*UHt?hB(pmOipKwf3Lz8rG$heEB30Sg*2rx zV<|KN86$soN(I!BwO`1n^^uF2*x&vJ$2d$>+`(romzHP|)K_KkO6Hc>_dwMW-M(#S zK(~SiXT1@fvc#U+?|?PniDRm01)f^#55;nhM|wi?oG>yBsa?~?^xTU|fX-R(sTA+5 zaq}-8Tx7zrOy#3*JLIIVsBmHYLdD}!0NP!+ITW+Thn0)8SS!$@)HXwB3tY!fMxc#1 zMp3H?q3eD?u&Njx4;KQ5G>32+GRp1Ee5qMO0lZjaRRu&{W<&~DoJNGkcYF<5(Ab+J zgO>VhBl{okDPn78<%&e2mR{jwVCz5Og;*Z;;3%VvoGo_;HaGLWYF7q#jDX=Z#Ml`H z858YVV$%J|e<1n`%6Vsvq7GmnAV0wW4$5qQ3uR@1i>tW{xrl|ExywIc?fNgYlA?C5 zh$ezAFb5{rQu6i7BSS5*J-|9DQ{6^BVQ{b*lq`xS@RyrsJN?-t=MTMPY;WYeKBCNg z^2|pN!Q^WPJuuO4!|P@jzt&tY1Y8d%FNK5xK(!@`jO2aEA*4 zkO6b|UVBipci?){-Ke=+1;mGlND8)6+P;8sq}UXw2hn;fc7nM>g}GSMWu&v&fqh

iViYT=fZ(|3Ox^$aWPp4a8h24tD<|8-!aK0lHgL$N7Efw}J zVIB!7=T$U`ao1?upi5V4Et*-lTG0XvExbf!ya{cua==$WJyVG(CmA6Of*8E@DSE%L z`V^$qz&RU$7G5mg;8;=#`@rRG`-uS18$0WPN@!v2d{H2sOqP|!(cQ@ zUHo!d>>yFArLPf1q`uBvY32miqShLT1B@gDL4XoVTK&@owOoD)OIHXrYK-a1d$B{v zF^}8D3Y^g%^cnvScOSJR5QNH+BI%d|;J;wWM3~l>${fb8DNPg)wrf|GBP8p%LNGN# z3EaIiItgwtGgT&iYCFy9-LG}bMI|4LdmmJt@V@% zb6B)1kc=T)(|L@0;wr<>=?r04N;E&ef+7C^`wPWtyQe(*pD1pI_&XHy|0gIGHMekd zF_*M4yi6J&Z4LQj65)S zXwdM{SwUo%3SbPwFsHgqF@V|6afT|R6?&S;lw=8% z3}@9B=#JI3@B*#4s!O))~z zc>2_4Q_#&+5V`GFd?88^;c1i7;Vv_I*qt!_Yx*n=;rj!82rrR2rQ8u5(Ejlo{15P% zs~!{%XJ>FmJ})H^I9bn^Re&38H{xA!0l3^89k(oU;bZWXM@kn$#aoS&Y4l^-WEn-fH39Jb9lA%s*WsKJQl?n9B7_~P z-XM&WL7Z!PcoF6_D>V@$CvUIEy=+Z&0kt{szMk=f1|M+r*a43^$$B^MidrT0J;RI` z(?f!O<8UZkm$_Ny$Hth1J#^4ni+im8M9mr&k|3cIgwvjAgjH z8`N&h25xV#v*d$qBX5jkI|xOhQn!>IYZK7l5#^P4M&twe9&Ey@@GxYMxBZq2e7?`q z$~Szs0!g{2fGcp9PZEt|rdQ6bhAgpcLHPz?f-vB?$dc*!9OL?Q8mn7->bFD2Si60* z!O%y)fCdMSV|lkF9w%x~J*A&srMyYY3{=&$}H zGQ4VG_?$2X(0|vT0{=;W$~icCI{b6W{B!Q8xdGhF|D{25G_5_+%s(46lhvNLkik~R z>nr(&C#5wwOzJZQo9m|U<;&Wk!_#q|V>fsmj1g<6%hB{jGoNUPjgJslld>xmODzGjYc?7JSuA?A_QzjDw5AsRgi@Y|Z0{F{!1=!NES-#*f^s4l0Hu zz468))2IY5dmD9pa*(yT5{EyP^G>@ZWumealS-*WeRcZ}B%gxq{MiJ|RyX-^C1V=0 z@iKdrGi1jTe8Ya^x7yyH$kBNvM4R~`fbPq$BzHum-3Zo8C6=KW@||>zsA8-Y9uV5V z#oq-f5L5}V<&wF4@X@<3^C%ptp6+Ce)~hGl`kwj)bsAjmo_GU^r940Z-|`<)oGnh7 zFF0Tde3>ui?8Yj{sF-Z@)yQd~CGZ*w-6p2U<8}JO-sRsVI5dBji`01W8A&3$?}lxBaC&vn0E$c5tW* zX>5(zzZ=qn&!J~KdsPl;P@bmA-Pr8T*)eh_+Dv5=Ma|XSle6t(k8qcgNyar{*ReQ8 zTXwi=8vr>!3Ywr+BhggHDw8ke==NTQVMCK`$69fhzEFB*4+H9LIvdt-#IbhZvpS}} zO3lz;P?zr0*0$%-Rq_y^k(?I{Mk}h@w}cZpMUp|ucs55bcloL2)($u%mXQw({Wzc~ z;6nu5MkjP)0C(@%6Q_I_vsWrfhl7Zpoxw#WoE~r&GOSCz;_ro6i(^hM>I$8y>`!wW z*U^@?B!MMmb89I}2(hcE4zN2G^kwyWCZp5JG>$Ez7zP~D=J^LMjSM)27_0B_X^C(M z`fFT+%DcKlu?^)FCK>QzSnV%IsXVcUFhFdBP!6~se&xxrIxsvySAWu++IrH;FbcY$ z2DWTvSBRfLwdhr0nMx+URA$j3i7_*6BWv#DXfym?ZRDcX9C?cY9sD3q)uBDR3uWg= z(lUIzB)G$Hr!){>E{s4Dew+tb9kvToZp-1&c?y2wn@Z~(VBhqz`cB;{E4(P3N2*nJ z_>~g@;UF2iG{Kt(<1PyePTKahF8<)pozZ*xH~U-kfoAayCwJViIrnqwqO}7{0pHw$ zs2Kx?s#vQr7XZ264>5RNKSL8|Ty^=PsIx^}QqOOcfpGUU4tRkUc|kc7-!Ae6!+B{o~7nFpm3|G5^=0#Bnm6`V}oSQlrX(u%OWnC zoLPy&Q;1Jui&7ST0~#+}I^&?vcE*t47~Xq#YwvA^6^} z`WkC)$AkNub|t@S!$8CBlwbV~?yp&@9h{D|3z-vJXgzRC5^nYm+PyPcgRzAnEi6Q^gslXYRv4nycsy-SJu?lMps-? zV`U*#WnFsdPLL)Q$AmD|0`UaC4ND07+&UmOu!eHruzV|OUox<+Jl|Mr@6~C`T@P%s zW7sgXLF2SSe9Fl^O(I*{9wsFSYb2l%-;&Pi^dpv!{)C3d0AlNY6!4fgmSgj_wQ*7Am7&$z;Jg&wgR-Ih;lUvWS|KTSg!&s_E9_bXBkZvGiC6bFKDWZxsD$*NZ#_8bl zG1P-#@?OQzED7@jlMJTH@V!6k;W>auvft)}g zhoV{7$q=*;=l{O>Q4a@ ziMjf_u*o^PsO)#BjC%0^h>Xp@;5$p{JSYDt)zbb}s{Kbt!T*I@Pk@X0zds6wsefuU zW$XY%yyRGC94=6mf?x+bbA5CDQ2AgW1T-jVAJbm7K(gp+;v6E0WI#kuACgV$r}6L? zd|Tj?^%^*N&b>Dd{Wr$FS2qI#Ucs1yd4N+RBUQiSZGujH`#I)mG&VKoDh=KKFl4=G z&MagXl6*<)$6P}*Tiebpz5L=oMaPrN+caUXRJ`D?=K9!e0f{@D&cZLKN?iNP@X0aF zE(^pl+;*T5qt?1jRC=5PMgV!XNITRLS_=9{CJExaQj;lt!&pdzpK?8p>%Mb+D z?yO*uSung=-`QQ@yX@Hyd4@CI^r{2oiu`%^bNkz+Nkk!IunjwNC|WcqvX~k=><-I3 zDQdbdb|!v+Iz01$w@aMl!R)koD77Xp;eZwzSl-AT zr@Vu{=xvgfq9akRrrM)}=!=xcs+U1JO}{t(avgz`6RqiiX<|hGG1pmop8k6Q+G_mv zJv|RfDheUp2L3=^C=4aCBMBn0aRCU(DQwX-W(RkRwmLeuJYF<0urcaf(=7)JPg<3P zQs!~G)9CT18o!J4{zX{_e}4eS)U-E)0FAt}wEI(c0%HkxgggW;(1E=>J17_hsH^sP z%lT0LGgbUXHx-K*CI-MCrP66UP0PvGqM$MkeLyqHdbgP|_Cm!7te~b8p+e6sQ_3k| zVcwTh6d83ltdnR>D^)BYQpDKlLk3g0Hdcgz2}%qUs9~~Rie)A-BV1mS&naYai#xcZ z(d{8=-LVpTp}2*y)|gR~;qc7fp26}lPcLZ#=JpYcn3AT9(UIdOyg+d(P5T7D&*P}# zQCYplZO5|7+r19%9e`v^vfSS1sbX1c%=w1;oyruXB%Kl$ACgKQ6=qNWLsc=28xJjg zwvsI5-%SGU|3p>&zXVl^vVtQT3o-#$UT9LI@Npz~6=4!>mc431VRNN8od&Ul^+G_kHC`G=6WVWM z%9eWNyy(FTO|A+@x}Ou3CH)oi;t#7rAxdIXfNFwOj_@Y&TGz6P_sqiB`Q6Lxy|Q{`|fgmRG(k+!#b*M+Z9zFce)f-7;?Km5O=LHV9f9_87; zF7%R2B+$?@sH&&-$@tzaPYkw0;=i|;vWdI|Wl3q_Zu>l;XdIw2FjV=;Mq5t1Q0|f< zs08j54Bp`3RzqE=2enlkZxmX6OF+@|2<)A^RNQpBd6o@OXl+i)zO%D4iGiQNuXd+zIR{_lb96{lc~bxsBveIw6umhShTX+3@ZJ=YHh@ zWY3(d0azg;7oHn>H<>?4@*RQbi>SmM=JrHvIG(~BrvI)#W(EAeO6fS+}mxxcc+X~W6&YVl86W9WFSS}Vz-f9vS?XUDBk)3TcF z8V?$4Q)`uKFq>xT=)Y9mMFVTUk*NIA!0$?RP6Ig0TBmUFrq*Q-Agq~DzxjStQyJ({ zBeZ;o5qUUKg=4Hypm|}>>L=XKsZ!F$yNTDO)jt4H0gdQ5$f|d&bnVCMMXhNh)~mN z@_UV6D7MVlsWz+zM+inZZp&P4fj=tm6fX)SG5H>OsQf_I8c~uGCig$GzuwViK54bcgL;VN|FnyQl>Ed7(@>=8$a_UKIz|V6CeVSd2(P z0Uu>A8A+muM%HLFJQ9UZ5c)BSAv_zH#1f02x?h9C}@pN@6{>UiAp>({Fn(T9Q8B z^`zB;kJ5b`>%dLm+Ol}ty!3;8f1XDSVX0AUe5P#@I+FQ-`$(a;zNgz)4x5hz$Hfbg z!Q(z26wHLXko(1`;(BAOg_wShpX0ixfWq3ponndY+u%1gyX)_h=v1zR#V}#q{au6; z!3K=7fQwnRfg6FXtNQmP>`<;!N137paFS%y?;lb1@BEdbvQHYC{976l`cLqn;b8lp zIDY>~m{gDj(wfnK!lpW6pli)HyLEiUrNc%eXTil|F2s(AY+LW5hkKb>TQ3|Q4S9rr zpDs4uK_co6XPsn_z$LeS{K4jFF`2>U`tbgKdyDne`xmR<@6AA+_hPNKCOR-Zqv;xk zu5!HsBUb^!4uJ7v0RuH-7?l?}b=w5lzzXJ~gZcxRKOovSk@|#V+MuX%Y+=;14i*%{)_gSW9(#4%)AV#3__kac1|qUy!uyP{>?U#5wYNq}y$S9pCc zFc~4mgSC*G~j0u#qqp9 z${>3HV~@->GqEhr_Xwoxq?Hjn#=s2;i~g^&Hn|aDKpA>Oc%HlW(KA1?BXqpxB;Ydx)w;2z^MpjJ(Qi(X!$5RC z*P{~%JGDQqojV>2JbEeCE*OEu!$XJ>bWA9Oa_Hd;y)F%MhBRi*LPcdqR8X`NQ&1L# z5#9L*@qxrx8n}LfeB^J{%-?SU{FCwiWyHp682F+|pa+CQa3ZLzBqN1{)h4d6+vBbV zC#NEbQLC;}me3eeYnOG*nXOJZEU$xLZ1<1Y=7r0(-U0P6-AqwMAM`a(Ed#7vJkn6plb4eI4?2y3yOTGmmDQ!z9`wzbf z_OY#0@5=bnep;MV0X_;;SJJWEf^E6Bd^tVJ9znWx&Ks8t*B>AM@?;D4oWUGc z!H*`6d7Cxo6VuyS4Eye&L1ZRhrRmN6Lr`{NL(wDbif|y&z)JN>Fl5#Wi&mMIr5i;x zBx}3YfF>>8EC(fYnmpu~)CYHuHCyr5*`ECap%t@y=jD>!_%3iiE|LN$mK9>- zHdtpy8fGZtkZF?%TW~29JIAfi2jZT8>OA7=h;8T{{k?c2`nCEx9$r zS+*&vt~2o^^J+}RDG@+9&M^K*z4p{5#IEVbz`1%`m5c2};aGt=V?~vIM}ZdPECDI)47|CWBCfDWUbxBCnmYivQ*0Nu_xb*C>~C9(VjHM zxe<*D<#dQ8TlpMX2c@M<9$w!RP$hpG4cs%AI){jp*Sj|*`m)5(Bw*A0$*i-(CA5#%>a)$+jI2C9r6|(>J8InryENI z$NohnxDUB;wAYDwrb*!N3noBTKPpPN}~09SEL18tkG zxgz(RYU_;DPT{l?Q$+eaZaxnsWCA^ds^0PVRkIM%bOd|G2IEBBiz{&^JtNsODs;5z zICt_Zj8wo^KT$7Bg4H+y!Df#3mbl%%?|EXe!&(Vmac1DJ*y~3+kRKAD=Ovde4^^%~ zw<9av18HLyrf*_>Slp;^i`Uy~`mvBjZ|?Ad63yQa#YK`4+c6;pW4?XIY9G1(Xh9WO8{F-Aju+nS9Vmv=$Ac0ienZ+p9*O%NG zMZKy5?%Z6TAJTE?o5vEr0r>f>hb#2w2U3DL64*au_@P!J!TL`oH2r*{>ffu6|A7tv zL4juf$DZ1MW5ZPsG!5)`k8d8c$J$o;%EIL0va9&GzWvkS%ZsGb#S(?{!UFOZ9<$a| zY|a+5kmD5N&{vRqkgY>aHsBT&`rg|&kezoD)gP0fsNYHsO#TRc_$n6Lf1Z{?+DLziXlHrq4sf(!>O{?Tj;Eh@%)+nRE_2VxbN&&%%caU#JDU%vL3}Cb zsb4AazPI{>8H&d=jUaZDS$-0^AxE@utGs;-Ez_F(qC9T=UZX=>ok2k2 ziTn{K?y~a5reD2A)P${NoI^>JXn>`IeArow(41c-Wm~)wiryEP(OS{YXWi7;%dG9v zI?mwu1MxD{yp_rrk!j^cKM)dc4@p4Ezyo%lRN|XyD}}>v=Xoib0gOcdXrQ^*61HNj z=NP|pd>@yfvr-=m{8$3A8TQGMTE7g=z!%yt`8`Bk-0MMwW~h^++;qyUP!J~ykh1GO z(FZ59xuFR$(WE;F@UUyE@Sp>`aVNjyj=Ty>_Vo}xf`e7`F;j-IgL5`1~-#70$9_=uBMq!2&1l zomRgpD58@)YYfvLtPW}{C5B35R;ZVvB<<#)x%srmc_S=A7F@DW8>QOEGwD6suhwCg z>Pa+YyULhmw%BA*4yjDp|2{!T98~<6Yfd(wo1mQ!KWwq0eg+6)o1>W~f~kL<-S+P@$wx*zeI|1t7z#Sxr5 zt6w+;YblPQNplq4Z#T$GLX#j6yldXAqj>4gAnnWtBICUnA&-dtnlh=t0Ho_vEKwV` z)DlJi#!@nkYV#$!)@>udAU*hF?V`2$Hf=V&6PP_|r#Iv*J$9)pF@X3`k;5})9^o4y z&)~?EjX5yX12O(BsFy-l6}nYeuKkiq`u9145&3Ssg^y{5G3Pse z9w(YVa0)N-fLaBq1`P!_#>SS(8fh_5!f{UrgZ~uEdeMJIz7DzI5!NHHqQtm~#CPij z?=N|J>nPR6_sL7!f4hD_|KH`vf8(Wpnj-(gPWH+ZvID}%?~68SwhPTC3u1_cB`otq z)U?6qo!ZLi5b>*KnYHWW=3F!p%h1;h{L&(Q&{qY6)_qxNfbP6E3yYpW!EO+IW3?@J z);4>g4gnl^8klu7uA>eGF6rIGSynacogr)KUwE_R4E5Xzi*Qir@b-jy55-JPC8c~( zo!W8y9OGZ&`xmc8;=4-U9=h{vCqfCNzYirONmGbRQlR`WWlgnY+1wCXbMz&NT~9*| z6@FrzP!LX&{no2!Ln_3|I==_4`@}V?4a;YZKTdw;vT<+K+z=uWbW(&bXEaWJ^W8Td z-3&1bY^Z*oM<=M}LVt>_j+p=2Iu7pZmbXrhQ_k)ysE9yXKygFNw$5hwDn(M>H+e1&9BM5!|81vd%r%vEm zqxY3?F@fb6O#5UunwgAHR9jp_W2zZ}NGp2%mTW@(hz7$^+a`A?mb8|_G*GNMJ) zjqegXQio=i@AINre&%ofexAr95aop5C+0MZ0m-l=MeO8m3epm7U%vZB8+I+C*iNFM z#T3l`gknX;D$-`2XT^Cg*vrv=RH+P;_dfF++cP?B_msQI4j+lt&rX2)3GaJx%W*Nn zkML%D{z5tpHH=dksQ*gzc|}gzW;lwAbxoR07VNgS*-c3d&8J|;@3t^ zVUz*J*&r7DFRuFVDCJDK8V9NN5hvpgGjwx+5n)qa;YCKe8TKtdnh{I7NU9BCN!0dq zczrBk8pE{{@vJa9ywR@mq*J=v+PG;?fwqlJVhijG!3VmIKs>9T6r7MJpC)m!Tc#>g zMtVsU>wbwFJEfwZ{vB|ZlttNe83)$iz`~#8UJ^r)lJ@HA&G#}W&ZH*;k{=TavpjWE z7hdyLZPf*X%Gm}i`Y{OGeeu^~nB8=`{r#TUrM-`;1cBvEd#d!kPqIgYySYhN-*1;L z^byj%Yi}Gx)Wnkosi337BKs}+5H5dth1JA{Ir-JKN$7zC)*}hqeoD(WfaUDPT>0`- z(6sa0AoIqASwF`>hP}^|)a_j2s^PQn*qVC{Q}htR z5-)duBFXT_V56-+UohKXlq~^6uf!6sA#ttk1o~*QEy_Y-S$gAvq47J9Vtk$5oA$Ct zYhYJ@8{hsC^98${!#Ho?4y5MCa7iGnfz}b9jE~h%EAAv~Qxu)_rAV;^cygV~5r_~?l=B`zObj7S=H=~$W zPtI_m%g$`kL_fVUk9J@>EiBH zOO&jtn~&`hIFMS5S`g8w94R4H40mdNUH4W@@XQk1sr17b{@y|JB*G9z1|CrQjd+GX z6+KyURG3;!*BQrentw{B2R&@2&`2}n(z-2&X7#r!{yg@Soy}cRD~j zj9@UBW+N|4HW4AWapy4wfUI- zZ`gSL6DUlgj*f1hSOGXG0IVH8HxK?o2|3HZ;KW{K+yPAlxtb)NV_2AwJm|E)FRs&& z=c^e7bvUsztY|+f^k7NXs$o1EUq>cR7C0$UKi6IooHWlK_#?IWDkvywnzg&ThWo^? z2O_N{5X39#?eV9l)xI(>@!vSB{DLt*oY!K1R8}_?%+0^C{d9a%N4 zoxHVT1&Lm|uDX%$QrBun5e-F`HJ^T$ zmzv)p@4ZHd_w9!%Hf9UYNvGCw2TTTbrj9pl+T9%-_-}L(tES>Or-}Z4F*{##n3~L~TuxjirGuIY#H7{%$E${?p{Q01 zi6T`n;rbK1yIB9jmQNycD~yZq&mbIsFWHo|ZAChSFPQa<(%d8mGw*V3fh|yFoxOOiWJd(qvVb!Z$b88cg->N=qO*4k~6;R==|9ihg&riu#P~s4Oap9O7f%crSr^rljeIfXDEg>wi)&v*a%7zpz<9w z*r!3q9J|390x`Zk;g$&OeN&ctp)VKRpDSV@kU2Q>jtok($Y-*x8_$2piTxun81@vt z!Vj?COa0fg2RPXMSIo26T=~0d`{oGP*eV+$!0I<(4azk&Vj3SiG=Q!6mX0p$z7I}; z9BJUFgT-K9MQQ-0@Z=^7R<{bn2Fm48endsSs`V7_@%8?Bxkqv>BDoVcj?K#dV#uUP zL1ND~?D-|VGKe3Rw_7-Idpht>H6XRLh*U7epS6byiGvJpr%d}XwfusjH9g;Z98H`x zyde%%5mhGOiL4wljCaWCk-&uE4_OOccb9c!ZaWt4B(wYl!?vyzl%7n~QepN&eFUrw zFIOl9c({``6~QD+43*_tzP{f2x41h(?b43^y6=iwyB)2os5hBE!@YUS5?N_tXd=h( z)WE286Fbd>R4M^P{!G)f;h<3Q>Fipuy+d2q-)!RyTgt;wr$(?9ox3;q+{E*ZQHhOn;lM`cjnu9 zXa48ks-v(~b*;MAI<>YZH(^NV8vjb34beE<_cwKlJoR;k6lJNSP6v}uiyRD?|0w+X@o1ONrH8a$fCxXpf? z?$DL0)7|X}Oc%h^zrMKWc-NS9I0Utu@>*j}b@tJ=ixQSJ={4@854wzW@E>VSL+Y{i z#0b=WpbCZS>kUCO_iQz)LoE>P5LIG-hv9E+oG}DtlIDF>$tJ1aw9^LuhLEHt?BCj& z(O4I8v1s#HUi5A>nIS-JK{v!7dJx)^Yg%XjNmlkWAq2*cv#tHgz`Y(bETc6CuO1VkN^L-L3j_x<4NqYb5rzrLC-7uOv z!5e`GZt%B782C5-fGnn*GhDF$%(qP<74Z}3xx+{$4cYKy2ikxI7B2N+2r07DN;|-T->nU&!=Cm#rZt%O_5c&1Z%nlWq3TKAW0w zQqemZw_ue--2uKQsx+niCUou?HjD`xhEjjQd3%rrBi82crq*~#uA4+>vR<_S{~5ce z-2EIl?~s z1=GVL{NxP1N3%=AOaC}j_Fv=ur&THz zyO!d9kHq|c73kpq`$+t+8Bw7MgeR5~`d7ChYyGCBWSteTB>8WAU(NPYt2Dk`@#+}= zI4SvLlyk#pBgVigEe`?NG*vl7V6m+<}%FwPV=~PvvA)=#ths==DRTDEYh4V5}Cf$z@#;< zyWfLY_5sP$gc3LLl2x+Ii)#b2nhNXJ{R~vk`s5U7Nyu^3yFg&D%Txwj6QezMX`V(x z=C`{76*mNb!qHHs)#GgGZ_7|vkt9izl_&PBrsu@}L`X{95-2jf99K)0=*N)VxBX2q z((vkpP2RneSIiIUEnGb?VqbMb=Zia+rF~+iqslydE34cSLJ&BJW^3knX@M;t*b=EA zNvGzv41Ld_T+WT#XjDB840vovUU^FtN_)G}7v)1lPetgpEK9YS^OWFkPoE{ovj^=@ zO9N$S=G$1ecndT_=5ehth2Lmd1II-PuT~C9`XVePw$y8J#dpZ?Tss<6wtVglm(Ok7 z3?^oi@pPio6l&!z8JY(pJvG=*pI?GIOu}e^EB6QYk$#FJQ%^AIK$I4epJ+9t?KjqA+bkj&PQ*|vLttme+`9G=L% ziadyMw_7-M)hS(3E$QGNCu|o23|%O+VN7;Qggp?PB3K-iSeBa2b}V4_wY`G1Jsfz4 z9|SdB^;|I8E8gWqHKx!vj_@SMY^hLEIbSMCuE?WKq=c2mJK z8LoG-pnY!uhqFv&L?yEuxo{dpMTsmCn)95xanqBrNPTgXP((H$9N${Ow~Is-FBg%h z53;|Y5$MUN)9W2HBe2TD`ct^LHI<(xWrw}$qSoei?}s)&w$;&!14w6B6>Yr6Y8b)S z0r71`WmAvJJ`1h&poLftLUS6Ir zC$bG9!Im_4Zjse)#K=oJM9mHW1{%l8sz$1o?ltdKlLTxWWPB>Vk22czVt|1%^wnN@*!l)}?EgtvhC>vlHm^t+ogpgHI1_$1ox9e;>0!+b(tBrmXRB`PY1vp-R**8N7 zGP|QqI$m(Rdu#=(?!(N}G9QhQ%o!aXE=aN{&wtGP8|_qh+7a_j_sU5|J^)vxq;# zjvzLn%_QPHZZIWu1&mRAj;Sa_97p_lLq_{~j!M9N^1yp3U_SxRqK&JnR%6VI#^E12 z>CdOVI^_9aPK2eZ4h&^{pQs}xsijXgFYRIxJ~N7&BB9jUR1fm!(xl)mvy|3e6-B3j zJn#ajL;bFTYJ2+Q)tDjx=3IklO@Q+FFM}6UJr6km7hj7th9n_&JR7fnqC!hTZoM~T zBeaVFp%)0cbPhejX<8pf5HyRUj2>aXnXBqDJe73~J%P(2C?-RT{c3NjE`)om! zl$uewSgWkE66$Kb34+QZZvRn`fob~Cl9=cRk@Es}KQm=?E~CE%spXaMO6YmrMl%9Q zlA3Q$3|L1QJ4?->UjT&CBd!~ru{Ih^in&JXO=|<6J!&qp zRe*OZ*cj5bHYlz!!~iEKcuE|;U4vN1rk$xq6>bUWD*u(V@8sG^7>kVuo(QL@Ki;yL zWC!FT(q{E8#on>%1iAS0HMZDJg{Z{^!De(vSIq&;1$+b)oRMwA3nc3mdTSG#3uYO_ z>+x;7p4I;uHz?ZB>dA-BKl+t-3IB!jBRgdvAbW!aJ(Q{aT>+iz?91`C-xbe)IBoND z9_Xth{6?(y3rddwY$GD65IT#f3<(0o#`di{sh2gm{dw*#-Vnc3r=4==&PU^hCv$qd zjw;>i&?L*Wq#TxG$mFIUf>eK+170KG;~+o&1;Tom9}}mKo23KwdEM6UonXgc z!6N(@k8q@HPw{O8O!lAyi{rZv|DpgfU{py+j(X_cwpKqcalcqKIr0kM^%Br3SdeD> zHSKV94Yxw;pjzDHo!Q?8^0bb%L|wC;4U^9I#pd5O&eexX+Im{ z?jKnCcsE|H?{uGMqVie_C~w7GX)kYGWAg%-?8|N_1#W-|4F)3YTDC+QSq1s!DnOML3@d`mG%o2YbYd#jww|jD$gotpa)kntakp#K;+yo-_ZF9qrNZw<%#C zuPE@#3RocLgPyiBZ+R_-FJ_$xP!RzWm|aN)S+{$LY9vvN+IW~Kf3TsEIvP+B9Mtm! zpfNNxObWQpLoaO&cJh5>%slZnHl_Q~(-Tfh!DMz(dTWld@LG1VRF`9`DYKhyNv z2pU|UZ$#_yUx_B_|MxUq^glT}O5Xt(Vm4Mr02><%C)@v;vPb@pT$*yzJ4aPc_FZ3z z3}PLoMBIM>q_9U2rl^sGhk1VUJ89=*?7|v`{!Z{6bqFMq(mYiA?%KbsI~JwuqVA9$H5vDE+VocjX+G^%bieqx->s;XWlKcuv(s%y%D5Xbc9+ zc(_2nYS1&^yL*ey664&4`IoOeDIig}y-E~_GS?m;D!xv5-xwz+G`5l6V+}CpeJDi^ z%4ed$qowm88=iYG+(`ld5Uh&>Dgs4uPHSJ^TngXP_V6fPyl~>2bhi20QB%lSd#yYn zO05?KT1z@?^-bqO8Cg`;ft>ilejsw@2%RR7;`$Vs;FmO(Yr3Fp`pHGr@P2hC%QcA|X&N2Dn zYf`MqXdHi%cGR@%y7Rg7?d3?an){s$zA{!H;Ie5exE#c~@NhQUFG8V=SQh%UxUeiV zd7#UcYqD=lk-}sEwlpu&H^T_V0{#G?lZMxL7ih_&{(g)MWBnCZxtXg znr#}>U^6!jA%e}@Gj49LWG@*&t0V>Cxc3?oO7LSG%~)Y5}f7vqUUnQ;STjdDU}P9IF9d9<$;=QaXc zL1^X7>fa^jHBu_}9}J~#-oz3Oq^JmGR#?GO7b9a(=R@fw@}Q{{@`Wy1vIQ#Bw?>@X z-_RGG@wt|%u`XUc%W{J z>iSeiz8C3H7@St3mOr_mU+&bL#Uif;+Xw-aZdNYUpdf>Rvu0i0t6k*}vwU`XNO2he z%miH|1tQ8~ZK!zmL&wa3E;l?!!XzgV#%PMVU!0xrDsNNZUWKlbiOjzH-1Uoxm8E#r`#2Sz;-o&qcqB zC-O_R{QGuynW14@)7&@yw1U}uP(1cov)twxeLus0s|7ayrtT8c#`&2~Fiu2=R;1_4bCaD=*E@cYI>7YSnt)nQc zohw5CsK%m?8Ack)qNx`W0_v$5S}nO|(V|RZKBD+btO?JXe|~^Qqur%@eO~<8-L^9d z=GA3-V14ng9L29~XJ>a5k~xT2152zLhM*@zlp2P5Eu}bywkcqR;ISbas&#T#;HZSf z2m69qTV(V@EkY(1Dk3`}j)JMo%ZVJ*5eB zYOjIisi+igK0#yW*gBGj?@I{~mUOvRFQR^pJbEbzFxTubnrw(Muk%}jI+vXmJ;{Q6 zrSobKD>T%}jV4Ub?L1+MGOD~0Ir%-`iTnWZN^~YPrcP5y3VMAzQ+&en^VzKEb$K!Q z<7Dbg&DNXuow*eD5yMr+#08nF!;%4vGrJI++5HdCFcGLfMW!KS*Oi@=7hFwDG!h2< zPunUEAF+HncQkbfFj&pbzp|MU*~60Z(|Ik%Tn{BXMN!hZOosNIseT?R;A`W?=d?5X zK(FB=9mZusYahp|K-wyb={rOpdn=@;4YI2W0EcbMKyo~-#^?h`BA9~o285%oY zfifCh5Lk$SY@|2A@a!T2V+{^!psQkx4?x0HSV`(w9{l75QxMk!)U52Lbhn{8ol?S) zCKo*7R(z!uk<6*qO=wh!Pul{(qq6g6xW;X68GI_CXp`XwO zxuSgPRAtM8K7}5E#-GM!*ydOOG_{A{)hkCII<|2=ma*71ci_-}VPARm3crFQjLYV! z9zbz82$|l01mv`$WahE2$=fAGWkd^X2kY(J7iz}WGS z@%MyBEO=A?HB9=^?nX`@nh;7;laAjs+fbo!|K^mE!tOB>$2a_O0y-*uaIn8k^6Y zSbuv;5~##*4Y~+y7Z5O*3w4qgI5V^17u*ZeupVGH^nM&$qmAk|anf*>r zWc5CV;-JY-Z@Uq1Irpb^O`L_7AGiqd*YpGUShb==os$uN3yYvb`wm6d=?T*it&pDk zo`vhw)RZX|91^^Wa_ti2zBFyWy4cJu#g)_S6~jT}CC{DJ_kKpT`$oAL%b^!2M;JgT zM3ZNbUB?}kP(*YYvXDIH8^7LUxz5oE%kMhF!rnPqv!GiY0o}NR$OD=ITDo9r%4E>E0Y^R(rS^~XjWyVI6 zMOR5rPXhTp*G*M&X#NTL`Hu*R+u*QNoiOKg4CtNPrjgH>c?Hi4MUG#I917fx**+pJfOo!zFM&*da&G_x)L(`k&TPI*t3e^{crd zX<4I$5nBQ8Ax_lmNRa~E*zS-R0sxkz`|>7q_?*e%7bxqNm3_eRG#1ae3gtV9!fQpY z+!^a38o4ZGy9!J5sylDxZTx$JmG!wg7;>&5H1)>f4dXj;B+@6tMlL=)cLl={jLMxY zbbf1ax3S4>bwB9-$;SN2?+GULu;UA-35;VY*^9Blx)Jwyb$=U!D>HhB&=jSsd^6yw zL)?a|>GxU!W}ocTC(?-%z3!IUhw^uzc`Vz_g>-tv)(XA#JK^)ZnC|l1`@CdX1@|!| z_9gQ)7uOf?cR@KDp97*>6X|;t@Y`k_N@)aH7gY27)COv^P3ya9I{4z~vUjLR9~z1Z z5=G{mVtKH*&$*t0@}-i_v|3B$AHHYale7>E+jP`ClqG%L{u;*ff_h@)al?RuL7tOO z->;I}>%WI{;vbLP3VIQ^iA$4wl6@0sDj|~112Y4OFjMs`13!$JGkp%b&E8QzJw_L5 zOnw9joc0^;O%OpF$Qp)W1HI!$4BaXX84`%@#^dk^hFp^pQ@rx4g(8Xjy#!X%+X5Jd@fs3amGT`}mhq#L97R>OwT5-m|h#yT_-v@(k$q7P*9X~T*3)LTdzP!*B} z+SldbVWrrwQo9wX*%FyK+sRXTa@O?WM^FGWOE?S`R(0P{<6p#f?0NJvnBia?k^fX2 zNQs7K-?EijgHJY}&zsr;qJ<*PCZUd*x|dD=IQPUK_nn)@X4KWtqoJNHkT?ZWL_hF? zS8lp2(q>;RXR|F;1O}EE#}gCrY~#n^O`_I&?&z5~7N;zL0)3Tup`%)oHMK-^r$NT% zbFg|o?b9w(q@)6w5V%si<$!U<#}s#x@0aX-hP>zwS#9*75VXA4K*%gUc>+yzupTDBOKH8WR4V0pM(HrfbQ&eJ79>HdCvE=F z|J>s;;iDLB^3(9}?biKbxf1$lI!*Z%*0&8UUq}wMyPs_hclyQQi4;NUY+x2qy|0J; zhn8;5)4ED1oHwg+VZF|80<4MrL97tGGXc5Sw$wAI#|2*cvQ=jB5+{AjMiDHmhUC*a zlmiZ`LAuAn_}hftXh;`Kq0zblDk8?O-`tnilIh|;3lZp@F_osJUV9`*R29M?7H{Fy z`nfVEIDIWXmU&YW;NjU8)EJpXhxe5t+scf|VXM!^bBlwNh)~7|3?fWwo_~ZFk(22% zTMesYw+LNx3J-_|DM~`v93yXe=jPD{q;li;5PD?Dyk+b? zo21|XpT@)$BM$%F=P9J19Vi&1#{jM3!^Y&fr&_`toi`XB1!n>sbL%U9I5<7!@?t)~ z;&H%z>bAaQ4f$wIzkjH70;<8tpUoxzKrPhn#IQfS%9l5=Iu))^XC<58D!-O z{B+o5R^Z21H0T9JQ5gNJnqh#qH^na|z92=hONIM~@_iuOi|F>jBh-?aA20}Qx~EpDGElELNn~|7WRXRFnw+Wdo`|# zBpU=Cz3z%cUJ0mx_1($X<40XEIYz(`noWeO+x#yb_pwj6)R(__%@_Cf>txOQ74wSJ z0#F3(zWWaR-jMEY$7C*3HJrohc79>MCUu26mfYN)f4M~4gD`}EX4e}A!U}QV8!S47 z6y-U-%+h`1n`*pQuKE%Av0@)+wBZr9mH}@vH@i{v(m-6QK7Ncf17x_D=)32`FOjjo zg|^VPf5c6-!FxN{25dvVh#fog=NNpXz zfB$o+0jbRkHH{!TKhE709f+jI^$3#v1Nmf80w`@7-5$1Iv_`)W^px8P-({xwb;D0y z7LKDAHgX<84?l!I*Dvi2#D@oAE^J|g$3!)x1Ua;_;<@#l1fD}lqU2_tS^6Ht$1Wl} zBESo7o^)9-Tjuz$8YQSGhfs{BQV6zW7dA?0b(Dbt=UnQs&4zHfe_sj{RJ4uS-vQpC zX;Bbsuju4%!o8?&m4UZU@~ZZjeFF6ex2ss5_60_JS_|iNc+R0GIjH1@Z z=rLT9%B|WWgOrR7IiIwr2=T;Ne?30M!@{%Qf8o`!>=s<2CBpCK_TWc(DX51>e^xh8 z&@$^b6CgOd7KXQV&Y4%}_#uN*mbanXq(2=Nj`L7H7*k(6F8s6{FOw@(DzU`4-*77{ zF+dxpv}%mFpYK?>N_2*#Y?oB*qEKB}VoQ@bzm>ptmVS_EC(#}Lxxx730trt0G)#$b zE=wVvtqOct1%*9}U{q<)2?{+0TzZzP0jgf9*)arV)*e!f`|jgT{7_9iS@e)recI#z zbzolURQ+TOzE!ymqvBY7+5NnAbWxvMLsLTwEbFqW=CPyCsmJ}P1^V30|D5E|p3BC5 z)3|qgw@ra7aXb-wsa|l^in~1_fm{7bS9jhVRkYVO#U{qMp z)Wce+|DJ}4<2gp8r0_xfZpMo#{Hl2MfjLcZdRB9(B(A(f;+4s*FxV{1F|4d`*sRNd zp4#@sEY|?^FIJ;tmH{@keZ$P(sLh5IdOk@k^0uB^BWr@pk6mHy$qf&~rI>P*a;h0C{%oA*i!VjWn&D~O#MxN&f@1Po# zKN+ zrGrkSjcr?^R#nGl<#Q722^wbYcgW@{+6CBS<1@%dPA8HC!~a`jTz<`g_l5N1M@9wn9GOAZ>nqNgq!yOCbZ@1z`U_N`Z>}+1HIZxk*5RDc&rd5{3qjRh8QmT$VyS;jK z;AF+r6XnnCp=wQYoG|rT2@8&IvKq*IB_WvS%nt%e{MCFm`&W*#LXc|HrD?nVBo=(8*=Aq?u$sDA_sC_RPDUiQ+wnIJET8vx$&fxkW~kP9qXKt zozR)@xGC!P)CTkjeWvXW5&@2?)qt)jiYWWBU?AUtzAN}{JE1I)dfz~7$;}~BmQF`k zpn11qmObXwRB8&rnEG*#4Xax3XBkKlw(;tb?Np^i+H8m(Wyz9k{~ogba@laiEk;2! zV*QV^6g6(QG%vX5Um#^sT&_e`B1pBW5yVth~xUs#0}nv?~C#l?W+9Lsb_5)!71rirGvY zTIJ$OPOY516Y|_014sNv+Z8cc5t_V=i>lWV=vNu#!58y9Zl&GsMEW#pPYPYGHQ|;vFvd*9eM==$_=vc7xnyz0~ zY}r??$<`wAO?JQk@?RGvkWVJlq2dk9vB(yV^vm{=NVI8dhsX<)O(#nr9YD?I?(VmQ z^r7VfUBn<~p3()8yOBjm$#KWx!5hRW)5Jl7wY@ky9lNM^jaT##8QGVsYeaVywmpv>X|Xj7gWE1Ezai&wVLt3p)k4w~yrskT-!PR!kiyQlaxl(( zXhF%Q9x}1TMt3~u@|#wWm-Vq?ZerK={8@~&@9r5JW}r#45#rWii};t`{5#&3$W)|@ zbAf2yDNe0q}NEUvq_Quq3cTjcw z@H_;$hu&xllCI9CFDLuScEMg|x{S7GdV8<&Mq=ezDnRZAyX-8gv97YTm0bg=d)(>N z+B2FcqvI9>jGtnK%eO%y zoBPkJTk%y`8TLf4)IXPBn`U|9>O~WL2C~C$z~9|0m*YH<-vg2CD^SX#&)B4ngOSG$ zV^wmy_iQk>dfN@Pv(ckfy&#ak@MLC7&Q6Ro#!ezM*VEh`+b3Jt%m(^T&p&WJ2Oqvj zs-4nq0TW6cv~(YI$n0UkfwN}kg3_fp?(ijSV#tR9L0}l2qjc7W?i*q01=St0eZ=4h zyGQbEw`9OEH>NMuIe)hVwYHsGERWOD;JxEiO7cQv%pFCeR+IyhwQ|y@&^24k+|8fD zLiOWFNJ2&vu2&`Jv96_z-Cd5RLgmeY3*4rDOQo?Jm`;I_(+ejsPM03!ly!*Cu}Cco zrQSrEDHNyzT(D5s1rZq!8#?f6@v6dB7a-aWs(Qk>N?UGAo{gytlh$%_IhyL7h?DLXDGx zgxGEBQoCAWo-$LRvM=F5MTle`M})t3vVv;2j0HZY&G z22^iGhV@uaJh(XyyY%} zd4iH_UfdV#T=3n}(Lj^|n;O4|$;xhu*8T3hR1mc_A}fK}jfZ7LX~*n5+`8N2q#rI$ z@<_2VANlYF$vIH$ zl<)+*tIWW78IIINA7Rr7i{<;#^yzxoLNkXL)eSs=%|P>$YQIh+ea_3k z_s7r4%j7%&*NHSl?R4k%1>Z=M9o#zxY!n8sL5>BO-ZP;T3Gut>iLS@U%IBrX6BA3k z)&@q}V8a{X<5B}K5s(c(LQ=%v1ocr`t$EqqY0EqVjr65usa=0bkf|O#ky{j3)WBR(((L^wmyHRzoWuL2~WTC=`yZ zn%VX`L=|Ok0v7?s>IHg?yArBcync5rG#^+u)>a%qjES%dRZoIyA8gQ;StH z1Ao7{<&}6U=5}4v<)1T7t!J_CL%U}CKNs-0xWoTTeqj{5{?Be$L0_tk>M9o8 zo371}S#30rKZFM{`H_(L`EM9DGp+Mifk&IP|C2Zu_)Ghr4Qtpmkm1osCf@%Z$%t+7 zYH$Cr)Ro@3-QDeQJ8m+x6%;?YYT;k6Z0E-?kr>x33`H%*ueBD7Zx~3&HtWn0?2Wt} zTG}*|v?{$ajzt}xPzV%lL1t-URi8*Zn)YljXNGDb>;!905Td|mpa@mHjIH%VIiGx- zd@MqhpYFu4_?y5N4xiHn3vX&|e6r~Xt> zZG`aGq|yTNjv;9E+Txuoa@A(9V7g?1_T5FzRI;!=NP1Kqou1z5?%X~Wwb{trRfd>i z8&y^H)8YnKyA_Fyx>}RNmQIczT?w2J4SNvI{5J&}Wto|8FR(W;Qw#b1G<1%#tmYzQ zQ2mZA-PAdi%RQOhkHy9Ea#TPSw?WxwL@H@cbkZwIq0B!@ns}niALidmn&W?!Vd4Gj zO7FiuV4*6Mr^2xlFSvM;Cp_#r8UaqIzHJQg_z^rEJw&OMm_8NGAY2)rKvki|o1bH~ z$2IbfVeY2L(^*rMRU1lM5Y_sgrDS`Z??nR2lX;zyR=c%UyGb*%TC-Dil?SihkjrQy~TMv6;BMs7P8il`H7DmpVm@rJ;b)hW)BL)GjS154b*xq-NXq2cwE z^;VP7ua2pxvCmxrnqUYQMH%a%nHmwmI33nJM(>4LznvY*k&C0{8f*%?zggpDgkuz&JBx{9mfb@wegEl2v!=}Sq2Gaty0<)UrOT0{MZtZ~j5y&w zXlYa_jY)I_+VA-^#mEox#+G>UgvM!Ac8zI<%JRXM_73Q!#i3O|)lOP*qBeJG#BST0 zqohi)O!|$|2SeJQo(w6w7%*92S})XfnhrH_Z8qe!G5>CglP=nI7JAOW?(Z29;pXJ9 zR9`KzQ=WEhy*)WH>$;7Cdz|>*i>=##0bB)oU0OR>>N<21e4rMCHDemNi2LD>Nc$;& zQRFthpWniC1J6@Zh~iJCoLOxN`oCKD5Q4r%ynwgUKPlIEd#?QViIqovY|czyK8>6B zSP%{2-<;%;1`#0mG^B(8KbtXF;Nf>K#Di72UWE4gQ%(_26Koiad)q$xRL~?pN71ZZ zujaaCx~jXjygw;rI!WB=xrOJO6HJ!!w}7eiivtCg5K|F6$EXa)=xUC za^JXSX98W`7g-tm@uo|BKj39Dl;sg5ta;4qjo^pCh~{-HdLl6qI9Ix6f$+qiZ$}s= zNguKrU;u+T@ko(Vr1>)Q%h$?UKXCY>3se%&;h2osl2D zE4A9bd7_|^njDd)6cI*FupHpE3){4NQ*$k*cOWZ_?CZ>Z4_fl@n(mMnYK62Q1d@+I zr&O))G4hMihgBqRIAJkLdk(p(D~X{-oBUA+If@B}j& zsHbeJ3RzTq96lB7d($h$xTeZ^gP0c{t!Y0c)aQE;$FY2!mACg!GDEMKXFOPI^)nHZ z`aSPJpvV0|bbrzhWWkuPURlDeN%VT8tndV8?d)eN*i4I@u zVKl^6{?}A?P)Fsy?3oi#clf}L18t;TjNI2>eI&(ezDK7RyqFxcv%>?oxUlonv(px) z$vnPzRH`y5A(x!yOIfL0bmgeMQB$H5wenx~!ujQK*nUBW;@Em&6Xv2%s(~H5WcU2R z;%Nw<$tI)a`Ve!>x+qegJnQsN2N7HaKzrFqM>`6R*gvh%O*-%THt zrB$Nk;lE;z{s{r^PPm5qz(&lM{sO*g+W{sK+m3M_z=4=&CC>T`{X}1Vg2PEfSj2x_ zmT*(x;ov%3F?qoEeeM>dUn$a*?SIGyO8m806J1W1o+4HRhc2`9$s6hM#qAm zChQ87b~GEw{ADfs+5}FJ8+|bIlIv(jT$Ap#hSHoXdd9#w<#cA<1Rkq^*EEkknUd4& zoIWIY)sAswy6fSERVm&!SO~#iN$OgOX*{9@_BWFyJTvC%S++ilSfCrO(?u=Dc?CXZ zzCG&0yVR{Z`|ZF0eEApWEo#s9osV>F{uK{QA@BES#&;#KsScf>y zvs?vIbI>VrT<*!;XmQS=bhq%46-aambZ(8KU-wOO2=en~D}MCToB_u;Yz{)1ySrPZ z@=$}EvjTdzTWU7c0ZI6L8=yP+YRD_eMMos}b5vY^S*~VZysrkq<`cK3>>v%uy7jgq z0ilW9KjVDHLv0b<1K_`1IkbTOINs0=m-22c%M~l=^S}%hbli-3?BnNq?b`hx^HX2J zIe6ECljRL0uBWb`%{EA=%!i^4sMcj+U_TaTZRb+~GOk z^ZW!nky0n*Wb*r+Q|9H@ml@Z5gU&W`(z4-j!OzC1wOke`TRAYGZVl$PmQ16{3196( zO*?`--I}Qf(2HIwb2&1FB^!faPA2=sLg(@6P4mN)>Dc3i(B0;@O-y2;lM4akD>@^v z=u>*|!s&9zem70g7zfw9FXl1bpJW(C#5w#uy5!V?Q(U35A~$dR%LDVnq@}kQm13{} zd53q3N(s$Eu{R}k2esbftfjfOITCL;jWa$}(mmm}d(&7JZ6d3%IABCapFFYjdEjdK z&4Edqf$G^MNAtL=uCDRs&Fu@FXRgX{*0<(@c3|PNHa>L%zvxWS={L8%qw`STm+=Rd zA}FLspESSIpE_^41~#5yI2bJ=9`oc;GIL!JuW&7YetZ?0H}$$%8rW@*J37L-~Rsx!)8($nI4 zZhcZ2^=Y+p4YPl%j!nFJA|*M^gc(0o$i3nlphe+~-_m}jVkRN{spFs(o0ajW@f3K{ zDV!#BwL322CET$}Y}^0ixYj2w>&Xh12|R8&yEw|wLDvF!lZ#dOTHM9pK6@Nm-@9Lnng4ZHBgBSrr7KI8YCC9DX5Kg|`HsiwJHg2(7#nS;A{b3tVO?Z% za{m5b3rFV6EpX;=;n#wltDv1LE*|g5pQ+OY&*6qCJZc5oDS6Z6JD#6F)bWxZSF@q% z+1WV;m!lRB!n^PC>RgQCI#D1br_o^#iPk>;K2hB~0^<~)?p}LG%kigm@moD#q3PE+ zA^Qca)(xnqw6x>XFhV6ku9r$E>bWNrVH9fum0?4s?Rn2LG{Vm_+QJHse6xa%nzQ?k zKug4PW~#Gtb;#5+9!QBgyB@q=sk9=$S{4T>wjFICStOM?__fr+Kei1 z3j~xPqW;W@YkiUM;HngG!;>@AITg}vAE`M2Pj9Irl4w1fo4w<|Bu!%rh%a(Ai^Zhi zs92>v5;@Y(Zi#RI*ua*h`d_7;byQSa*v9E{2x$<-_=5Z<7{%)}4XExANcz@rK69T0x3%H<@frW>RA8^swA+^a(FxK| zFl3LD*ImHN=XDUkrRhp6RY5$rQ{bRgSO*(vEHYV)3Mo6Jy3puiLmU&g82p{qr0F?ohmbz)f2r{X2|T2 z$4fdQ=>0BeKbiVM!e-lIIs8wVTuC_m7}y4A_%ikI;Wm5$9j(^Y z(cD%U%k)X>_>9~t8;pGzL6L-fmQO@K; zo&vQzMlgY95;1BSkngY)e{`n0!NfVgf}2mB3t}D9@*N;FQ{HZ3Pb%BK6;5#-O|WI( zb6h@qTLU~AbVW#_6?c!?Dj65Now7*pU{h!1+eCV^KCuPAGs28~3k@ueL5+u|Z-7}t z9|lskE`4B7W8wMs@xJa{#bsCGDFoRSNSnmNYB&U7 zVGKWe%+kFB6kb)e;TyHfqtU6~fRg)f|>=5(N36)0+C z`hv65J<$B}WUc!wFAb^QtY31yNleq4dzmG`1wHTj=c*=hay9iD071Hc?oYoUk|M*_ zU1GihAMBsM@5rUJ(qS?9ZYJ6@{bNqJ`2Mr+5#hKf?doa?F|+^IR!8lq9)wS3tF_9n zW_?hm)G(M+MYb?V9YoX^_mu5h-LP^TL^!Q9Z7|@sO(rg_4+@=PdI)WL(B7`!K^ND- z-uIuVDCVEdH_C@c71YGYT^_Scf_dhB8Z2Xy6vGtBSlYud9vggOqv^L~F{BraSE_t} zIkP+Hp2&nH^-MNEs}^`oMLy11`PQW$T|K(`Bu*(f@)mv1-qY(_YG&J2M2<7k;;RK~ zL{Fqj9yCz8(S{}@c)S!65aF<=&eLI{hAMErCx&>i7OeDN>okvegO87OaG{Jmi<|}D zaT@b|0X{d@OIJ7zvT>r+eTzgLq~|Dpu)Z&db-P4z*`M$UL51lf>FLlq6rfG)%doyp z)3kk_YIM!03eQ8Vu_2fg{+osaEJPtJ-s36R+5_AEG12`NG)IQ#TF9c@$99%0iye+ zUzZ57=m2)$D(5Nx!n)=5Au&O0BBgwxIBaeI(mro$#&UGCr<;C{UjJVAbVi%|+WP(a zL$U@TYCxJ=1{Z~}rnW;7UVb7+ZnzgmrogDxhjLGo>c~MiJAWs&&;AGg@%U?Y^0JhL ze(x6Z74JG6FlOFK(T}SXQfhr}RIFl@QXKnIcXYF)5|V~e-}suHILKT-k|<*~Ij|VF zC;t@=uj=hot~*!C68G8hTA%8SzOfETOXQ|3FSaIEjvBJp(A)7SWUi5!Eu#yWgY+;n zlm<$+UDou*V+246_o#V4kMdto8hF%%Lki#zPh}KYXmMf?hrN0;>Mv%`@{0Qn`Ujp) z=lZe+13>^Q!9zT);H<(#bIeRWz%#*}sgUX9P|9($kexOyKIOc`dLux}c$7It4u|Rl z6SSkY*V~g_B-hMPo_ak>>z@AVQ(_N)VY2kB3IZ0G(iDUYw+2d7W^~(Jq}KY=JnWS( z#rzEa&0uNhJ>QE8iiyz;n2H|SV#Og+wEZv=f2%1ELX!SX-(d3tEj$5$1}70Mp<&eI zCkfbByL7af=qQE@5vDVxx1}FSGt_a1DoE3SDI+G)mBAna)KBG4p8Epxl9QZ4BfdAN zFnF|Y(umr;gRgG6NLQ$?ZWgllEeeq~z^ZS7L?<(~O&$5|y)Al^iMKy}&W+eMm1W z7EMU)u^ke(A1#XCV>CZ71}P}0x)4wtHO8#JRG3MA-6g=`ZM!FcICCZ{IEw8Dm2&LQ z1|r)BUG^0GzI6f946RrBlfB1Vs)~8toZf~7)+G;pv&XiUO(%5bm)pl=p>nV^o*;&T z;}@oZSibzto$arQgfkp|z4Z($P>dTXE{4O=vY0!)kDO* zGF8a4wq#VaFpLfK!iELy@?-SeRrdz%F*}hjKcA*y@mj~VD3!it9lhRhX}5YOaR9$} z3mS%$2Be7{l(+MVx3 z(4?h;P!jnRmX9J9sYN#7i=iyj_5q7n#X(!cdqI2lnr8T$IfOW<_v`eB!d9xY1P=2q&WtOXY=D9QYteP)De?S4}FK6#6Ma z=E*V+#s8>L;8aVroK^6iKo=MH{4yEZ_>N-N z`(|;aOATba1^asjxlILk<4}f~`39dBFlxj>Dw(hMYKPO3EEt1@S`1lxFNM+J@uB7T zZ8WKjz7HF1-5&2=l=fqF-*@>n5J}jIxdDwpT?oKM3s8Nr`x8JnN-kCE?~aM1H!hAE z%%w(3kHfGwMnMmNj(SU(w42OrC-euI>Dsjk&jz3ts}WHqmMpzQ3vZrsXrZ|}+MHA7 z068obeXZTsO*6RS@o3x80E4ok``rV^Y3hr&C1;|ZZ0|*EKO`$lECUYG2gVFtUTw)R z4Um<0ZzlON`zTdvVdL#KFoMFQX*a5wM0Czp%wTtfK4Sjs)P**RW&?lP$(<}q%r68Z zS53Y!d@&~ne9O)A^tNrXHhXBkj~$8j%pT1%%mypa9AW5E&s9)rjF4@O3ytH{0z6riz|@< zB~UPh*wRFg2^7EbQrHf0y?E~dHlkOxof_a?M{LqQ^C!i2dawHTPYUE=X@2(3<=OOxs8qn_(y>pU>u^}3y&df{JarR0@VJn0f+U%UiF=$Wyq zQvnVHESil@d|8&R<%}uidGh7@u^(%?$#|&J$pvFC-n8&A>utA=n3#)yMkz+qnG3wd zP7xCnF|$9Dif@N~L)Vde3hW8W!UY0BgT2v(wzp;tlLmyk2%N|0jfG$%<;A&IVrOI< z!L)o>j>;dFaqA3pL}b-Je(bB@VJ4%!JeX@3x!i{yIeIso^=n?fDX`3bU=eG7sTc%g%ye8$v8P@yKE^XD=NYxTb zbf!Mk=h|otpqjFaA-vs5YOF-*GwWPc7VbaOW&stlANnCN8iftFMMrUdYNJ_Bnn5Vt zxfz@Ah|+4&P;reZxp;MmEI7C|FOv8NKUm8njF7Wb6Gi7DeODLl&G~}G4be&*Hi0Qw z5}77vL0P+7-B%UL@3n1&JPxW^d@vVwp?u#gVcJqY9#@-3X{ok#UfW3<1fb%FT`|)V~ggq z(3AUoUS-;7)^hCjdT0Kf{i}h)mBg4qhtHHBti=~h^n^OTH5U*XMgDLIR@sre`AaB$ zg)IGBET_4??m@cx&c~bA80O7B8CHR7(LX7%HThkeC*@vi{-pL%e)yXp!B2InafbDF zjPXf1mko3h59{lT6EEbxKO1Z5GF71)WwowO6kY|6tjSVSWdQ}NsK2x{>i|MKZK8%Q zfu&_0D;CO-Jg0#YmyfctyJ!mRJp)e#@O0mYdp|8x;G1%OZQ3Q847YWTyy|%^cpA;m zze0(5p{tMu^lDkpe?HynyO?a1$_LJl2L&mpeKu%8YvgRNr=%2z${%WThHG=vrWY@4 zsA`OP#O&)TetZ>s%h!=+CE15lOOls&nvC~$Qz0Ph7tHiP;O$i|eDwpT{cp>+)0-|; zY$|bB+Gbel>5aRN3>c0x)4U=|X+z+{ zn*_p*EQoquRL+=+p;=lm`d71&1NqBz&_ph)MXu(Nv6&XE7(RsS)^MGj5Q?Fwude-(sq zjJ>aOq!7!EN>@(fK7EE#;i_BGvli`5U;r!YA{JRodLBc6-`n8K+Fjgwb%sX;j=qHQ z7&Tr!)!{HXoO<2BQrV9Sw?JRaLXV8HrsNevvnf>Y-6|{T!pYLl7jp$-nEE z#X!4G4L#K0qG_4Z;Cj6=;b|Be$hi4JvMH!-voxqx^@8cXp`B??eFBz2lLD8RRaRGh zn7kUfy!YV~p(R|p7iC1Rdgt$_24i0cd-S8HpG|`@my70g^y`gu%#Tf_L21-k?sRRZHK&at(*ED0P8iw{7?R$9~OF$Ko;Iu5)ur5<->x!m93Eb zFYpIx60s=Wxxw=`$aS-O&dCO_9?b1yKiPCQmSQb>T)963`*U+Ydj5kI(B(B?HNP8r z*bfSBpSu)w(Z3j7HQoRjUG(+d=IaE~tv}y14zHHs|0UcN52fT8V_<@2ep_ee{QgZG zmgp8iv4V{k;~8@I%M3<#B;2R>Ef(Gg_cQM7%}0s*^)SK6!Ym+~P^58*wnwV1BW@eG z4sZLqsUvBbFsr#8u7S1r4teQ;t)Y@jnn_m5jS$CsW1um!p&PqAcc8!zyiXHVta9QC zY~wCwCF0U%xiQPD_INKtTb;A|Zf29(mu9NI;E zc-e>*1%(LSXB`g}kd`#}O;veb<(sk~RWL|f3ljxCnEZDdNSTDV6#Td({6l&y4IjKF z^}lIUq*ZUqgTPumD)RrCN{M^jhY>E~1pn|KOZ5((%F)G|*ZQ|r4zIbrEiV%42hJV8 z3xS)=!X1+=olbdGJ=yZil?oXLct8FM{(6ikLL3E%=q#O6(H$p~gQu6T8N!plf!96| z&Q3=`L~>U0zZh;z(pGR2^S^{#PrPxTRHD1RQOON&f)Siaf`GLj#UOk&(|@0?zm;Sx ztsGt8=29-MZs5CSf1l1jNFtNt5rFNZxJPvkNu~2}7*9468TWm>nN9TP&^!;J{-h)_ z7WsHH9|F%I`Pb!>KAS3jQWKfGivTVkMJLO-HUGM_a4UQ_%RgL6WZvrW+Z4ujZn;y@ zz9$=oO!7qVTaQAA^BhX&ZxS*|5dj803M=k&2%QrXda`-Q#IoZL6E(g+tN!6CA!CP* zCpWtCujIea)ENl0liwVfj)Nc<9mV%+e@=d`haoZ*`B7+PNjEbXBkv=B+Pi^~L#EO$D$ZqTiD8f<5$eyb54-(=3 zh)6i8i|jp(@OnRrY5B8t|LFXFQVQ895n*P16cEKTrT*~yLH6Z4e*bZ5otpRDri&+A zfNbK1D5@O=sm`fN=WzWyse!za5n%^+6dHPGX#8DyIK>?9qyX}2XvBWVqbP%%D)7$= z=#$WulZlZR<{m#gU7lwqK4WS1Ne$#_P{b17qe$~UOXCl>5b|6WVh;5vVnR<%d+Lnp z$uEmML38}U4vaW8>shm6CzB(Wei3s#NAWE3)a2)z@i{4jTn;;aQS)O@l{rUM`J@K& l00vQ5JBs~;vo!vr%%-k{2_Fq1Mn4QF81S)AQ99zk{{c4yR+0b! diff --git a/todoffin/gradle/wrapper/gradle-wrapper.properties b/todoffin/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 1af9e093..00000000 --- a/todoffin/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/todoffin/gradlew b/todoffin/gradlew deleted file mode 100755 index 1aa94a42..00000000 --- a/todoffin/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/todoffin/gradlew.bat b/todoffin/gradlew.bat deleted file mode 100644 index 6689b85b..00000000 --- a/todoffin/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/todoffin/settings.gradle b/todoffin/settings.gradle deleted file mode 100644 index c39c0ad4..00000000 --- a/todoffin/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'todoffin' diff --git a/todoffin/src/main/java/com/genius/todoffin/TodoffinApplication.java b/todoffin/src/main/java/com/genius/todoffin/TodoffinApplication.java deleted file mode 100644 index 239cbcda..00000000 --- a/todoffin/src/main/java/com/genius/todoffin/TodoffinApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.genius.todoffin; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class TodoffinApplication { - - public static void main(String[] args) { - SpringApplication.run(TodoffinApplication.class, args); - } - -} diff --git a/todoffin/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java b/todoffin/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java deleted file mode 100644 index 0774dfab..00000000 --- a/todoffin/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.genius.todoffin; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class TodoffinApplicationTests { - - @Test - void contextLoads() { - } - -} From e1c602ff455e6d6cf6eca31f69bf2b03311cc90a Mon Sep 17 00:00:00 2001 From: SSung023 Date: Mon, 25 Dec 2023 20:42:34 +0900 Subject: [PATCH 002/234] =?UTF-8?q?init:=20=EC=B4=88=EA=B8=B0=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85=20revert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todoffin/.gradle/8.5/checksums/checksums.lock | Bin 0 -> 17 bytes .../.gradle/8.5/checksums/md5-checksums.bin | Bin 0 -> 23797 bytes .../.gradle/8.5/checksums/sha1-checksums.bin | Bin 0 -> 35183 bytes .../dependencies-accessors.lock | Bin 0 -> 17 bytes .../8.5/dependencies-accessors/gc.properties | 0 .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .../.gradle/8.5/fileChanges/last-build.bin | Bin 0 -> 1 bytes .../.gradle/8.5/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes todoffin/.gradle/8.5/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .../buildOutputCleanup/cache.properties | 2 ++ todoffin/.gradle/vcs-1/gc.properties | 0 todoffin/.idea/.gitignore | 8 +++++ todoffin/.idea/compiler.xml | 16 +++++++++ todoffin/.idea/gradle.xml | 17 +++++++++ todoffin/.idea/jarRepositories.xml | 20 +++++++++++ todoffin/.idea/misc.xml | 8 +++++ todoffin/.idea/vcs.xml | 6 ++++ todoffin/HELP.md | 34 ++++++++++++++++++ .../genius/todoffin/TodoffinApplication.class | Bin 0 -> 721 bytes .../out/production/resources/application.yml | 0 todoffin/src/main/resources/application.yml | 0 22 files changed, 111 insertions(+) create mode 100644 todoffin/.gradle/8.5/checksums/checksums.lock create mode 100644 todoffin/.gradle/8.5/checksums/md5-checksums.bin create mode 100644 todoffin/.gradle/8.5/checksums/sha1-checksums.bin create mode 100644 todoffin/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock create mode 100644 todoffin/.gradle/8.5/dependencies-accessors/gc.properties create mode 100644 todoffin/.gradle/8.5/executionHistory/executionHistory.lock create mode 100644 todoffin/.gradle/8.5/fileChanges/last-build.bin create mode 100644 todoffin/.gradle/8.5/fileHashes/fileHashes.lock create mode 100644 todoffin/.gradle/8.5/gc.properties create mode 100644 todoffin/.gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 todoffin/.gradle/buildOutputCleanup/cache.properties create mode 100644 todoffin/.gradle/vcs-1/gc.properties create mode 100644 todoffin/.idea/.gitignore create mode 100644 todoffin/.idea/compiler.xml create mode 100644 todoffin/.idea/gradle.xml create mode 100644 todoffin/.idea/jarRepositories.xml create mode 100644 todoffin/.idea/misc.xml create mode 100644 todoffin/.idea/vcs.xml create mode 100644 todoffin/HELP.md create mode 100644 todoffin/out/production/classes/com/genius/todoffin/TodoffinApplication.class create mode 100644 todoffin/out/production/resources/application.yml create mode 100644 todoffin/src/main/resources/application.yml diff --git a/todoffin/.gradle/8.5/checksums/checksums.lock b/todoffin/.gradle/8.5/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..671d96fcd5b35de384926089009b508402f5f3c4 GIT binary patch literal 17 VcmZR6pOnF6|9*i20~jza0{}2(1Y7_B literal 0 HcmV?d00001 diff --git a/todoffin/.gradle/8.5/checksums/md5-checksums.bin b/todoffin/.gradle/8.5/checksums/md5-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..aebb79692a0426814a5cc114877b31f5eeb47f44 GIT binary patch literal 23797 zcmeI3i91zWAIFbU9Osy#%tHxj5GqlK1~QMwn2@2tjY=w%1`VigxrLM>G?Jl6N;i_s z5oth!ijokg$WY$3&sxj-*r$KseV@Ia=PvH2_1V9*zGt7ccTan-q|qcrMp2{vw@v-; zuhef;0#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pM3FA1=~ zLImJqBP{op{ui140S>xUt?RKh$$Vu3@H zZdU{3Ho;>&-b78eEc+AW)~1*rw)?wC^z@45kXxu>9_c$U^z>TBDaaj)F^{%c@Znjt zOgrSRx?{YwHPs-vC>3(cPRx&8)6$bO_w9$=F#_|L!||K%Rv*%a+{qmC#QD3@e8n%F zLE}GRp7hFS=Zn#8-H@;Gz&ynyhkYw^`vu6&OEJIn$?mP{n_dHS{8r3wl>aidOR+zR zj-Q8lW~E{3QX#u$$Sry?&k5q|?C^|{h1_aC<~MgO2s;s@um*Bg)EK{?|FONv_%`BB zW4!X^C8+_QV~|_F#r)2`Pu8E`CW%At7LWP;70Xs>cF4Vg+)-hSZ*g4vF7yPN=b94C zOJZ-H*FW|h&Byu%<`qNH2K8y*+fZEz%&VFU?zbEGK8AdC5au)-db#= zo!PK73mtzV=I?KtY{}p6o(Q>{FXkVnANjraP4;TYUHvg{tM7VPBFjDwx$Q;FKkFn~ zwU~Uy=bO|RH)vm2R@z$($6L6K@vXNX=?wq854rQTF&@RwVp~=gLT+V^c}LqeMlNG{ zKIC>AG5;bXTN3_wtp((!bj&+L81r5PcCClpeh~BSA+GFn{z0 za@X^i57w0TKO9Ma2f67|%z2m-e>9uvf_;!^rcW`S_$s<*XmAr7a`OSqc`xqxH9gcx z6LROBnDbp&TOMrm`Y;-Q3Uj)~tmQd()5;)sNyS{CZo&Kzwha?27Aa|1TlAcm>S&QV}g!v zBZ|4}%xz}=Yd^W8uGeOZF!yc`}Nl(w9lI*U>^1(GUe+T-?ebOeHrHXC%7ZzfqzEzR0322R0322R0322R0322 zR0322R0322R0322R0322R0322R0322R0322R0322R0322R0322R0322R0322R0322 zR0322|LX}54_~W+|A>bsr;A+8FL@~P?~0oz9(e~`+O3#CqwyR|zU_{lXq{9I2C0uX z8a3$A|6EG7Q4f2cDl$n7JhMs|YqP;f&=|zC_#|Vrj$bI5|CqgujCz%EP&|10k7J;x zs0IAFjdMI74EtW}EsXt`yg5d->NhfO5fwmrParZbIz!oO{SX zPg&E6r|d~JF8lc;e7os&HeuTCxeiP}Fn`X`c{&xNntzcV4+P>%c{D zY>rU{4cQsoMoz#3`*r34nQvbYzdCh#8A<0W{FBIj*g2gz?&nS zm7`fr3*$CK1@C-#ekxQfXCABRq*f|8%Q?m`q5_S^Py8g+&^~wf`R10Vx0}Lwr_3oT zGKGc*dU8`*kJ~u7-~M*zHt`#Dmf}RFo%us#5CuS!)&(7@hU(Dhv%N<$<#-3R9JgGN zcR&VuBS`iJw-KN%pqqTx*wdkUk?r1YOG9Kxfsvpk#et4gBh4h%I>s%psmpY6ZjgD6 zJTiDW0!_Ms+u)s)<#P4ew2qApQAG`F{nDVZ7CqNHn|L0cRHIL2OGc!@gc&N~EZ>Zf zC9?QljN1^;_*QrRXMI+C*uyyYPX^A&IDrf^ZeztotDABW?av-`omi$l(G@(^ zOMHSJ$PftUHl9`Vy%@S{9Qb`|RBe^ussUu6_gR>PNm|E7h4 zAY&@=ls#!yo2@P%Hk_BQTX&LHw(NRP;CMrVxGIngtG($Al*pULa3qvLoB?N=UP zrz08@>=a~UB+=&mxwz&tjv_;nv6+%DP>p{%0!_k)+gNrZzj5pQ{))b%mJ3rHlaC<- z?TM2oaT}kSH_O|&C`&CrK4PQQngi10tQ^!L8Y2R9q;=TM3zJB$5%tu2q&?$>noKP+ zz`jK^CLicXhQ3I+w2*GNpU29e4f>6&UE>Y99k;Qm;e1<EAz4j=)^bWWmAy)2*-sxcc!7wp=q?mNhJ9|lguGN6sPjJX_ z2JyB4DJ%BNHKD0Fb9<_kR4VW6mQh0ndhdl_l-uz8`EUHwbt8diwC(n%HC;G_3}rA9 zG`c0|NHy}pR9Rtz4J`WPz;-vQL~vE+%!=64L1V539myE-%CN9KBl{1l=*Pj92Pxpp z;~2yl2-+I(y(b$&qYRhpq3v%fRw@RS%W9uSh6zWYN%e3Wd>{EbrEAvjH}ML(u>E2r zm_Mfm3mQ|vEXg%`H%JtJFVQqjw_O*j_4h(>Y>u%S8iMb*jkx`B-5(||SKMkbcj+~m zST|(&K_eiM+ptK?%i7Vd&TcV3cYkH$(kIWX_ccXeYe`7MUZVxin zLnD%SYmQ{ZwOrA>GHuq*5k``YajJp}GMu2n3$BY~ z#MKP6*~Dj0s-aoeq{G`CnaJL?*k)5w$7NI_nHUf5Oa4$}Rm07@s!dO}=jbi1n6DzH zKi&}d!}nufq1fXCBTT2L2VoZ%d*oIl1Kou)iTg#;tYi*#9~FLBRFz(;MK5WOHbMq^ zC2@LgJx6}QAw;?jnRZZm%x8cAJNNBZ`7|5-4Fjv@q3tVA1XJR=R z2^yaT=t#5baMTQ&v8`6vT%cHO`D-th^3eRuI;ZX?p0NA;nBPO5>Q=U0Qp#aZJGI{1Hb4Q;pU^5G)wUFppDo}(Uf&LV>i zXEkLDw^3QI-{R`3WpSL=koDvX3+zvvS&cwr-aBsNN|kk&;^lKZs$Yxd#du_(yKpaA zXv`JnHcod0X9_*u`bI-WGhewRJr&l#22+OH(8~|*bqn8FbNQG1iMZ*CO~_!t8e+4# zjlY^^+V*?4EiFrQ{Kur|>KSAZI~!=CbkLDL51s&KcHzB`o!i`X4BJPD0mRC^kRc$# zZM^iiV`%Ra={Tis>!@xL0!DHSV#T1D2ZD}NL$pA1qvT8LT#a>FXO%apO+^N{x)F`} z9CReZbHlD)Rn_8=V$+E1!y9=T#~VU__Nn7WKOxR)c^j7Oj&r7JqQWqt7w*OmOp zAW{WQ<_}kcI`@d+NlvlyxvyfU_!|XJ9B-(AJ2Y|)r7w;rYOPN8@(bjp-T8hNjNoKN z+`)iGe+xR&%4w`Se1o=SmZ6dFnh$-H1y^H^L7W|+(TRI&k^#22PdhF}saUg>>xTsY z5=TZcN1*ZkVfQI~UmmC>8=SL{wN(CUmctWd5c@4?llwtOs?le)ZNobDgz$)xa2`9u z;X-J5pW+BKesBb`F=v|x&*zFXnSJTQcCYV0M&BrJl&<7AZX+j8we83J+toh)ev{?q zX@F0RSh+WGK7qFCE9gkG$__3ypAr@!sNv~#+A;E-7cxpY0*zmi+Yl@d;P;8kX6=<| z_V3LyzJv_oIu6=o@SP;r`22H~_otn!(m$I`{VBSytPL5p9DycsklQ#F{L0&4sg*~b z|9jB{AHG~%8=PZ`{8)<6@F7+Vn!qN|k!t7+y}B0N%U*D@F2u|vdm%VBF)N>FjzAM5uIuXm E0q@IqVgLXD literal 0 HcmV?d00001 diff --git a/todoffin/.gradle/8.5/checksums/sha1-checksums.bin b/todoffin/.gradle/8.5/checksums/sha1-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..78e488e10e41deada73f8eb9489133dafee168f4 GIT binary patch literal 35183 zcmeI4c{o+w`~Q#3vm`T-WUh!bNTFm_=6T4Nd58>|Ge)RXG*G5A7*e4^L@H4!Dj6yv zR1``izkT*vdw;&q**?GP`aRe8uis@|*Rzl3{oJp0uXXQx-D~ZA&gZj>MB?RGfQ|H@ zw(>uJF8#F>fu#s6MPMlcOA%O#z)}R3BCr&Jr3frVU?~Di5m<`AQUsPFuoQu%2rNZl zDFRCoSc<^^FA*?BCc*?J!z;Ut{0I9X5@`bqiA1Fo-7Xn-zJRU-{OA1}vF{IJcAUT1 zSjn^%=$2`?-o-X{rltEi(0!LkVRh!VbkXt?J(* zf$n`8*Nu+dUw%?U9q9UrxE>Lz=YQ|ZQJ~wo;d-6u^?}{JoUtMhq}7k>)@x@|h9 zXN06#{HdFq2YPrMu2*h*XDGaZ7U=qun4Y=3X}GWI)_I`EE93ffpwSvj?)5D{@Kh9#}lv()3eG74}Mv_1Lj-QcT7L67WMs(aVyL>-w{kd zqv2VmI(#-9^dA+E>DdoW_D}bzDgoUf3)6Eh*O@#n7`+Shn2VTxu6DNj$z#XuKtF=p zpBp=}{#LFK9FN&E%szLQmY5jdkO;8Xuf_Dd+Ec|$^@oRnZV`p)7bQGC#F%dl0D5pB zu3wJYDa+>r^UZV>reAWgs#`N03G*{a2GjH3=I1!oHevTs4A=Xnlb@ceXafC*;`vbE ztY;UP()R@DhE$k+(ZjYt$JL?9KsWZq^kUwwW3L6Pe*@j)Ev|2l;H94Ii2%BzIIgSu zc5h8B;Q_jq8m^a~3Q|5kqy==}2wd-va6J9Rr~&Aq_`19HhGWYyKWasw+r7l>uk*=o zG#5mx?$MmwzzYfi37c&BT2OeB+ioEci)HU<*fd*!a3YYKsR&3bwx>cgLl@gK;I{X>)EDvN^)O^0X=d6(<^cXzb5nc z!Fm|)jp;YMf~#eVH9@;h=pLpl zzXWs}T}-cHjXxIic(e=X{&?Qr5sWmA-lD1t^vFY){T+{J$$~R^>wtbR4cGgYvwodX z?gP3OZhu$DD~7hb{xi@+nlbyk*~#_`tU9os9LC3gw=&Rugkb{Kf18!K{Se2IAnRF} zZ!WgDJ}jzXOjDo=`j5}Y^}k1SI(jdi2D(3fUuv&q-EKdXr~>q`514&jR~UUI`e zpljd7^hYb&l3YmBiLf6$K9Azh?|!D{4)qu%+&=5R^w;@IV!-~;S4@9$?T@am{s>&B zdxtUoX<2=i@P*!3U~lDu>22<|CVcyn=z;Ez*S)rAqkF*`J3av2?-y>LIxr!@+-m^z zpclB_U>NFqhhZbo_wL5@XQNYnsWQfJoc8#-c#)I0QFlB=6xc^}V)h+cZ42_kM>BwK zPK)c0&%Be^RBH_M0Dnw>ng6rJG4x;z&=0g?`l~Z1HC6Nncm+8&mnVctEGC=_DABnH;7MCw!-_(oW(9 zdO$I*XZg=F2<$Zkdg3&ue^wn*Y-#=u_b)?yfBc*v!b$RY2KO&#{CxZQ-HO`dIk#s) zKl=}0{frC-FyCZWuL8Qo3QQkWYE0T*DXb6lh>e&&c1R{EaYh%OAKXH*e#So;i1Vkh z7Xf=CJa5Oxs5`#V=fm@ottDpvgG1OR>ilg^VDGa9*Z;~njJ5T_{MW(bGrg{nLx5gC z2-pYrWA@X&bkZx0lE#5A!V4vO1d^-GJ^xjqB%iRQm=w zLxH{z&%@uXYs^pew!{21J&M`S$+%q9(0mQ|DU%3XH@hj~Ce*$I^b@Or>#poiuH5g| z1bUPqrvF*B<@=4aJbpMHUtF)?rFtvsvLEP&=P~{7l+S=`K{VXg5_aSIg1^Si%eUaV z+n0>#3nvmU?3jGS0Q#}U>*PXN45MP{2wX1-c>XN(o{%Pum&3Z}*n;&#N{pBn6g)cs z`mw_EpK7_D2<_BYSD**DGIg%@)v@f_`*gV>-i!FsB8*|Wx$(_E@woWS+h{6!nBY~eoQg~yrAB{TZ$;7B#_ ztB0>U_F(DZH7o-qK=;G@ce9v*8vU{zGVkHST6wkLot|N&TX4%27VRR z0^MW;*U!K0oD})y2y}}vT(8=&>E&M;U!cd~^@Q_NV}~$lIUCRu@$s+tl^wWzETR$U zMtFRP0UjKP%sm@&WBEj=R zSP<3^A4|=&#KLlzp9iuqT|n0+cPq_0xDUot;d*D@XPrnfVc^$f52g!gJGeI;7J=tm zhd5ks8jp9X?t%6`?=W51dTaO7zpF(+KlY56E^vh77POW z(DRtSn!$A7(2NxF-iO?bPUHGz>XMkg_)?$;xnR1OqAt^V*H1}6_o~PAHBx~!@w&fZ zo(It4y4wNuzK!YUfPFX*uFvlbQ(t>!6zI-){)->`Ni!>{51-S`@%1PXq~OW;+~6~? zkI2RPk>o$fpQ73_1N2BWOqa^L!)1C;T>$95c$}q6Puda4UY3*lq)# z`)u%Yugsl&t>et4r+~dNHP-(+W$U%JEhqDU9)qvz^?sQ}x6|7;0X@71vtNIz|GB_t z$7Z1WR${vR=?8VnLn-h%!b=F#71BR&NeFtvda^$k*N5mQ{7*SbfPM~(W4f}=_UM2Y z?p#3kTlF8k!LXO(n*q=*ZejXnvwKw)7p}qQtZ2MGY|h;tW+g;&0`}hcI@+4GWJg_%v!|YYK!-dQyPzlu>+KPIoN6*R&t{(( zg!eb;G-j_MHsAVA|I<3qeTn#;rsipve$vS9QJfTKU9U;YtnrC;nZSOhAun2t>AtXh}Y*mMg`GQlJaoRoT|s- zDd<1i7T5QC1dKclhxry;hUq%)y^lp#uYu>}C_Y^Gh%W!Ti~+{ex&+hp%dPaBrMl-q z|0ekQ(tkC6{FZwm9OprE%-&%CowCBgXFGwt({)TY6nS8K{AAtrIFU4&Ta^1NLD%aQj4-Kf(04t{xQgv*RT^|KQu>g59m3O7YdHmw>$eaJU82oua}bzt1xG0Q+c6UT})aPFXnR3Gc59 zz8^Uc7VV=;?$rbKPKPkSdGy<@BWD@c0^L3c(_PQJ9voz)&tYXEJT`U232^)L?o5UCysT;iQ{D4_c4w`kU;B?Qik` zJs}*^4>kz}*>Ogw1Kltm(}P}B#-8?Yhx+&CS#ntoPpKIdZVmkIoA_>8@4alYIQUsPFuoQu%2rNZl zDFRCoSc1ePMO6oI7(EJa``0!tBCioj9?{(p@C`AbOhi*#}FKU{}P3u}4Sd`ak2 zER5V(u#IZ9c-2C0V6!FRZf?L^A%#vC8Nm>|{W3%`qsL?3+Dh)YL)S%yXf z{sMzJkVpk-*4>iS4gcJrn;(3SeImE)lT{w1;O`6=$-i@;7-1^k?ly)hRjr+0A8wCL zJhyxyw8I-xPY`RQ2?Zk;3W|sIveAJ$9kpGTI~>$sSm+wpv2KPG`92{{a5ZwFppq1; z9t|8*Vz-srPQ9ylx0%YxrdN<^Lm`ro1d)lP5wZB6@vPL6ypgZ z)hIbg#U3ZHRcYI*{&?=!8tP`U$o=`B6n8k0O2{k;=d@hKYf0-kc&sZ`Aj-rN%X#>X zue3dpsutvSU3XkqkKMgz?du4sfb6w#GSClK49wO! zd-2gnK#^UL8~+yMLP6dFh8Re5kaTEJpK?L6yt>U1Q7$n&zNLLm~r3X!^%+2VAy zBJ+D|h4EkSTvn41^FR?u(Lm}Yk*aZHP&KnHU9d{aIloqAnUAa2GGv9IzW4x@dx}U| zxfyb<`Yjqe6p-&jka<!VKN_n$4#Gs6kN-k+lUn7B`TiAOS3~EX4F};zKz2QdV1Vss6z<_9QGu_XPX0M}+~{2;)$QB$h{{T$~D?-cr>J$(K}XeplZ8;r`ky z>~Mr>fZ}*Sqy$zAxe7{YJbk+R+QPir{RoaziYFnp0Z^K{MC!>xT}|DIy&+7UI}asK zZF0_VP)EKKLt{wJTBPxXAs32yt+Q)Bo?dWqleqr&ztzpYd&;%ne1()c3XyD7iIm!9 zpF&QFz#Qwt`5H{0Q$OiA7f3_uAfULH6RCffz14T0wT*L2i~TVRz3?HkqPv)X0p4W^ zWTfOKM$UbTdHuWfWn`I86QQXsPTP06TEeq>gHpi^5@~dO9YbA_m{$;e?Wk%WQyX{| z^?uST;2AiBzRd6W z6E6)IT=Gi@2>2(p@&J+2GUs&r9sb8g{e8^33vPX!CT?0t-u+KsJmj+*g)ga*0c%w@ znau15YgL(#wnnDCU_A?|N-#o}BSh-w+fPZXs~Ss+UUTz?9pTrzZmB;2sX9RMI1?#V zFP2AwEBi@-_8NyLRWF!c-nT*@Qt-S%M@ys>e-^%1$`cF}%&5E2#-r8EebKZ7Qm=t8 zW^(0*UPlsr2r}7-+q?}8|-){=mQQQrwH-KWHCsM8<$6124n}mGWHnVKrlAk%h zA$Hk6DMlkAbux)f-eg4gzRJ)W!`)gZ8*jL%n?R}?`a-gqa)eKvok>--5zWT}le%Vzg*2w%esDczc+pu^Nsn;e6qJmPw(^i|N)Y(2(Mt*!MkJNWGqsG7ph2ImY z{VwCBFXvCB%#`o*rrGbWbME0$Bqzv}1As^j#YAdTiBGI$fp_6_pVMADx{9!~NnQ{C zNijMRDdyTs3Rh?o1J9?isQN#?=OsNE%>*e{;EZ_@UonL`96hzu$+r!hY97(_xl~f+ zBN0bOh*9!HUpI>{>CcxRf0(7N^r?E2k`nj5qc)J@2fi2fG@srBE?oSp;@8y{L#6%?;mZlUHIkBQ7b@-Tyv2oy-3d2 zhFNUgsd7*#e50HGfvpkBi3KZ>Jp_%R7NA()BcBwjQh_D>VMd{I(50mL9Hg8zV+?6yxf2tALL(phg07_ty$Pf2E z9Di_e#=ET{E`y$Z_~>|>r6Huu0Yy_l^flPl)Uhq$^*Ie+?!O%j#*zjJ=`LhSuFdZ{ zp!iCN)W4fo>u~m~P`0qDmbvveuVrOET^k(H3`gh+eEFOs`jXVF5c+%BPRq)v-Rqv< zhI+%^n01hH15^?cQ_B1G?|l8c$ZHJkdF5QgA11cWOM4&fnHPJ!`ai%C27(c?9w(0Q zMI38qfX*>0&-?2cZ%15yD>trHhEf&cLx5Vh$QeMbNR&EM{`b_CME3)_Bh#&x{c-(d zO0ErA(rDZK6ZxQ6DgSPS|1R?XE)Tn&@F}MibFQF$BMTXp?dj)o6 z#7vNqt<_gS>K34w*@#pJOPk@fv7GL6NruKZ@5oIRwSSI=)FVLA>k%ox_o)xLSwux_ zD?g=pnz}{}-VFjL^V<&al)^@S0;U+@%CBmVmPZuc47p+^TId%svFRoQb7ZRj5dam3wS_5xDmT>)v!k-Vg&mN6RB zTF(qwB%FJa++*`JxI|cCsfD zN!J&-P|VA@8l&y^Z>9sBc^bEXi(gArgxKkzy^(5T?!1dHU^w(A`g~ zwpdyCK0(gA|C5p>`=s#o@2Gz_LT4+6)=#ESUg#g46VJDeveYwvg1i$#N2m|n>aL|%${&mJHyE+cD9Qw0 zcSm;d|4Ffl5h>9Z$_-@-6D1$*e$jqBzLI__SPi?&JD{)YMC#QGnaLa#C5PulXFX}J zUloo0-n|R@+6gJ-q)vIi-rq4gc~x`k(AfHuIlVT!&E0aYT!R!mN72m^DNX6obqjOb zKiB05y{Kt-YxJ`xU8JCDb)e zUe~{7J^kDIhHJv&Ri2_}fOP^~p@3ScNThV%uhlC(n$W`WxZGN$m^;L1DtdMkyd}u0j)STW- zBE(pgnmtP}52jB*uOs_3Y!rKmE%t+%l_EHT(M6-FP48Wh5t1U*udz8YB71 zp2An`vZ6$fKu&2VZL=JOAO`>KM{=J*sv3nzbU%sI$H`6aWE>0j=u$g9yzFuGiH3R~ zVvYK002EEx{}!d*e&OVIs+xw(gSX7SeM>8x*tSCs`uYWYIUvupl=F&uI??lJyVPk8 zAAiTTAJ2D+3weqIYCnKTDvMMAyS!gdV#F=PJC2@+EKl=~PR#2@PU7Tw?I-U*NK?{4 zE)+3*RB!)TEJyFjlP~gL7`mIoXdCxgK#CKENLtxMs@E?nY_#c<%AV?!p+XDh2`&Fm z$Zml8;s+E1@;pZAtNhjl`_wDbWg~ihA+JVKF5WMsg}eEFavek(`zho?F~U$`=ja_$ z24&CKcM5Jfzu)6|z)Hjj^+n!~kw&-3ygF6wWR3Ut>~PZ>zs8Y~Gh-&Bg!v+$)sV)s z8o5yT8mn3xU-aR=PtaR~@VF1RjuzTi29fo_#e&pOiZu;ZMg@V#Ci!~2i#US*y&~?mal-=<5gB!@{1fACx z6e78jbDx4*({uMr8cz@XVP2;|xoz*<^0*%O0?G+_qCy(gwne`2DXH!mwvAXY-FxqocCL#B|5ZA12i><){ZH+D>w^YS-- zZ6C9*&${{NWGPF<3#2BYBScmw(x_xQkt@Xr{~eVlXF^N_ql=Y{OgEH;T0gB@Yk&>~AsX16y-rZSpVYtJDiTN&z5?qx|?*PTHfk?d*+P?0?eZ4Vi9r;Wy z_BPMMFWTdwuaAIYy-K78UJnmG9KNA$R%_}kw(IiSv(AG^g+?P!t};lYmqacUF&r#f zEmHExzm+qtcW6*1YL)0CbtHBuMcyOH08$bDcc_$(WkQ?mejDd$pG%Ivd)$BZNwZ$y z%b5ptMdDt>mt^I!a&PUYRC%vMIe3!F|7t!zy$w>LfTCaIY*W_2ef&g-pxL3Z7_GKq zpOQo8kNt)e+#MKNiSy#s6=>{ZT&M86l^hLcu$HOhbBUCx*ZV7R zKNU*1u;|ux2zRRKWv?uR6gel5#^ixqC?a3*mWs2#Q)}0j6aB8=UNwBp>{=%SDI_1s zZN(zKK1ObOwR-)l&g3D*4=vLZxzyZThXLg(1Srl$R?2oQPD722LC>gGTsh#dLDb3G z^b)ckljns~XAqiVgadtonr&ifrW1{Y)1zv>*V{?d$l67zRnS)+k^0;{tu8J3=EeX` z9x3DU-$3SWHZMrQ`^C8kWs+}qu}?PQV++-Y^3K(IosYF5Nwl5{JG@iN3yc@9h)0>aC(Zx}jf>FP0|VR{k2KU>0r`)fM)h;Mv9%P*i7vh`xkFnsP6)2VAR~ zVpAMxeDKt$U7CJ(Z3|0j40;y>B4f)?OP=2XtOJVD~*pZ>FL?| zNVV;n@e^mgO!mAs+YBiU)E7P7c_MYTg8g%t+`C zuTW%Q_7mOJYJ+vlwT2FQ4a_jX%I1d9f(dIhh;x)R)h*R2o;MsT<(G+1k|$lSR%!~wk9z|+3P{h9L?PA z`PU}f#*=?QioC9nAL&GrcRz{|zHXZO{MYKU{)_HlMuX3J#UHtjZ--PcN>OPc^^uaY zTJ|Ykbid)1=>`4lvPVa$+&zApLMjTSXlCXlqt<_M@S=IrmMAX;He)1C)#k?}hW^d2vh{paI>9MafJmi@3uH7aB^(-l!!69RCTVEKP`evapaPI2 zJ&083ov9((v2Ax9Dw={`>4bf09~E+d6g$oaAD1qJ z)K$b9`Sbl2A{D(dwOpj7)eBjscJ{ENep^-6LlMrc#f`?XF$$P=YVoQp*MzfnkyBIi2N7Nevt{>)?2zoN<; z__N-SyLYN<-dh=t@FNN_IWOWXCt0^WShII$f{R>yb@YnWxncoGl_9?g6e3$AO@s@% zP{i=RQOF396+>=|Q5QZ=>h9yx4N6Qss~&uer!T#>1@(ncKTwE98`(Q4eVuMmZS%Vq zd2IZKm)+=*ANz`SaKJhbDU!@3B2{p~ zZ`U~hSBkqTN-1SJ6DeJ-1Bb2(v~~78ykeWODN2xms~$OLpd-{lDdsU-BDKAJzTcH` zNLxr*Z+txKP>Ow9 zCz1LPH*|COl2r4%h7Vp2J0A?aDfy1nKk^7Yhfs>dnM|b0XFTi9YqZ|~#X7GNoYfjR zvMEFjQa>S;N~A9HFYxb>4+L^_h?4-Ii^6*wEIdH}VTo*m<5@xhd|l;Vx>1SmT#7 zFBwml?|*!-tJCrcFQmv74QZTl$c2KMIP2X!_R2+}wJ+Fm^vC;r`Mhc#NNq!*MXA8; zKfgbIwu-Y$(6OjrDSsK?)fyp4X+vs}lR1_D6aJ&q;%|!izIN=$ybv#OFPa)s9Viv> zlDzv-j4)G#rd#(<^__uSXSWy8@gb`V9w8?N@?G`@t{B&}EJ8`%y!Jx-XH|x4a;HqS zD4XrZ3St}*_9fjY2+CQ^`(hYE9e&4iA+i+Y3MGB9B;1E3J4Q6I(}2OCj?S9C`EHm zfjB~B)AC5#P5;idFxyzJq*=ByZxX(9@CM!*-0^(L)DHq96W890^=3|09ch(^Pubq(C`A=pPo&zEnwsj}q^Y&f zovD8)aE0xlpg1k`)sIs2m99i8`5nLerW1k-<29<=+^4Se@39?2b~`kN$dd=sSPB+7 zrMR!Q$Uk#-_Y}JF%RV5xH%e_peNlU?CHk^f(zp5}(asr5 zCG5}Nk>p46U4`sGD5Zx|Y@s`dl(rRvS@g`@Ia}kdm-qLu{xXPiK%#(Bfhfi0{E0}N zsIS~0_ezY<&7CIaAkEBM>P@3pAcZ^)AdN1yjYz3A%Bg7uvK1#udl#e_=xo+Yx$_-T zcTtMZ5y@uC80JK8aj9G7#u#cf`aL4dzacTR2kt38Pf?2cvO1AkVQrAOZDmFJEmd0< zGwImzcaguFp|3uaTK0vHNQIj*kCbWG@OQ7Ld;9gus)ZH3kKj(^gXAO9=(o_)fMKeF46FLB*yaSrI8$mNPQBR)V z)<6~ACDwmRRhT3n3n}CiX;iXI$dzJVDZ(J-z%Hwo%homtOg`ZqubFkXnsW^!$tXatKkZezQp|OXg(5 zMA+SXtDd(;azl!|10l`E8M#nI!!gmbY0zk4Td;XX?gO!PCx;5!k3q^5g*Xj6h?Lxe z`R-lnZq-LL8Lh|G@LFH`*;xZA@*YVdQ9mG$l){&$)3|nXQJ(qx^pnM-V}qi3O13GG z0ySz=F0!^MDG}#u_ET#o@2@p0?U4^Ps$Sz1f~-dJv%@YUvMc07M2B1`eEl~n@3vnT z3N1=0Q~eC#A7uIabaWvs#r@*)Hwx>KwTt>fY8AQhAu*-&_1`GO*C!NOlsa~&Wt6L% z=0^j6<)BO4)sLEr&WB($egJBH6w#NSWZ8#o{*yw7mi=9^|AEWIWoIE_K>2e5N(#9X zlp{P;_*>WPRTjhhZyWdQduJUo__UJ+Qpo8NX;cj4U7aF^9?eN|qgz*+8!3<5@|V*I+uTddKtGJ`6Coos5G{%n=>-8-P;{-x?46v+b_G35x8$2NL> z=T&(hB(8E;Iq3?Yx7ja#NWq-nL*6qf=H-2si-V?)DlBJxE!jBcxf_O{CU@n#T91%>H7M?Nkgo zKr?EQd>?uLghoCFrC2i;*&QlIwg%dimKg3;%^La}T7Rxb_>3*2GEqt}^cB$;<3YMh zrym(j6$rbh@c29o`n4`j3R2`sO(Jnsl4D5`dH>qBz6(8f-)l`4v~K46VqPaAhQy9M z!T|D#0m=DQD~MDxDRcD-j$OBwXJ@$feodULoHByh8z6{MbU*hIDV5BxmdkQNw!}&X z&*v(JlYG1UkS8b97kRfs{@k<3d1Lt&8b$BZ@A{3BR5Hc|+nB2ts`McR&NifCWXGkP zmy+ZLsqSkd^W(G%VsC=ZuQ8L`R|%<|WLG3=hCj%aA{s#xjFF-tAG|*8A1-~6u03jg zGZAM)#%PcLsqB8}cJ zWPV6n7H&nPlvXoN$jxlf?DMJb+H*rLz$?GR1X7;?6}(6uW@l_TlU(eh=VI0nv$H+k zvfAVh7Wp}pq8?l%=Us&z&o?Zm>loc!(tcO+clsT*;G?n81D&serd z%%1uAI?F`0bu2Rrc@`(nE07EIMM^_@p^Q8gkDzB!-o#GF*wy#>>$3Rm{eInm6gg{= zKRJ+pr=+riqP{ZbDxApQ$C7k;>w0FX_`jGhIg}z8-nDN9=CVcMFlX+_V2Z|xiywz`0x0g!nGq7;oJC literal 0 HcmV?d00001 diff --git a/todoffin/.gradle/8.5/gc.properties b/todoffin/.gradle/8.5/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/todoffin/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/todoffin/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000000000000000000000000000000000000..12deec080cd6ed0a115aedb03b0c8b0c3d8a7153 GIT binary patch literal 17 UcmZRs`E4tqJNL_d1_)pT05yUHZU6uP literal 0 HcmV?d00001 diff --git a/todoffin/.gradle/buildOutputCleanup/cache.properties b/todoffin/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 00000000..443c8e8f --- /dev/null +++ b/todoffin/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Mon Dec 25 20:22:34 KST 2023 +gradle.version=8.5 diff --git a/todoffin/.gradle/vcs-1/gc.properties b/todoffin/.gradle/vcs-1/gc.properties new file mode 100644 index 00000000..e69de29b diff --git a/todoffin/.idea/.gitignore b/todoffin/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/todoffin/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/todoffin/.idea/compiler.xml b/todoffin/.idea/compiler.xml new file mode 100644 index 00000000..93d2f743 --- /dev/null +++ b/todoffin/.idea/compiler.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/todoffin/.idea/gradle.xml b/todoffin/.idea/gradle.xml new file mode 100644 index 00000000..f07d8ac4 --- /dev/null +++ b/todoffin/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/todoffin/.idea/jarRepositories.xml b/todoffin/.idea/jarRepositories.xml new file mode 100644 index 00000000..fdc392fe --- /dev/null +++ b/todoffin/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/todoffin/.idea/misc.xml b/todoffin/.idea/misc.xml new file mode 100644 index 00000000..c0492069 --- /dev/null +++ b/todoffin/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/todoffin/.idea/vcs.xml b/todoffin/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/todoffin/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/todoffin/HELP.md b/todoffin/HELP.md new file mode 100644 index 00000000..c824cc02 --- /dev/null +++ b/todoffin/HELP.md @@ -0,0 +1,34 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Gradle documentation](https://docs.gradle.org) +* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.2.1/gradle-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.2.1/gradle-plugin/reference/html/#build-image) +* [Spring Boot DevTools](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#using.devtools) +* [Spring Security](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#web.security) +* [OAuth2 Client](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#web.security.oauth2.client) +* [Spring Web](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#web) +* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#data.sql.jpa-and-spring-data) +* [WebSocket](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#messaging.websockets) +* [Validation](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#io.validation) + +### Guides +The following guides illustrate how to use some features concretely: + +* [Securing a Web Application](https://spring.io/guides/gs/securing-web/) +* [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/) +* [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/) +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) +* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) +* [Using WebSocket to build an interactive web application](https://spring.io/guides/gs/messaging-stomp-websocket/) +* [Validation](https://spring.io/guides/gs/validating-form-input/) + +### Additional Links +These additional references should also help you: + +* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) + diff --git a/todoffin/out/production/classes/com/genius/todoffin/TodoffinApplication.class b/todoffin/out/production/classes/com/genius/todoffin/TodoffinApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..be48752917bf08e34a4ceb17d2425c95fcc48030 GIT binary patch literal 721 zcma)4O;g)25PeDl4k0Zee7C*y)FcOVGQ|S5N!&{P-Wh4}9sMfo6bK7b{q0=-r4r!Be3Vei7Zs*fFer zS6aDahUQ>63(#ix7MqMGQmZ28&Rm=MTxou_{5e@HQWXoQjP9U=^#I*2KHww6$k>GE z3#)W8w<44G#@_PC7{{k2SgE9Dw$KdGAbcBWl8QVZ|EeHU=gG#yu#P!4+Q~nTPmG?c zq_84NWu*ndK0Rh=P0Y1q*a(%D=S3Dtd*#nE^g|PibSA9w@e*vg-zsMqhVK-?H^_vd z%j^#7W}i`B*n}vzYSIjysVQtM&y=5NzeeRluMoq*WucwQ0 literal 0 HcmV?d00001 diff --git a/todoffin/out/production/resources/application.yml b/todoffin/out/production/resources/application.yml new file mode 100644 index 00000000..e69de29b diff --git a/todoffin/src/main/resources/application.yml b/todoffin/src/main/resources/application.yml new file mode 100644 index 00000000..e69de29b From 03de9128b78939781f1ba83d4b31b33dcae96474 Mon Sep 17 00:00:00 2001 From: SSung023 Date: Mon, 25 Dec 2023 20:48:00 +0900 Subject: [PATCH 003/234] =?UTF-8?q?init:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=B4=88=EA=B8=B0=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .yml 파일에 DB 추가 세팅 필요 --- .gitignore | 43 +++ build.gradle | 41 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++++++++++++++++++ gradlew.bat | 92 +++++++ settings.gradle | 1 + .../genius/todoffin/TodoffinApplication.java | 13 + .../todoffin/TodoffinApplicationTests.java | 13 + todoffin/.gradle/8.5/checksums/checksums.lock | Bin 17 -> 0 bytes .../.gradle/8.5/checksums/md5-checksums.bin | Bin 23797 -> 0 bytes .../.gradle/8.5/checksums/sha1-checksums.bin | Bin 35183 -> 0 bytes .../dependencies-accessors.lock | Bin 17 -> 0 bytes .../8.5/dependencies-accessors/gc.properties | 0 .../executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .../.gradle/8.5/fileChanges/last-build.bin | Bin 1 -> 0 bytes .../.gradle/8.5/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes todoffin/.gradle/8.5/gc.properties | 0 .../buildOutputCleanup.lock | Bin 17 -> 0 bytes .../buildOutputCleanup/cache.properties | 2 - todoffin/.gradle/vcs-1/gc.properties | 0 todoffin/.idea/.gitignore | 8 - todoffin/.idea/compiler.xml | 16 -- todoffin/.idea/gradle.xml | 17 -- todoffin/.idea/jarRepositories.xml | 20 -- todoffin/.idea/misc.xml | 8 - todoffin/.idea/vcs.xml | 6 - todoffin/HELP.md | 34 --- .../genius/todoffin/TodoffinApplication.class | Bin 721 -> 0 bytes .../out/production/resources/application.yml | 0 todoffin/src/main/resources/application.yml | 0 31 files changed, 459 insertions(+), 111 deletions(-) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/com/genius/todoffin/TodoffinApplication.java create mode 100644 src/test/java/com/genius/todoffin/TodoffinApplicationTests.java delete mode 100644 todoffin/.gradle/8.5/checksums/checksums.lock delete mode 100644 todoffin/.gradle/8.5/checksums/md5-checksums.bin delete mode 100644 todoffin/.gradle/8.5/checksums/sha1-checksums.bin delete mode 100644 todoffin/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock delete mode 100644 todoffin/.gradle/8.5/dependencies-accessors/gc.properties delete mode 100644 todoffin/.gradle/8.5/executionHistory/executionHistory.lock delete mode 100644 todoffin/.gradle/8.5/fileChanges/last-build.bin delete mode 100644 todoffin/.gradle/8.5/fileHashes/fileHashes.lock delete mode 100644 todoffin/.gradle/8.5/gc.properties delete mode 100644 todoffin/.gradle/buildOutputCleanup/buildOutputCleanup.lock delete mode 100644 todoffin/.gradle/buildOutputCleanup/cache.properties delete mode 100644 todoffin/.gradle/vcs-1/gc.properties delete mode 100644 todoffin/.idea/.gitignore delete mode 100644 todoffin/.idea/compiler.xml delete mode 100644 todoffin/.idea/gradle.xml delete mode 100644 todoffin/.idea/jarRepositories.xml delete mode 100644 todoffin/.idea/misc.xml delete mode 100644 todoffin/.idea/vcs.xml delete mode 100644 todoffin/HELP.md delete mode 100644 todoffin/out/production/classes/com/genius/todoffin/TodoffinApplication.class delete mode 100644 todoffin/out/production/resources/application.yml delete mode 100644 todoffin/src/main/resources/application.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..59217f0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +@@ -0,0 +1,42 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +*.yml + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### MAC ### +*.DS_Store \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..846cd36c --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.2.1' + id 'io.spring.dependency-management' version '1.1.4' +} + +group = 'com.genius' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-websocket' + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d64cd4917707c1f8861d8cb53dd15194d4248596 GIT binary patch literal 43462 zcma&NWl&^owk(X(xVyW%ySuwf;qI=D6|RlDJ2cR^yEKh!@I- zp9QeisK*rlxC>+~7Dk4IxIRsKBHqdR9b3+fyL=ynHmIDe&|>O*VlvO+%z5;9Z$|DJ zb4dO}-R=MKr^6EKJiOrJdLnCJn>np?~vU-1sSFgPu;pthGwf}bG z(1db%xwr#x)r+`4AGu$j7~u2MpVs3VpLp|mx&;>`0p0vH6kF+D2CY0fVdQOZ@h;A` z{infNyvmFUiu*XG}RNMNwXrbec_*a3N=2zJ|Wh5z* z5rAX$JJR{#zP>KY**>xHTuw?|-Rg|o24V)74HcfVT;WtQHXlE+_4iPE8QE#DUm%x0 zEKr75ur~W%w#-My3Tj`hH6EuEW+8K-^5P62$7Sc5OK+22qj&Pd1;)1#4tKihi=~8C zHiQSst0cpri6%OeaR`PY>HH_;CPaRNty%WTm4{wDK8V6gCZlG@U3$~JQZ;HPvDJcT1V{ z?>H@13MJcCNe#5z+MecYNi@VT5|&UiN1D4ATT+%M+h4c$t;C#UAs3O_q=GxK0}8%8 z8J(_M9bayxN}69ex4dzM_P3oh@ZGREjVvn%%r7=xjkqxJP4kj}5tlf;QosR=%4L5y zWhgejO=vao5oX%mOHbhJ8V+SG&K5dABn6!WiKl{|oPkq(9z8l&Mm%(=qGcFzI=eLu zWc_oCLyf;hVlB@dnwY98?75B20=n$>u3b|NB28H0u-6Rpl((%KWEBOfElVWJx+5yg z#SGqwza7f}$z;n~g%4HDU{;V{gXIhft*q2=4zSezGK~nBgu9-Q*rZ#2f=Q}i2|qOp z!!y4p)4o=LVUNhlkp#JL{tfkhXNbB=Ox>M=n6soptJw-IDI|_$is2w}(XY>a=H52d z3zE$tjPUhWWS+5h=KVH&uqQS=$v3nRs&p$%11b%5qtF}S2#Pc`IiyBIF4%A!;AVoI zXU8-Rpv!DQNcF~(qQnyyMy=-AN~U>#&X1j5BLDP{?K!%h!;hfJI>$mdLSvktEr*89 zdJHvby^$xEX0^l9g$xW-d?J;L0#(`UT~zpL&*cEh$L|HPAu=P8`OQZV!-}l`noSp_ zQ-1$q$R-gDL)?6YaM!=8H=QGW$NT2SeZlb8PKJdc=F-cT@j7Xags+Pr*jPtlHFnf- zh?q<6;)27IdPc^Wdy-mX%2s84C1xZq9Xms+==F4);O`VUASmu3(RlgE#0+#giLh-& zcxm3_e}n4{%|X zJp{G_j+%`j_q5}k{eW&TlP}J2wtZ2^<^E(O)4OQX8FDp6RJq!F{(6eHWSD3=f~(h} zJXCf7=r<16X{pHkm%yzYI_=VDP&9bmI1*)YXZeB}F? z(%QsB5fo*FUZxK$oX~X^69;x~j7ms8xlzpt-T15e9}$4T-pC z6PFg@;B-j|Ywajpe4~bk#S6(fO^|mm1hKOPfA%8-_iGCfICE|=P_~e;Wz6my&)h_~ zkv&_xSAw7AZ%ThYF(4jADW4vg=oEdJGVOs>FqamoL3Np8>?!W#!R-0%2Bg4h?kz5I zKV-rKN2n(vUL%D<4oj@|`eJ>0i#TmYBtYmfla;c!ATW%;xGQ0*TW@PTlGG><@dxUI zg>+3SiGdZ%?5N=8uoLA|$4isK$aJ%i{hECP$bK{J#0W2gQ3YEa zZQ50Stn6hqdfxJ*9#NuSLwKFCUGk@c=(igyVL;;2^wi4o30YXSIb2g_ud$ zgpCr@H0qWtk2hK8Q|&wx)}4+hTYlf;$a4#oUM=V@Cw#!$(nOFFpZ;0lc!qd=c$S}Z zGGI-0jg~S~cgVT=4Vo)b)|4phjStD49*EqC)IPwyeKBLcN;Wu@Aeph;emROAwJ-0< z_#>wVm$)ygH|qyxZaet&(Vf%pVdnvKWJn9`%DAxj3ot;v>S$I}jJ$FLBF*~iZ!ZXE zkvui&p}fI0Y=IDX)mm0@tAd|fEHl~J&K}ZX(Mm3cm1UAuwJ42+AO5@HwYfDH7ipIc zmI;1J;J@+aCNG1M`Btf>YT>~c&3j~Qi@Py5JT6;zjx$cvOQW@3oQ>|}GH?TW-E z1R;q^QFjm5W~7f}c3Ww|awg1BAJ^slEV~Pk`Kd`PS$7;SqJZNj->it4DW2l15}xP6 zoCl$kyEF%yJni0(L!Z&14m!1urXh6Btj_5JYt1{#+H8w?5QI%% zo-$KYWNMJVH?Hh@1n7OSu~QhSswL8x0=$<8QG_zepi_`y_79=nK=_ZP_`Em2UI*tyQoB+r{1QYZCpb?2OrgUw#oRH$?^Tj!Req>XiE#~B|~ z+%HB;=ic+R@px4Ld8mwpY;W^A%8%l8$@B@1m5n`TlKI6bz2mp*^^^1mK$COW$HOfp zUGTz-cN9?BGEp}5A!mDFjaiWa2_J2Iq8qj0mXzk; z66JBKRP{p%wN7XobR0YjhAuW9T1Gw3FDvR5dWJ8ElNYF94eF3ebu+QwKjtvVu4L zI9ip#mQ@4uqVdkl-TUQMb^XBJVLW(-$s;Nq;@5gr4`UfLgF$adIhd?rHOa%D);whv z=;krPp~@I+-Z|r#s3yCH+c1US?dnm+C*)r{m+86sTJusLdNu^sqLrfWed^ndHXH`m zd3#cOe3>w-ga(Dus_^ppG9AC>Iq{y%%CK+Cro_sqLCs{VLuK=dev>OL1dis4(PQ5R zcz)>DjEkfV+MO;~>VUlYF00SgfUo~@(&9$Iy2|G0T9BSP?&T22>K46D zL*~j#yJ?)^*%J3!16f)@Y2Z^kS*BzwfAQ7K96rFRIh>#$*$_Io;z>ux@}G98!fWR@ zGTFxv4r~v)Gsd|pF91*-eaZ3Qw1MH$K^7JhWIdX%o$2kCbvGDXy)a?@8T&1dY4`;L z4Kn+f%SSFWE_rpEpL9bnlmYq`D!6F%di<&Hh=+!VI~j)2mfil03T#jJ_s?}VV0_hp z7T9bWxc>Jm2Z0WMU?`Z$xE74Gu~%s{mW!d4uvKCx@WD+gPUQ zV0vQS(Ig++z=EHN)BR44*EDSWIyT~R4$FcF*VEY*8@l=218Q05D2$|fXKFhRgBIEE zdDFB}1dKkoO^7}{5crKX!p?dZWNz$m>1icsXG2N+((x0OIST9Zo^DW_tytvlwXGpn zs8?pJXjEG;T@qrZi%#h93?FP$!&P4JA(&H61tqQi=opRzNpm zkrG}$^t9&XduK*Qa1?355wd8G2CI6QEh@Ua>AsD;7oRUNLPb76m4HG3K?)wF~IyS3`fXuNM>${?wmB zpVz;?6_(Fiadfd{vUCBM*_kt$+F3J+IojI;9L(gc9n3{sEZyzR9o!_mOwFC#tQ{Q~ zP3-`#uK#tP3Q7~Q;4H|wjZHO8h7e4IuBxl&vz2w~D8)w=Wtg31zpZhz%+kzSzL*dV zwp@{WU4i;hJ7c2f1O;7Mz6qRKeASoIv0_bV=i@NMG*l<#+;INk-^`5w@}Dj~;k=|}qM1vq_P z|GpBGe_IKq|LNy9SJhKOQ$c=5L{Dv|Q_lZl=-ky*BFBJLW9&y_C|!vyM~rQx=!vun z?rZJQB5t}Dctmui5i31C_;_}CEn}_W%>oSXtt>@kE1=JW*4*v4tPp;O6 zmAk{)m!)}34pTWg8{i>($%NQ(Tl;QC@J@FfBoc%Gr&m560^kgSfodAFrIjF}aIw)X zoXZ`@IsMkc8_=w%-7`D6Y4e*CG8k%Ud=GXhsTR50jUnm+R*0A(O3UKFg0`K;qp1bl z7``HN=?39ic_kR|^R^~w-*pa?Vj#7|e9F1iRx{GN2?wK!xR1GW!qa=~pjJb-#u1K8 zeR?Y2i-pt}yJq;SCiVHODIvQJX|ZJaT8nO+(?HXbLefulKKgM^B(UIO1r+S=7;kLJ zcH}1J=Px2jsh3Tec&v8Jcbng8;V-`#*UHt?hB(pmOipKwf3Lz8rG$heEB30Sg*2rx zV<|KN86$soN(I!BwO`1n^^uF2*x&vJ$2d$>+`(romzHP|)K_KkO6Hc>_dwMW-M(#S zK(~SiXT1@fvc#U+?|?PniDRm01)f^#55;nhM|wi?oG>yBsa?~?^xTU|fX-R(sTA+5 zaq}-8Tx7zrOy#3*JLIIVsBmHYLdD}!0NP!+ITW+Thn0)8SS!$@)HXwB3tY!fMxc#1 zMp3H?q3eD?u&Njx4;KQ5G>32+GRp1Ee5qMO0lZjaRRu&{W<&~DoJNGkcYF<5(Ab+J zgO>VhBl{okDPn78<%&e2mR{jwVCz5Og;*Z;;3%VvoGo_;HaGLWYF7q#jDX=Z#Ml`H z858YVV$%J|e<1n`%6Vsvq7GmnAV0wW4$5qQ3uR@1i>tW{xrl|ExywIc?fNgYlA?C5 zh$ezAFb5{rQu6i7BSS5*J-|9DQ{6^BVQ{b*lq`xS@RyrsJN?-t=MTMPY;WYeKBCNg z^2|pN!Q^WPJuuO4!|P@jzt&tY1Y8d%FNK5xK(!@`jO2aEA*4 zkO6b|UVBipci?){-Ke=+1;mGlND8)6+P;8sq}UXw2hn;fc7nM>g}GSMWu&v&fqh

iViYT=fZ(|3Ox^$aWPp4a8h24tD<|8-!aK0lHgL$N7Efw}J zVIB!7=T$U`ao1?upi5V4Et*-lTG0XvExbf!ya{cua==$WJyVG(CmA6Of*8E@DSE%L z`V^$qz&RU$7G5mg;8;=#`@rRG`-uS18$0WPN@!v2d{H2sOqP|!(cQ@ zUHo!d>>yFArLPf1q`uBvY32miqShLT1B@gDL4XoVTK&@owOoD)OIHXrYK-a1d$B{v zF^}8D3Y^g%^cnvScOSJR5QNH+BI%d|;J;wWM3~l>${fb8DNPg)wrf|GBP8p%LNGN# z3EaIiItgwtGgT&iYCFy9-LG}bMI|4LdmmJt@V@% zb6B)1kc=T)(|L@0;wr<>=?r04N;E&ef+7C^`wPWtyQe(*pD1pI_&XHy|0gIGHMekd zF_*M4yi6J&Z4LQj65)S zXwdM{SwUo%3SbPwFsHgqF@V|6afT|R6?&S;lw=8% z3}@9B=#JI3@B*#4s!O))~z zc>2_4Q_#&+5V`GFd?88^;c1i7;Vv_I*qt!_Yx*n=;rj!82rrR2rQ8u5(Ejlo{15P% zs~!{%XJ>FmJ})H^I9bn^Re&38H{xA!0l3^89k(oU;bZWXM@kn$#aoS&Y4l^-WEn-fH39Jb9lA%s*WsKJQl?n9B7_~P z-XM&WL7Z!PcoF6_D>V@$CvUIEy=+Z&0kt{szMk=f1|M+r*a43^$$B^MidrT0J;RI` z(?f!O<8UZkm$_Ny$Hth1J#^4ni+im8M9mr&k|3cIgwvjAgjH z8`N&h25xV#v*d$qBX5jkI|xOhQn!>IYZK7l5#^P4M&twe9&Ey@@GxYMxBZq2e7?`q z$~Szs0!g{2fGcp9PZEt|rdQ6bhAgpcLHPz?f-vB?$dc*!9OL?Q8mn7->bFD2Si60* z!O%y)fCdMSV|lkF9w%x~J*A&srMyYY3{=&$}H zGQ4VG_?$2X(0|vT0{=;W$~icCI{b6W{B!Q8xdGhF|D{25G_5_+%s(46lhvNLkik~R z>nr(&C#5wwOzJZQo9m|U<;&Wk!_#q|V>fsmj1g<6%hB{jGoNUPjgJslld>xmODzGjYc?7JSuA?A_QzjDw5AsRgi@Y|Z0{F{!1=!NES-#*f^s4l0Hu zz468))2IY5dmD9pa*(yT5{EyP^G>@ZWumealS-*WeRcZ}B%gxq{MiJ|RyX-^C1V=0 z@iKdrGi1jTe8Ya^x7yyH$kBNvM4R~`fbPq$BzHum-3Zo8C6=KW@||>zsA8-Y9uV5V z#oq-f5L5}V<&wF4@X@<3^C%ptp6+Ce)~hGl`kwj)bsAjmo_GU^r940Z-|`<)oGnh7 zFF0Tde3>ui?8Yj{sF-Z@)yQd~CGZ*w-6p2U<8}JO-sRsVI5dBji`01W8A&3$?}lxBaC&vn0E$c5tW* zX>5(zzZ=qn&!J~KdsPl;P@bmA-Pr8T*)eh_+Dv5=Ma|XSle6t(k8qcgNyar{*ReQ8 zTXwi=8vr>!3Ywr+BhggHDw8ke==NTQVMCK`$69fhzEFB*4+H9LIvdt-#IbhZvpS}} zO3lz;P?zr0*0$%-Rq_y^k(?I{Mk}h@w}cZpMUp|ucs55bcloL2)($u%mXQw({Wzc~ z;6nu5MkjP)0C(@%6Q_I_vsWrfhl7Zpoxw#WoE~r&GOSCz;_ro6i(^hM>I$8y>`!wW z*U^@?B!MMmb89I}2(hcE4zN2G^kwyWCZp5JG>$Ez7zP~D=J^LMjSM)27_0B_X^C(M z`fFT+%DcKlu?^)FCK>QzSnV%IsXVcUFhFdBP!6~se&xxrIxsvySAWu++IrH;FbcY$ z2DWTvSBRfLwdhr0nMx+URA$j3i7_*6BWv#DXfym?ZRDcX9C?cY9sD3q)uBDR3uWg= z(lUIzB)G$Hr!){>E{s4Dew+tb9kvToZp-1&c?y2wn@Z~(VBhqz`cB;{E4(P3N2*nJ z_>~g@;UF2iG{Kt(<1PyePTKahF8<)pozZ*xH~U-kfoAayCwJViIrnqwqO}7{0pHw$ zs2Kx?s#vQr7XZ264>5RNKSL8|Ty^=PsIx^}QqOOcfpGUU4tRkUc|kc7-!Ae6!+B{o~7nFpm3|G5^=0#Bnm6`V}oSQlrX(u%OWnC zoLPy&Q;1Jui&7ST0~#+}I^&?vcE*t47~Xq#YwvA^6^} z`WkC)$AkNub|t@S!$8CBlwbV~?yp&@9h{D|3z-vJXgzRC5^nYm+PyPcgRzAnEi6Q^gslXYRv4nycsy-SJu?lMps-? zV`U*#WnFsdPLL)Q$AmD|0`UaC4ND07+&UmOu!eHruzV|OUox<+Jl|Mr@6~C`T@P%s zW7sgXLF2SSe9Fl^O(I*{9wsFSYb2l%-;&Pi^dpv!{)C3d0AlNY6!4fgmSgj_wQ*7Am7&$z;Jg&wgR-Ih;lUvWS|KTSg!&s_E9_bXBkZvGiC6bFKDWZxsD$*NZ#_8bl zG1P-#@?OQzED7@jlMJTH@V!6k;W>auvft)}g zhoV{7$q=*;=l{O>Q4a@ ziMjf_u*o^PsO)#BjC%0^h>Xp@;5$p{JSYDt)zbb}s{Kbt!T*I@Pk@X0zds6wsefuU zW$XY%yyRGC94=6mf?x+bbA5CDQ2AgW1T-jVAJbm7K(gp+;v6E0WI#kuACgV$r}6L? zd|Tj?^%^*N&b>Dd{Wr$FS2qI#Ucs1yd4N+RBUQiSZGujH`#I)mG&VKoDh=KKFl4=G z&MagXl6*<)$6P}*Tiebpz5L=oMaPrN+caUXRJ`D?=K9!e0f{@D&cZLKN?iNP@X0aF zE(^pl+;*T5qt?1jRC=5PMgV!XNITRLS_=9{CJExaQj;lt!&pdzpK?8p>%Mb+D z?yO*uSung=-`QQ@yX@Hyd4@CI^r{2oiu`%^bNkz+Nkk!IunjwNC|WcqvX~k=><-I3 zDQdbdb|!v+Iz01$w@aMl!R)koD77Xp;eZwzSl-AT zr@Vu{=xvgfq9akRrrM)}=!=xcs+U1JO}{t(avgz`6RqiiX<|hGG1pmop8k6Q+G_mv zJv|RfDheUp2L3=^C=4aCBMBn0aRCU(DQwX-W(RkRwmLeuJYF<0urcaf(=7)JPg<3P zQs!~G)9CT18o!J4{zX{_e}4eS)U-E)0FAt}wEI(c0%HkxgggW;(1E=>J17_hsH^sP z%lT0LGgbUXHx-K*CI-MCrP66UP0PvGqM$MkeLyqHdbgP|_Cm!7te~b8p+e6sQ_3k| zVcwTh6d83ltdnR>D^)BYQpDKlLk3g0Hdcgz2}%qUs9~~Rie)A-BV1mS&naYai#xcZ z(d{8=-LVpTp}2*y)|gR~;qc7fp26}lPcLZ#=JpYcn3AT9(UIdOyg+d(P5T7D&*P}# zQCYplZO5|7+r19%9e`v^vfSS1sbX1c%=w1;oyruXB%Kl$ACgKQ6=qNWLsc=28xJjg zwvsI5-%SGU|3p>&zXVl^vVtQT3o-#$UT9LI@Npz~6=4!>mc431VRNN8od&Ul^+G_kHC`G=6WVWM z%9eWNyy(FTO|A+@x}Ou3CH)oi;t#7rAxdIXfNFwOj_@Y&TGz6P_sqiB`Q6Lxy|Q{`|fgmRG(k+!#b*M+Z9zFce)f-7;?Km5O=LHV9f9_87; zF7%R2B+$?@sH&&-$@tzaPYkw0;=i|;vWdI|Wl3q_Zu>l;XdIw2FjV=;Mq5t1Q0|f< zs08j54Bp`3RzqE=2enlkZxmX6OF+@|2<)A^RNQpBd6o@OXl+i)zO%D4iGiQNuXd+zIR{_lb96{lc~bxsBveIw6umhShTX+3@ZJ=YHh@ zWY3(d0azg;7oHn>H<>?4@*RQbi>SmM=JrHvIG(~BrvI)#W(EAeO6fS+}mxxcc+X~W6&YVl86W9WFSS}Vz-f9vS?XUDBk)3TcF z8V?$4Q)`uKFq>xT=)Y9mMFVTUk*NIA!0$?RP6Ig0TBmUFrq*Q-Agq~DzxjStQyJ({ zBeZ;o5qUUKg=4Hypm|}>>L=XKsZ!F$yNTDO)jt4H0gdQ5$f|d&bnVCMMXhNh)~mN z@_UV6D7MVlsWz+zM+inZZp&P4fj=tm6fX)SG5H>OsQf_I8c~uGCig$GzuwViK54bcgL;VN|FnyQl>Ed7(@>=8$a_UKIz|V6CeVSd2(P z0Uu>A8A+muM%HLFJQ9UZ5c)BSAv_zH#1f02x?h9C}@pN@6{>UiAp>({Fn(T9Q8B z^`zB;kJ5b`>%dLm+Ol}ty!3;8f1XDSVX0AUe5P#@I+FQ-`$(a;zNgz)4x5hz$Hfbg z!Q(z26wHLXko(1`;(BAOg_wShpX0ixfWq3ponndY+u%1gyX)_h=v1zR#V}#q{au6; z!3K=7fQwnRfg6FXtNQmP>`<;!N137paFS%y?;lb1@BEdbvQHYC{976l`cLqn;b8lp zIDY>~m{gDj(wfnK!lpW6pli)HyLEiUrNc%eXTil|F2s(AY+LW5hkKb>TQ3|Q4S9rr zpDs4uK_co6XPsn_z$LeS{K4jFF`2>U`tbgKdyDne`xmR<@6AA+_hPNKCOR-Zqv;xk zu5!HsBUb^!4uJ7v0RuH-7?l?}b=w5lzzXJ~gZcxRKOovSk@|#V+MuX%Y+=;14i*%{)_gSW9(#4%)AV#3__kac1|qUy!uyP{>?U#5wYNq}y$S9pCc zFc~4mgSC*G~j0u#qqp9 z${>3HV~@->GqEhr_Xwoxq?Hjn#=s2;i~g^&Hn|aDKpA>Oc%HlW(KA1?BXqpxB;Ydx)w;2z^MpjJ(Qi(X!$5RC z*P{~%JGDQqojV>2JbEeCE*OEu!$XJ>bWA9Oa_Hd;y)F%MhBRi*LPcdqR8X`NQ&1L# z5#9L*@qxrx8n}LfeB^J{%-?SU{FCwiWyHp682F+|pa+CQa3ZLzBqN1{)h4d6+vBbV zC#NEbQLC;}me3eeYnOG*nXOJZEU$xLZ1<1Y=7r0(-U0P6-AqwMAM`a(Ed#7vJkn6plb4eI4?2y3yOTGmmDQ!z9`wzbf z_OY#0@5=bnep;MV0X_;;SJJWEf^E6Bd^tVJ9znWx&Ks8t*B>AM@?;D4oWUGc z!H*`6d7Cxo6VuyS4Eye&L1ZRhrRmN6Lr`{NL(wDbif|y&z)JN>Fl5#Wi&mMIr5i;x zBx}3YfF>>8EC(fYnmpu~)CYHuHCyr5*`ECap%t@y=jD>!_%3iiE|LN$mK9>- zHdtpy8fGZtkZF?%TW~29JIAfi2jZT8>OA7=h;8T{{k?c2`nCEx9$r zS+*&vt~2o^^J+}RDG@+9&M^K*z4p{5#IEVbz`1%`m5c2};aGt=V?~vIM}ZdPECDI)47|CWBCfDWUbxBCnmYivQ*0Nu_xb*C>~C9(VjHM zxe<*D<#dQ8TlpMX2c@M<9$w!RP$hpG4cs%AI){jp*Sj|*`m)5(Bw*A0$*i-(CA5#%>a)$+jI2C9r6|(>J8InryENI z$NohnxDUB;wAYDwrb*!N3noBTKPpPN}~09SEL18tkG zxgz(RYU_;DPT{l?Q$+eaZaxnsWCA^ds^0PVRkIM%bOd|G2IEBBiz{&^JtNsODs;5z zICt_Zj8wo^KT$7Bg4H+y!Df#3mbl%%?|EXe!&(Vmac1DJ*y~3+kRKAD=Ovde4^^%~ zw<9av18HLyrf*_>Slp;^i`Uy~`mvBjZ|?Ad63yQa#YK`4+c6;pW4?XIY9G1(Xh9WO8{F-Aju+nS9Vmv=$Ac0ienZ+p9*O%NG zMZKy5?%Z6TAJTE?o5vEr0r>f>hb#2w2U3DL64*au_@P!J!TL`oH2r*{>ffu6|A7tv zL4juf$DZ1MW5ZPsG!5)`k8d8c$J$o;%EIL0va9&GzWvkS%ZsGb#S(?{!UFOZ9<$a| zY|a+5kmD5N&{vRqkgY>aHsBT&`rg|&kezoD)gP0fsNYHsO#TRc_$n6Lf1Z{?+DLziXlHrq4sf(!>O{?Tj;Eh@%)+nRE_2VxbN&&%%caU#JDU%vL3}Cb zsb4AazPI{>8H&d=jUaZDS$-0^AxE@utGs;-Ez_F(qC9T=UZX=>ok2k2 ziTn{K?y~a5reD2A)P${NoI^>JXn>`IeArow(41c-Wm~)wiryEP(OS{YXWi7;%dG9v zI?mwu1MxD{yp_rrk!j^cKM)dc4@p4Ezyo%lRN|XyD}}>v=Xoib0gOcdXrQ^*61HNj z=NP|pd>@yfvr-=m{8$3A8TQGMTE7g=z!%yt`8`Bk-0MMwW~h^++;qyUP!J~ykh1GO z(FZ59xuFR$(WE;F@UUyE@Sp>`aVNjyj=Ty>_Vo}xf`e7`F;j-IgL5`1~-#70$9_=uBMq!2&1l zomRgpD58@)YYfvLtPW}{C5B35R;ZVvB<<#)x%srmc_S=A7F@DW8>QOEGwD6suhwCg z>Pa+YyULhmw%BA*4yjDp|2{!T98~<6Yfd(wo1mQ!KWwq0eg+6)o1>W~f~kL<-S+P@$wx*zeI|1t7z#Sxr5 zt6w+;YblPQNplq4Z#T$GLX#j6yldXAqj>4gAnnWtBICUnA&-dtnlh=t0Ho_vEKwV` z)DlJi#!@nkYV#$!)@>udAU*hF?V`2$Hf=V&6PP_|r#Iv*J$9)pF@X3`k;5})9^o4y z&)~?EjX5yX12O(BsFy-l6}nYeuKkiq`u9145&3Ssg^y{5G3Pse z9w(YVa0)N-fLaBq1`P!_#>SS(8fh_5!f{UrgZ~uEdeMJIz7DzI5!NHHqQtm~#CPij z?=N|J>nPR6_sL7!f4hD_|KH`vf8(Wpnj-(gPWH+ZvID}%?~68SwhPTC3u1_cB`otq z)U?6qo!ZLi5b>*KnYHWW=3F!p%h1;h{L&(Q&{qY6)_qxNfbP6E3yYpW!EO+IW3?@J z);4>g4gnl^8klu7uA>eGF6rIGSynacogr)KUwE_R4E5Xzi*Qir@b-jy55-JPC8c~( zo!W8y9OGZ&`xmc8;=4-U9=h{vCqfCNzYirONmGbRQlR`WWlgnY+1wCXbMz&NT~9*| z6@FrzP!LX&{no2!Ln_3|I==_4`@}V?4a;YZKTdw;vT<+K+z=uWbW(&bXEaWJ^W8Td z-3&1bY^Z*oM<=M}LVt>_j+p=2Iu7pZmbXrhQ_k)ysE9yXKygFNw$5hwDn(M>H+e1&9BM5!|81vd%r%vEm zqxY3?F@fb6O#5UunwgAHR9jp_W2zZ}NGp2%mTW@(hz7$^+a`A?mb8|_G*GNMJ) zjqegXQio=i@AINre&%ofexAr95aop5C+0MZ0m-l=MeO8m3epm7U%vZB8+I+C*iNFM z#T3l`gknX;D$-`2XT^Cg*vrv=RH+P;_dfF++cP?B_msQI4j+lt&rX2)3GaJx%W*Nn zkML%D{z5tpHH=dksQ*gzc|}gzW;lwAbxoR07VNgS*-c3d&8J|;@3t^ zVUz*J*&r7DFRuFVDCJDK8V9NN5hvpgGjwx+5n)qa;YCKe8TKtdnh{I7NU9BCN!0dq zczrBk8pE{{@vJa9ywR@mq*J=v+PG;?fwqlJVhijG!3VmIKs>9T6r7MJpC)m!Tc#>g zMtVsU>wbwFJEfwZ{vB|ZlttNe83)$iz`~#8UJ^r)lJ@HA&G#}W&ZH*;k{=TavpjWE z7hdyLZPf*X%Gm}i`Y{OGeeu^~nB8=`{r#TUrM-`;1cBvEd#d!kPqIgYySYhN-*1;L z^byj%Yi}Gx)Wnkosi337BKs}+5H5dth1JA{Ir-JKN$7zC)*}hqeoD(WfaUDPT>0`- z(6sa0AoIqASwF`>hP}^|)a_j2s^PQn*qVC{Q}htR z5-)duBFXT_V56-+UohKXlq~^6uf!6sA#ttk1o~*QEy_Y-S$gAvq47J9Vtk$5oA$Ct zYhYJ@8{hsC^98${!#Ho?4y5MCa7iGnfz}b9jE~h%EAAv~Qxu)_rAV;^cygV~5r_~?l=B`zObj7S=H=~$W zPtI_m%g$`kL_fVUk9J@>EiBH zOO&jtn~&`hIFMS5S`g8w94R4H40mdNUH4W@@XQk1sr17b{@y|JB*G9z1|CrQjd+GX z6+KyURG3;!*BQrentw{B2R&@2&`2}n(z-2&X7#r!{yg@Soy}cRD~j zj9@UBW+N|4HW4AWapy4wfUI- zZ`gSL6DUlgj*f1hSOGXG0IVH8HxK?o2|3HZ;KW{K+yPAlxtb)NV_2AwJm|E)FRs&& z=c^e7bvUsztY|+f^k7NXs$o1EUq>cR7C0$UKi6IooHWlK_#?IWDkvywnzg&ThWo^? z2O_N{5X39#?eV9l)xI(>@!vSB{DLt*oY!K1R8}_?%+0^C{d9a%N4 zoxHVT1&Lm|uDX%$QrBun5e-F`HJ^T$ zmzv)p@4ZHd_w9!%Hf9UYNvGCw2TTTbrj9pl+T9%-_-}L(tES>Or-}Z4F*{##n3~L~TuxjirGuIY#H7{%$E${?p{Q01 zi6T`n;rbK1yIB9jmQNycD~yZq&mbIsFWHo|ZAChSFPQa<(%d8mGw*V3fh|yFoxOOiWJd(qvVb!Z$b88cg->N=qO*4k~6;R==|9ihg&riu#P~s4Oap9O7f%crSr^rljeIfXDEg>wi)&v*a%7zpz<9w z*r!3q9J|390x`Zk;g$&OeN&ctp)VKRpDSV@kU2Q>jtok($Y-*x8_$2piTxun81@vt z!Vj?COa0fg2RPXMSIo26T=~0d`{oGP*eV+$!0I<(4azk&Vj3SiG=Q!6mX0p$z7I}; z9BJUFgT-K9MQQ-0@Z=^7R<{bn2Fm48endsSs`V7_@%8?Bxkqv>BDoVcj?K#dV#uUP zL1ND~?D-|VGKe3Rw_7-Idpht>H6XRLh*U7epS6byiGvJpr%d}XwfusjH9g;Z98H`x zyde%%5mhGOiL4wljCaWCk-&uE4_OOccb9c!ZaWt4B(wYl!?vyzl%7n~QepN&eFUrw zFIOl9c({``6~QD+43*_tzP{f2x41h(?b43^y6=iwyB)2os5hBE!@YUS5?N_tXd=h( z)WE286Fbd>R4M^P{!G)f;h<3Q>Fipuy+d2q-)!RyTgt;wr$(?9ox3;q+{E*ZQHhOn;lM`cjnu9 zXa48ks-v(~b*;MAI<>YZH(^NV8vjb34beE<_cwKlJoR;k6lJNSP6v}uiyRD?|0w+X@o1ONrH8a$fCxXpf? z?$DL0)7|X}Oc%h^zrMKWc-NS9I0Utu@>*j}b@tJ=ixQSJ={4@854wzW@E>VSL+Y{i z#0b=WpbCZS>kUCO_iQz)LoE>P5LIG-hv9E+oG}DtlIDF>$tJ1aw9^LuhLEHt?BCj& z(O4I8v1s#HUi5A>nIS-JK{v!7dJx)^Yg%XjNmlkWAq2*cv#tHgz`Y(bETc6CuO1VkN^L-L3j_x<4NqYb5rzrLC-7uOv z!5e`GZt%B782C5-fGnn*GhDF$%(qP<74Z}3xx+{$4cYKy2ikxI7B2N+2r07DN;|-T->nU&!=Cm#rZt%O_5c&1Z%nlWq3TKAW0w zQqemZw_ue--2uKQsx+niCUou?HjD`xhEjjQd3%rrBi82crq*~#uA4+>vR<_S{~5ce z-2EIl?~s z1=GVL{NxP1N3%=AOaC}j_Fv=ur&THz zyO!d9kHq|c73kpq`$+t+8Bw7MgeR5~`d7ChYyGCBWSteTB>8WAU(NPYt2Dk`@#+}= zI4SvLlyk#pBgVigEe`?NG*vl7V6m+<}%FwPV=~PvvA)=#ths==DRTDEYh4V5}Cf$z@#;< zyWfLY_5sP$gc3LLl2x+Ii)#b2nhNXJ{R~vk`s5U7Nyu^3yFg&D%Txwj6QezMX`V(x z=C`{76*mNb!qHHs)#GgGZ_7|vkt9izl_&PBrsu@}L`X{95-2jf99K)0=*N)VxBX2q z((vkpP2RneSIiIUEnGb?VqbMb=Zia+rF~+iqslydE34cSLJ&BJW^3knX@M;t*b=EA zNvGzv41Ld_T+WT#XjDB840vovUU^FtN_)G}7v)1lPetgpEK9YS^OWFkPoE{ovj^=@ zO9N$S=G$1ecndT_=5ehth2Lmd1II-PuT~C9`XVePw$y8J#dpZ?Tss<6wtVglm(Ok7 z3?^oi@pPio6l&!z8JY(pJvG=*pI?GIOu}e^EB6QYk$#FJQ%^AIK$I4epJ+9t?KjqA+bkj&PQ*|vLttme+`9G=L% ziadyMw_7-M)hS(3E$QGNCu|o23|%O+VN7;Qggp?PB3K-iSeBa2b}V4_wY`G1Jsfz4 z9|SdB^;|I8E8gWqHKx!vj_@SMY^hLEIbSMCuE?WKq=c2mJK z8LoG-pnY!uhqFv&L?yEuxo{dpMTsmCn)95xanqBrNPTgXP((H$9N${Ow~Is-FBg%h z53;|Y5$MUN)9W2HBe2TD`ct^LHI<(xWrw}$qSoei?}s)&w$;&!14w6B6>Yr6Y8b)S z0r71`WmAvJJ`1h&poLftLUS6Ir zC$bG9!Im_4Zjse)#K=oJM9mHW1{%l8sz$1o?ltdKlLTxWWPB>Vk22czVt|1%^wnN@*!l)}?EgtvhC>vlHm^t+ogpgHI1_$1ox9e;>0!+b(tBrmXRB`PY1vp-R**8N7 zGP|QqI$m(Rdu#=(?!(N}G9QhQ%o!aXE=aN{&wtGP8|_qh+7a_j_sU5|J^)vxq;# zjvzLn%_QPHZZIWu1&mRAj;Sa_97p_lLq_{~j!M9N^1yp3U_SxRqK&JnR%6VI#^E12 z>CdOVI^_9aPK2eZ4h&^{pQs}xsijXgFYRIxJ~N7&BB9jUR1fm!(xl)mvy|3e6-B3j zJn#ajL;bFTYJ2+Q)tDjx=3IklO@Q+FFM}6UJr6km7hj7th9n_&JR7fnqC!hTZoM~T zBeaVFp%)0cbPhejX<8pf5HyRUj2>aXnXBqDJe73~J%P(2C?-RT{c3NjE`)om! zl$uewSgWkE66$Kb34+QZZvRn`fob~Cl9=cRk@Es}KQm=?E~CE%spXaMO6YmrMl%9Q zlA3Q$3|L1QJ4?->UjT&CBd!~ru{Ih^in&JXO=|<6J!&qp zRe*OZ*cj5bHYlz!!~iEKcuE|;U4vN1rk$xq6>bUWD*u(V@8sG^7>kVuo(QL@Ki;yL zWC!FT(q{E8#on>%1iAS0HMZDJg{Z{^!De(vSIq&;1$+b)oRMwA3nc3mdTSG#3uYO_ z>+x;7p4I;uHz?ZB>dA-BKl+t-3IB!jBRgdvAbW!aJ(Q{aT>+iz?91`C-xbe)IBoND z9_Xth{6?(y3rddwY$GD65IT#f3<(0o#`di{sh2gm{dw*#-Vnc3r=4==&PU^hCv$qd zjw;>i&?L*Wq#TxG$mFIUf>eK+170KG;~+o&1;Tom9}}mKo23KwdEM6UonXgc z!6N(@k8q@HPw{O8O!lAyi{rZv|DpgfU{py+j(X_cwpKqcalcqKIr0kM^%Br3SdeD> zHSKV94Yxw;pjzDHo!Q?8^0bb%L|wC;4U^9I#pd5O&eexX+Im{ z?jKnCcsE|H?{uGMqVie_C~w7GX)kYGWAg%-?8|N_1#W-|4F)3YTDC+QSq1s!DnOML3@d`mG%o2YbYd#jww|jD$gotpa)kntakp#K;+yo-_ZF9qrNZw<%#C zuPE@#3RocLgPyiBZ+R_-FJ_$xP!RzWm|aN)S+{$LY9vvN+IW~Kf3TsEIvP+B9Mtm! zpfNNxObWQpLoaO&cJh5>%slZnHl_Q~(-Tfh!DMz(dTWld@LG1VRF`9`DYKhyNv z2pU|UZ$#_yUx_B_|MxUq^glT}O5Xt(Vm4Mr02><%C)@v;vPb@pT$*yzJ4aPc_FZ3z z3}PLoMBIM>q_9U2rl^sGhk1VUJ89=*?7|v`{!Z{6bqFMq(mYiA?%KbsI~JwuqVA9$H5vDE+VocjX+G^%bieqx->s;XWlKcuv(s%y%D5Xbc9+ zc(_2nYS1&^yL*ey664&4`IoOeDIig}y-E~_GS?m;D!xv5-xwz+G`5l6V+}CpeJDi^ z%4ed$qowm88=iYG+(`ld5Uh&>Dgs4uPHSJ^TngXP_V6fPyl~>2bhi20QB%lSd#yYn zO05?KT1z@?^-bqO8Cg`;ft>ilejsw@2%RR7;`$Vs;FmO(Yr3Fp`pHGr@P2hC%QcA|X&N2Dn zYf`MqXdHi%cGR@%y7Rg7?d3?an){s$zA{!H;Ie5exE#c~@NhQUFG8V=SQh%UxUeiV zd7#UcYqD=lk-}sEwlpu&H^T_V0{#G?lZMxL7ih_&{(g)MWBnCZxtXg znr#}>U^6!jA%e}@Gj49LWG@*&t0V>Cxc3?oO7LSG%~)Y5}f7vqUUnQ;STjdDU}P9IF9d9<$;=QaXc zL1^X7>fa^jHBu_}9}J~#-oz3Oq^JmGR#?GO7b9a(=R@fw@}Q{{@`Wy1vIQ#Bw?>@X z-_RGG@wt|%u`XUc%W{J z>iSeiz8C3H7@St3mOr_mU+&bL#Uif;+Xw-aZdNYUpdf>Rvu0i0t6k*}vwU`XNO2he z%miH|1tQ8~ZK!zmL&wa3E;l?!!XzgV#%PMVU!0xrDsNNZUWKlbiOjzH-1Uoxm8E#r`#2Sz;-o&qcqB zC-O_R{QGuynW14@)7&@yw1U}uP(1cov)twxeLus0s|7ayrtT8c#`&2~Fiu2=R;1_4bCaD=*E@cYI>7YSnt)nQc zohw5CsK%m?8Ack)qNx`W0_v$5S}nO|(V|RZKBD+btO?JXe|~^Qqur%@eO~<8-L^9d z=GA3-V14ng9L29~XJ>a5k~xT2152zLhM*@zlp2P5Eu}bywkcqR;ISbas&#T#;HZSf z2m69qTV(V@EkY(1Dk3`}j)JMo%ZVJ*5eB zYOjIisi+igK0#yW*gBGj?@I{~mUOvRFQR^pJbEbzFxTubnrw(Muk%}jI+vXmJ;{Q6 zrSobKD>T%}jV4Ub?L1+MGOD~0Ir%-`iTnWZN^~YPrcP5y3VMAzQ+&en^VzKEb$K!Q z<7Dbg&DNXuow*eD5yMr+#08nF!;%4vGrJI++5HdCFcGLfMW!KS*Oi@=7hFwDG!h2< zPunUEAF+HncQkbfFj&pbzp|MU*~60Z(|Ik%Tn{BXMN!hZOosNIseT?R;A`W?=d?5X zK(FB=9mZusYahp|K-wyb={rOpdn=@;4YI2W0EcbMKyo~-#^?h`BA9~o285%oY zfifCh5Lk$SY@|2A@a!T2V+{^!psQkx4?x0HSV`(w9{l75QxMk!)U52Lbhn{8ol?S) zCKo*7R(z!uk<6*qO=wh!Pul{(qq6g6xW;X68GI_CXp`XwO zxuSgPRAtM8K7}5E#-GM!*ydOOG_{A{)hkCII<|2=ma*71ci_-}VPARm3crFQjLYV! z9zbz82$|l01mv`$WahE2$=fAGWkd^X2kY(J7iz}WGS z@%MyBEO=A?HB9=^?nX`@nh;7;laAjs+fbo!|K^mE!tOB>$2a_O0y-*uaIn8k^6Y zSbuv;5~##*4Y~+y7Z5O*3w4qgI5V^17u*ZeupVGH^nM&$qmAk|anf*>r zWc5CV;-JY-Z@Uq1Irpb^O`L_7AGiqd*YpGUShb==os$uN3yYvb`wm6d=?T*it&pDk zo`vhw)RZX|91^^Wa_ti2zBFyWy4cJu#g)_S6~jT}CC{DJ_kKpT`$oAL%b^!2M;JgT zM3ZNbUB?}kP(*YYvXDIH8^7LUxz5oE%kMhF!rnPqv!GiY0o}NR$OD=ITDo9r%4E>E0Y^R(rS^~XjWyVI6 zMOR5rPXhTp*G*M&X#NTL`Hu*R+u*QNoiOKg4CtNPrjgH>c?Hi4MUG#I917fx**+pJfOo!zFM&*da&G_x)L(`k&TPI*t3e^{crd zX<4I$5nBQ8Ax_lmNRa~E*zS-R0sxkz`|>7q_?*e%7bxqNm3_eRG#1ae3gtV9!fQpY z+!^a38o4ZGy9!J5sylDxZTx$JmG!wg7;>&5H1)>f4dXj;B+@6tMlL=)cLl={jLMxY zbbf1ax3S4>bwB9-$;SN2?+GULu;UA-35;VY*^9Blx)Jwyb$=U!D>HhB&=jSsd^6yw zL)?a|>GxU!W}ocTC(?-%z3!IUhw^uzc`Vz_g>-tv)(XA#JK^)ZnC|l1`@CdX1@|!| z_9gQ)7uOf?cR@KDp97*>6X|;t@Y`k_N@)aH7gY27)COv^P3ya9I{4z~vUjLR9~z1Z z5=G{mVtKH*&$*t0@}-i_v|3B$AHHYale7>E+jP`ClqG%L{u;*ff_h@)al?RuL7tOO z->;I}>%WI{;vbLP3VIQ^iA$4wl6@0sDj|~112Y4OFjMs`13!$JGkp%b&E8QzJw_L5 zOnw9joc0^;O%OpF$Qp)W1HI!$4BaXX84`%@#^dk^hFp^pQ@rx4g(8Xjy#!X%+X5Jd@fs3amGT`}mhq#L97R>OwT5-m|h#yT_-v@(k$q7P*9X~T*3)LTdzP!*B} z+SldbVWrrwQo9wX*%FyK+sRXTa@O?WM^FGWOE?S`R(0P{<6p#f?0NJvnBia?k^fX2 zNQs7K-?EijgHJY}&zsr;qJ<*PCZUd*x|dD=IQPUK_nn)@X4KWtqoJNHkT?ZWL_hF? zS8lp2(q>;RXR|F;1O}EE#}gCrY~#n^O`_I&?&z5~7N;zL0)3Tup`%)oHMK-^r$NT% zbFg|o?b9w(q@)6w5V%si<$!U<#}s#x@0aX-hP>zwS#9*75VXA4K*%gUc>+yzupTDBOKH8WR4V0pM(HrfbQ&eJ79>HdCvE=F z|J>s;;iDLB^3(9}?biKbxf1$lI!*Z%*0&8UUq}wMyPs_hclyQQi4;NUY+x2qy|0J; zhn8;5)4ED1oHwg+VZF|80<4MrL97tGGXc5Sw$wAI#|2*cvQ=jB5+{AjMiDHmhUC*a zlmiZ`LAuAn_}hftXh;`Kq0zblDk8?O-`tnilIh|;3lZp@F_osJUV9`*R29M?7H{Fy z`nfVEIDIWXmU&YW;NjU8)EJpXhxe5t+scf|VXM!^bBlwNh)~7|3?fWwo_~ZFk(22% zTMesYw+LNx3J-_|DM~`v93yXe=jPD{q;li;5PD?Dyk+b? zo21|XpT@)$BM$%F=P9J19Vi&1#{jM3!^Y&fr&_`toi`XB1!n>sbL%U9I5<7!@?t)~ z;&H%z>bAaQ4f$wIzkjH70;<8tpUoxzKrPhn#IQfS%9l5=Iu))^XC<58D!-O z{B+o5R^Z21H0T9JQ5gNJnqh#qH^na|z92=hONIM~@_iuOi|F>jBh-?aA20}Qx~EpDGElELNn~|7WRXRFnw+Wdo`|# zBpU=Cz3z%cUJ0mx_1($X<40XEIYz(`noWeO+x#yb_pwj6)R(__%@_Cf>txOQ74wSJ z0#F3(zWWaR-jMEY$7C*3HJrohc79>MCUu26mfYN)f4M~4gD`}EX4e}A!U}QV8!S47 z6y-U-%+h`1n`*pQuKE%Av0@)+wBZr9mH}@vH@i{v(m-6QK7Ncf17x_D=)32`FOjjo zg|^VPf5c6-!FxN{25dvVh#fog=NNpXz zfB$o+0jbRkHH{!TKhE709f+jI^$3#v1Nmf80w`@7-5$1Iv_`)W^px8P-({xwb;D0y z7LKDAHgX<84?l!I*Dvi2#D@oAE^J|g$3!)x1Ua;_;<@#l1fD}lqU2_tS^6Ht$1Wl} zBESo7o^)9-Tjuz$8YQSGhfs{BQV6zW7dA?0b(Dbt=UnQs&4zHfe_sj{RJ4uS-vQpC zX;Bbsuju4%!o8?&m4UZU@~ZZjeFF6ex2ss5_60_JS_|iNc+R0GIjH1@Z z=rLT9%B|WWgOrR7IiIwr2=T;Ne?30M!@{%Qf8o`!>=s<2CBpCK_TWc(DX51>e^xh8 z&@$^b6CgOd7KXQV&Y4%}_#uN*mbanXq(2=Nj`L7H7*k(6F8s6{FOw@(DzU`4-*77{ zF+dxpv}%mFpYK?>N_2*#Y?oB*qEKB}VoQ@bzm>ptmVS_EC(#}Lxxx730trt0G)#$b zE=wVvtqOct1%*9}U{q<)2?{+0TzZzP0jgf9*)arV)*e!f`|jgT{7_9iS@e)recI#z zbzolURQ+TOzE!ymqvBY7+5NnAbWxvMLsLTwEbFqW=CPyCsmJ}P1^V30|D5E|p3BC5 z)3|qgw@ra7aXb-wsa|l^in~1_fm{7bS9jhVRkYVO#U{qMp z)Wce+|DJ}4<2gp8r0_xfZpMo#{Hl2MfjLcZdRB9(B(A(f;+4s*FxV{1F|4d`*sRNd zp4#@sEY|?^FIJ;tmH{@keZ$P(sLh5IdOk@k^0uB^BWr@pk6mHy$qf&~rI>P*a;h0C{%oA*i!VjWn&D~O#MxN&f@1Po# zKN+ zrGrkSjcr?^R#nGl<#Q722^wbYcgW@{+6CBS<1@%dPA8HC!~a`jTz<`g_l5N1M@9wn9GOAZ>nqNgq!yOCbZ@1z`U_N`Z>}+1HIZxk*5RDc&rd5{3qjRh8QmT$VyS;jK z;AF+r6XnnCp=wQYoG|rT2@8&IvKq*IB_WvS%nt%e{MCFm`&W*#LXc|HrD?nVBo=(8*=Aq?u$sDA_sC_RPDUiQ+wnIJET8vx$&fxkW~kP9qXKt zozR)@xGC!P)CTkjeWvXW5&@2?)qt)jiYWWBU?AUtzAN}{JE1I)dfz~7$;}~BmQF`k zpn11qmObXwRB8&rnEG*#4Xax3XBkKlw(;tb?Np^i+H8m(Wyz9k{~ogba@laiEk;2! zV*QV^6g6(QG%vX5Um#^sT&_e`B1pBW5yVth~xUs#0}nv?~C#l?W+9Lsb_5)!71rirGvY zTIJ$OPOY516Y|_014sNv+Z8cc5t_V=i>lWV=vNu#!58y9Zl&GsMEW#pPYPYGHQ|;vFvd*9eM==$_=vc7xnyz0~ zY}r??$<`wAO?JQk@?RGvkWVJlq2dk9vB(yV^vm{=NVI8dhsX<)O(#nr9YD?I?(VmQ z^r7VfUBn<~p3()8yOBjm$#KWx!5hRW)5Jl7wY@ky9lNM^jaT##8QGVsYeaVywmpv>X|Xj7gWE1Ezai&wVLt3p)k4w~yrskT-!PR!kiyQlaxl(( zXhF%Q9x}1TMt3~u@|#wWm-Vq?ZerK={8@~&@9r5JW}r#45#rWii};t`{5#&3$W)|@ zbAf2yDNe0q}NEUvq_Quq3cTjcw z@H_;$hu&xllCI9CFDLuScEMg|x{S7GdV8<&Mq=ezDnRZAyX-8gv97YTm0bg=d)(>N z+B2FcqvI9>jGtnK%eO%y zoBPkJTk%y`8TLf4)IXPBn`U|9>O~WL2C~C$z~9|0m*YH<-vg2CD^SX#&)B4ngOSG$ zV^wmy_iQk>dfN@Pv(ckfy&#ak@MLC7&Q6Ro#!ezM*VEh`+b3Jt%m(^T&p&WJ2Oqvj zs-4nq0TW6cv~(YI$n0UkfwN}kg3_fp?(ijSV#tR9L0}l2qjc7W?i*q01=St0eZ=4h zyGQbEw`9OEH>NMuIe)hVwYHsGERWOD;JxEiO7cQv%pFCeR+IyhwQ|y@&^24k+|8fD zLiOWFNJ2&vu2&`Jv96_z-Cd5RLgmeY3*4rDOQo?Jm`;I_(+ejsPM03!ly!*Cu}Cco zrQSrEDHNyzT(D5s1rZq!8#?f6@v6dB7a-aWs(Qk>N?UGAo{gytlh$%_IhyL7h?DLXDGx zgxGEBQoCAWo-$LRvM=F5MTle`M})t3vVv;2j0HZY&G z22^iGhV@uaJh(XyyY%} zd4iH_UfdV#T=3n}(Lj^|n;O4|$;xhu*8T3hR1mc_A}fK}jfZ7LX~*n5+`8N2q#rI$ z@<_2VANlYF$vIH$ zl<)+*tIWW78IIINA7Rr7i{<;#^yzxoLNkXL)eSs=%|P>$YQIh+ea_3k z_s7r4%j7%&*NHSl?R4k%1>Z=M9o#zxY!n8sL5>BO-ZP;T3Gut>iLS@U%IBrX6BA3k z)&@q}V8a{X<5B}K5s(c(LQ=%v1ocr`t$EqqY0EqVjr65usa=0bkf|O#ky{j3)WBR(((L^wmyHRzoWuL2~WTC=`yZ zn%VX`L=|Ok0v7?s>IHg?yArBcync5rG#^+u)>a%qjES%dRZoIyA8gQ;StH z1Ao7{<&}6U=5}4v<)1T7t!J_CL%U}CKNs-0xWoTTeqj{5{?Be$L0_tk>M9o8 zo371}S#30rKZFM{`H_(L`EM9DGp+Mifk&IP|C2Zu_)Ghr4Qtpmkm1osCf@%Z$%t+7 zYH$Cr)Ro@3-QDeQJ8m+x6%;?YYT;k6Z0E-?kr>x33`H%*ueBD7Zx~3&HtWn0?2Wt} zTG}*|v?{$ajzt}xPzV%lL1t-URi8*Zn)YljXNGDb>;!905Td|mpa@mHjIH%VIiGx- zd@MqhpYFu4_?y5N4xiHn3vX&|e6r~Xt> zZG`aGq|yTNjv;9E+Txuoa@A(9V7g?1_T5FzRI;!=NP1Kqou1z5?%X~Wwb{trRfd>i z8&y^H)8YnKyA_Fyx>}RNmQIczT?w2J4SNvI{5J&}Wto|8FR(W;Qw#b1G<1%#tmYzQ zQ2mZA-PAdi%RQOhkHy9Ea#TPSw?WxwL@H@cbkZwIq0B!@ns}niALidmn&W?!Vd4Gj zO7FiuV4*6Mr^2xlFSvM;Cp_#r8UaqIzHJQg_z^rEJw&OMm_8NGAY2)rKvki|o1bH~ z$2IbfVeY2L(^*rMRU1lM5Y_sgrDS`Z??nR2lX;zyR=c%UyGb*%TC-Dil?SihkjrQy~TMv6;BMs7P8il`H7DmpVm@rJ;b)hW)BL)GjS154b*xq-NXq2cwE z^;VP7ua2pxvCmxrnqUYQMH%a%nHmwmI33nJM(>4LznvY*k&C0{8f*%?zggpDgkuz&JBx{9mfb@wegEl2v!=}Sq2Gaty0<)UrOT0{MZtZ~j5y&w zXlYa_jY)I_+VA-^#mEox#+G>UgvM!Ac8zI<%JRXM_73Q!#i3O|)lOP*qBeJG#BST0 zqohi)O!|$|2SeJQo(w6w7%*92S})XfnhrH_Z8qe!G5>CglP=nI7JAOW?(Z29;pXJ9 zR9`KzQ=WEhy*)WH>$;7Cdz|>*i>=##0bB)oU0OR>>N<21e4rMCHDemNi2LD>Nc$;& zQRFthpWniC1J6@Zh~iJCoLOxN`oCKD5Q4r%ynwgUKPlIEd#?QViIqovY|czyK8>6B zSP%{2-<;%;1`#0mG^B(8KbtXF;Nf>K#Di72UWE4gQ%(_26Koiad)q$xRL~?pN71ZZ zujaaCx~jXjygw;rI!WB=xrOJO6HJ!!w}7eiivtCg5K|F6$EXa)=xUC za^JXSX98W`7g-tm@uo|BKj39Dl;sg5ta;4qjo^pCh~{-HdLl6qI9Ix6f$+qiZ$}s= zNguKrU;u+T@ko(Vr1>)Q%h$?UKXCY>3se%&;h2osl2D zE4A9bd7_|^njDd)6cI*FupHpE3){4NQ*$k*cOWZ_?CZ>Z4_fl@n(mMnYK62Q1d@+I zr&O))G4hMihgBqRIAJkLdk(p(D~X{-oBUA+If@B}j& zsHbeJ3RzTq96lB7d($h$xTeZ^gP0c{t!Y0c)aQE;$FY2!mACg!GDEMKXFOPI^)nHZ z`aSPJpvV0|bbrzhWWkuPURlDeN%VT8tndV8?d)eN*i4I@u zVKl^6{?}A?P)Fsy?3oi#clf}L18t;TjNI2>eI&(ezDK7RyqFxcv%>?oxUlonv(px) z$vnPzRH`y5A(x!yOIfL0bmgeMQB$H5wenx~!ujQK*nUBW;@Em&6Xv2%s(~H5WcU2R z;%Nw<$tI)a`Ve!>x+qegJnQsN2N7HaKzrFqM>`6R*gvh%O*-%THt zrB$Nk;lE;z{s{r^PPm5qz(&lM{sO*g+W{sK+m3M_z=4=&CC>T`{X}1Vg2PEfSj2x_ zmT*(x;ov%3F?qoEeeM>dUn$a*?SIGyO8m806J1W1o+4HRhc2`9$s6hM#qAm zChQ87b~GEw{ADfs+5}FJ8+|bIlIv(jT$Ap#hSHoXdd9#w<#cA<1Rkq^*EEkknUd4& zoIWIY)sAswy6fSERVm&!SO~#iN$OgOX*{9@_BWFyJTvC%S++ilSfCrO(?u=Dc?CXZ zzCG&0yVR{Z`|ZF0eEApWEo#s9osV>F{uK{QA@BES#&;#KsScf>y zvs?vIbI>VrT<*!;XmQS=bhq%46-aambZ(8KU-wOO2=en~D}MCToB_u;Yz{)1ySrPZ z@=$}EvjTdzTWU7c0ZI6L8=yP+YRD_eMMos}b5vY^S*~VZysrkq<`cK3>>v%uy7jgq z0ilW9KjVDHLv0b<1K_`1IkbTOINs0=m-22c%M~l=^S}%hbli-3?BnNq?b`hx^HX2J zIe6ECljRL0uBWb`%{EA=%!i^4sMcj+U_TaTZRb+~GOk z^ZW!nky0n*Wb*r+Q|9H@ml@Z5gU&W`(z4-j!OzC1wOke`TRAYGZVl$PmQ16{3196( zO*?`--I}Qf(2HIwb2&1FB^!faPA2=sLg(@6P4mN)>Dc3i(B0;@O-y2;lM4akD>@^v z=u>*|!s&9zem70g7zfw9FXl1bpJW(C#5w#uy5!V?Q(U35A~$dR%LDVnq@}kQm13{} zd53q3N(s$Eu{R}k2esbftfjfOITCL;jWa$}(mmm}d(&7JZ6d3%IABCapFFYjdEjdK z&4Edqf$G^MNAtL=uCDRs&Fu@FXRgX{*0<(@c3|PNHa>L%zvxWS={L8%qw`STm+=Rd zA}FLspESSIpE_^41~#5yI2bJ=9`oc;GIL!JuW&7YetZ?0H}$$%8rW@*J37L-~Rsx!)8($nI4 zZhcZ2^=Y+p4YPl%j!nFJA|*M^gc(0o$i3nlphe+~-_m}jVkRN{spFs(o0ajW@f3K{ zDV!#BwL322CET$}Y}^0ixYj2w>&Xh12|R8&yEw|wLDvF!lZ#dOTHM9pK6@Nm-@9Lnng4ZHBgBSrr7KI8YCC9DX5Kg|`HsiwJHg2(7#nS;A{b3tVO?Z% za{m5b3rFV6EpX;=;n#wltDv1LE*|g5pQ+OY&*6qCJZc5oDS6Z6JD#6F)bWxZSF@q% z+1WV;m!lRB!n^PC>RgQCI#D1br_o^#iPk>;K2hB~0^<~)?p}LG%kigm@moD#q3PE+ zA^Qca)(xnqw6x>XFhV6ku9r$E>bWNrVH9fum0?4s?Rn2LG{Vm_+QJHse6xa%nzQ?k zKug4PW~#Gtb;#5+9!QBgyB@q=sk9=$S{4T>wjFICStOM?__fr+Kei1 z3j~xPqW;W@YkiUM;HngG!;>@AITg}vAE`M2Pj9Irl4w1fo4w<|Bu!%rh%a(Ai^Zhi zs92>v5;@Y(Zi#RI*ua*h`d_7;byQSa*v9E{2x$<-_=5Z<7{%)}4XExANcz@rK69T0x3%H<@frW>RA8^swA+^a(FxK| zFl3LD*ImHN=XDUkrRhp6RY5$rQ{bRgSO*(vEHYV)3Mo6Jy3puiLmU&g82p{qr0F?ohmbz)f2r{X2|T2 z$4fdQ=>0BeKbiVM!e-lIIs8wVTuC_m7}y4A_%ikI;Wm5$9j(^Y z(cD%U%k)X>_>9~t8;pGzL6L-fmQO@K; zo&vQzMlgY95;1BSkngY)e{`n0!NfVgf}2mB3t}D9@*N;FQ{HZ3Pb%BK6;5#-O|WI( zb6h@qTLU~AbVW#_6?c!?Dj65Now7*pU{h!1+eCV^KCuPAGs28~3k@ueL5+u|Z-7}t z9|lskE`4B7W8wMs@xJa{#bsCGDFoRSNSnmNYB&U7 zVGKWe%+kFB6kb)e;TyHfqtU6~fRg)f|>=5(N36)0+C z`hv65J<$B}WUc!wFAb^QtY31yNleq4dzmG`1wHTj=c*=hay9iD071Hc?oYoUk|M*_ zU1GihAMBsM@5rUJ(qS?9ZYJ6@{bNqJ`2Mr+5#hKf?doa?F|+^IR!8lq9)wS3tF_9n zW_?hm)G(M+MYb?V9YoX^_mu5h-LP^TL^!Q9Z7|@sO(rg_4+@=PdI)WL(B7`!K^ND- z-uIuVDCVEdH_C@c71YGYT^_Scf_dhB8Z2Xy6vGtBSlYud9vggOqv^L~F{BraSE_t} zIkP+Hp2&nH^-MNEs}^`oMLy11`PQW$T|K(`Bu*(f@)mv1-qY(_YG&J2M2<7k;;RK~ zL{Fqj9yCz8(S{}@c)S!65aF<=&eLI{hAMErCx&>i7OeDN>okvegO87OaG{Jmi<|}D zaT@b|0X{d@OIJ7zvT>r+eTzgLq~|Dpu)Z&db-P4z*`M$UL51lf>FLlq6rfG)%doyp z)3kk_YIM!03eQ8Vu_2fg{+osaEJPtJ-s36R+5_AEG12`NG)IQ#TF9c@$99%0iye+ zUzZ57=m2)$D(5Nx!n)=5Au&O0BBgwxIBaeI(mro$#&UGCr<;C{UjJVAbVi%|+WP(a zL$U@TYCxJ=1{Z~}rnW;7UVb7+ZnzgmrogDxhjLGo>c~MiJAWs&&;AGg@%U?Y^0JhL ze(x6Z74JG6FlOFK(T}SXQfhr}RIFl@QXKnIcXYF)5|V~e-}suHILKT-k|<*~Ij|VF zC;t@=uj=hot~*!C68G8hTA%8SzOfETOXQ|3FSaIEjvBJp(A)7SWUi5!Eu#yWgY+;n zlm<$+UDou*V+246_o#V4kMdto8hF%%Lki#zPh}KYXmMf?hrN0;>Mv%`@{0Qn`Ujp) z=lZe+13>^Q!9zT);H<(#bIeRWz%#*}sgUX9P|9($kexOyKIOc`dLux}c$7It4u|Rl z6SSkY*V~g_B-hMPo_ak>>z@AVQ(_N)VY2kB3IZ0G(iDUYw+2d7W^~(Jq}KY=JnWS( z#rzEa&0uNhJ>QE8iiyz;n2H|SV#Og+wEZv=f2%1ELX!SX-(d3tEj$5$1}70Mp<&eI zCkfbByL7af=qQE@5vDVxx1}FSGt_a1DoE3SDI+G)mBAna)KBG4p8Epxl9QZ4BfdAN zFnF|Y(umr;gRgG6NLQ$?ZWgllEeeq~z^ZS7L?<(~O&$5|y)Al^iMKy}&W+eMm1W z7EMU)u^ke(A1#XCV>CZ71}P}0x)4wtHO8#JRG3MA-6g=`ZM!FcICCZ{IEw8Dm2&LQ z1|r)BUG^0GzI6f946RrBlfB1Vs)~8toZf~7)+G;pv&XiUO(%5bm)pl=p>nV^o*;&T z;}@oZSibzto$arQgfkp|z4Z($P>dTXE{4O=vY0!)kDO* zGF8a4wq#VaFpLfK!iELy@?-SeRrdz%F*}hjKcA*y@mj~VD3!it9lhRhX}5YOaR9$} z3mS%$2Be7{l(+MVx3 z(4?h;P!jnRmX9J9sYN#7i=iyj_5q7n#X(!cdqI2lnr8T$IfOW<_v`eB!d9xY1P=2q&WtOXY=D9QYteP)De?S4}FK6#6Ma z=E*V+#s8>L;8aVroK^6iKo=MH{4yEZ_>N-N z`(|;aOATba1^asjxlILk<4}f~`39dBFlxj>Dw(hMYKPO3EEt1@S`1lxFNM+J@uB7T zZ8WKjz7HF1-5&2=l=fqF-*@>n5J}jIxdDwpT?oKM3s8Nr`x8JnN-kCE?~aM1H!hAE z%%w(3kHfGwMnMmNj(SU(w42OrC-euI>Dsjk&jz3ts}WHqmMpzQ3vZrsXrZ|}+MHA7 z068obeXZTsO*6RS@o3x80E4ok``rV^Y3hr&C1;|ZZ0|*EKO`$lECUYG2gVFtUTw)R z4Um<0ZzlON`zTdvVdL#KFoMFQX*a5wM0Czp%wTtfK4Sjs)P**RW&?lP$(<}q%r68Z zS53Y!d@&~ne9O)A^tNrXHhXBkj~$8j%pT1%%mypa9AW5E&s9)rjF4@O3ytH{0z6riz|@< zB~UPh*wRFg2^7EbQrHf0y?E~dHlkOxof_a?M{LqQ^C!i2dawHTPYUE=X@2(3<=OOxs8qn_(y>pU>u^}3y&df{JarR0@VJn0f+U%UiF=$Wyq zQvnVHESil@d|8&R<%}uidGh7@u^(%?$#|&J$pvFC-n8&A>utA=n3#)yMkz+qnG3wd zP7xCnF|$9Dif@N~L)Vde3hW8W!UY0BgT2v(wzp;tlLmyk2%N|0jfG$%<;A&IVrOI< z!L)o>j>;dFaqA3pL}b-Je(bB@VJ4%!JeX@3x!i{yIeIso^=n?fDX`3bU=eG7sTc%g%ye8$v8P@yKE^XD=NYxTb zbf!Mk=h|otpqjFaA-vs5YOF-*GwWPc7VbaOW&stlANnCN8iftFMMrUdYNJ_Bnn5Vt zxfz@Ah|+4&P;reZxp;MmEI7C|FOv8NKUm8njF7Wb6Gi7DeODLl&G~}G4be&*Hi0Qw z5}77vL0P+7-B%UL@3n1&JPxW^d@vVwp?u#gVcJqY9#@-3X{ok#UfW3<1fb%FT`|)V~ggq z(3AUoUS-;7)^hCjdT0Kf{i}h)mBg4qhtHHBti=~h^n^OTH5U*XMgDLIR@sre`AaB$ zg)IGBET_4??m@cx&c~bA80O7B8CHR7(LX7%HThkeC*@vi{-pL%e)yXp!B2InafbDF zjPXf1mko3h59{lT6EEbxKO1Z5GF71)WwowO6kY|6tjSVSWdQ}NsK2x{>i|MKZK8%Q zfu&_0D;CO-Jg0#YmyfctyJ!mRJp)e#@O0mYdp|8x;G1%OZQ3Q847YWTyy|%^cpA;m zze0(5p{tMu^lDkpe?HynyO?a1$_LJl2L&mpeKu%8YvgRNr=%2z${%WThHG=vrWY@4 zsA`OP#O&)TetZ>s%h!=+CE15lOOls&nvC~$Qz0Ph7tHiP;O$i|eDwpT{cp>+)0-|; zY$|bB+Gbel>5aRN3>c0x)4U=|X+z+{ zn*_p*EQoquRL+=+p;=lm`d71&1NqBz&_ph)MXu(Nv6&XE7(RsS)^MGj5Q?Fwude-(sq zjJ>aOq!7!EN>@(fK7EE#;i_BGvli`5U;r!YA{JRodLBc6-`n8K+Fjgwb%sX;j=qHQ z7&Tr!)!{HXoO<2BQrV9Sw?JRaLXV8HrsNevvnf>Y-6|{T!pYLl7jp$-nEE z#X!4G4L#K0qG_4Z;Cj6=;b|Be$hi4JvMH!-voxqx^@8cXp`B??eFBz2lLD8RRaRGh zn7kUfy!YV~p(R|p7iC1Rdgt$_24i0cd-S8HpG|`@my70g^y`gu%#Tf_L21-k?sRRZHK&at(*ED0P8iw{7?R$9~OF$Ko;Iu5)ur5<->x!m93Eb zFYpIx60s=Wxxw=`$aS-O&dCO_9?b1yKiPCQmSQb>T)963`*U+Ydj5kI(B(B?HNP8r z*bfSBpSu)w(Z3j7HQoRjUG(+d=IaE~tv}y14zHHs|0UcN52fT8V_<@2ep_ee{QgZG zmgp8iv4V{k;~8@I%M3<#B;2R>Ef(Gg_cQM7%}0s*^)SK6!Ym+~P^58*wnwV1BW@eG z4sZLqsUvBbFsr#8u7S1r4teQ;t)Y@jnn_m5jS$CsW1um!p&PqAcc8!zyiXHVta9QC zY~wCwCF0U%xiQPD_INKtTb;A|Zf29(mu9NI;E zc-e>*1%(LSXB`g}kd`#}O;veb<(sk~RWL|f3ljxCnEZDdNSTDV6#Td({6l&y4IjKF z^}lIUq*ZUqgTPumD)RrCN{M^jhY>E~1pn|KOZ5((%F)G|*ZQ|r4zIbrEiV%42hJV8 z3xS)=!X1+=olbdGJ=yZil?oXLct8FM{(6ikLL3E%=q#O6(H$p~gQu6T8N!plf!96| z&Q3=`L~>U0zZh;z(pGR2^S^{#PrPxTRHD1RQOON&f)Siaf`GLj#UOk&(|@0?zm;Sx ztsGt8=29-MZs5CSf1l1jNFtNt5rFNZxJPvkNu~2}7*9468TWm>nN9TP&^!;J{-h)_ z7WsHH9|F%I`Pb!>KAS3jQWKfGivTVkMJLO-HUGM_a4UQ_%RgL6WZvrW+Z4ujZn;y@ zz9$=oO!7qVTaQAA^BhX&ZxS*|5dj803M=k&2%QrXda`-Q#IoZL6E(g+tN!6CA!CP* zCpWtCujIea)ENl0liwVfj)Nc<9mV%+e@=d`haoZ*`B7+PNjEbXBkv=B+Pi^~L#EO$D$ZqTiD8f<5$eyb54-(=3 zh)6i8i|jp(@OnRrY5B8t|LFXFQVQ895n*P16cEKTrT*~yLH6Z4e*bZ5otpRDri&+A zfNbK1D5@O=sm`fN=WzWyse!za5n%^+6dHPGX#8DyIK>?9qyX}2XvBWVqbP%%D)7$= z=#$WulZlZR<{m#gU7lwqK4WS1Ne$#_P{b17qe$~UOXCl>5b|6WVh;5vVnR<%d+Lnp z$uEmML38}U4vaW8>shm6CzB(Wei3s#NAWE3)a2)z@i{4jTn;;aQS)O@l{rUM`J@K& l00vQ5JBs~;vo!vr%%-k{2_Fq1Mn4QF81S)AQ99zk{{c4yR+0b! literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..6689b85b --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..c39c0ad4 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'todoffin' diff --git a/src/main/java/com/genius/todoffin/TodoffinApplication.java b/src/main/java/com/genius/todoffin/TodoffinApplication.java new file mode 100644 index 00000000..239cbcda --- /dev/null +++ b/src/main/java/com/genius/todoffin/TodoffinApplication.java @@ -0,0 +1,13 @@ +package com.genius.todoffin; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TodoffinApplication { + + public static void main(String[] args) { + SpringApplication.run(TodoffinApplication.class, args); + } + +} diff --git a/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java b/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java new file mode 100644 index 00000000..0774dfab --- /dev/null +++ b/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java @@ -0,0 +1,13 @@ +package com.genius.todoffin; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TodoffinApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/todoffin/.gradle/8.5/checksums/checksums.lock b/todoffin/.gradle/8.5/checksums/checksums.lock deleted file mode 100644 index 671d96fcd5b35de384926089009b508402f5f3c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZR6pOnF6|9*i20~jza0{}2(1Y7_B diff --git a/todoffin/.gradle/8.5/checksums/md5-checksums.bin b/todoffin/.gradle/8.5/checksums/md5-checksums.bin deleted file mode 100644 index aebb79692a0426814a5cc114877b31f5eeb47f44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23797 zcmeI3i91zWAIFbU9Osy#%tHxj5GqlK1~QMwn2@2tjY=w%1`VigxrLM>G?Jl6N;i_s z5oth!ijokg$WY$3&sxj-*r$KseV@Ia=PvH2_1V9*zGt7ccTan-q|qcrMp2{vw@v-; zuhef;0#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pK20#pM3FA1=~ zLImJqBP{op{ui140S>xUt?RKh$$Vu3@H zZdU{3Ho;>&-b78eEc+AW)~1*rw)?wC^z@45kXxu>9_c$U^z>TBDaaj)F^{%c@Znjt zOgrSRx?{YwHPs-vC>3(cPRx&8)6$bO_w9$=F#_|L!||K%Rv*%a+{qmC#QD3@e8n%F zLE}GRp7hFS=Zn#8-H@;Gz&ynyhkYw^`vu6&OEJIn$?mP{n_dHS{8r3wl>aidOR+zR zj-Q8lW~E{3QX#u$$Sry?&k5q|?C^|{h1_aC<~MgO2s;s@um*Bg)EK{?|FONv_%`BB zW4!X^C8+_QV~|_F#r)2`Pu8E`CW%At7LWP;70Xs>cF4Vg+)-hSZ*g4vF7yPN=b94C zOJZ-H*FW|h&Byu%<`qNH2K8y*+fZEz%&VFU?zbEGK8AdC5au)-db#= zo!PK73mtzV=I?KtY{}p6o(Q>{FXkVnANjraP4;TYUHvg{tM7VPBFjDwx$Q;FKkFn~ zwU~Uy=bO|RH)vm2R@z$($6L6K@vXNX=?wq854rQTF&@RwVp~=gLT+V^c}LqeMlNG{ zKIC>AG5;bXTN3_wtp((!bj&+L81r5PcCClpeh~BSA+GFn{z0 za@X^i57w0TKO9Ma2f67|%z2m-e>9uvf_;!^rcW`S_$s<*XmAr7a`OSqc`xqxH9gcx z6LROBnDbp&TOMrm`Y;-Q3Uj)~tmQd()5;)sNyS{CZo&Kzwha?27Aa|1TlAcm>S&QV}g!v zBZ|4}%xz}=Yd^W8uGeOZF!yc`}Nl(w9lI*U>^1(GUe+T-?ebOeHrHXC%7ZzfqzEzR0322R0322R0322R0322 zR0322R0322R0322R0322R0322R0322R0322R0322R0322R0322R0322R0322R0322 zR0322|LX}54_~W+|A>bsr;A+8FL@~P?~0oz9(e~`+O3#CqwyR|zU_{lXq{9I2C0uX z8a3$A|6EG7Q4f2cDl$n7JhMs|YqP;f&=|zC_#|Vrj$bI5|CqgujCz%EP&|10k7J;x zs0IAFjdMI74EtW}EsXt`yg5d->NhfO5fwmrParZbIz!oO{SX zPg&E6r|d~JF8lc;e7os&HeuTCxeiP}Fn`X`c{&xNntzcV4+P>%c{D zY>rU{4cQsoMoz#3`*r34nQvbYzdCh#8A<0W{FBIj*g2gz?&nS zm7`fr3*$CK1@C-#ekxQfXCABRq*f|8%Q?m`q5_S^Py8g+&^~wf`R10Vx0}Lwr_3oT zGKGc*dU8`*kJ~u7-~M*zHt`#Dmf}RFo%us#5CuS!)&(7@hU(Dhv%N<$<#-3R9JgGN zcR&VuBS`iJw-KN%pqqTx*wdkUk?r1YOG9Kxfsvpk#et4gBh4h%I>s%psmpY6ZjgD6 zJTiDW0!_Ms+u)s)<#P4ew2qApQAG`F{nDVZ7CqNHn|L0cRHIL2OGc!@gc&N~EZ>Zf zC9?QljN1^;_*QrRXMI+C*uyyYPX^A&IDrf^ZeztotDABW?av-`omi$l(G@(^ zOMHSJ$PftUHl9`Vy%@S{9Qb`|RBe^ussUu6_gR>PNm|E7h4 zAY&@=ls#!yo2@P%Hk_BQTX&LHw(NRP;CMrVxGIngtG($Al*pULa3qvLoB?N=UP zrz08@>=a~UB+=&mxwz&tjv_;nv6+%DP>p{%0!_k)+gNrZzj5pQ{))b%mJ3rHlaC<- z?TM2oaT}kSH_O|&C`&CrK4PQQngi10tQ^!L8Y2R9q;=TM3zJB$5%tu2q&?$>noKP+ zz`jK^CLicXhQ3I+w2*GNpU29e4f>6&UE>Y99k;Qm;e1<EAz4j=)^bWWmAy)2*-sxcc!7wp=q?mNhJ9|lguGN6sPjJX_ z2JyB4DJ%BNHKD0Fb9<_kR4VW6mQh0ndhdl_l-uz8`EUHwbt8diwC(n%HC;G_3}rA9 zG`c0|NHy}pR9Rtz4J`WPz;-vQL~vE+%!=64L1V539myE-%CN9KBl{1l=*Pj92Pxpp z;~2yl2-+I(y(b$&qYRhpq3v%fRw@RS%W9uSh6zWYN%e3Wd>{EbrEAvjH}ML(u>E2r zm_Mfm3mQ|vEXg%`H%JtJFVQqjw_O*j_4h(>Y>u%S8iMb*jkx`B-5(||SKMkbcj+~m zST|(&K_eiM+ptK?%i7Vd&TcV3cYkH$(kIWX_ccXeYe`7MUZVxin zLnD%SYmQ{ZwOrA>GHuq*5k``YajJp}GMu2n3$BY~ z#MKP6*~Dj0s-aoeq{G`CnaJL?*k)5w$7NI_nHUf5Oa4$}Rm07@s!dO}=jbi1n6DzH zKi&}d!}nufq1fXCBTT2L2VoZ%d*oIl1Kou)iTg#;tYi*#9~FLBRFz(;MK5WOHbMq^ zC2@LgJx6}QAw;?jnRZZm%x8cAJNNBZ`7|5-4Fjv@q3tVA1XJR=R z2^yaT=t#5baMTQ&v8`6vT%cHO`D-th^3eRuI;ZX?p0NA;nBPO5>Q=U0Qp#aZJGI{1Hb4Q;pU^5G)wUFppDo}(Uf&LV>i zXEkLDw^3QI-{R`3WpSL=koDvX3+zvvS&cwr-aBsNN|kk&;^lKZs$Yxd#du_(yKpaA zXv`JnHcod0X9_*u`bI-WGhewRJr&l#22+OH(8~|*bqn8FbNQG1iMZ*CO~_!t8e+4# zjlY^^+V*?4EiFrQ{Kur|>KSAZI~!=CbkLDL51s&KcHzB`o!i`X4BJPD0mRC^kRc$# zZM^iiV`%Ra={Tis>!@xL0!DHSV#T1D2ZD}NL$pA1qvT8LT#a>FXO%apO+^N{x)F`} z9CReZbHlD)Rn_8=V$+E1!y9=T#~VU__Nn7WKOxR)c^j7Oj&r7JqQWqt7w*OmOp zAW{WQ<_}kcI`@d+NlvlyxvyfU_!|XJ9B-(AJ2Y|)r7w;rYOPN8@(bjp-T8hNjNoKN z+`)iGe+xR&%4w`Se1o=SmZ6dFnh$-H1y^H^L7W|+(TRI&k^#22PdhF}saUg>>xTsY z5=TZcN1*ZkVfQI~UmmC>8=SL{wN(CUmctWd5c@4?llwtOs?le)ZNobDgz$)xa2`9u z;X-J5pW+BKesBb`F=v|x&*zFXnSJTQcCYV0M&BrJl&<7AZX+j8we83J+toh)ev{?q zX@F0RSh+WGK7qFCE9gkG$__3ypAr@!sNv~#+A;E-7cxpY0*zmi+Yl@d;P;8kX6=<| z_V3LyzJv_oIu6=o@SP;r`22H~_otn!(m$I`{VBSytPL5p9DycsklQ#F{L0&4sg*~b z|9jB{AHG~%8=PZ`{8)<6@F7+Vn!qN|k!t7+y}B0N%U*D@F2u|vdm%VBF)N>FjzAM5uIuXm E0q@IqVgLXD diff --git a/todoffin/.gradle/8.5/checksums/sha1-checksums.bin b/todoffin/.gradle/8.5/checksums/sha1-checksums.bin deleted file mode 100644 index 78e488e10e41deada73f8eb9489133dafee168f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35183 zcmeI4c{o+w`~Q#3vm`T-WUh!bNTFm_=6T4Nd58>|Ge)RXG*G5A7*e4^L@H4!Dj6yv zR1``izkT*vdw;&q**?GP`aRe8uis@|*Rzl3{oJp0uXXQx-D~ZA&gZj>MB?RGfQ|H@ zw(>uJF8#F>fu#s6MPMlcOA%O#z)}R3BCr&Jr3frVU?~Di5m<`AQUsPFuoQu%2rNZl zDFRCoSc<^^FA*?BCc*?J!z;Ut{0I9X5@`bqiA1Fo-7Xn-zJRU-{OA1}vF{IJcAUT1 zSjn^%=$2`?-o-X{rltEi(0!LkVRh!VbkXt?J(* zf$n`8*Nu+dUw%?U9q9UrxE>Lz=YQ|ZQJ~wo;d-6u^?}{JoUtMhq}7k>)@x@|h9 zXN06#{HdFq2YPrMu2*h*XDGaZ7U=qun4Y=3X}GWI)_I`EE93ffpwSvj?)5D{@Kh9#}lv()3eG74}Mv_1Lj-QcT7L67WMs(aVyL>-w{kd zqv2VmI(#-9^dA+E>DdoW_D}bzDgoUf3)6Eh*O@#n7`+Shn2VTxu6DNj$z#XuKtF=p zpBp=}{#LFK9FN&E%szLQmY5jdkO;8Xuf_Dd+Ec|$^@oRnZV`p)7bQGC#F%dl0D5pB zu3wJYDa+>r^UZV>reAWgs#`N03G*{a2GjH3=I1!oHevTs4A=Xnlb@ceXafC*;`vbE ztY;UP()R@DhE$k+(ZjYt$JL?9KsWZq^kUwwW3L6Pe*@j)Ev|2l;H94Ii2%BzIIgSu zc5h8B;Q_jq8m^a~3Q|5kqy==}2wd-va6J9Rr~&Aq_`19HhGWYyKWasw+r7l>uk*=o zG#5mx?$MmwzzYfi37c&BT2OeB+ioEci)HU<*fd*!a3YYKsR&3bwx>cgLl@gK;I{X>)EDvN^)O^0X=d6(<^cXzb5nc z!Fm|)jp;YMf~#eVH9@;h=pLpl zzXWs}T}-cHjXxIic(e=X{&?Qr5sWmA-lD1t^vFY){T+{J$$~R^>wtbR4cGgYvwodX z?gP3OZhu$DD~7hb{xi@+nlbyk*~#_`tU9os9LC3gw=&Rugkb{Kf18!K{Se2IAnRF} zZ!WgDJ}jzXOjDo=`j5}Y^}k1SI(jdi2D(3fUuv&q-EKdXr~>q`514&jR~UUI`e zpljd7^hYb&l3YmBiLf6$K9Azh?|!D{4)qu%+&=5R^w;@IV!-~;S4@9$?T@am{s>&B zdxtUoX<2=i@P*!3U~lDu>22<|CVcyn=z;Ez*S)rAqkF*`J3av2?-y>LIxr!@+-m^z zpclB_U>NFqhhZbo_wL5@XQNYnsWQfJoc8#-c#)I0QFlB=6xc^}V)h+cZ42_kM>BwK zPK)c0&%Be^RBH_M0Dnw>ng6rJG4x;z&=0g?`l~Z1HC6Nncm+8&mnVctEGC=_DABnH;7MCw!-_(oW(9 zdO$I*XZg=F2<$Zkdg3&ue^wn*Y-#=u_b)?yfBc*v!b$RY2KO&#{CxZQ-HO`dIk#s) zKl=}0{frC-FyCZWuL8Qo3QQkWYE0T*DXb6lh>e&&c1R{EaYh%OAKXH*e#So;i1Vkh z7Xf=CJa5Oxs5`#V=fm@ottDpvgG1OR>ilg^VDGa9*Z;~njJ5T_{MW(bGrg{nLx5gC z2-pYrWA@X&bkZx0lE#5A!V4vO1d^-GJ^xjqB%iRQm=w zLxH{z&%@uXYs^pew!{21J&M`S$+%q9(0mQ|DU%3XH@hj~Ce*$I^b@Or>#poiuH5g| z1bUPqrvF*B<@=4aJbpMHUtF)?rFtvsvLEP&=P~{7l+S=`K{VXg5_aSIg1^Si%eUaV z+n0>#3nvmU?3jGS0Q#}U>*PXN45MP{2wX1-c>XN(o{%Pum&3Z}*n;&#N{pBn6g)cs z`mw_EpK7_D2<_BYSD**DGIg%@)v@f_`*gV>-i!FsB8*|Wx$(_E@woWS+h{6!nBY~eoQg~yrAB{TZ$;7B#_ ztB0>U_F(DZH7o-qK=;G@ce9v*8vU{zGVkHST6wkLot|N&TX4%27VRR z0^MW;*U!K0oD})y2y}}vT(8=&>E&M;U!cd~^@Q_NV}~$lIUCRu@$s+tl^wWzETR$U zMtFRP0UjKP%sm@&WBEj=R zSP<3^A4|=&#KLlzp9iuqT|n0+cPq_0xDUot;d*D@XPrnfVc^$f52g!gJGeI;7J=tm zhd5ks8jp9X?t%6`?=W51dTaO7zpF(+KlY56E^vh77POW z(DRtSn!$A7(2NxF-iO?bPUHGz>XMkg_)?$;xnR1OqAt^V*H1}6_o~PAHBx~!@w&fZ zo(It4y4wNuzK!YUfPFX*uFvlbQ(t>!6zI-){)->`Ni!>{51-S`@%1PXq~OW;+~6~? zkI2RPk>o$fpQ73_1N2BWOqa^L!)1C;T>$95c$}q6Puda4UY3*lq)# z`)u%Yugsl&t>et4r+~dNHP-(+W$U%JEhqDU9)qvz^?sQ}x6|7;0X@71vtNIz|GB_t z$7Z1WR${vR=?8VnLn-h%!b=F#71BR&NeFtvda^$k*N5mQ{7*SbfPM~(W4f}=_UM2Y z?p#3kTlF8k!LXO(n*q=*ZejXnvwKw)7p}qQtZ2MGY|h;tW+g;&0`}hcI@+4GWJg_%v!|YYK!-dQyPzlu>+KPIoN6*R&t{(( zg!eb;G-j_MHsAVA|I<3qeTn#;rsipve$vS9QJfTKU9U;YtnrC;nZSOhAun2t>AtXh}Y*mMg`GQlJaoRoT|s- zDd<1i7T5QC1dKclhxry;hUq%)y^lp#uYu>}C_Y^Gh%W!Ti~+{ex&+hp%dPaBrMl-q z|0ekQ(tkC6{FZwm9OprE%-&%CowCBgXFGwt({)TY6nS8K{AAtrIFU4&Ta^1NLD%aQj4-Kf(04t{xQgv*RT^|KQu>g59m3O7YdHmw>$eaJU82oua}bzt1xG0Q+c6UT})aPFXnR3Gc59 zz8^Uc7VV=;?$rbKPKPkSdGy<@BWD@c0^L3c(_PQJ9voz)&tYXEJT`U232^)L?o5UCysT;iQ{D4_c4w`kU;B?Qik` zJs}*^4>kz}*>Ogw1Kltm(}P}B#-8?Yhx+&CS#ntoPpKIdZVmkIoA_>8@4alYIQUsPFuoQu%2rNZl zDFRCoSc1ePMO6oI7(EJa``0!tBCioj9?{(p@C`AbOhi*#}FKU{}P3u}4Sd`ak2 zER5V(u#IZ9c-2C0V6!FRZf?L^A%#vC8Nm>|{W3%`qsL?3+Dh)YL)S%yXf z{sMzJkVpk-*4>iS4gcJrn;(3SeImE)lT{w1;O`6=$-i@;7-1^k?ly)hRjr+0A8wCL zJhyxyw8I-xPY`RQ2?Zk;3W|sIveAJ$9kpGTI~>$sSm+wpv2KPG`92{{a5ZwFppq1; z9t|8*Vz-srPQ9ylx0%YxrdN<^Lm`ro1d)lP5wZB6@vPL6ypgZ z)hIbg#U3ZHRcYI*{&?=!8tP`U$o=`B6n8k0O2{k;=d@hKYf0-kc&sZ`Aj-rN%X#>X zue3dpsutvSU3XkqkKMgz?du4sfb6w#GSClK49wO! zd-2gnK#^UL8~+yMLP6dFh8Re5kaTEJpK?L6yt>U1Q7$n&zNLLm~r3X!^%+2VAy zBJ+D|h4EkSTvn41^FR?u(Lm}Yk*aZHP&KnHU9d{aIloqAnUAa2GGv9IzW4x@dx}U| zxfyb<`Yjqe6p-&jka<!VKN_n$4#Gs6kN-k+lUn7B`TiAOS3~EX4F};zKz2QdV1Vss6z<_9QGu_XPX0M}+~{2;)$QB$h{{T$~D?-cr>J$(K}XeplZ8;r`ky z>~Mr>fZ}*Sqy$zAxe7{YJbk+R+QPir{RoaziYFnp0Z^K{MC!>xT}|DIy&+7UI}asK zZF0_VP)EKKLt{wJTBPxXAs32yt+Q)Bo?dWqleqr&ztzpYd&;%ne1()c3XyD7iIm!9 zpF&QFz#Qwt`5H{0Q$OiA7f3_uAfULH6RCffz14T0wT*L2i~TVRz3?HkqPv)X0p4W^ zWTfOKM$UbTdHuWfWn`I86QQXsPTP06TEeq>gHpi^5@~dO9YbA_m{$;e?Wk%WQyX{| z^?uST;2AiBzRd6W z6E6)IT=Gi@2>2(p@&J+2GUs&r9sb8g{e8^33vPX!CT?0t-u+KsJmj+*g)ga*0c%w@ znau15YgL(#wnnDCU_A?|N-#o}BSh-w+fPZXs~Ss+UUTz?9pTrzZmB;2sX9RMI1?#V zFP2AwEBi@-_8NyLRWF!c-nT*@Qt-S%M@ys>e-^%1$`cF}%&5E2#-r8EebKZ7Qm=t8 zW^(0*UPlsr2r}7-+q?}8|-){=mQQQrwH-KWHCsM8<$6124n}mGWHnVKrlAk%h zA$Hk6DMlkAbux)f-eg4gzRJ)W!`)gZ8*jL%n?R}?`a-gqa)eKvok>--5zWT}le%Vzg*2w%esDczc+pu^Nsn;e6qJmPw(^i|N)Y(2(Mt*!MkJNWGqsG7ph2ImY z{VwCBFXvCB%#`o*rrGbWbME0$Bqzv}1As^j#YAdTiBGI$fp_6_pVMADx{9!~NnQ{C zNijMRDdyTs3Rh?o1J9?isQN#?=OsNE%>*e{;EZ_@UonL`96hzu$+r!hY97(_xl~f+ zBN0bOh*9!HUpI>{>CcxRf0(7N^r?E2k`nj5qc)J@2fi2fG@srBE?oSp;@8y{L#6%?;mZlUHIkBQ7b@-Tyv2oy-3d2 zhFNUgsd7*#e50HGfvpkBi3KZ>Jp_%R7NA()BcBwjQh_D>VMd{I(50mL9Hg8zV+?6yxf2tALL(phg07_ty$Pf2E z9Di_e#=ET{E`y$Z_~>|>r6Huu0Yy_l^flPl)Uhq$^*Ie+?!O%j#*zjJ=`LhSuFdZ{ zp!iCN)W4fo>u~m~P`0qDmbvveuVrOET^k(H3`gh+eEFOs`jXVF5c+%BPRq)v-Rqv< zhI+%^n01hH15^?cQ_B1G?|l8c$ZHJkdF5QgA11cWOM4&fnHPJ!`ai%C27(c?9w(0Q zMI38qfX*>0&-?2cZ%15yD>trHhEf&cLx5Vh$QeMbNR&EM{`b_CME3)_Bh#&x{c-(d zO0ErA(rDZK6ZxQ6DgSPS|1R?XE)Tn&@F}MibFQF$BMTXp?dj)o6 z#7vNqt<_gS>K34w*@#pJOPk@fv7GL6NruKZ@5oIRwSSI=)FVLA>k%ox_o)xLSwux_ zD?g=pnz}{}-VFjL^V<&al)^@S0;U+@%CBmVmPZuc47p+^TId%svFRoQb7ZRj5dam3wS_5xDmT>)v!k-Vg&mN6RB zTF(qwB%FJa++*`JxI|cCsfD zN!J&-P|VA@8l&y^Z>9sBc^bEXi(gArgxKkzy^(5T?!1dHU^w(A`g~ zwpdyCK0(gA|C5p>`=s#o@2Gz_LT4+6)=#ESUg#g46VJDeveYwvg1i$#N2m|n>aL|%${&mJHyE+cD9Qw0 zcSm;d|4Ffl5h>9Z$_-@-6D1$*e$jqBzLI__SPi?&JD{)YMC#QGnaLa#C5PulXFX}J zUloo0-n|R@+6gJ-q)vIi-rq4gc~x`k(AfHuIlVT!&E0aYT!R!mN72m^DNX6obqjOb zKiB05y{Kt-YxJ`xU8JCDb)e zUe~{7J^kDIhHJv&Ri2_}fOP^~p@3ScNThV%uhlC(n$W`WxZGN$m^;L1DtdMkyd}u0j)STW- zBE(pgnmtP}52jB*uOs_3Y!rKmE%t+%l_EHT(M6-FP48Wh5t1U*udz8YB71 zp2An`vZ6$fKu&2VZL=JOAO`>KM{=J*sv3nzbU%sI$H`6aWE>0j=u$g9yzFuGiH3R~ zVvYK002EEx{}!d*e&OVIs+xw(gSX7SeM>8x*tSCs`uYWYIUvupl=F&uI??lJyVPk8 zAAiTTAJ2D+3weqIYCnKTDvMMAyS!gdV#F=PJC2@+EKl=~PR#2@PU7Tw?I-U*NK?{4 zE)+3*RB!)TEJyFjlP~gL7`mIoXdCxgK#CKENLtxMs@E?nY_#c<%AV?!p+XDh2`&Fm z$Zml8;s+E1@;pZAtNhjl`_wDbWg~ihA+JVKF5WMsg}eEFavek(`zho?F~U$`=ja_$ z24&CKcM5Jfzu)6|z)Hjj^+n!~kw&-3ygF6wWR3Ut>~PZ>zs8Y~Gh-&Bg!v+$)sV)s z8o5yT8mn3xU-aR=PtaR~@VF1RjuzTi29fo_#e&pOiZu;ZMg@V#Ci!~2i#US*y&~?mal-=<5gB!@{1fACx z6e78jbDx4*({uMr8cz@XVP2;|xoz*<^0*%O0?G+_qCy(gwne`2DXH!mwvAXY-FxqocCL#B|5ZA12i><){ZH+D>w^YS-- zZ6C9*&${{NWGPF<3#2BYBScmw(x_xQkt@Xr{~eVlXF^N_ql=Y{OgEH;T0gB@Yk&>~AsX16y-rZSpVYtJDiTN&z5?qx|?*PTHfk?d*+P?0?eZ4Vi9r;Wy z_BPMMFWTdwuaAIYy-K78UJnmG9KNA$R%_}kw(IiSv(AG^g+?P!t};lYmqacUF&r#f zEmHExzm+qtcW6*1YL)0CbtHBuMcyOH08$bDcc_$(WkQ?mejDd$pG%Ivd)$BZNwZ$y z%b5ptMdDt>mt^I!a&PUYRC%vMIe3!F|7t!zy$w>LfTCaIY*W_2ef&g-pxL3Z7_GKq zpOQo8kNt)e+#MKNiSy#s6=>{ZT&M86l^hLcu$HOhbBUCx*ZV7R zKNU*1u;|ux2zRRKWv?uR6gel5#^ixqC?a3*mWs2#Q)}0j6aB8=UNwBp>{=%SDI_1s zZN(zKK1ObOwR-)l&g3D*4=vLZxzyZThXLg(1Srl$R?2oQPD722LC>gGTsh#dLDb3G z^b)ckljns~XAqiVgadtonr&ifrW1{Y)1zv>*V{?d$l67zRnS)+k^0;{tu8J3=EeX` z9x3DU-$3SWHZMrQ`^C8kWs+}qu}?PQV++-Y^3K(IosYF5Nwl5{JG@iN3yc@9h)0>aC(Zx}jf>FP0|VR{k2KU>0r`)fM)h;Mv9%P*i7vh`xkFnsP6)2VAR~ zVpAMxeDKt$U7CJ(Z3|0j40;y>B4f)?OP=2XtOJVD~*pZ>FL?| zNVV;n@e^mgO!mAs+YBiU)E7P7c_MYTg8g%t+`C zuTW%Q_7mOJYJ+vlwT2FQ4a_jX%I1d9f(dIhh;x)R)h*R2o;MsT<(G+1k|$lSR%!~wk9z|+3P{h9L?PA z`PU}f#*=?QioC9nAL&GrcRz{|zHXZO{MYKU{)_HlMuX3J#UHtjZ--PcN>OPc^^uaY zTJ|Ykbid)1=>`4lvPVa$+&zApLMjTSXlCXlqt<_M@S=IrmMAX;He)1C)#k?}hW^d2vh{paI>9MafJmi@3uH7aB^(-l!!69RCTVEKP`evapaPI2 zJ&083ov9((v2Ax9Dw={`>4bf09~E+d6g$oaAD1qJ z)K$b9`Sbl2A{D(dwOpj7)eBjscJ{ENep^-6LlMrc#f`?XF$$P=YVoQp*MzfnkyBIi2N7Nevt{>)?2zoN<; z__N-SyLYN<-dh=t@FNN_IWOWXCt0^WShII$f{R>yb@YnWxncoGl_9?g6e3$AO@s@% zP{i=RQOF396+>=|Q5QZ=>h9yx4N6Qss~&uer!T#>1@(ncKTwE98`(Q4eVuMmZS%Vq zd2IZKm)+=*ANz`SaKJhbDU!@3B2{p~ zZ`U~hSBkqTN-1SJ6DeJ-1Bb2(v~~78ykeWODN2xms~$OLpd-{lDdsU-BDKAJzTcH` zNLxr*Z+txKP>Ow9 zCz1LPH*|COl2r4%h7Vp2J0A?aDfy1nKk^7Yhfs>dnM|b0XFTi9YqZ|~#X7GNoYfjR zvMEFjQa>S;N~A9HFYxb>4+L^_h?4-Ii^6*wEIdH}VTo*m<5@xhd|l;Vx>1SmT#7 zFBwml?|*!-tJCrcFQmv74QZTl$c2KMIP2X!_R2+}wJ+Fm^vC;r`Mhc#NNq!*MXA8; zKfgbIwu-Y$(6OjrDSsK?)fyp4X+vs}lR1_D6aJ&q;%|!izIN=$ybv#OFPa)s9Viv> zlDzv-j4)G#rd#(<^__uSXSWy8@gb`V9w8?N@?G`@t{B&}EJ8`%y!Jx-XH|x4a;HqS zD4XrZ3St}*_9fjY2+CQ^`(hYE9e&4iA+i+Y3MGB9B;1E3J4Q6I(}2OCj?S9C`EHm zfjB~B)AC5#P5;idFxyzJq*=ByZxX(9@CM!*-0^(L)DHq96W890^=3|09ch(^Pubq(C`A=pPo&zEnwsj}q^Y&f zovD8)aE0xlpg1k`)sIs2m99i8`5nLerW1k-<29<=+^4Se@39?2b~`kN$dd=sSPB+7 zrMR!Q$Uk#-_Y}JF%RV5xH%e_peNlU?CHk^f(zp5}(asr5 zCG5}Nk>p46U4`sGD5Zx|Y@s`dl(rRvS@g`@Ia}kdm-qLu{xXPiK%#(Bfhfi0{E0}N zsIS~0_ezY<&7CIaAkEBM>P@3pAcZ^)AdN1yjYz3A%Bg7uvK1#udl#e_=xo+Yx$_-T zcTtMZ5y@uC80JK8aj9G7#u#cf`aL4dzacTR2kt38Pf?2cvO1AkVQrAOZDmFJEmd0< zGwImzcaguFp|3uaTK0vHNQIj*kCbWG@OQ7Ld;9gus)ZH3kKj(^gXAO9=(o_)fMKeF46FLB*yaSrI8$mNPQBR)V z)<6~ACDwmRRhT3n3n}CiX;iXI$dzJVDZ(J-z%Hwo%homtOg`ZqubFkXnsW^!$tXatKkZezQp|OXg(5 zMA+SXtDd(;azl!|10l`E8M#nI!!gmbY0zk4Td;XX?gO!PCx;5!k3q^5g*Xj6h?Lxe z`R-lnZq-LL8Lh|G@LFH`*;xZA@*YVdQ9mG$l){&$)3|nXQJ(qx^pnM-V}qi3O13GG z0ySz=F0!^MDG}#u_ET#o@2@p0?U4^Ps$Sz1f~-dJv%@YUvMc07M2B1`eEl~n@3vnT z3N1=0Q~eC#A7uIabaWvs#r@*)Hwx>KwTt>fY8AQhAu*-&_1`GO*C!NOlsa~&Wt6L% z=0^j6<)BO4)sLEr&WB($egJBH6w#NSWZ8#o{*yw7mi=9^|AEWIWoIE_K>2e5N(#9X zlp{P;_*>WPRTjhhZyWdQduJUo__UJ+Qpo8NX;cj4U7aF^9?eN|qgz*+8!3<5@|V*I+uTddKtGJ`6Coos5G{%n=>-8-P;{-x?46v+b_G35x8$2NL> z=T&(hB(8E;Iq3?Yx7ja#NWq-nL*6qf=H-2si-V?)DlBJxE!jBcxf_O{CU@n#T91%>H7M?Nkgo zKr?EQd>?uLghoCFrC2i;*&QlIwg%dimKg3;%^La}T7Rxb_>3*2GEqt}^cB$;<3YMh zrym(j6$rbh@c29o`n4`j3R2`sO(Jnsl4D5`dH>qBz6(8f-)l`4v~K46VqPaAhQy9M z!T|D#0m=DQD~MDxDRcD-j$OBwXJ@$feodULoHByh8z6{MbU*hIDV5BxmdkQNw!}&X z&*v(JlYG1UkS8b97kRfs{@k<3d1Lt&8b$BZ@A{3BR5Hc|+nB2ts`McR&NifCWXGkP zmy+ZLsqSkd^W(G%VsC=ZuQ8L`R|%<|WLG3=hCj%aA{s#xjFF-tAG|*8A1-~6u03jg zGZAM)#%PcLsqB8}cJ zWPV6n7H&nPlvXoN$jxlf?DMJb+H*rLz$?GR1X7;?6}(6uW@l_TlU(eh=VI0nv$H+k zvfAVh7Wp}pq8?l%=Us&z&o?Zm>loc!(tcO+clsT*;G?n81D&serd z%%1uAI?F`0bu2Rrc@`(nE07EIMM^_@p^Q8gkDzB!-o#GF*wy#>>$3Rm{eInm6gg{= zKRJ+pr=+riqP{ZbDxApQ$C7k;>w0FX_`jGhIg}z8-nDN9=CVcMFlX+_V2Z|xiywz`0x0g!nGq7;oJC diff --git a/todoffin/.gradle/8.5/gc.properties b/todoffin/.gradle/8.5/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/todoffin/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/todoffin/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 12deec080cd6ed0a115aedb03b0c8b0c3d8a7153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZRs`E4tqJNL_d1_)pT05yUHZU6uP diff --git a/todoffin/.gradle/buildOutputCleanup/cache.properties b/todoffin/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 443c8e8f..00000000 --- a/todoffin/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Mon Dec 25 20:22:34 KST 2023 -gradle.version=8.5 diff --git a/todoffin/.gradle/vcs-1/gc.properties b/todoffin/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/todoffin/.idea/.gitignore b/todoffin/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/todoffin/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/todoffin/.idea/compiler.xml b/todoffin/.idea/compiler.xml deleted file mode 100644 index 93d2f743..00000000 --- a/todoffin/.idea/compiler.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/todoffin/.idea/gradle.xml b/todoffin/.idea/gradle.xml deleted file mode 100644 index f07d8ac4..00000000 --- a/todoffin/.idea/gradle.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/todoffin/.idea/jarRepositories.xml b/todoffin/.idea/jarRepositories.xml deleted file mode 100644 index fdc392fe..00000000 --- a/todoffin/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/todoffin/.idea/misc.xml b/todoffin/.idea/misc.xml deleted file mode 100644 index c0492069..00000000 --- a/todoffin/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/todoffin/.idea/vcs.xml b/todoffin/.idea/vcs.xml deleted file mode 100644 index 6c0b8635..00000000 --- a/todoffin/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/todoffin/HELP.md b/todoffin/HELP.md deleted file mode 100644 index c824cc02..00000000 --- a/todoffin/HELP.md +++ /dev/null @@ -1,34 +0,0 @@ -# Getting Started - -### Reference Documentation -For further reference, please consider the following sections: - -* [Official Gradle documentation](https://docs.gradle.org) -* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.2.1/gradle-plugin/reference/html/) -* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.2.1/gradle-plugin/reference/html/#build-image) -* [Spring Boot DevTools](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#using.devtools) -* [Spring Security](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#web.security) -* [OAuth2 Client](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#web.security.oauth2.client) -* [Spring Web](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#web) -* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#data.sql.jpa-and-spring-data) -* [WebSocket](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#messaging.websockets) -* [Validation](https://docs.spring.io/spring-boot/docs/3.2.1/reference/htmlsingle/index.html#io.validation) - -### Guides -The following guides illustrate how to use some features concretely: - -* [Securing a Web Application](https://spring.io/guides/gs/securing-web/) -* [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/) -* [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/) -* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) -* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) -* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) -* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) -* [Using WebSocket to build an interactive web application](https://spring.io/guides/gs/messaging-stomp-websocket/) -* [Validation](https://spring.io/guides/gs/validating-form-input/) - -### Additional Links -These additional references should also help you: - -* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) - diff --git a/todoffin/out/production/classes/com/genius/todoffin/TodoffinApplication.class b/todoffin/out/production/classes/com/genius/todoffin/TodoffinApplication.class deleted file mode 100644 index be48752917bf08e34a4ceb17d2425c95fcc48030..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 721 zcma)4O;g)25PeDl4k0Zee7C*y)FcOVGQ|S5N!&{P-Wh4}9sMfo6bK7b{q0=-r4r!Be3Vei7Zs*fFer zS6aDahUQ>63(#ix7MqMGQmZ28&Rm=MTxou_{5e@HQWXoQjP9U=^#I*2KHww6$k>GE z3#)W8w<44G#@_PC7{{k2SgE9Dw$KdGAbcBWl8QVZ|EeHU=gG#yu#P!4+Q~nTPmG?c zq_84NWu*ndK0Rh=P0Y1q*a(%D=S3Dtd*#nE^g|PibSA9w@e*vg-zsMqhVK-?H^_vd z%j^#7W}i`B*n}vzYSIjysVQtM&y=5NzeeRluMoq*WucwQ0 diff --git a/todoffin/out/production/resources/application.yml b/todoffin/out/production/resources/application.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/todoffin/src/main/resources/application.yml b/todoffin/src/main/resources/application.yml deleted file mode 100644 index e69de29b..00000000 From ed9ef2799075a0ee06beeac6130e1cfd607899cd Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Mon, 25 Dec 2023 21:15:11 +0900 Subject: [PATCH 004/234] Create pull_request_template.md --- .github/pull_request_template.md | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..c88fcb5e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,34 @@ +### PR 타입(하나 이상의 PR 타입을 선택해주세요) +- [ ] 기능 추가 + +- [ ] 기능 삭제 + +- [ ] 버그 수정 + +- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 + +
+ +### 반영 브랜치 +ex) feat/login -> dev + +
+ +### 변경 사항 +ex) 로그인 시, 구글 소셜 로그인 기능을 추가했습니다. + +
+ +### 테스트 결과 +ex) 베이스 브랜치에 포함되기 위한 코드는 모두 정상적으로 동작해야 합니다. 결과물에 대한 스크린샷, GIF, 혹은 라이브 데모가 가능하도록 샘플API를 첨부할 수도 있습니다. + +
+ +### 연관된 이슈 +ex) #이슈번호, #이슈번호 + +
+ +### 리뷰 요구사항(선택) +> 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요 +> ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요? From 4ca2ccf2d8bdb7d767987cbd53969fae228f3a40 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Mon, 25 Dec 2023 21:21:38 +0900 Subject: [PATCH 005/234] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 34 ++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..9c1f61e3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,34 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +### Issue 타입(하나 이상의 Issue 타입을 선택해주세요) +- [X] 기능 추가 +- [ ] 기능 삭제 +- [ ] 버그 수정 +- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 + +### 상세 내용 +#### 어떤 기능인가요? +\> 추가하려는 기능에 대해 간결하게 설명해주세요 + +#### 작업 상세 내용 +- [ ] TO DO + +### 예상 소요 시간 +-[] `0.5h` +-[] `1h` +-[] `1.5h` +-[] `2h` +-[] `2.5h` +-[] `3h` + +### 라벨 +- 예상 소요 시간: `E: 1h` +- 그룹: `client`, `server` +- 긴급도: `High`, `Middle`, `Low` From 8ae3e86e7916db6951020743d1ed28c54430cbfa Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Mon, 25 Dec 2023 21:23:18 +0900 Subject: [PATCH 006/234] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 50 ++++++++++------------- .github/ISSUE_TEMPLATE/feature_request.md | 1 + 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea78..9224e4f0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,32 +7,24 @@ assignees: '' --- -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. +### Issue 타입(하나 이상의 Issue 타입을 선택해주세요) +- [ ] 기능 추가 +- [ ] 기능 삭제 +- [x] 버그 리포트 +- [ ] 버그 수정 +- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 + +### 상세 내용 +#### 어떤 버그인가요? +\> 어떤 버그인지 간결하게 설명해주세요 + +#### 어떤 상황에서 발생한 버그인가요? +\> (가능하면) Given-When-Then 형식으로 서술해주세요 + +#### 예상 결과 +\> 예상했던 정상적인 결과가 어떤 것이었는지 설명해주세요 + +### 라벨 +- 예상 소요 시간: `E: 1h` +- 그룹: `client`, `server` +- 긴급도: `High`, `Middle`, `Low` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9c1f61e3..9e92dd98 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -10,6 +10,7 @@ assignees: '' ### Issue 타입(하나 이상의 Issue 타입을 선택해주세요) - [X] 기능 추가 - [ ] 기능 삭제 +- [ ] 버그 리포트 - [ ] 버그 수정 - [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 From 9f887ff18e8e5642c268a60143700eeeb3ca6988 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 29 Dec 2023 01:23:23 +0900 Subject: [PATCH 007/234] =?UTF-8?q?feat:=20SecurityConfig=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=86=8C=EC=85=9C=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OAuth2.0을 이용한 소셜로그인에 필요한 설정 추가 (filterChain을 통해 구현) - 실제 객체가 생성되어있지 않아 작성할 수 없는 부분은 주석 처리 (추후 주석 해제 필요) - application-oauth.yml 파일에 소셜로그인에 대한 설정 후, 테스트 시 정상작동 확인 --- .../security/config/SecurityConfig.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/java/com/genius/security/config/SecurityConfig.java diff --git a/src/main/java/com/genius/security/config/SecurityConfig.java b/src/main/java/com/genius/security/config/SecurityConfig.java new file mode 100644 index 00000000..2cb17124 --- /dev/null +++ b/src/main/java/com/genius/security/config/SecurityConfig.java @@ -0,0 +1,52 @@ +package com.genius.security.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsUtils; + +@Configuration +@Order(1) +@RequiredArgsConstructor +@EnableWebSecurity +public class SecurityConfig { + private static final String permitURI[] = {"/api/auth/**", "/swagger-ui.html", "/swagger-ui/**" + , "/v3/api-docs/**", "/v3/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**"}; + private static final String permittedRoles[] = {"USER", "ADMIN"}; +// private final CustomOAuth2UserService customOAuthService; +// private final OAuthSuccessHandler successHandler; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http.csrf().disable() + .httpBasic().disable() + .formLogin().disable() + .anonymous().and() + .authorizeRequests() + .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + .requestMatchers(permitURI).permitAll() + .anyRequest().hasAnyRole(permittedRoles) + .and() + + // JWT 사용으로 인한 세션 미사용 + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + + // OAuth 로그인 설정 + .oauth2Login(); +// .successHandler(successHandler) +// .authorizationEndpoint() + +// .and() +// .userInfoEndpoint().userService(customOAuthService); + + return http.build(); + } +} From 8ec6058776a0ed0beaa87062b3d9471c1246b7f3 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 29 Dec 2023 01:24:55 +0900 Subject: [PATCH 008/234] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9=EB=90=98=EB=8A=94=20Provider=20E?= =?UTF-8?q?num=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 정적 팩토리 메서드를 통해 provider에 맞는 AuthProvider를 반환 --- .../genius/security/constants/AuthProvider.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/com/genius/security/constants/AuthProvider.java diff --git a/src/main/java/com/genius/security/constants/AuthProvider.java b/src/main/java/com/genius/security/constants/AuthProvider.java new file mode 100644 index 00000000..f763ffce --- /dev/null +++ b/src/main/java/com/genius/security/constants/AuthProvider.java @@ -0,0 +1,17 @@ +package com.genius.security.constants; + +import java.util.Arrays; + +public enum AuthProvider { + KAKAO, + NAVER, + GOOGLE, + FACEBOOK; + + public static AuthProvider from(String provider) { + return Arrays.stream(AuthProvider.values()) + .filter(item -> item.name().equals(provider)) + .findFirst() + .orElseThrow(); + } +} From 844cedddb9ea95f5cffc7073e62777cb2e9da227 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 29 Dec 2023 02:10:30 +0900 Subject: [PATCH 009/234] =?UTF-8?q?feat:=20=EA=B3=B5=ED=86=B5=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EA=B0=80=EC=A7=80=EA=B3=A0=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20Entity=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 생성시간, 수정시간, 삭제시간 필드를 가지고 있는 공통 Entity 추가 - createdDate, modifiedDate 자동 기록을 위해 @EnableJpaAuditing 어노테이션 추가 --- .../genius/todoffin/TodoffinApplication.java | 8 +++--- .../common/domain/BaseTimeEntity.java | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java diff --git a/src/main/java/com/genius/todoffin/TodoffinApplication.java b/src/main/java/com/genius/todoffin/TodoffinApplication.java index 239cbcda..4d503904 100644 --- a/src/main/java/com/genius/todoffin/TodoffinApplication.java +++ b/src/main/java/com/genius/todoffin/TodoffinApplication.java @@ -2,12 +2,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class TodoffinApplication { - public static void main(String[] args) { - SpringApplication.run(TodoffinApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(TodoffinApplication.class, args); + } } diff --git a/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java b/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java new file mode 100644 index 00000000..4b0813b8 --- /dev/null +++ b/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java @@ -0,0 +1,27 @@ +package com.genius.todoffin.common.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.ToString; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Getter +@ToString +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class BaseTimeEntity { + + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime modifiedDate; + + private LocalDateTime deletedDate; +} \ No newline at end of file From 6bf4468a966d026e7b56c54a2fca0a142ea534eb Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 29 Dec 2023 02:20:07 +0900 Subject: [PATCH 010/234] =?UTF-8?q?feat:=20User=20Entity=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JPA를 활용하여 User 엔티티 클래스 작성 - BaseTimeEntity 상속 - 사용자의 회원가입 여부/ 권한에 대한 정보를 담고 있는 Enum class 작성 --- .../com/genius/todoffin/user/domain/Role.java | 16 ++++++ .../com/genius/todoffin/user/domain/User.java | 53 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/user/domain/Role.java create mode 100644 src/main/java/com/genius/todoffin/user/domain/User.java diff --git a/src/main/java/com/genius/todoffin/user/domain/Role.java b/src/main/java/com/genius/todoffin/user/domain/Role.java new file mode 100644 index 00000000..d7c92be4 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/domain/Role.java @@ -0,0 +1,16 @@ +package com.genius.todoffin.user.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Role { + NOT_REGISTERED("ROLE_NOT_REGISTERED", "회원가입 이전 사용자"), + USER("ROLE_USER", "일반 사용자"), + ADMIN("ROLE_ADMIN", "관리자"); + + private final String key; + private final String title; +} + diff --git a/src/main/java/com/genius/todoffin/user/domain/User.java b/src/main/java/com/genius/todoffin/user/domain/User.java new file mode 100644 index 00000000..529018c8 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/domain/User.java @@ -0,0 +1,53 @@ +package com.genius.todoffin.user.domain; + +import com.genius.todoffin.common.domain.BaseTimeEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; + +@Entity +@Getter +public class User extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @NotNull + private String provider; + @NotNull + private String email; + + @Enumerated(EnumType.STRING) + private Role role; + + @NotNull + @Column(unique = true, length = 16) + private String nickname; + + @Column(length = 160) + private String information; + private String interest; + + + @Builder + public User(String provider, String email, Role role, String nickname, String information, + String interest) { + this.provider = provider; + this.email = email; + this.role = role; + this.nickname = nickname; + this.information = information; + this.interest = interest; + } + + public User() { + } +} From 29e6938f3642fe13d9884d1b175bfc08023a870b Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 29 Dec 2023 02:21:47 +0900 Subject: [PATCH 011/234] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=B4=EB=A9=94=EC=9D=BC,=20provider=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EC=B0=BE=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JpaRepository 인터페이스를 상속하여 구현 - 이메일 / 이메일+provider 를 통해 DB에서 사용자를 찾는 기능 구현 - 두 메서드에 대해 단위 테스트 코드 작성 --- .../user/repository/UserRepository.java | 16 +++++ .../user/repository/UserRepositoryTest.java | 68 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/user/repository/UserRepository.java create mode 100644 src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java new file mode 100644 index 00000000..d94106f8 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -0,0 +1,16 @@ +package com.genius.todoffin.user.repository; + +import com.genius.todoffin.user.domain.User; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + + @Query("select u from User u where u.email = :email and u.provider = :provider") + Optional findByOAuthInfo(@Param("email") String email, @Param("provider") String provider); +} diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java new file mode 100644 index 00000000..599c9d5b --- /dev/null +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -0,0 +1,68 @@ +package com.genius.todoffin.user.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.todoffin.user.domain.Role; +import com.genius.todoffin.user.domain.User; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class UserRepositoryTest { + @Autowired + private UserRepository userRepository; + + @Test + @DisplayName("email을 통해 저장한 User 객체 찾은 후, 검증") + public void email을_통해_저장한_User_객체를_찾을수있다() { + //given + String email = "test@naver.com"; + String provider = "NAVER"; + String nickname = "test_nickname"; + User user = getUnsavedUser(email, provider, nickname); + + //when + User savedUser = userRepository.save(user); + User foundUser = userRepository.findByEmail(email).get(); + + //then + assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); + assertThat(savedUser.getEmail()).isEqualTo(foundUser.getEmail()); + assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); + assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); + } + + @Test + @DisplayName("User 객체 저장 테스트") + public void email_provider를_통해_저장한_User_객체를_찾을수있다() { + //given + String email = "test@naver.com"; + String provider = "NAVER"; + String nickname = "test_nickname"; + User user = getUnsavedUser(email, provider, nickname); + + //when + User savedUser = userRepository.save(user); + User foundUser = userRepository.findByOAuthInfo(email, provider).get(); + + //then + assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); + assertThat(savedUser.getEmail()).isEqualTo(foundUser.getEmail()); + assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); + assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); + } + + + private User getUnsavedUser(String email, String provider, String nickname) { + return User.builder() + .email(email) + .provider(provider) + .role(Role.USER) + .nickname(nickname) + .build(); + } +} \ No newline at end of file From 3d17f1e8b3c619c49612e6f14489ecb7dc200a35 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 29 Dec 2023 23:59:03 +0900 Subject: [PATCH 012/234] =?UTF-8?q?hotfix:=20security=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genius/{ => todoffin}/security/config/SecurityConfig.java | 2 +- .../genius/{ => todoffin}/security/constants/AuthProvider.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/java/com/genius/{ => todoffin}/security/config/SecurityConfig.java (97%) rename src/main/java/com/genius/{ => todoffin}/security/constants/AuthProvider.java (87%) diff --git a/src/main/java/com/genius/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java similarity index 97% rename from src/main/java/com/genius/security/config/SecurityConfig.java rename to src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 2cb17124..12f6f694 100644 --- a/src/main/java/com/genius/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.genius.security.config; +package com.genius.todoffin.security.config; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/com/genius/security/constants/AuthProvider.java b/src/main/java/com/genius/todoffin/security/constants/AuthProvider.java similarity index 87% rename from src/main/java/com/genius/security/constants/AuthProvider.java rename to src/main/java/com/genius/todoffin/security/constants/AuthProvider.java index f763ffce..5562a079 100644 --- a/src/main/java/com/genius/security/constants/AuthProvider.java +++ b/src/main/java/com/genius/todoffin/security/constants/AuthProvider.java @@ -1,4 +1,4 @@ -package com.genius.security.constants; +package com.genius.todoffin.security.constants; import java.util.Arrays; From c3fe05f2ffe9888fce0d9d9d93ecf1b8b9c56c55 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sat, 30 Dec 2023 02:14:48 +0900 Subject: [PATCH 013/234] =?UTF-8?q?feat:=20Exception=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B0=8F=20ExceptionHandler=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BusinessException: 비지니스 로직에서 발생한 예외를 처리할 때 해당 예외 클래스를 throw - BusinessExceptionHandler : 사용자가 처리한 예외(BusinessException)을 어떻게 처리할 것인지 구현 - GlobalExceptionHandler: 개발자가 처리하지 않은 예외가 던져졌을 때 처리되는 부분 -> 추가 처리 필요함을 날리는 곳 - ErrorCode: 예외 메시지를 저장하는 Enum 클래스 --- .../util/exception/BusinessException.java | 29 +++++++++++++++++++ .../exception/BusinessExceptionHandler.java | 20 +++++++++++++ .../todoffin/util/exception/ErrorCode.java | 17 +++++++++++ .../exception/GlobalExceptionHandler.java | 23 +++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/util/exception/BusinessException.java create mode 100644 src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java create mode 100644 src/main/java/com/genius/todoffin/util/exception/ErrorCode.java create mode 100644 src/main/java/com/genius/todoffin/util/exception/GlobalExceptionHandler.java diff --git a/src/main/java/com/genius/todoffin/util/exception/BusinessException.java b/src/main/java/com/genius/todoffin/util/exception/BusinessException.java new file mode 100644 index 00000000..9a288c09 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/exception/BusinessException.java @@ -0,0 +1,29 @@ +package com.genius.todoffin.util.exception; + +import org.springframework.http.HttpStatus; + + +public class BusinessException extends RuntimeException { + private HttpStatus status; + + public BusinessException() { + super(); + } + + public BusinessException(HttpStatus status, ErrorCode errorCode) { + super(errorCode.getMessage()); + this.status = status; + } + + public BusinessException(String message) { + super(message); + } + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } + + public BusinessException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java b/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java new file mode 100644 index 00000000..4547acf4 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java @@ -0,0 +1,20 @@ +package com.genius.todoffin.util.exception; + +import com.genius.todoffin.util.response.dto.CommonResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +@RequiredArgsConstructor +public class BusinessExceptionHandler { + @ExceptionHandler(BusinessException.class) + protected CommonResponse globalBusinessExceptionHandler(BusinessException e) { + log.info("[ERROR]" + e.getMessage(), e); + + return new CommonResponse(HttpStatus.BAD_REQUEST, e.getMessage()); + } +} diff --git a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java new file mode 100644 index 00000000..d61ffe3a --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java @@ -0,0 +1,17 @@ +package com.genius.todoffin.util.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ErrorCode { + + UNDEFINED_ERROR("미정의에러"), + NO_AUTHORITY("접근권한이 없습니다."), + ACCESS_DENIED("접근이 거부되었습니다."), + DATA_ERROR_NOT_FOUND("해당 데이터를 찾을 수 없습니다."), + MEMBER_NOT_FOUND("로그인 정보를 찾을 수 없습니다."); + + private final String message; +} diff --git a/src/main/java/com/genius/todoffin/util/exception/GlobalExceptionHandler.java b/src/main/java/com/genius/todoffin/util/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..d4104d3b --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/exception/GlobalExceptionHandler.java @@ -0,0 +1,23 @@ +package com.genius.todoffin.util.exception; + +import com.genius.todoffin.util.response.dto.CommonResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +@RequiredArgsConstructor +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + protected ResponseEntity globalExceptionHandler(Exception e) { + log.error("예외처리 되지 않은 Exception 발생 - 처리 필요"); + log.error("[UNHANDLED ERROR] " + e.getMessage(), e); + return ResponseEntity.badRequest().body( + new CommonResponse(HttpStatus.BAD_REQUEST, e.getMessage())); + } +} From 7534ca1d2e3f2b1309058832c58e9fd8bb14b542 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sat, 30 Dec 2023 02:16:25 +0900 Subject: [PATCH 014/234] =?UTF-8?q?feat:=20Response=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=83=80=EC=9E=85=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 반환하는 값의 형식에 따른 Response DTO 타입 지정 --- .../util/response/dto/CommonResponse.java | 17 +++++++++++++++++ .../util/response/dto/CommonResult.java | 17 +++++++++++++++++ .../util/response/dto/ListResponse.java | 18 ++++++++++++++++++ .../util/response/dto/PagingResponse.java | 15 +++++++++++++++ .../util/response/dto/SingleResponse.java | 14 ++++++++++++++ .../util/response/dto/SlicingResponse.java | 11 +++++++++++ 6 files changed, 92 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java create mode 100644 src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java create mode 100644 src/main/java/com/genius/todoffin/util/response/dto/ListResponse.java create mode 100644 src/main/java/com/genius/todoffin/util/response/dto/PagingResponse.java create mode 100644 src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java create mode 100644 src/main/java/com/genius/todoffin/util/response/dto/SlicingResponse.java diff --git a/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java b/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java new file mode 100644 index 00000000..86d1bfce --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java @@ -0,0 +1,17 @@ +package com.genius.todoffin.util.response.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.http.HttpStatus; + + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CommonResponse { + private HttpStatus code; + private String message; +} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java b/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java new file mode 100644 index 00000000..78d1c305 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java @@ -0,0 +1,17 @@ +package com.genius.todoffin.util.response.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + + +@Getter +@AllArgsConstructor +@RequiredArgsConstructor +public enum CommonResult { + SUCCESS(1, "성공"), + FAIL(0, "실패"); + + private final int code; + private final String message; +} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/util/response/dto/ListResponse.java b/src/main/java/com/genius/todoffin/util/response/dto/ListResponse.java new file mode 100644 index 00000000..e3e3032a --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/response/dto/ListResponse.java @@ -0,0 +1,18 @@ +package com.genius.todoffin.util.response.dto; + +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + + +@Getter +@RequiredArgsConstructor +public class ListResponse extends CommonResponse { + private List dataList; + private int count; + + public ListResponse(List dataList) { + this.dataList = dataList; + this.count = dataList.size(); + } +} diff --git a/src/main/java/com/genius/todoffin/util/response/dto/PagingResponse.java b/src/main/java/com/genius/todoffin/util/response/dto/PagingResponse.java new file mode 100644 index 00000000..37d574d3 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/response/dto/PagingResponse.java @@ -0,0 +1,15 @@ +package com.genius.todoffin.util.response.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; + +@Getter +@RequiredArgsConstructor +public class PagingResponse extends CommonResponse { + private Page data; + + public PagingResponse(Page data) { + this.data = data; + } +} diff --git a/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java b/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java new file mode 100644 index 00000000..74c08ff6 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java @@ -0,0 +1,14 @@ +package com.genius.todoffin.util.response.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class SingleResponse extends CommonResponse { + private T data; + + public SingleResponse(T data) { + this.data = data; + } +} diff --git a/src/main/java/com/genius/todoffin/util/response/dto/SlicingResponse.java b/src/main/java/com/genius/todoffin/util/response/dto/SlicingResponse.java new file mode 100644 index 00000000..ad556c73 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/response/dto/SlicingResponse.java @@ -0,0 +1,11 @@ +package com.genius.todoffin.util.response.dto; + +import org.springframework.data.domain.Slice; + +public class SlicingResponse extends CommonResponse { + private Slice data; + + public SlicingResponse(Slice data) { + this.data = data; + } +} From 27f54dbc88c2b8b23c9326e9a381b8ec05c087b3 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sat, 30 Dec 2023 02:16:50 +0900 Subject: [PATCH 015/234] =?UTF-8?q?feat:=20Time=20formatting=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/formatter/CommonPattern.java | 8 +++++++ .../util/formatter/LocalDateFormatter.java | 21 ++++++++++++++++++ .../formatter/LocalDateTimeFormatter.java | 22 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/util/formatter/CommonPattern.java create mode 100644 src/main/java/com/genius/todoffin/util/formatter/LocalDateFormatter.java create mode 100644 src/main/java/com/genius/todoffin/util/formatter/LocalDateTimeFormatter.java diff --git a/src/main/java/com/genius/todoffin/util/formatter/CommonPattern.java b/src/main/java/com/genius/todoffin/util/formatter/CommonPattern.java new file mode 100644 index 00000000..6e1086ba --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/formatter/CommonPattern.java @@ -0,0 +1,8 @@ +package com.genius.todoffin.util.formatter; + +public final class CommonPattern { + public static final String MANAGER_ID_PATTERN = "^(?=.*[a-z])(?=.*\\d)[a-z\\d]{8,16}$"; + public static final String MANAGER_PASSWORD_PATTERN = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,16}$"; + public static final String IP_ADDRESS_PATTERN = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"; + public static final String Y_OR_N = "^[YN]$"; +} diff --git a/src/main/java/com/genius/todoffin/util/formatter/LocalDateFormatter.java b/src/main/java/com/genius/todoffin/util/formatter/LocalDateFormatter.java new file mode 100644 index 00000000..2aa6b887 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/formatter/LocalDateFormatter.java @@ -0,0 +1,21 @@ +package com.genius.todoffin.util.formatter; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import org.springframework.format.Formatter; +import org.springframework.stereotype.Component; + +@Component +public class LocalDateFormatter implements Formatter { + + @Override + public LocalDate parse(String text, Locale locale) { + return LocalDate.parse(text, DateTimeFormatter.ISO_LOCAL_DATE); + } + + @Override + public String print(LocalDate object, Locale locale) { + return DateTimeFormatter.ISO_LOCAL_DATE.format(object); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/util/formatter/LocalDateTimeFormatter.java b/src/main/java/com/genius/todoffin/util/formatter/LocalDateTimeFormatter.java new file mode 100644 index 00000000..8f0f3314 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/formatter/LocalDateTimeFormatter.java @@ -0,0 +1,22 @@ +package com.genius.todoffin.util.formatter; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.Locale; +import org.springframework.format.Formatter; +import org.springframework.stereotype.Component; + +@Component +public class LocalDateTimeFormatter implements Formatter { + + @Override + public LocalDateTime parse(String text, Locale locale) { + return LocalDateTime.parse(text, DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT)); + } + + @Override + public String print(LocalDateTime object, Locale locale) { + return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT).format(object); + } +} From 264c5e2b37ecb781fdcd2714a6560f114ac7bfe2 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sat, 30 Dec 2023 02:19:56 +0900 Subject: [PATCH 016/234] =?UTF-8?q?feat:=20CORS=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todoffin/util/filter/CorsFilter.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/util/filter/CorsFilter.java diff --git a/src/main/java/com/genius/todoffin/util/filter/CorsFilter.java b/src/main/java/com/genius/todoffin/util/filter/CorsFilter.java new file mode 100644 index 00000000..2743579c --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/filter/CorsFilter.java @@ -0,0 +1,49 @@ +package com.genius.todoffin.util.filter; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class CorsFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + + response.setHeader("Access-Control-Allow-Origin", "*"); // 모든 요청에 대해 허용 + response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setHeader("Access-Control-Max-Age", "3600"); + response.setHeader("Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept, Authorization, Set-Cookie"); + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + response.setStatus(HttpServletResponse.SC_OK); + } else { + chain.doFilter(req, res); + } + } + + @Override + public void destroy() { + + } +} From 6c828bbe32b96749e2da988304f5a6b6e97465d4 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Sat, 30 Dec 2023 16:51:44 +0900 Subject: [PATCH 017/234] =?UTF-8?q?Feat:=20Config=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20entity=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/{domain => }/BaseTimeEntity.java | 8 +-- .../todoffin/config/SecurityConfig.java | 51 ++++++++++++++++++ .../genius/todoffin/user/entity/Constant.java | 7 +++ .../todoffin/user/entity/GoogleOAuth.java | 54 +++++++++++++++++++ .../com/genius/todoffin/user/entity/Role.java | 15 ++++++ .../todoffin/user/entity/SocialOAuth.java | 6 +++ .../com/genius/todoffin/user/entity/User.java | 53 ++++++++++++++++++ 7 files changed, 191 insertions(+), 3 deletions(-) rename src/main/java/com/genius/todoffin/common/{domain => }/BaseTimeEntity.java (93%) create mode 100644 src/main/java/com/genius/todoffin/config/SecurityConfig.java create mode 100644 src/main/java/com/genius/todoffin/user/entity/Constant.java create mode 100644 src/main/java/com/genius/todoffin/user/entity/GoogleOAuth.java create mode 100644 src/main/java/com/genius/todoffin/user/entity/Role.java create mode 100644 src/main/java/com/genius/todoffin/user/entity/SocialOAuth.java create mode 100644 src/main/java/com/genius/todoffin/user/entity/User.java diff --git a/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java b/src/main/java/com/genius/todoffin/common/BaseTimeEntity.java similarity index 93% rename from src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java rename to src/main/java/com/genius/todoffin/common/BaseTimeEntity.java index 4b0813b8..90ceee66 100644 --- a/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java +++ b/src/main/java/com/genius/todoffin/common/BaseTimeEntity.java @@ -1,15 +1,17 @@ -package com.genius.todoffin.common.domain; +package com.genius.todoffin.common; + import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; -import java.time.LocalDateTime; import lombok.Getter; import lombok.ToString; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.time.LocalDateTime; + @Getter @ToString @MappedSuperclass @@ -22,6 +24,6 @@ public class BaseTimeEntity { @LastModifiedDate private LocalDateTime modifiedDate; - + private LocalDateTime deletedDate; } \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/config/SecurityConfig.java new file mode 100644 index 00000000..81f7158d --- /dev/null +++ b/src/main/java/com/genius/todoffin/config/SecurityConfig.java @@ -0,0 +1,51 @@ +package com.genius.todoffin.config; + + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsUtils; + +@Configuration +@Order(1) +@RequiredArgsConstructor +@EnableWebSecurity +public class SecurityConfig { + private static final String permitURI[] = {"/api/auth/**", "/swagger-ui.html", "/swagger-ui/**" + , "/v3/api-docs/**", "/v3/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**"}; + private static final String permittedRoles[] = {"USER", "ADMIN"}; +// private final CustomOAuth2UserService customOAuthService; +// private final OAuthSuccessHandler successHandler; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http.csrf().disable() + .httpBasic().disable() + .formLogin().disable() + .anonymous().and() + .authorizeHttpRequests(request -> request + .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + .requestMatchers(permitURI).permitAll() + .anyRequest().hasAnyRole(permittedRoles)) + + // JWT 사용으로 인한 세션 미사용 + .sessionManagement( configurer -> configurer + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + + // OAuth 로그인 설정 + .oauth2Login(); +// .successHandler(successHandler) +// .authorizationEndpoint() + +// .and() +// .userInfoEndpoint().userService(customOAuthService); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/user/entity/Constant.java b/src/main/java/com/genius/todoffin/user/entity/Constant.java new file mode 100644 index 00000000..a74ad347 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/entity/Constant.java @@ -0,0 +1,7 @@ +package com.genius.todoffin.user.entity; + +public class Constant { + public enum SocialLoginType { + GOOGLE, KAKAO, NAVER, FACEBOOK + } +} diff --git a/src/main/java/com/genius/todoffin/user/entity/GoogleOAuth.java b/src/main/java/com/genius/todoffin/user/entity/GoogleOAuth.java new file mode 100644 index 00000000..d48ee34e --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/entity/GoogleOAuth.java @@ -0,0 +1,54 @@ +package com.genius.todoffin.user.entity; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class GoogleOAuth implements SocialOAuth{ + + @Value("${spring.security.oath2.client.registration.google.client_id}") + private String GOOGLE_SNS_CLIENT_ID; + + @Value("${spring.security.oath2.client.registration.google.redirect_uri}") + private String GOOGLE_SNS_CALLBACK_URL; + + @Value("${spring.security.oath2.client.registration.google.client-secret}") + private String GOOGLE_SNS_CLIENT_SECRET; + + @Value("${spring.security.oath2.client.registration.google.scope}") + private String GOOGLE_DATA_ACCESS_SCOPE; + + private final ObjectMapper objectMapper; + + @Override + public String getOAuthRedirectURL() { + + Map params = new HashMap<>(); + params.put("scope", GOOGLE_DATA_ACCESS_SCOPE); + params.put("response_type", "code"); + params.put("client_id", GOOGLE_SNS_CLIENT_ID); + params.put("redirect_uri", GOOGLE_SNS_CALLBACK_URL); + + //parameter를 형식에 맞춰 구성해주는 함수 + String parameterString = params.entrySet().stream() + .map(x -> x.getKey() + "=" + x.getValue()) + .collect(Collectors.joining("&")); + String redirectURL = "https://accounts.google.com/o/oauth2/v2/auth" + "?" + parameterString; + System.out.println("redirectURL = " + redirectURL); + + return redirectURL; + + /* + * https://accounts.google.com/o/oauth2/v2/auth?scope=profile&response_type=code + * &client_id="할당받은 id"&redirect_uri="access token 처리") + * 로 Redirect URL을 생성하는 로직을 구성 + * */ + } +} diff --git a/src/main/java/com/genius/todoffin/user/entity/Role.java b/src/main/java/com/genius/todoffin/user/entity/Role.java new file mode 100644 index 00000000..2b3c33fe --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/entity/Role.java @@ -0,0 +1,15 @@ +package com.genius.todoffin.user.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Role { + NOT_REGISTERED("ROLE_NOT_REGISTERED", "회원가입 이전 사용자"), + USER("ROLE_USER", "일반 사용자"), + ADMIN("ROLE_ADMIN", "관리자"); + + private final String key; + private final String title; +} diff --git a/src/main/java/com/genius/todoffin/user/entity/SocialOAuth.java b/src/main/java/com/genius/todoffin/user/entity/SocialOAuth.java new file mode 100644 index 00000000..f74ac7fe --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/entity/SocialOAuth.java @@ -0,0 +1,6 @@ +package com.genius.todoffin.user.entity; + + +public interface SocialOAuth { + String getOAuthRedirectURL(); +} diff --git a/src/main/java/com/genius/todoffin/user/entity/User.java b/src/main/java/com/genius/todoffin/user/entity/User.java new file mode 100644 index 00000000..28805a96 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/entity/User.java @@ -0,0 +1,53 @@ +package com.genius.todoffin.user.entity; + + +import com.genius.todoffin.common.BaseTimeEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Entity +@Getter +@RequiredArgsConstructor +public class User extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @NotNull + private String provider; + @NotNull + private String email; + + @Enumerated(EnumType.STRING) + private Role role; + + @NotNull + @Column(unique = true, length = 16) + private String nickname; + + @Column(length = 160) + private String information; + private String interest; + + + // 임시 + private String password; + + + @Builder + public User(String provider, String email, Role role, String nickname, String information, + String interest, String password) { + this.provider = provider; + this.email = email; + this.role = role; + this.nickname = nickname; + this.information = information; + this.interest = interest; + this.password = password; + } + +} From 5005b951fa755cc17b3c4fe1165a861027d2cd6b Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Sat, 30 Dec 2023 16:53:24 +0900 Subject: [PATCH 018/234] =?UTF-8?q?Feat:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=BB=A4=EC=8A=A4=ED=84=B0?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EC=A7=95=20=EB=B0=8F=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/CustomOAuth2UserService.java | 68 ++++++++++++++++ .../security/CustomUserDetailsService.java | 35 +++++++++ .../todoffin/security/UserPrincipal.java | 78 +++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/security/CustomOAuth2UserService.java create mode 100644 src/main/java/com/genius/todoffin/security/CustomUserDetailsService.java create mode 100644 src/main/java/com/genius/todoffin/security/UserPrincipal.java diff --git a/src/main/java/com/genius/todoffin/security/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/CustomOAuth2UserService.java new file mode 100644 index 00000000..797cac1a --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/CustomOAuth2UserService.java @@ -0,0 +1,68 @@ +package com.genius.todoffin.security; + +import com.genius.todoffin.user.dto.OAuthAttributes; +import com.genius.todoffin.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +@Slf4j +public class CustomOAuth2UserService implements OAuth2UserService { + + private final UserRepository userRepository; + + @Override + @Transactional + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2UserService delegate = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = delegate.loadUser(userRequest); + + // RegistrationId() : 서비스를 구분하는 코드 + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + + // OAuth2 로그인 진행 시 키가 되는 필드값을 이야기 한다. Primary Key와 같은 의미. + // 구글의 경우 기본적으로 코드를 지원하지만, 네이버 카카 등은 기본 지원하지 않는다. 구굴의 기본 코드는 "sub" + String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); + + // OAuthAttributes + // OAuth2UserService를 통해 가져온 OAuth2User의 attribute를 담은 클래스. + // 이후 네이버 등 다른 소셜 로그인도 이 클래스를 사용 + OAuthAttributes authAttributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); + + + String email = ""; + + switch (registrationId) { + case "kakao": + // + + case "facebook" : + // + + case "naver" : + // + + case "google " : + // + default: + throw new IllegalArgumentException("알 수 없는 소셜 로그인 형식입니다."); + } + + // 유저가 존재하거나, 새로운 유저일 경우 비즈니스 로직 / + + + +// return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(user.getRole().getKey())) +// ,oAuth2User.getAttributes() +// ,userNameAttributeName); + } +} diff --git a/src/main/java/com/genius/todoffin/security/CustomUserDetailsService.java b/src/main/java/com/genius/todoffin/security/CustomUserDetailsService.java new file mode 100644 index 00000000..85b219f4 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/CustomUserDetailsService.java @@ -0,0 +1,35 @@ +package com.genius.todoffin.security; + + +import com.genius.todoffin.user.entity.User; +import com.genius.todoffin.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + @Transactional + public UserDetails loadUserByUsername(String email) + throws UsernameNotFoundException { + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("User not found with email : " + email)); + return UserPrincipal.create(user); + } + + @Transactional + public UserDetails loadUserById(Long id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new UsernameNotFoundException("User not found with id : " + id)); + return UserPrincipal.create(user); + } + +} diff --git a/src/main/java/com/genius/todoffin/security/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/UserPrincipal.java new file mode 100644 index 00000000..533322d9 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/UserPrincipal.java @@ -0,0 +1,78 @@ +package com.genius.todoffin.security; + + +import com.genius.todoffin.user.entity.Role; +import com.genius.todoffin.user.entity.User; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Getter +@AllArgsConstructor +public class UserPrincipal implements OAuth2User, UserDetails { + + private long id; + private String email; + private String password; + private Collection authorities; + @Setter + private Map attributes; + + public static UserPrincipal create(User user) { + List authorities = + Collections.singletonList(new SimpleGrantedAuthority("" + Role.USER)); + return new UserPrincipal( + user.getId(), + user.getEmail(), + user.getPassword(), + authorities, + null + ); + } + + public static UserPrincipal create(User user, Map attributes) { + UserPrincipal userPrincipal = UserPrincipal.create(user); + userPrincipal.setAttributes(attributes); + return userPrincipal; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public String getName() { + return String.valueOf(id); + } + + @Override + public String getUsername() { + return email; + } + +} \ No newline at end of file From db87b1a7287d7bf9535b80d59062214603e62285 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Sat, 30 Dec 2023 16:54:32 +0900 Subject: [PATCH 019/234] =?UTF-8?q?Feat:=20=EC=86=8C=EC=85=9C=EB=B3=84=20U?= =?UTF-8?q?serInfo=20DTO=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todoffin/user/dto/AuthProvider.java | 17 ++++ .../user/dto/FacebookOAuth2UserInfo.java | 26 ++++++ .../user/dto/GoogleOAuth2UserInfo.java | 26 ++++++ .../user/dto/KakaoOAuth2UserInfo.java | 28 +++++++ .../user/dto/NaverOAuth2UserInfo.java | 28 +++++++ .../todoffin/user/dto/OAuth2UserInfo.java | 18 +++++ .../user/dto/OAuth2UserInfoFactory.java | 21 +++++ .../todoffin/user/dto/OAuthAttributes.java | 79 +++++++++++++++++++ 8 files changed, 243 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/user/dto/AuthProvider.java create mode 100644 src/main/java/com/genius/todoffin/user/dto/FacebookOAuth2UserInfo.java create mode 100644 src/main/java/com/genius/todoffin/user/dto/GoogleOAuth2UserInfo.java create mode 100644 src/main/java/com/genius/todoffin/user/dto/KakaoOAuth2UserInfo.java create mode 100644 src/main/java/com/genius/todoffin/user/dto/NaverOAuth2UserInfo.java create mode 100644 src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfo.java create mode 100644 src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfoFactory.java create mode 100644 src/main/java/com/genius/todoffin/user/dto/OAuthAttributes.java diff --git a/src/main/java/com/genius/todoffin/user/dto/AuthProvider.java b/src/main/java/com/genius/todoffin/user/dto/AuthProvider.java new file mode 100644 index 00000000..9da6db7c --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/AuthProvider.java @@ -0,0 +1,17 @@ +package com.genius.todoffin.user.dto; + +import java.util.Arrays; + +public enum AuthProvider { + KAKAO, + NAVER, + GOOGLE, + FACEBOOK; + + public static AuthProvider from(String provider) { + return Arrays.stream(AuthProvider.values()) + .filter(item -> item.name().equals(provider)) + .findFirst() + .orElseThrow(); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/user/dto/FacebookOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/user/dto/FacebookOAuth2UserInfo.java new file mode 100644 index 00000000..585c79ba --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/FacebookOAuth2UserInfo.java @@ -0,0 +1,26 @@ +package com.genius.todoffin.user.dto; + +import java.util.Map; + +public class FacebookOAuth2UserInfo extends OAuth2UserInfo { + + public FacebookOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return (String) attributes.get("sub"); + } + + @Override + public String getEmail() { + return (String) attributes.get("email"); + } + + @Override + public String getName() { + return (String) attributes.get("name"); + } +} + diff --git a/src/main/java/com/genius/todoffin/user/dto/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/user/dto/GoogleOAuth2UserInfo.java new file mode 100644 index 00000000..1c5cc8d1 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/GoogleOAuth2UserInfo.java @@ -0,0 +1,26 @@ +package com.genius.todoffin.user.dto; + + +import java.util.Map; + +public class GoogleOAuth2UserInfo extends OAuth2UserInfo { + + public GoogleOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return (String) attributes.get("sub"); + } + + @Override + public String getEmail() { + return (String) attributes.get("email"); + } + + @Override + public String getName() { + return (String) attributes.get("name"); + } +} diff --git a/src/main/java/com/genius/todoffin/user/dto/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/user/dto/KakaoOAuth2UserInfo.java new file mode 100644 index 00000000..3433494e --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/KakaoOAuth2UserInfo.java @@ -0,0 +1,28 @@ +package com.genius.todoffin.user.dto; + +import java.util.Map; + +public class KakaoOAuth2UserInfo extends OAuth2UserInfo { + + private Integer id; + + public KakaoOAuth2UserInfo(Map attributes) { + super((Map) attributes.get("kakao_account")); + this.id = (Integer) attributes.get("id"); + } + + @Override + public String getId() { + return this.id.toString(); + } + + @Override + public String getName() { + return (String) ((Map) attributes.get("profile")).get("nickname"); + } + + @Override + public String getEmail() { + return (String) attributes.get("email"); + } +} diff --git a/src/main/java/com/genius/todoffin/user/dto/NaverOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/user/dto/NaverOAuth2UserInfo.java new file mode 100644 index 00000000..3e795940 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/NaverOAuth2UserInfo.java @@ -0,0 +1,28 @@ +package com.genius.todoffin.user.dto; + + +import java.util.Map; + +public class NaverOAuth2UserInfo extends OAuth2UserInfo { + + public NaverOAuth2UserInfo(Map attributes) { + super((Map) attributes.get("response")); + } + + @Override + public String getId() { + return (String) attributes.get("id"); + } + + @Override + public String getEmail() { + return (String) attributes.get("email"); + } + + @Override + public String getName() { + return (String) attributes.get("name"); + } + + +} diff --git a/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfo.java b/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfo.java new file mode 100644 index 00000000..244c4a5a --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfo.java @@ -0,0 +1,18 @@ +package com.genius.todoffin.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; + +@Getter +@AllArgsConstructor +public abstract class OAuth2UserInfo { + + protected Map attributes; + + public abstract String getId(); + public abstract String getEmail(); + public abstract String getName(); + +} diff --git a/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfoFactory.java b/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfoFactory.java new file mode 100644 index 00000000..9822a18d --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfoFactory.java @@ -0,0 +1,21 @@ +package com.genius.todoffin.user.dto; + +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; + +import java.util.Map; + +public class OAuth2UserInfoFactory { + public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map attributes) { + if (registrationId.equalsIgnoreCase(AuthProvider.GOOGLE.toString())) { + return new GoogleOAuth2UserInfo(attributes); + } else if (registrationId.equalsIgnoreCase(AuthProvider.NAVER.toString())) { + return new NaverOAuth2UserInfo(attributes); + } else if (registrationId.equalsIgnoreCase(AuthProvider.KAKAO.toString())) { + return new KakaoOAuth2UserInfo(attributes); + } else if (registrationId.equalsIgnoreCase(AuthProvider.FACEBOOK.toString())) { + return new FacebookOAuth2UserInfo(attributes); + }else { + throw new OAuth2AuthenticationException("Unsupported Login Type : " + registrationId); + } + } +} diff --git a/src/main/java/com/genius/todoffin/user/dto/OAuthAttributes.java b/src/main/java/com/genius/todoffin/user/dto/OAuthAttributes.java new file mode 100644 index 00000000..364f1383 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/OAuthAttributes.java @@ -0,0 +1,79 @@ +package com.genius.todoffin.user.dto; + +import lombok.Builder; +import lombok.Getter; + +import java.util.Map; + +@Getter +public class OAuthAttributes { + private Map attributes; + private String nameAttributeKey; + private String name; + private String email; + + @Builder + public OAuthAttributes(Map attributes, String nameAttributeKey, String name, String email) { + this.attributes = attributes; + this.nameAttributeKey = nameAttributeKey; + this.name = name; + this.email = email; + } + + // OAuth2User에서 반환하는 사용자 정보는 Map이기 때문에 값 하나하나를 변환해야 한다. + public static OAuthAttributes of(String registrationId, String userNAmeAttributeName, Map attributes) { + if ("naver".equals(registrationId)) return ofNaver("id", attributes); + if ("facebook".equals(registrationId)) return ofFacebook("id", attributes); + if ("kakao".equals(registrationId)) return ofkakao("id", attributes); + return ofGoogle(userNAmeAttributeName, attributes); + } + + // google login OAuth + private static OAuthAttributes ofGoogle(String userNameAttributeName, Map attributes) { + return OAuthAttributes.builder() + .name((String) attributes.get("name")) + .email((String) attributes.get("email")) + .attributes(attributes) + .nameAttributeKey(userNameAttributeName) + .build(); + } + + // naver login OAuth + private static OAuthAttributes ofNaver(String userNameAttributeName, Map attributes) { + Map response = (Map) attributes.get("response"); + return OAuthAttributes.builder() + .name((String) response.get("name")) + .email((String) response.get("email")) + .attributes(response) + .nameAttributeKey(userNameAttributeName) + .build(); + } + + // 수정 필요 시작 + + + // Facebook login OAuth + private static OAuthAttributes ofFacebook(String userNameAttributeName, Map attributes) { + Map response = (Map) attributes.get("response"); + return OAuthAttributes.builder() + .name((String) response.get("name")) + .email((String) response.get("email")) + .attributes(response) + .nameAttributeKey(userNameAttributeName) + .build(); + } + + + // Kakao login OAuth + private static OAuthAttributes ofkakao(String userNameAttributeName, Map attributes) { + Map response = (Map) attributes.get("response"); + return OAuthAttributes.builder() + .name((String) response.get("name")) + .email((String) response.get("email")) + .attributes(response) + .nameAttributeKey(userNameAttributeName) + .build(); + } + + // 수정 필요 끝 +} From 368c804680dac110069481cd4f7b56a17efcd857 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Sat, 30 Dec 2023 16:58:09 +0900 Subject: [PATCH 020/234] =?UTF-8?q?Feat:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 846cd36c..b8ffdbb9 100644 --- a/build.gradle +++ b/build.gradle @@ -28,10 +28,12 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.springframework.boot:spring-boot-starter-mustache' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' + annotationProcessor 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' - annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' } From 7e6f06a8007ff56881ecea6ec58eeccdd8f17342 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Sat, 30 Dec 2023 16:58:37 +0900 Subject: [PATCH 021/234] =?UTF-8?q?Feat:=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genius/todoffin/TodoffinApplication.java | 11 +++- .../handler/OAuth2SuccessHandler.java | 21 ++++++++ .../com/genius/todoffin/user/domain/Role.java | 16 ------ .../com/genius/todoffin/user/domain/User.java | 53 ------------------- .../user/repository/UserRepository.java | 7 ++- .../user/repository/UserRepositoryTest.java | 1 - 6 files changed, 33 insertions(+), 76 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/handler/OAuth2SuccessHandler.java delete mode 100644 src/main/java/com/genius/todoffin/user/domain/Role.java delete mode 100644 src/main/java/com/genius/todoffin/user/domain/User.java diff --git a/src/main/java/com/genius/todoffin/TodoffinApplication.java b/src/main/java/com/genius/todoffin/TodoffinApplication.java index 4d503904..6920fca2 100644 --- a/src/main/java/com/genius/todoffin/TodoffinApplication.java +++ b/src/main/java/com/genius/todoffin/TodoffinApplication.java @@ -1,15 +1,22 @@ package com.genius.todoffin; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @SpringBootApplication -@EnableJpaAuditing public class TodoffinApplication { + @Bean + public BCryptPasswordEncoder encoder() { + return new BCryptPasswordEncoder(); + } + public static void main(String[] args) { SpringApplication.run(TodoffinApplication.class, args); } - } diff --git a/src/main/java/com/genius/todoffin/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/handler/OAuth2SuccessHandler.java new file mode 100644 index 00000000..9ad7230f --- /dev/null +++ b/src/main/java/com/genius/todoffin/handler/OAuth2SuccessHandler.java @@ -0,0 +1,21 @@ +package com.genius.todoffin.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; + +import java.io.IOException; +import java.util.Map; + +public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + Map attributes = oAuth2User.getAttributes(); + + String email = (String) attributes.get("email"); + } +} diff --git a/src/main/java/com/genius/todoffin/user/domain/Role.java b/src/main/java/com/genius/todoffin/user/domain/Role.java deleted file mode 100644 index d7c92be4..00000000 --- a/src/main/java/com/genius/todoffin/user/domain/Role.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.genius.todoffin.user.domain; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum Role { - NOT_REGISTERED("ROLE_NOT_REGISTERED", "회원가입 이전 사용자"), - USER("ROLE_USER", "일반 사용자"), - ADMIN("ROLE_ADMIN", "관리자"); - - private final String key; - private final String title; -} - diff --git a/src/main/java/com/genius/todoffin/user/domain/User.java b/src/main/java/com/genius/todoffin/user/domain/User.java deleted file mode 100644 index 529018c8..00000000 --- a/src/main/java/com/genius/todoffin/user/domain/User.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.genius.todoffin.user.domain; - -import com.genius.todoffin.common.domain.BaseTimeEntity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import lombok.Getter; - -@Entity -@Getter -public class User extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id") - private Long id; - - @NotNull - private String provider; - @NotNull - private String email; - - @Enumerated(EnumType.STRING) - private Role role; - - @NotNull - @Column(unique = true, length = 16) - private String nickname; - - @Column(length = 160) - private String information; - private String interest; - - - @Builder - public User(String provider, String email, Role role, String nickname, String information, - String interest) { - this.provider = provider; - this.email = email; - this.role = role; - this.nickname = nickname; - this.information = information; - this.interest = interest; - } - - public User() { - } -} diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java index d94106f8..61ef62ae 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -1,13 +1,12 @@ package com.genius.todoffin.user.repository; -import com.genius.todoffin.user.domain.User; -import java.util.Optional; +import com.genius.todoffin.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; -@Repository +import java.util.Optional; + public interface UserRepository extends JpaRepository { Optional findByEmail(String email); diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java index 599c9d5b..f3a149f2 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.todoffin.user.domain.Role; import com.genius.todoffin.user.domain.User; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 431013464b7112a4ed472982f39c4f3c0d532def Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sat, 30 Dec 2023 23:06:06 +0900 Subject: [PATCH 022/234] =?UTF-8?q?refactor:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=93=A4=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todoffin/config/SecurityConfig.java | 51 ------------ .../security/CustomUserDetailsService.java | 35 -------- .../todoffin/security/UserPrincipal.java | 78 ------------------ .../security/config/SecurityConfig.java | 33 ++++---- .../security/constants/AuthProvider.java | 17 ---- .../security/constants/ProviderType.java | 19 +++++ .../domain}/SocialOAuth.java | 2 +- .../handler/OAuth2SuccessHandler.java | 16 ++-- .../dto => security/info}/OAuth2UserInfo.java | 7 +- .../security/info/OAuth2UserInfoFactory.java | 28 +++++++ .../info/impl}/FacebookOAuth2UserInfo.java | 3 +- .../info/impl}/GoogleOAuth2UserInfo.java | 3 +- .../info/impl}/KakaoOAuth2UserInfo.java | 3 +- .../info/impl}/NaverOAuth2UserInfo.java | 3 +- .../CustomOAuth2UserService.java | 45 +++-------- .../todoffin/user/dto/AuthProvider.java | 17 ---- .../user/dto/OAuth2UserInfoFactory.java | 21 ----- .../todoffin/user/dto/OAuthAttributes.java | 79 ------------------- .../genius/todoffin/user/entity/Constant.java | 7 -- .../todoffin/user/entity/GoogleOAuth.java | 54 ------------- .../com/genius/todoffin/user/entity/User.java | 15 ++-- .../user/repository/UserRepositoryTest.java | 3 +- 22 files changed, 109 insertions(+), 430 deletions(-) delete mode 100644 src/main/java/com/genius/todoffin/config/SecurityConfig.java delete mode 100644 src/main/java/com/genius/todoffin/security/CustomUserDetailsService.java delete mode 100644 src/main/java/com/genius/todoffin/security/UserPrincipal.java delete mode 100644 src/main/java/com/genius/todoffin/security/constants/AuthProvider.java create mode 100644 src/main/java/com/genius/todoffin/security/constants/ProviderType.java rename src/main/java/com/genius/todoffin/{user/entity => security/domain}/SocialOAuth.java (60%) rename src/main/java/com/genius/todoffin/{ => security}/handler/OAuth2SuccessHandler.java (68%) rename src/main/java/com/genius/todoffin/{user/dto => security/info}/OAuth2UserInfo.java (87%) create mode 100644 src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java rename src/main/java/com/genius/todoffin/{user/dto => security/info/impl}/FacebookOAuth2UserInfo.java (82%) rename src/main/java/com/genius/todoffin/{user/dto => security/info/impl}/GoogleOAuth2UserInfo.java (82%) rename src/main/java/com/genius/todoffin/{user/dto => security/info/impl}/KakaoOAuth2UserInfo.java (85%) rename src/main/java/com/genius/todoffin/{user/dto => security/info/impl}/NaverOAuth2UserInfo.java (83%) rename src/main/java/com/genius/todoffin/security/{ => service}/CustomOAuth2UserService.java (54%) delete mode 100644 src/main/java/com/genius/todoffin/user/dto/AuthProvider.java delete mode 100644 src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfoFactory.java delete mode 100644 src/main/java/com/genius/todoffin/user/dto/OAuthAttributes.java delete mode 100644 src/main/java/com/genius/todoffin/user/entity/Constant.java delete mode 100644 src/main/java/com/genius/todoffin/user/entity/GoogleOAuth.java diff --git a/src/main/java/com/genius/todoffin/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/config/SecurityConfig.java deleted file mode 100644 index 81f7158d..00000000 --- a/src/main/java/com/genius/todoffin/config/SecurityConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.genius.todoffin.config; - - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.web.cors.CorsUtils; - -@Configuration -@Order(1) -@RequiredArgsConstructor -@EnableWebSecurity -public class SecurityConfig { - private static final String permitURI[] = {"/api/auth/**", "/swagger-ui.html", "/swagger-ui/**" - , "/v3/api-docs/**", "/v3/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**"}; - private static final String permittedRoles[] = {"USER", "ADMIN"}; -// private final CustomOAuth2UserService customOAuthService; -// private final OAuthSuccessHandler successHandler; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - - http.csrf().disable() - .httpBasic().disable() - .formLogin().disable() - .anonymous().and() - .authorizeHttpRequests(request -> request - .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .requestMatchers(permitURI).permitAll() - .anyRequest().hasAnyRole(permittedRoles)) - - // JWT 사용으로 인한 세션 미사용 - .sessionManagement( configurer -> configurer - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - - // OAuth 로그인 설정 - .oauth2Login(); -// .successHandler(successHandler) -// .authorizationEndpoint() - -// .and() -// .userInfoEndpoint().userService(customOAuthService); - - return http.build(); - } -} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/security/CustomUserDetailsService.java b/src/main/java/com/genius/todoffin/security/CustomUserDetailsService.java deleted file mode 100644 index 85b219f4..00000000 --- a/src/main/java/com/genius/todoffin/security/CustomUserDetailsService.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.genius.todoffin.security; - - -import com.genius.todoffin.user.entity.User; -import com.genius.todoffin.user.repository.UserRepository; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class CustomUserDetailsService implements UserDetailsService { - - private final UserRepository userRepository; - - @Override - @Transactional - public UserDetails loadUserByUsername(String email) - throws UsernameNotFoundException { - User user = userRepository.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("User not found with email : " + email)); - return UserPrincipal.create(user); - } - - @Transactional - public UserDetails loadUserById(Long id) { - User user = userRepository.findById(id) - .orElseThrow(() -> new UsernameNotFoundException("User not found with id : " + id)); - return UserPrincipal.create(user); - } - -} diff --git a/src/main/java/com/genius/todoffin/security/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/UserPrincipal.java deleted file mode 100644 index 533322d9..00000000 --- a/src/main/java/com/genius/todoffin/security/UserPrincipal.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.genius.todoffin.security; - - -import com.genius.todoffin.user.entity.Role; -import com.genius.todoffin.user.entity.User; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.oauth2.core.user.OAuth2User; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -@Getter -@AllArgsConstructor -public class UserPrincipal implements OAuth2User, UserDetails { - - private long id; - private String email; - private String password; - private Collection authorities; - @Setter - private Map attributes; - - public static UserPrincipal create(User user) { - List authorities = - Collections.singletonList(new SimpleGrantedAuthority("" + Role.USER)); - return new UserPrincipal( - user.getId(), - user.getEmail(), - user.getPassword(), - authorities, - null - ); - } - - public static UserPrincipal create(User user, Map attributes) { - UserPrincipal userPrincipal = UserPrincipal.create(user); - userPrincipal.setAttributes(attributes); - return userPrincipal; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public String getName() { - return String.valueOf(id); - } - - @Override - public String getUsername() { - return email; - } - -} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 12f6f694..962a42d2 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -1,5 +1,8 @@ package com.genius.todoffin.security.config; + +import com.genius.todoffin.security.handler.OAuth2SuccessHandler; +import com.genius.todoffin.security.service.CustomOAuth2UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -18,8 +21,8 @@ public class SecurityConfig { private static final String permitURI[] = {"/api/auth/**", "/swagger-ui.html", "/swagger-ui/**" , "/v3/api-docs/**", "/v3/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**"}; private static final String permittedRoles[] = {"USER", "ADMIN"}; -// private final CustomOAuth2UserService customOAuthService; -// private final OAuthSuccessHandler successHandler; + private final CustomOAuth2UserService customOAuthService; + private final OAuth2SuccessHandler successHandler; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -28,25 +31,23 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .httpBasic().disable() .formLogin().disable() .anonymous().and() - .authorizeRequests() - .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .requestMatchers(permitURI).permitAll() - .anyRequest().hasAnyRole(permittedRoles) - .and() + .authorizeHttpRequests(request -> request + .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + .requestMatchers(permitURI).permitAll() + .anyRequest().hasAnyRole(permittedRoles)) // JWT 사용으로 인한 세션 미사용 - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() + .sessionManagement(configurer -> configurer + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // OAuth 로그인 설정 - .oauth2Login(); -// .successHandler(successHandler) -// .authorizationEndpoint() + .oauth2Login() + .successHandler(successHandler) + .authorizationEndpoint() -// .and() -// .userInfoEndpoint().userService(customOAuthService); + .and() + .userInfoEndpoint().userService(customOAuthService); return http.build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/security/constants/AuthProvider.java b/src/main/java/com/genius/todoffin/security/constants/AuthProvider.java deleted file mode 100644 index 5562a079..00000000 --- a/src/main/java/com/genius/todoffin/security/constants/AuthProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.genius.todoffin.security.constants; - -import java.util.Arrays; - -public enum AuthProvider { - KAKAO, - NAVER, - GOOGLE, - FACEBOOK; - - public static AuthProvider from(String provider) { - return Arrays.stream(AuthProvider.values()) - .filter(item -> item.name().equals(provider)) - .findFirst() - .orElseThrow(); - } -} diff --git a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java b/src/main/java/com/genius/todoffin/security/constants/ProviderType.java new file mode 100644 index 00000000..eedef71b --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/constants/ProviderType.java @@ -0,0 +1,19 @@ +package com.genius.todoffin.security.constants; + +import java.util.Arrays; + +public enum ProviderType { + KAKAO, + NAVER, + GOOGLE, + FACEBOOK; + + public static ProviderType from(String provider) { + String upperCastedProvider = provider.toUpperCase(); + + return Arrays.stream(ProviderType.values()) + .filter(item -> item.name().equals(upperCastedProvider)) + .findFirst() + .orElseThrow(); + } +} diff --git a/src/main/java/com/genius/todoffin/user/entity/SocialOAuth.java b/src/main/java/com/genius/todoffin/security/domain/SocialOAuth.java similarity index 60% rename from src/main/java/com/genius/todoffin/user/entity/SocialOAuth.java rename to src/main/java/com/genius/todoffin/security/domain/SocialOAuth.java index f74ac7fe..672afd5a 100644 --- a/src/main/java/com/genius/todoffin/user/entity/SocialOAuth.java +++ b/src/main/java/com/genius/todoffin/security/domain/SocialOAuth.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.user.entity; +package com.genius.todoffin.security.domain; public interface SocialOAuth { diff --git a/src/main/java/com/genius/todoffin/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java similarity index 68% rename from src/main/java/com/genius/todoffin/handler/OAuth2SuccessHandler.java rename to src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index 9ad7230f..063f3c95 100644 --- a/src/main/java/com/genius/todoffin/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -1,18 +1,24 @@ -package com.genius.todoffin.handler; +package com.genius.todoffin.security.handler; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; -import java.io.IOException; -import java.util.Map; - +@Component +@Slf4j +@RequiredArgsConstructor public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); Map attributes = oAuth2User.getAttributes(); diff --git a/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java similarity index 87% rename from src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfo.java rename to src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java index 244c4a5a..36483ec4 100644 --- a/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java @@ -1,10 +1,9 @@ -package com.genius.todoffin.user.dto; +package com.genius.todoffin.security.info; +import java.util.Map; import lombok.AllArgsConstructor; import lombok.Getter; -import java.util.Map; - @Getter @AllArgsConstructor public abstract class OAuth2UserInfo { @@ -12,7 +11,9 @@ public abstract class OAuth2UserInfo { protected Map attributes; public abstract String getId(); + public abstract String getEmail(); + public abstract String getName(); } diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java new file mode 100644 index 00000000..7320f3b0 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java @@ -0,0 +1,28 @@ +package com.genius.todoffin.security.info; + +import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.security.info.impl.FacebookOAuth2UserInfo; +import com.genius.todoffin.security.info.impl.GoogleOAuth2UserInfo; +import com.genius.todoffin.security.info.impl.KakaoOAuth2UserInfo; +import com.genius.todoffin.security.info.impl.NaverOAuth2UserInfo; +import java.util.Map; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; + +public class OAuth2UserInfoFactory { + public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map attributes) { + if (registrationId.equalsIgnoreCase(ProviderType.GOOGLE.toString())) { + return new GoogleOAuth2UserInfo(attributes); + } + if (registrationId.equalsIgnoreCase(ProviderType.NAVER.toString())) { + return new NaverOAuth2UserInfo(attributes); + } + if (registrationId.equalsIgnoreCase(ProviderType.KAKAO.toString())) { + return new KakaoOAuth2UserInfo(attributes); + } + if (registrationId.equalsIgnoreCase(ProviderType.FACEBOOK.toString())) { + return new FacebookOAuth2UserInfo(attributes); + } + + throw new OAuth2AuthenticationException("Unsupported Login Type : " + registrationId); + } +} diff --git a/src/main/java/com/genius/todoffin/user/dto/FacebookOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java similarity index 82% rename from src/main/java/com/genius/todoffin/user/dto/FacebookOAuth2UserInfo.java rename to src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java index 585c79ba..60ac6c41 100644 --- a/src/main/java/com/genius/todoffin/user/dto/FacebookOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java @@ -1,5 +1,6 @@ -package com.genius.todoffin.user.dto; +package com.genius.todoffin.security.info.impl; +import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; public class FacebookOAuth2UserInfo extends OAuth2UserInfo { diff --git a/src/main/java/com/genius/todoffin/user/dto/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java similarity index 82% rename from src/main/java/com/genius/todoffin/user/dto/GoogleOAuth2UserInfo.java rename to src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java index 1c5cc8d1..3b7e8878 100644 --- a/src/main/java/com/genius/todoffin/user/dto/GoogleOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java @@ -1,6 +1,7 @@ -package com.genius.todoffin.user.dto; +package com.genius.todoffin.security.info.impl; +import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; public class GoogleOAuth2UserInfo extends OAuth2UserInfo { diff --git a/src/main/java/com/genius/todoffin/user/dto/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java similarity index 85% rename from src/main/java/com/genius/todoffin/user/dto/KakaoOAuth2UserInfo.java rename to src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java index 3433494e..105735ba 100644 --- a/src/main/java/com/genius/todoffin/user/dto/KakaoOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java @@ -1,5 +1,6 @@ -package com.genius.todoffin.user.dto; +package com.genius.todoffin.security.info.impl; +import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; public class KakaoOAuth2UserInfo extends OAuth2UserInfo { diff --git a/src/main/java/com/genius/todoffin/user/dto/NaverOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java similarity index 83% rename from src/main/java/com/genius/todoffin/user/dto/NaverOAuth2UserInfo.java rename to src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java index 3e795940..b074d834 100644 --- a/src/main/java/com/genius/todoffin/user/dto/NaverOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java @@ -1,6 +1,7 @@ -package com.genius.todoffin.user.dto; +package com.genius.todoffin.security.info.impl; +import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; public class NaverOAuth2UserInfo extends OAuth2UserInfo { diff --git a/src/main/java/com/genius/todoffin/security/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java similarity index 54% rename from src/main/java/com/genius/todoffin/security/CustomOAuth2UserService.java rename to src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index 797cac1a..670dfff0 100644 --- a/src/main/java/com/genius/todoffin/security/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -1,14 +1,16 @@ -package com.genius.todoffin.security; +package com.genius.todoffin.security.service; -import com.genius.todoffin.user.dto.OAuthAttributes; import com.genius.todoffin.user.repository.UserRepository; import jakarta.transaction.Transactional; +import java.util.Collections; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; @@ -27,42 +29,17 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic OAuth2User oAuth2User = delegate.loadUser(userRequest); // RegistrationId() : 서비스를 구분하는 코드 - String registrationId = userRequest.getClientRegistration().getRegistrationId(); + String providerId = userRequest.getClientRegistration().getRegistrationId(); // OAuth2 로그인 진행 시 키가 되는 필드값을 이야기 한다. Primary Key와 같은 의미. - // 구글의 경우 기본적으로 코드를 지원하지만, 네이버 카카 등은 기본 지원하지 않는다. 구굴의 기본 코드는 "sub" - String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); - - // OAuthAttributes - // OAuth2UserService를 통해 가져온 OAuth2User의 attribute를 담은 클래스. - // 이후 네이버 등 다른 소셜 로그인도 이 클래스를 사용 - OAuthAttributes authAttributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); - + // 구글의 경우 기본적으로 코드를 지원하지만, 네이버 카카 등은 기본 지원하지 않는다. 구글의 기본 코드는 "sub" + String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint() + .getUserNameAttributeName(); String email = ""; - switch (registrationId) { - case "kakao": - // - - case "facebook" : - // - - case "naver" : - // - - case "google " : - // - default: - throw new IllegalArgumentException("알 수 없는 소셜 로그인 형식입니다."); - } - - // 유저가 존재하거나, 새로운 유저일 경우 비즈니스 로직 / - - - -// return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority(user.getRole().getKey())) -// ,oAuth2User.getAttributes() -// ,userNameAttributeName); + return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")) + , oAuth2User.getAttributes() + , userNameAttributeName); } } diff --git a/src/main/java/com/genius/todoffin/user/dto/AuthProvider.java b/src/main/java/com/genius/todoffin/user/dto/AuthProvider.java deleted file mode 100644 index 9da6db7c..00000000 --- a/src/main/java/com/genius/todoffin/user/dto/AuthProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.genius.todoffin.user.dto; - -import java.util.Arrays; - -public enum AuthProvider { - KAKAO, - NAVER, - GOOGLE, - FACEBOOK; - - public static AuthProvider from(String provider) { - return Arrays.stream(AuthProvider.values()) - .filter(item -> item.name().equals(provider)) - .findFirst() - .orElseThrow(); - } -} \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfoFactory.java b/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfoFactory.java deleted file mode 100644 index 9822a18d..00000000 --- a/src/main/java/com/genius/todoffin/user/dto/OAuth2UserInfoFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.genius.todoffin.user.dto; - -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; - -import java.util.Map; - -public class OAuth2UserInfoFactory { - public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map attributes) { - if (registrationId.equalsIgnoreCase(AuthProvider.GOOGLE.toString())) { - return new GoogleOAuth2UserInfo(attributes); - } else if (registrationId.equalsIgnoreCase(AuthProvider.NAVER.toString())) { - return new NaverOAuth2UserInfo(attributes); - } else if (registrationId.equalsIgnoreCase(AuthProvider.KAKAO.toString())) { - return new KakaoOAuth2UserInfo(attributes); - } else if (registrationId.equalsIgnoreCase(AuthProvider.FACEBOOK.toString())) { - return new FacebookOAuth2UserInfo(attributes); - }else { - throw new OAuth2AuthenticationException("Unsupported Login Type : " + registrationId); - } - } -} diff --git a/src/main/java/com/genius/todoffin/user/dto/OAuthAttributes.java b/src/main/java/com/genius/todoffin/user/dto/OAuthAttributes.java deleted file mode 100644 index 364f1383..00000000 --- a/src/main/java/com/genius/todoffin/user/dto/OAuthAttributes.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.genius.todoffin.user.dto; - -import lombok.Builder; -import lombok.Getter; - -import java.util.Map; - -@Getter -public class OAuthAttributes { - private Map attributes; - private String nameAttributeKey; - private String name; - private String email; - - @Builder - public OAuthAttributes(Map attributes, String nameAttributeKey, String name, String email) { - this.attributes = attributes; - this.nameAttributeKey = nameAttributeKey; - this.name = name; - this.email = email; - } - - // OAuth2User에서 반환하는 사용자 정보는 Map이기 때문에 값 하나하나를 변환해야 한다. - public static OAuthAttributes of(String registrationId, String userNAmeAttributeName, Map attributes) { - if ("naver".equals(registrationId)) return ofNaver("id", attributes); - if ("facebook".equals(registrationId)) return ofFacebook("id", attributes); - if ("kakao".equals(registrationId)) return ofkakao("id", attributes); - return ofGoogle(userNAmeAttributeName, attributes); - } - - // google login OAuth - private static OAuthAttributes ofGoogle(String userNameAttributeName, Map attributes) { - return OAuthAttributes.builder() - .name((String) attributes.get("name")) - .email((String) attributes.get("email")) - .attributes(attributes) - .nameAttributeKey(userNameAttributeName) - .build(); - } - - // naver login OAuth - private static OAuthAttributes ofNaver(String userNameAttributeName, Map attributes) { - Map response = (Map) attributes.get("response"); - return OAuthAttributes.builder() - .name((String) response.get("name")) - .email((String) response.get("email")) - .attributes(response) - .nameAttributeKey(userNameAttributeName) - .build(); - } - - // 수정 필요 시작 - - - // Facebook login OAuth - private static OAuthAttributes ofFacebook(String userNameAttributeName, Map attributes) { - Map response = (Map) attributes.get("response"); - return OAuthAttributes.builder() - .name((String) response.get("name")) - .email((String) response.get("email")) - .attributes(response) - .nameAttributeKey(userNameAttributeName) - .build(); - } - - - // Kakao login OAuth - private static OAuthAttributes ofkakao(String userNameAttributeName, Map attributes) { - Map response = (Map) attributes.get("response"); - return OAuthAttributes.builder() - .name((String) response.get("name")) - .email((String) response.get("email")) - .attributes(response) - .nameAttributeKey(userNameAttributeName) - .build(); - } - - // 수정 필요 끝 -} diff --git a/src/main/java/com/genius/todoffin/user/entity/Constant.java b/src/main/java/com/genius/todoffin/user/entity/Constant.java deleted file mode 100644 index a74ad347..00000000 --- a/src/main/java/com/genius/todoffin/user/entity/Constant.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.genius.todoffin.user.entity; - -public class Constant { - public enum SocialLoginType { - GOOGLE, KAKAO, NAVER, FACEBOOK - } -} diff --git a/src/main/java/com/genius/todoffin/user/entity/GoogleOAuth.java b/src/main/java/com/genius/todoffin/user/entity/GoogleOAuth.java deleted file mode 100644 index d48ee34e..00000000 --- a/src/main/java/com/genius/todoffin/user/entity/GoogleOAuth.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.genius.todoffin.user.entity; - -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -@Component -@RequiredArgsConstructor -public class GoogleOAuth implements SocialOAuth{ - - @Value("${spring.security.oath2.client.registration.google.client_id}") - private String GOOGLE_SNS_CLIENT_ID; - - @Value("${spring.security.oath2.client.registration.google.redirect_uri}") - private String GOOGLE_SNS_CALLBACK_URL; - - @Value("${spring.security.oath2.client.registration.google.client-secret}") - private String GOOGLE_SNS_CLIENT_SECRET; - - @Value("${spring.security.oath2.client.registration.google.scope}") - private String GOOGLE_DATA_ACCESS_SCOPE; - - private final ObjectMapper objectMapper; - - @Override - public String getOAuthRedirectURL() { - - Map params = new HashMap<>(); - params.put("scope", GOOGLE_DATA_ACCESS_SCOPE); - params.put("response_type", "code"); - params.put("client_id", GOOGLE_SNS_CLIENT_ID); - params.put("redirect_uri", GOOGLE_SNS_CALLBACK_URL); - - //parameter를 형식에 맞춰 구성해주는 함수 - String parameterString = params.entrySet().stream() - .map(x -> x.getKey() + "=" + x.getValue()) - .collect(Collectors.joining("&")); - String redirectURL = "https://accounts.google.com/o/oauth2/v2/auth" + "?" + parameterString; - System.out.println("redirectURL = " + redirectURL); - - return redirectURL; - - /* - * https://accounts.google.com/o/oauth2/v2/auth?scope=profile&response_type=code - * &client_id="할당받은 id"&redirect_uri="access token 처리") - * 로 Redirect URL을 생성하는 로직을 구성 - * */ - } -} diff --git a/src/main/java/com/genius/todoffin/user/entity/User.java b/src/main/java/com/genius/todoffin/user/entity/User.java index 28805a96..002030f8 100644 --- a/src/main/java/com/genius/todoffin/user/entity/User.java +++ b/src/main/java/com/genius/todoffin/user/entity/User.java @@ -2,7 +2,13 @@ import com.genius.todoffin.common.BaseTimeEntity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; @@ -34,20 +40,15 @@ public class User extends BaseTimeEntity { private String interest; - // 임시 - private String password; - - @Builder public User(String provider, String email, Role role, String nickname, String information, - String interest, String password) { + String interest) { this.provider = provider; this.email = email; this.role = role; this.nickname = nickname; this.information = information; this.interest = interest; - this.password = password; } } diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java index f3a149f2..5794eb91 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -2,7 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.todoffin.user.domain.User; +import com.genius.todoffin.user.entity.Role; +import com.genius.todoffin.user.entity.User; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; From 52dbed24f9c1c4f837ada471d423c1c23f0d82e1 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 01:34:24 +0900 Subject: [PATCH 023/234] =?UTF-8?q?feat:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=EB=90=98=EB=8A=94=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서드파티로부터 access-token 받은 이후 실행되는 custom 서비스 클래스 로직 구현 - 사용자 정보(email)을 받아온 후 -> DB에 저장 여부 확인 -> 없다면 DB에 저장 --- .../security/domain/UserPrincipal.java | 32 +++++++++++++ .../security/info/OAuth2UserInfoFactory.java | 29 ++++++------ .../info/impl/KakaoOAuth2UserInfo.java | 7 +-- .../service/CustomOAuth2UserService.java | 45 ++++++++++++++----- .../com/genius/todoffin/user/entity/User.java | 13 ++++-- .../user/repository/UserRepository.java | 6 +-- .../user/repository/UserRepositoryTest.java | 7 +-- 7 files changed, 101 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java new file mode 100644 index 00000000..58e9e51f --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java @@ -0,0 +1,32 @@ +package com.genius.todoffin.security.domain; + +import com.genius.todoffin.user.entity.Role; +import com.genius.todoffin.user.entity.User; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +@Getter +@AllArgsConstructor +public class UserPrincipal implements OAuth2User { + private User user; + private Collection authorities; + private Map attributes; + + public UserPrincipal(User user, Map attributes) { + this.user = user; + this.authorities = Collections.singletonList(new SimpleGrantedAuthority(Role.USER.getKey())); + this.attributes = attributes; + } + + + @Override + public String getName() { + return user.getEmail(); + } +} diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java index 7320f3b0..07324de8 100644 --- a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java +++ b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java @@ -9,20 +9,21 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; public class OAuth2UserInfoFactory { - public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map attributes) { - if (registrationId.equalsIgnoreCase(ProviderType.GOOGLE.toString())) { - return new GoogleOAuth2UserInfo(attributes); + public static OAuth2UserInfo getOAuth2UserInfo(ProviderType providerType, Map attributes) { + switch (providerType) { + case KAKAO -> { + return new KakaoOAuth2UserInfo(attributes); + } + case NAVER -> { + return new NaverOAuth2UserInfo(attributes); + } + case GOOGLE -> { + return new GoogleOAuth2UserInfo(attributes); + } + case FACEBOOK -> { + return new FacebookOAuth2UserInfo(attributes); + } } - if (registrationId.equalsIgnoreCase(ProviderType.NAVER.toString())) { - return new NaverOAuth2UserInfo(attributes); - } - if (registrationId.equalsIgnoreCase(ProviderType.KAKAO.toString())) { - return new KakaoOAuth2UserInfo(attributes); - } - if (registrationId.equalsIgnoreCase(ProviderType.FACEBOOK.toString())) { - return new FacebookOAuth2UserInfo(attributes); - } - - throw new OAuth2AuthenticationException("Unsupported Login Type : " + registrationId); + throw new OAuth2AuthenticationException("INVALID PROVIDER TYPE"); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java index 105735ba..c2bca5d8 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java @@ -4,12 +4,13 @@ import java.util.Map; public class KakaoOAuth2UserInfo extends OAuth2UserInfo { + private final static String ATTRIBUTE_KEY = "kakao_account"; - private Integer id; + private Long id; public KakaoOAuth2UserInfo(Map attributes) { - super((Map) attributes.get("kakao_account")); - this.id = (Integer) attributes.get("id"); + super((Map) attributes.get(ATTRIBUTE_KEY)); + this.id = (Long) attributes.get("id"); } @Override diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index 670dfff0..942e2bb6 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -1,16 +1,21 @@ package com.genius.todoffin.security.service; +import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.security.domain.UserPrincipal; +import com.genius.todoffin.security.info.OAuth2UserInfo; +import com.genius.todoffin.security.info.OAuth2UserInfoFactory; +import com.genius.todoffin.user.entity.Role; +import com.genius.todoffin.user.entity.User; import com.genius.todoffin.user.repository.UserRepository; import jakarta.transaction.Transactional; -import java.util.Collections; +import java.util.Map; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; @@ -28,18 +33,36 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic OAuth2UserService delegate = new DefaultOAuth2UserService(); OAuth2User oAuth2User = delegate.loadUser(userRequest); - // RegistrationId() : 서비스를 구분하는 코드 - String providerId = userRequest.getClientRegistration().getRegistrationId(); - // OAuth2 로그인 진행 시 키가 되는 필드값을 이야기 한다. Primary Key와 같은 의미. // 구글의 경우 기본적으로 코드를 지원하지만, 네이버 카카 등은 기본 지원하지 않는다. 구글의 기본 코드는 "sub" - String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint() + String providerPK = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint() .getUserNameAttributeName(); - String email = ""; + // 서비스를 구분하는 코드 + String providerId = userRequest.getClientRegistration().getRegistrationId(); + + ProviderType providerType = ProviderType.from(providerId); + Map attributes = oAuth2User.getAttributes(); + + OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, attributes); + String email = oAuth2UserInfo.getEmail(); + + User user = getUser(email, providerType); + + return new UserPrincipal(user, oAuth2UserInfo.getAttributes()); + } + + private User getUser(String email, ProviderType providerType) { + Optional optionalUser = userRepository.findByOAuthInfo(email, providerType); - return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")) - , oAuth2User.getAttributes() - , userNameAttributeName); + if (optionalUser.isEmpty()) { + User unregisteredUser = User.builder() + .email(email) + .role(Role.NOT_REGISTERED) + .provider(providerType) + .build(); + return userRepository.save(unregisteredUser); + } + return optionalUser.get(); } } diff --git a/src/main/java/com/genius/todoffin/user/entity/User.java b/src/main/java/com/genius/todoffin/user/entity/User.java index 002030f8..292b9727 100644 --- a/src/main/java/com/genius/todoffin/user/entity/User.java +++ b/src/main/java/com/genius/todoffin/user/entity/User.java @@ -2,6 +2,7 @@ import com.genius.todoffin.common.BaseTimeEntity; +import com.genius.todoffin.security.constants.ProviderType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -24,14 +25,14 @@ public class User extends BaseTimeEntity { private Long id; @NotNull - private String provider; + @Enumerated(EnumType.STRING) + private ProviderType provider; @NotNull private String email; @Enumerated(EnumType.STRING) private Role role; - - @NotNull + @Column(unique = true, length = 16) private String nickname; @@ -41,7 +42,7 @@ public class User extends BaseTimeEntity { @Builder - public User(String provider, String email, Role role, String nickname, String information, + public User(ProviderType provider, String email, Role role, String nickname, String information, String interest) { this.provider = provider; this.email = email; @@ -51,4 +52,8 @@ public User(String provider, String email, Role role, String nickname, String in this.interest = interest; } + + public String getAuthorities() { + return role.getKey(); + } } diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java index 61ef62ae..6f7a89da 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -1,15 +1,15 @@ package com.genius.todoffin.user.repository; +import com.genius.todoffin.security.constants.ProviderType; import com.genius.todoffin.user.entity.User; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.Optional; - public interface UserRepository extends JpaRepository { Optional findByEmail(String email); @Query("select u from User u where u.email = :email and u.provider = :provider") - Optional findByOAuthInfo(@Param("email") String email, @Param("provider") String provider); + Optional findByOAuthInfo(@Param("email") String email, @Param("provider") ProviderType provider); } diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java index 5794eb91..0360c4e7 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.genius.todoffin.security.constants.ProviderType; import com.genius.todoffin.user.entity.Role; import com.genius.todoffin.user.entity.User; import org.junit.jupiter.api.DisplayName; @@ -21,7 +22,7 @@ class UserRepositoryTest { public void email을_통해_저장한_User_객체를_찾을수있다() { //given String email = "test@naver.com"; - String provider = "NAVER"; + ProviderType provider = ProviderType.GOOGLE; String nickname = "test_nickname"; User user = getUnsavedUser(email, provider, nickname); @@ -41,7 +42,7 @@ class UserRepositoryTest { public void email_provider를_통해_저장한_User_객체를_찾을수있다() { //given String email = "test@naver.com"; - String provider = "NAVER"; + ProviderType provider = ProviderType.GOOGLE; String nickname = "test_nickname"; User user = getUnsavedUser(email, provider, nickname); @@ -57,7 +58,7 @@ class UserRepositoryTest { } - private User getUnsavedUser(String email, String provider, String nickname) { + private User getUnsavedUser(String email, ProviderType provider, String nickname) { return User.builder() .email(email) .provider(provider) From 20580d9478afba19c63391a905f6dc334482099c Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 02:05:50 +0900 Subject: [PATCH 024/234] =?UTF-8?q?feat:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20Handler=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 소셜로그인을 통한 인증 성공 후, 실행되는 OAuth2SuccessHandler 클래스 로직 구현 - 소셜로그인 도중 모종의 이유로 인해 실패했을 경우, 실행되는 OAuth2FailureHandler 클래스 로직 구현 --- .../handler/OAuth2FailureHandler.java | 24 +++++++++++++++ .../handler/OAuth2SuccessHandler.java | 29 +++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java new file mode 100644 index 00000000..049ca8ae --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java @@ -0,0 +1,24 @@ +package com.genius.todoffin.security.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +@Component +public class OAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler { + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + String redirectUrl = UriComponentsBuilder.fromUriString("http://localhost:3000") + .queryParam("error", exception.getLocalizedMessage()) + .build() + .toUriString(); + + getRedirectStrategy().sendRedirect(request, response, redirectUrl); + } +} diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index 063f3c95..69dbb293 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -1,21 +1,25 @@ package com.genius.todoffin.security.handler; +import com.genius.todoffin.user.entity.Role; +import com.genius.todoffin.user.entity.User; +import com.genius.todoffin.user.repository.UserRepository; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; @Component -@Slf4j @RequiredArgsConstructor public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + private final UserRepository userRepository; + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { @@ -23,5 +27,26 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo Map attributes = oAuth2User.getAttributes(); String email = (String) attributes.get("email"); + + //TODO: 추후 Utils의 ErrorCode를 활용하여 orElseThrow에 에러 코드 넣기 + User user = userRepository.findByEmail(email) + .orElseThrow(); + + Role role = user.getRole(); + + if (role == Role.NOT_REGISTERED) { + //TODO: url에 대해 상수로 빼기 + String redirectUrl = UriComponentsBuilder.fromUriString("http://localhost:3000/login/signup") + .build() + .toUriString(); + + getRedirectStrategy().sendRedirect(request, response, redirectUrl); + return; + } + + String redirectUrl = UriComponentsBuilder.fromHttpUrl("http://localhost:3000/main") + .build() + .toUriString(); + getRedirectStrategy().sendRedirect(request, response, redirectUrl); } } From 25ca958b019ba9c96fb8234b19dccf77f5f05aba Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 03:17:50 +0900 Subject: [PATCH 025/234] =?UTF-8?q?refactor:=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=B3=84=20UserInfo=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 매직 넘버(magic number)에 대해 상수로 변경 - 인증 객체에 필요한 메서드 추가 --- .../security/constants/OAuthAttributeKey.java | 15 +++++++++++++ .../security/domain/UserPrincipal.java | 4 ++++ .../handler/OAuth2SuccessHandler.java | 21 +++++++++++-------- .../security/info/OAuth2UserInfo.java | 5 +---- .../info/impl/FacebookOAuth2UserInfo.java | 7 +------ .../info/impl/GoogleOAuth2UserInfo.java | 14 ++++++------- .../info/impl/KakaoOAuth2UserInfo.java | 19 +++++++---------- .../info/impl/NaverOAuth2UserInfo.java | 19 +++++++---------- .../service/CustomOAuth2UserService.java | 5 ++--- 9 files changed, 57 insertions(+), 52 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/constants/OAuthAttributeKey.java diff --git a/src/main/java/com/genius/todoffin/security/constants/OAuthAttributeKey.java b/src/main/java/com/genius/todoffin/security/constants/OAuthAttributeKey.java new file mode 100644 index 00000000..01812373 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/constants/OAuthAttributeKey.java @@ -0,0 +1,15 @@ +package com.genius.todoffin.security.constants; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum OAuthAttributeKey { + KAKAO_PROVIDER_ID("id"), + NAVER_PROVIDER_ID("id"), + GOOGLE_PROVIDER_ID("sub"), + EMAIL_KEY("email"); + + private final String value; +} diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java index 58e9e51f..6ec338d6 100644 --- a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java @@ -29,4 +29,8 @@ public UserPrincipal(User user, Map attributes) { public String getName() { return user.getEmail(); } + + public String getEmail() { + return user.getEmail(); + } } diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index 69dbb293..c8b14bcc 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -1,5 +1,7 @@ package com.genius.todoffin.security.handler; +import static com.genius.todoffin.security.constants.OAuthAttributeKey.EMAIL_KEY; + import com.genius.todoffin.user.entity.Role; import com.genius.todoffin.user.entity.User; import com.genius.todoffin.user.repository.UserRepository; @@ -18,6 +20,8 @@ @Component @RequiredArgsConstructor public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + private final String SIGNUP_URL = "http://localhost:3000/login/signup"; + private final String MAIN_URL = "http://localhost:3000/main"; private final UserRepository userRepository; @Override @@ -26,27 +30,26 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); Map attributes = oAuth2User.getAttributes(); - String email = (String) attributes.get("email"); + String email = (String) attributes.get(EMAIL_KEY.getValue()); //TODO: 추후 Utils의 ErrorCode를 활용하여 orElseThrow에 에러 코드 넣기 User user = userRepository.findByEmail(email) .orElseThrow(); - Role role = user.getRole(); + String redirectUrl = getRedirectUrlByRole(role); + getRedirectStrategy().sendRedirect(request, response, redirectUrl); + } + + private String getRedirectUrlByRole(Role role) { if (role == Role.NOT_REGISTERED) { - //TODO: url에 대해 상수로 빼기 - String redirectUrl = UriComponentsBuilder.fromUriString("http://localhost:3000/login/signup") + return UriComponentsBuilder.fromUriString(SIGNUP_URL) .build() .toUriString(); - - getRedirectStrategy().sendRedirect(request, response, redirectUrl); - return; } - String redirectUrl = UriComponentsBuilder.fromHttpUrl("http://localhost:3000/main") + return UriComponentsBuilder.fromHttpUrl(MAIN_URL) .build() .toUriString(); - getRedirectStrategy().sendRedirect(request, response, redirectUrl); } } diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java index 36483ec4..8589fafa 100644 --- a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java @@ -10,10 +10,7 @@ public abstract class OAuth2UserInfo { protected Map attributes; - public abstract String getId(); + public abstract String getProviderId(); public abstract String getEmail(); - - public abstract String getName(); - } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java index 60ac6c41..2d9d1c27 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java @@ -10,7 +10,7 @@ public FacebookOAuth2UserInfo(Map attributes) { } @Override - public String getId() { + public String getProviderId() { return (String) attributes.get("sub"); } @@ -18,10 +18,5 @@ public String getId() { public String getEmail() { return (String) attributes.get("email"); } - - @Override - public String getName() { - return (String) attributes.get("name"); - } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java index 3b7e8878..578b66bc 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java @@ -1,6 +1,9 @@ package com.genius.todoffin.security.info.impl; +import static com.genius.todoffin.security.constants.OAuthAttributeKey.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthAttributeKey.GOOGLE_PROVIDER_ID; + import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; @@ -11,17 +14,12 @@ public GoogleOAuth2UserInfo(Map attributes) { } @Override - public String getId() { - return (String) attributes.get("sub"); + public String getProviderId() { + return (String) attributes.get(GOOGLE_PROVIDER_ID.getValue()); } @Override public String getEmail() { - return (String) attributes.get("email"); - } - - @Override - public String getName() { - return (String) attributes.get("name"); + return (String) attributes.get(EMAIL_KEY.getValue()); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java index c2bca5d8..9ce14725 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java @@ -1,30 +1,27 @@ package com.genius.todoffin.security.info.impl; +import static com.genius.todoffin.security.constants.OAuthAttributeKey.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthAttributeKey.KAKAO_PROVIDER_ID; + import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; public class KakaoOAuth2UserInfo extends OAuth2UserInfo { private final static String ATTRIBUTE_KEY = "kakao_account"; - - private Long id; + private String providerId; public KakaoOAuth2UserInfo(Map attributes) { super((Map) attributes.get(ATTRIBUTE_KEY)); - this.id = (Long) attributes.get("id"); - } - - @Override - public String getId() { - return this.id.toString(); + this.providerId = String.valueOf(attributes.get(KAKAO_PROVIDER_ID.getValue())); } @Override - public String getName() { - return (String) ((Map) attributes.get("profile")).get("nickname"); + public String getProviderId() { + return providerId; } @Override public String getEmail() { - return (String) attributes.get("email"); + return (String) attributes.get(EMAIL_KEY.getValue()); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java index b074d834..8bcd05c2 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java @@ -1,29 +1,26 @@ package com.genius.todoffin.security.info.impl; +import static com.genius.todoffin.security.constants.OAuthAttributeKey.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthAttributeKey.NAVER_PROVIDER_ID; + import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; public class NaverOAuth2UserInfo extends OAuth2UserInfo { + private final static String ATTRIBUTE_KEY = "response"; public NaverOAuth2UserInfo(Map attributes) { - super((Map) attributes.get("response")); + super((Map) attributes.get(ATTRIBUTE_KEY)); } @Override - public String getId() { - return (String) attributes.get("id"); + public String getProviderId() { + return (String) attributes.get(NAVER_PROVIDER_ID.getValue()); } @Override public String getEmail() { - return (String) attributes.get("email"); - } - - @Override - public String getName() { - return (String) attributes.get("name"); + return (String) attributes.get(EMAIL_KEY.getValue()); } - - } diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index 942e2bb6..1e6977e3 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -33,9 +33,8 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic OAuth2UserService delegate = new DefaultOAuth2UserService(); OAuth2User oAuth2User = delegate.loadUser(userRequest); - // OAuth2 로그인 진행 시 키가 되는 필드값을 이야기 한다. Primary Key와 같은 의미. - // 구글의 경우 기본적으로 코드를 지원하지만, 네이버 카카 등은 기본 지원하지 않는다. 구글의 기본 코드는 "sub" - String providerPK = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint() + // OAuth2 로그인 진행 시 키가 되는 필드값. Primary Key와 같은 의미. + String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint() .getUserNameAttributeName(); // 서비스를 구분하는 코드 From ec2a0acf3248181747e9b04dc952a1c043c3ce65 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 03:26:46 +0900 Subject: [PATCH 026/234] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=83=81=EC=86=8D=EA=B4=80=EA=B3=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에 OAuth2User 인터페이스 상속에서 DefaultOAuth2User 상속으로 변경 - getName() 메서드 오버라이드: 사용자의 이메일을 반환하도록 설정 --- .../security/domain/UserPrincipal.java | 22 +++++-------------- .../handler/OAuth2SuccessHandler.java | 7 +----- .../service/CustomOAuth2UserService.java | 2 +- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java index 6ec338d6..86cee297 100644 --- a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java @@ -2,35 +2,25 @@ import com.genius.todoffin.user.entity.Role; import com.genius.todoffin.user.entity.User; -import java.util.Collection; import java.util.Collections; import java.util.Map; -import lombok.AllArgsConstructor; import lombok.Getter; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; @Getter -@AllArgsConstructor -public class UserPrincipal implements OAuth2User { +public class UserPrincipal extends DefaultOAuth2User { private User user; - private Collection authorities; - private Map attributes; - public UserPrincipal(User user, Map attributes) { + public UserPrincipal(User user, Map attributes, String nameAttributeKey) { + super(Collections.singletonList(new SimpleGrantedAuthority(Role.USER.getKey())), + attributes, + nameAttributeKey); this.user = user; - this.authorities = Collections.singletonList(new SimpleGrantedAuthority(Role.USER.getKey())); - this.attributes = attributes; } - @Override public String getName() { return user.getEmail(); } - - public String getEmail() { - return user.getEmail(); - } } diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index c8b14bcc..2489d808 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -1,7 +1,5 @@ package com.genius.todoffin.security.handler; -import static com.genius.todoffin.security.constants.OAuthAttributeKey.EMAIL_KEY; - import com.genius.todoffin.user.entity.Role; import com.genius.todoffin.user.entity.User; import com.genius.todoffin.user.repository.UserRepository; @@ -9,7 +7,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -28,9 +25,7 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); - Map attributes = oAuth2User.getAttributes(); - - String email = (String) attributes.get(EMAIL_KEY.getValue()); + String email = oAuth2User.getName(); //TODO: 추후 Utils의 ErrorCode를 활용하여 orElseThrow에 에러 코드 넣기 User user = userRepository.findByEmail(email) diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index 1e6977e3..3dd326fc 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -48,7 +48,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic User user = getUser(email, providerType); - return new UserPrincipal(user, oAuth2UserInfo.getAttributes()); + return new UserPrincipal(user, attributes, userNameAttributeName); } private User getUser(String email, ProviderType providerType) { From a103100304b1ba94c7094e40e86d29c7f6bdb398 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 12:05:54 +0900 Subject: [PATCH 027/234] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1=EC=9E=90=EC=97=90=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20=EC=97=AD=ED=95=A0?= =?UTF-8?q?=EC=9D=84=20=EC=A0=84=EB=8B=AC=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에 무조건 ROLE_USER를 전달하던 로직에서, 생성자에 사용자의 역할을 전달하도록 수정 --- .../com/genius/todoffin/security/domain/UserPrincipal.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java index 86cee297..84aca46a 100644 --- a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java @@ -1,6 +1,5 @@ package com.genius.todoffin.security.domain; -import com.genius.todoffin.user.entity.Role; import com.genius.todoffin.user.entity.User; import java.util.Collections; import java.util.Map; @@ -13,7 +12,7 @@ public class UserPrincipal extends DefaultOAuth2User { private User user; public UserPrincipal(User user, Map attributes, String nameAttributeKey) { - super(Collections.singletonList(new SimpleGrantedAuthority(Role.USER.getKey())), + super(Collections.singletonList(new SimpleGrantedAuthority(user.getRole().getKey())), attributes, nameAttributeKey); this.user = user; From ea5d3b225e0ac9a5f0b18acdd5ad2b4ec40f50a1 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 12:37:03 +0900 Subject: [PATCH 028/234] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20magic=20number=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/genius/todoffin/TodoffinApplication.java | 11 ----------- .../todoffin/common/{ => domain}/BaseTimeEntity.java | 5 ++--- .../{OAuthAttributeKey.java => OAuthRule.java} | 3 ++- .../genius/todoffin/security/domain/SocialOAuth.java | 6 ------ .../security/handler/OAuth2FailureHandler.java | 7 +++++-- .../security/info/impl/FacebookOAuth2UserInfo.java | 7 +++++-- .../security/info/impl/GoogleOAuth2UserInfo.java | 4 ++-- .../security/info/impl/KakaoOAuth2UserInfo.java | 4 ++-- .../security/info/impl/NaverOAuth2UserInfo.java | 4 ++-- .../java/com/genius/todoffin/user/entity/User.java | 4 ++-- 10 files changed, 22 insertions(+), 33 deletions(-) rename src/main/java/com/genius/todoffin/common/{ => domain}/BaseTimeEntity.java (94%) rename src/main/java/com/genius/todoffin/security/constants/{OAuthAttributeKey.java => OAuthRule.java} (83%) delete mode 100644 src/main/java/com/genius/todoffin/security/domain/SocialOAuth.java diff --git a/src/main/java/com/genius/todoffin/TodoffinApplication.java b/src/main/java/com/genius/todoffin/TodoffinApplication.java index 6920fca2..b2d10dd4 100644 --- a/src/main/java/com/genius/todoffin/TodoffinApplication.java +++ b/src/main/java/com/genius/todoffin/TodoffinApplication.java @@ -1,21 +1,10 @@ package com.genius.todoffin; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @SpringBootApplication public class TodoffinApplication { - - @Bean - public BCryptPasswordEncoder encoder() { - return new BCryptPasswordEncoder(); - } - public static void main(String[] args) { SpringApplication.run(TodoffinApplication.class, args); } diff --git a/src/main/java/com/genius/todoffin/common/BaseTimeEntity.java b/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java similarity index 94% rename from src/main/java/com/genius/todoffin/common/BaseTimeEntity.java rename to src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java index 90ceee66..c331380b 100644 --- a/src/main/java/com/genius/todoffin/common/BaseTimeEntity.java +++ b/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java @@ -1,17 +1,16 @@ -package com.genius.todoffin.common; +package com.genius.todoffin.common.domain; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; import lombok.Getter; import lombok.ToString; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; - @Getter @ToString @MappedSuperclass diff --git a/src/main/java/com/genius/todoffin/security/constants/OAuthAttributeKey.java b/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java similarity index 83% rename from src/main/java/com/genius/todoffin/security/constants/OAuthAttributeKey.java rename to src/main/java/com/genius/todoffin/security/constants/OAuthRule.java index 01812373..25adc75b 100644 --- a/src/main/java/com/genius/todoffin/security/constants/OAuthAttributeKey.java +++ b/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java @@ -5,10 +5,11 @@ @Getter @RequiredArgsConstructor -public enum OAuthAttributeKey { +public enum OAuthRule { KAKAO_PROVIDER_ID("id"), NAVER_PROVIDER_ID("id"), GOOGLE_PROVIDER_ID("sub"), + FACEBOOK_PROVIDER_ID("id"), EMAIL_KEY("email"); private final String value; diff --git a/src/main/java/com/genius/todoffin/security/domain/SocialOAuth.java b/src/main/java/com/genius/todoffin/security/domain/SocialOAuth.java deleted file mode 100644 index 672afd5a..00000000 --- a/src/main/java/com/genius/todoffin/security/domain/SocialOAuth.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.genius.todoffin.security.domain; - - -public interface SocialOAuth { - String getOAuthRedirectURL(); -} diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java index 049ca8ae..752370a2 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java @@ -11,11 +11,14 @@ @Component public class OAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler { + private final String REDIRECT_URL = "http://localhost:3000"; + private final String ERROR_PARAM_PREFIX = "error"; + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String redirectUrl = UriComponentsBuilder.fromUriString("http://localhost:3000") - .queryParam("error", exception.getLocalizedMessage()) + String redirectUrl = UriComponentsBuilder.fromUriString(REDIRECT_URL) + .queryParam(ERROR_PARAM_PREFIX, exception.getLocalizedMessage()) .build() .toUriString(); diff --git a/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java index 2d9d1c27..15ec98b4 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java @@ -1,5 +1,8 @@ package com.genius.todoffin.security.info.impl; +import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthRule.FACEBOOK_PROVIDER_ID; + import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; @@ -11,12 +14,12 @@ public FacebookOAuth2UserInfo(Map attributes) { @Override public String getProviderId() { - return (String) attributes.get("sub"); + return (String) attributes.get(FACEBOOK_PROVIDER_ID.getValue()); } @Override public String getEmail() { - return (String) attributes.get("email"); + return (String) attributes.get(EMAIL_KEY.getValue()); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java index 578b66bc..46ed0519 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java @@ -1,8 +1,8 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthAttributeKey.EMAIL_KEY; -import static com.genius.todoffin.security.constants.OAuthAttributeKey.GOOGLE_PROVIDER_ID; +import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthRule.GOOGLE_PROVIDER_ID; import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; diff --git a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java index 9ce14725..c18fa0a2 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java @@ -1,7 +1,7 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthAttributeKey.EMAIL_KEY; -import static com.genius.todoffin.security.constants.OAuthAttributeKey.KAKAO_PROVIDER_ID; +import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthRule.KAKAO_PROVIDER_ID; import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; diff --git a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java index 8bcd05c2..986010e0 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java @@ -1,8 +1,8 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthAttributeKey.EMAIL_KEY; -import static com.genius.todoffin.security.constants.OAuthAttributeKey.NAVER_PROVIDER_ID; +import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthRule.NAVER_PROVIDER_ID; import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; diff --git a/src/main/java/com/genius/todoffin/user/entity/User.java b/src/main/java/com/genius/todoffin/user/entity/User.java index 292b9727..2bf42fa2 100644 --- a/src/main/java/com/genius/todoffin/user/entity/User.java +++ b/src/main/java/com/genius/todoffin/user/entity/User.java @@ -1,7 +1,7 @@ package com.genius.todoffin.user.entity; -import com.genius.todoffin.common.BaseTimeEntity; +import com.genius.todoffin.common.domain.BaseTimeEntity; import com.genius.todoffin.security.constants.ProviderType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -32,7 +32,7 @@ public class User extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Role role; - + @Column(unique = true, length = 16) private String nickname; From af5971f3cd09e23180c41eaeeaa9a8af3c48350e Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 14:09:03 +0900 Subject: [PATCH 029/234] =?UTF-8?q?feat:=20swagger=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 47 ++++++++++-------- .../todoffin/util/config/AppConfig.java | 20 ++++++++ .../todoffin/util/config/SwaggerConfig.java | 19 +++++++ .../todoffin/util/filter/CorsFilter.java | 49 ------------------- .../util/response/dto/CommonResult.java | 2 - 5 files changed, 65 insertions(+), 72 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/util/config/AppConfig.java create mode 100644 src/main/java/com/genius/todoffin/util/config/SwaggerConfig.java delete mode 100644 src/main/java/com/genius/todoffin/util/filter/CorsFilter.java diff --git a/build.gradle b/build.gradle index 846cd36c..be9ce443 100644 --- a/build.gradle +++ b/build.gradle @@ -1,41 +1,46 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.2.1' - id 'io.spring.dependency-management' version '1.1.4' + id 'java' + id 'org.springframework.boot' version '3.2.1' + id 'io.spring.dependency-management' version '1.1.4' } group = 'com.genius' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '17' + sourceCompatibility = '17' } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-websocket' - compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.security:spring-security-test' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-websocket' + + // swagger + // https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/java/com/genius/todoffin/util/config/AppConfig.java b/src/main/java/com/genius/todoffin/util/config/AppConfig.java new file mode 100644 index 00000000..5baef352 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/config/AppConfig.java @@ -0,0 +1,20 @@ +package com.genius.todoffin.util.config; + +import com.genius.todoffin.util.formatter.LocalDateFormatter; +import com.genius.todoffin.util.formatter.LocalDateTimeFormatter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { + + @Bean + public LocalDateFormatter localDateFormatter() { + return new LocalDateFormatter(); + } + + @Bean + public LocalDateTimeFormatter localDateTimeFormatter() { + return new LocalDateTimeFormatter(); + } +} diff --git a/src/main/java/com/genius/todoffin/util/config/SwaggerConfig.java b/src/main/java/com/genius/todoffin/util/config/SwaggerConfig.java new file mode 100644 index 00000000..f95dfbb7 --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/config/SwaggerConfig.java @@ -0,0 +1,19 @@ +package com.genius.todoffin.util.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("Todoffin API") + .description("TeamTheGenius의 todoffin API 문서입니다.") + .version("1.0.0")); + } +} diff --git a/src/main/java/com/genius/todoffin/util/filter/CorsFilter.java b/src/main/java/com/genius/todoffin/util/filter/CorsFilter.java deleted file mode 100644 index 2743579c..00000000 --- a/src/main/java/com/genius/todoffin/util/filter/CorsFilter.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.genius.todoffin.util.filter; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; - -@Component -@Order(Ordered.HIGHEST_PRECEDENCE) -public class CorsFilter implements Filter { - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) res; - - response.setHeader("Access-Control-Allow-Origin", "*"); // 모든 요청에 대해 허용 - response.setHeader("Access-Control-Allow-Credentials", "true"); - response.setHeader("Access-Control-Allow-Methods", "*"); - response.setHeader("Access-Control-Max-Age", "3600"); - response.setHeader("Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept, Authorization, Set-Cookie"); - - if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { - response.setStatus(HttpServletResponse.SC_OK); - } else { - chain.doFilter(req, res); - } - } - - @Override - public void destroy() { - - } -} diff --git a/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java b/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java index 78d1c305..c0e94dd3 100644 --- a/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java +++ b/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java @@ -2,12 +2,10 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.RequiredArgsConstructor; @Getter @AllArgsConstructor -@RequiredArgsConstructor public enum CommonResult { SUCCESS(1, "성공"), FAIL(0, "실패"); From 17db90d6c66f971665979df363e13d4861ffb84c Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 14:56:02 +0900 Subject: [PATCH 030/234] =?UTF-8?q?chore:=20swagger=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20URI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genius/todoffin/security/config/SecurityConfig.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 962a42d2..009d900f 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -18,8 +18,7 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - private static final String permitURI[] = {"/api/auth/**", "/swagger-ui.html", "/swagger-ui/**" - , "/v3/api-docs/**", "/v3/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**"}; + private static final String permitURI[] = {"/v3/**", "/swagger-ui/**"}; private static final String permittedRoles[] = {"USER", "ADMIN"}; private final CustomOAuth2UserService customOAuthService; private final OAuth2SuccessHandler successHandler; From d074cc947e66bcefe7b671b6ac7e9ea4ebe86d69 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 15:54:08 +0900 Subject: [PATCH 031/234] =?UTF-8?q?chore:=20=EC=96=B4=ED=94=8C=EB=A6=AC?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=85=98=20baseURL=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 21 ++++++++++++++++++- .../handler/OAuth2FailureHandler.java | 2 +- .../handler/OAuth2SuccessHandler.java | 4 ++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 009d900f..70386d69 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -3,6 +3,8 @@ import com.genius.todoffin.security.handler.OAuth2SuccessHandler; import com.genius.todoffin.security.service.CustomOAuth2UserService; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Collections; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,6 +13,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsUtils; @Configuration @@ -26,7 +30,22 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.csrf().disable() + http.cors(corsCustomizer -> corsCustomizer.configurationSource( + new CorsConfigurationSource() { + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(Collections.singletonList("http://localhost:5173")); + config.setAllowedMethods(Collections.singletonList("*")); + config.setAllowCredentials(true); + config.setAllowedHeaders(Collections.singletonList("*")); + config.setMaxAge(3600L); + return config; + } + } + ) + ) + .csrf().disable() .httpBasic().disable() .formLogin().disable() .anonymous().and() diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java index 752370a2..35de4bba 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java @@ -11,7 +11,7 @@ @Component public class OAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler { - private final String REDIRECT_URL = "http://localhost:3000"; + private final String REDIRECT_URL = "http://localhost:5173"; private final String ERROR_PARAM_PREFIX = "error"; @Override diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index 2489d808..c388a2e8 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -17,8 +17,8 @@ @Component @RequiredArgsConstructor public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private final String SIGNUP_URL = "http://localhost:3000/login/signup"; - private final String MAIN_URL = "http://localhost:3000/main"; + private final String SIGNUP_URL = "http://localhost:5173/login/signup"; + private final String MAIN_URL = "http://localhost:5173/main"; private final UserRepository userRepository; @Override From 748cda29f95d3557139fe694309856462f8d3e78 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Dec 2023 17:17:55 +0900 Subject: [PATCH 032/234] =?UTF-8?q?chore:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genius/todoffin/security/domain/UserPrincipal.java | 2 +- .../todoffin/security/handler/OAuth2SuccessHandler.java | 4 ++-- .../todoffin/security/service/CustomOAuth2UserService.java | 4 ++-- .../com/genius/todoffin/user/{entity => domain}/Role.java | 2 +- .../com/genius/todoffin/user/{entity => domain}/User.java | 2 +- .../com/genius/todoffin/user/repository/UserRepository.java | 2 +- .../genius/todoffin/user/repository/UserRepositoryTest.java | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) rename src/main/java/com/genius/todoffin/user/{entity => domain}/Role.java (89%) rename src/main/java/com/genius/todoffin/user/{entity => domain}/User.java (97%) diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java index 84aca46a..a7715cb7 100644 --- a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java @@ -1,6 +1,6 @@ package com.genius.todoffin.security.domain; -import com.genius.todoffin.user.entity.User; +import com.genius.todoffin.user.domain.User; import java.util.Collections; import java.util.Map; import lombok.Getter; diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index c388a2e8..c53b489b 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -1,7 +1,7 @@ package com.genius.todoffin.security.handler; -import com.genius.todoffin.user.entity.Role; -import com.genius.todoffin.user.entity.User; +import com.genius.todoffin.user.domain.Role; +import com.genius.todoffin.user.domain.User; import com.genius.todoffin.user.repository.UserRepository; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index 3dd326fc..e68cf9ab 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -4,8 +4,8 @@ import com.genius.todoffin.security.domain.UserPrincipal; import com.genius.todoffin.security.info.OAuth2UserInfo; import com.genius.todoffin.security.info.OAuth2UserInfoFactory; -import com.genius.todoffin.user.entity.Role; -import com.genius.todoffin.user.entity.User; +import com.genius.todoffin.user.domain.Role; +import com.genius.todoffin.user.domain.User; import com.genius.todoffin.user.repository.UserRepository; import jakarta.transaction.Transactional; import java.util.Map; diff --git a/src/main/java/com/genius/todoffin/user/entity/Role.java b/src/main/java/com/genius/todoffin/user/domain/Role.java similarity index 89% rename from src/main/java/com/genius/todoffin/user/entity/Role.java rename to src/main/java/com/genius/todoffin/user/domain/Role.java index 2b3c33fe..dd0e2528 100644 --- a/src/main/java/com/genius/todoffin/user/entity/Role.java +++ b/src/main/java/com/genius/todoffin/user/domain/Role.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.user.entity; +package com.genius.todoffin.user.domain; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/todoffin/user/entity/User.java b/src/main/java/com/genius/todoffin/user/domain/User.java similarity index 97% rename from src/main/java/com/genius/todoffin/user/entity/User.java rename to src/main/java/com/genius/todoffin/user/domain/User.java index 2bf42fa2..65152853 100644 --- a/src/main/java/com/genius/todoffin/user/entity/User.java +++ b/src/main/java/com/genius/todoffin/user/domain/User.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.user.entity; +package com.genius.todoffin.user.domain; import com.genius.todoffin.common.domain.BaseTimeEntity; diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java index 6f7a89da..9892016d 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -1,7 +1,7 @@ package com.genius.todoffin.user.repository; import com.genius.todoffin.security.constants.ProviderType; -import com.genius.todoffin.user.entity.User; +import com.genius.todoffin.user.domain.User; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java index 0360c4e7..c5bb5fe9 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -3,8 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import com.genius.todoffin.security.constants.ProviderType; -import com.genius.todoffin.user.entity.Role; -import com.genius.todoffin.user.entity.User; +import com.genius.todoffin.user.domain.Role; +import com.genius.todoffin.user.domain.User; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; From 8f0137fd9f749d09eeba6e9cb632240a7267b250 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 1 Jan 2024 01:51:23 +0900 Subject: [PATCH 033/234] =?UTF-8?q?chore:=20build.gradle=EC=97=90=EC=84=9C?= =?UTF-8?q?=20lombok=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test 코드에서도 사용할 수 있도록 의존성 추가 --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index be9ce443..4dfc2f74 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-websocket' // swagger - // https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' compileOnly 'org.projectlombok:lombok' @@ -39,6 +38,8 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { From 76b6c8f5aec2090e1b54b3512bfd1cfdf8c107ac Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 1 Jan 2024 02:22:58 +0900 Subject: [PATCH 034/234] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 소셜로그인 성공 이후 회원가입 이전 사용자를 대상으로, 회원가입을 진행하는 로직 개발 - FE로부터 배열 형태로 받은 관심사를 처리하는 Converter 클래스 구현 필요 --- .../genius/todoffin/TodoffinApplication.java | 2 + .../security/config/SecurityConfig.java | 2 +- .../handler/OAuth2SuccessHandler.java | 12 ++-- .../user/controller/UserController.java | 28 +++++++++ .../com/genius/todoffin/user/domain/User.java | 10 ++- .../todoffin/user/dto/SignupRequest.java | 13 ++++ .../todoffin/user/service/UserService.java | 46 ++++++++++++++ .../util/exception/BusinessException.java | 4 +- .../todoffin/util/exception/ErrorCode.java | 8 +-- .../todoffin/util/exception/SuccessCode.java | 22 +++++++ .../user/service/UserServiceTest.java | 62 +++++++++++++++++++ 11 files changed, 195 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/user/controller/UserController.java create mode 100644 src/main/java/com/genius/todoffin/user/dto/SignupRequest.java create mode 100644 src/main/java/com/genius/todoffin/user/service/UserService.java create mode 100644 src/main/java/com/genius/todoffin/util/exception/SuccessCode.java create mode 100644 src/test/java/com/genius/todoffin/user/service/UserServiceTest.java diff --git a/src/main/java/com/genius/todoffin/TodoffinApplication.java b/src/main/java/com/genius/todoffin/TodoffinApplication.java index b2d10dd4..a08c43d4 100644 --- a/src/main/java/com/genius/todoffin/TodoffinApplication.java +++ b/src/main/java/com/genius/todoffin/TodoffinApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class TodoffinApplication { public static void main(String[] args) { SpringApplication.run(TodoffinApplication.class, args); diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 70386d69..5179e0b1 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -22,7 +22,7 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - private static final String permitURI[] = {"/v3/**", "/swagger-ui/**"}; + private static final String permitURI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; private static final String permittedRoles[] = {"USER", "ADMIN"}; private final CustomOAuth2UserService customOAuthService; private final OAuth2SuccessHandler successHandler; diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index c53b489b..4ffa458c 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -1,8 +1,12 @@ package com.genius.todoffin.security.handler; +import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; + import com.genius.todoffin.user.domain.Role; import com.genius.todoffin.user.domain.User; import com.genius.todoffin.user.repository.UserRepository; +import com.genius.todoffin.util.exception.BusinessException; +import com.genius.todoffin.util.exception.ErrorCode; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -27,18 +31,18 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); String email = oAuth2User.getName(); - //TODO: 추후 Utils의 ErrorCode를 활용하여 orElseThrow에 에러 코드 넣기 User user = userRepository.findByEmail(email) - .orElseThrow(); + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); Role role = user.getRole(); - String redirectUrl = getRedirectUrlByRole(role); + String redirectUrl = getRedirectUrlByRole(role, email); getRedirectStrategy().sendRedirect(request, response, redirectUrl); } - private String getRedirectUrlByRole(Role role) { + private String getRedirectUrlByRole(Role role, String email) { if (role == Role.NOT_REGISTERED) { return UriComponentsBuilder.fromUriString(SIGNUP_URL) + .queryParam(EMAIL_KEY.getValue(), email) .build() .toUriString(); } diff --git a/src/main/java/com/genius/todoffin/user/controller/UserController.java b/src/main/java/com/genius/todoffin/user/controller/UserController.java new file mode 100644 index 00000000..f40e4d11 --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/controller/UserController.java @@ -0,0 +1,28 @@ +package com.genius.todoffin.user.controller; + +import static com.genius.todoffin.util.exception.SuccessCode.CREATED; + +import com.genius.todoffin.user.dto.SignupRequest; +import com.genius.todoffin.user.service.UserService; +import com.genius.todoffin.util.response.dto.CommonResponse; +import lombok.RequiredArgsConstructor; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class UserController { + private final UserService userService; + + @PostMapping("/auth/signup") + public ResponseEntity signup(@RequestBody SignupRequest signupRequest) { + userService.signup(signupRequest); + return ResponseEntity.ok().body( + new CommonResponse(CREATED.getStatus(), CREATED.getMessage()) + ); + } +} diff --git a/src/main/java/com/genius/todoffin/user/domain/User.java b/src/main/java/com/genius/todoffin/user/domain/User.java index 65152853..2a186217 100644 --- a/src/main/java/com/genius/todoffin/user/domain/User.java +++ b/src/main/java/com/genius/todoffin/user/domain/User.java @@ -27,6 +27,7 @@ public class User extends BaseTimeEntity { @NotNull @Enumerated(EnumType.STRING) private ProviderType provider; + @NotNull private String email; @@ -52,8 +53,13 @@ public User(ProviderType provider, String email, Role role, String nickname, Str this.interest = interest; } + public void updateUser(String nickname, String information, String interest) { + this.nickname = nickname; + this.information = information; + this.interest = interest; + } - public String getAuthorities() { - return role.getKey(); + public void updateRole(Role role) { + this.role = role; } } diff --git a/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java b/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java new file mode 100644 index 00000000..f5de661e --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java @@ -0,0 +1,13 @@ +package com.genius.todoffin.user.dto; + +import java.util.List; +import lombok.Builder; + +@Builder +public record SignupRequest( + String email, + String nickname, + String information, + List interest +) { +} diff --git a/src/main/java/com/genius/todoffin/user/service/UserService.java b/src/main/java/com/genius/todoffin/user/service/UserService.java new file mode 100644 index 00000000..7179194a --- /dev/null +++ b/src/main/java/com/genius/todoffin/user/service/UserService.java @@ -0,0 +1,46 @@ +package com.genius.todoffin.user.service; + +import com.genius.todoffin.user.domain.Role; +import com.genius.todoffin.user.domain.User; +import com.genius.todoffin.user.dto.SignupRequest; +import com.genius.todoffin.user.repository.UserRepository; +import com.genius.todoffin.util.exception.BusinessException; +import com.genius.todoffin.util.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Slf4j +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class UserService { + private final UserRepository userRepository; + + + public User findUserById(Long id) { + return userRepository.findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + } + + public User findUserByEmail(String email) { + return userRepository.findByEmail(email) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + } + + @Transactional + public Long signup(SignupRequest requestUser) { + User targetUser = findUserByEmail(requestUser.email()); + + //TODO: Converter 클래스 만들어서 적용하기 + String interest = String.join(",", requestUser.interest()); + + targetUser.updateUser(requestUser.nickname(), + requestUser.information(), + interest); + targetUser.updateRole(Role.USER); + + return targetUser.getId(); + } +} diff --git a/src/main/java/com/genius/todoffin/util/exception/BusinessException.java b/src/main/java/com/genius/todoffin/util/exception/BusinessException.java index 9a288c09..1f97d488 100644 --- a/src/main/java/com/genius/todoffin/util/exception/BusinessException.java +++ b/src/main/java/com/genius/todoffin/util/exception/BusinessException.java @@ -10,9 +10,9 @@ public BusinessException() { super(); } - public BusinessException(HttpStatus status, ErrorCode errorCode) { + public BusinessException(ErrorCode errorCode) { super(errorCode.getMessage()); - this.status = status; + this.status = errorCode.getStatus(); } public BusinessException(String message) { diff --git a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java index d61ffe3a..54ef3c80 100644 --- a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java @@ -2,16 +2,14 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; @Getter @RequiredArgsConstructor public enum ErrorCode { - UNDEFINED_ERROR("미정의에러"), - NO_AUTHORITY("접근권한이 없습니다."), - ACCESS_DENIED("접근이 거부되었습니다."), - DATA_ERROR_NOT_FOUND("해당 데이터를 찾을 수 없습니다."), - MEMBER_NOT_FOUND("로그인 정보를 찾을 수 없습니다."); + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."); + private final HttpStatus status; private final String message; } diff --git a/src/main/java/com/genius/todoffin/util/exception/SuccessCode.java b/src/main/java/com/genius/todoffin/util/exception/SuccessCode.java new file mode 100644 index 00000000..93e5c4cd --- /dev/null +++ b/src/main/java/com/genius/todoffin/util/exception/SuccessCode.java @@ -0,0 +1,22 @@ +package com.genius.todoffin.util.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum SuccessCode { + + // 200 OK + SUCCESS(HttpStatus.OK, "OK", "요청이 정상적으로 처리되었습니다.") + + // 201 CREATED + , CREATED(HttpStatus.CREATED, "CREATED", "정상적으로 생성되었습니다."); + + + private final HttpStatus status; + private final String key; + private final String message; +} + diff --git a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java new file mode 100644 index 00000000..525fd240 --- /dev/null +++ b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java @@ -0,0 +1,62 @@ +package com.genius.todoffin.user.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.user.domain.Role; +import com.genius.todoffin.user.domain.User; +import com.genius.todoffin.user.dto.SignupRequest; +import com.genius.todoffin.user.repository.UserRepository; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Slf4j +class UserServiceTest { + @Autowired + private UserRepository userRepository; + + @Autowired + private UserService userService; + + @Test + @DisplayName("특정 사용자 가입 테스트") + public void should_matchValues_when_signupUser() { + //given + String email = "test@naver.com"; + saveUnsignedUser(); + SignupRequest signupRequest = SignupRequest.builder() + .email(email) + .nickname("nickname") + .information("information") + .interest("interest") + .build(); + + //when + User user = userService.findUserByEmail(email); + + Long signupUserId = userService.signup(signupRequest); + User foundUser = userService.findUserById(signupUserId); + + //then + assertThat(user.getEmail()).isEqualTo(foundUser.getEmail()); + assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); + assertThat(user.getProvider()).isEqualTo(foundUser.getProvider()); + assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); + assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); + } + + + private void saveUnsignedUser() { + userRepository.save(User.builder() + .role(Role.NOT_REGISTERED) + .provider(ProviderType.NAVER) + .email("test@naver.com") + .build()); + } +} \ No newline at end of file From 09330b408572662244ddfe08b75661e9d42cd470 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 1 Jan 2024 13:45:06 +0900 Subject: [PATCH 035/234] =?UTF-8?q?chore:=20final=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=84=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todoffin/security/config/SecurityConfig.java | 8 ++++---- .../todoffin/util/response/dto/CommonResult.java | 15 --------------- 2 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 5179e0b1..007d175e 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -22,8 +22,8 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - private static final String permitURI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; - private static final String permittedRoles[] = {"USER", "ADMIN"}; + private static final String PERMIT_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; + private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomOAuth2UserService customOAuthService; private final OAuth2SuccessHandler successHandler; @@ -51,8 +51,8 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { .anonymous().and() .authorizeHttpRequests(request -> request .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .requestMatchers(permitURI).permitAll() - .anyRequest().hasAnyRole(permittedRoles)) + .requestMatchers(PERMIT_URI).permitAll() + .anyRequest().hasAnyRole(PERMITTED_ROLES)) // JWT 사용으로 인한 세션 미사용 .sessionManagement(configurer -> configurer diff --git a/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java b/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java deleted file mode 100644 index c0e94dd3..00000000 --- a/src/main/java/com/genius/todoffin/util/response/dto/CommonResult.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.genius.todoffin.util.response.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; - - -@Getter -@AllArgsConstructor -public enum CommonResult { - SUCCESS(1, "성공"), - FAIL(0, "실패"); - - private final int code; - private final String message; -} \ No newline at end of file From 6cea0ac9e1170ca19ea6277ddc78a22f8cd392a6 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 1 Jan 2024 14:15:19 +0900 Subject: [PATCH 036/234] =?UTF-8?q?refactor:=20BusinessExceptionHandler?= =?UTF-8?q?=EC=9D=98=20=EB=B0=98=ED=99=98=ED=83=80=EC=9E=85=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 --- .../genius/todoffin/util/exception/BusinessException.java | 3 ++- .../todoffin/util/exception/BusinessExceptionHandler.java | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/genius/todoffin/util/exception/BusinessException.java b/src/main/java/com/genius/todoffin/util/exception/BusinessException.java index 1f97d488..15f09cac 100644 --- a/src/main/java/com/genius/todoffin/util/exception/BusinessException.java +++ b/src/main/java/com/genius/todoffin/util/exception/BusinessException.java @@ -1,8 +1,9 @@ package com.genius.todoffin.util.exception; +import lombok.Getter; import org.springframework.http.HttpStatus; - +@Getter public class BusinessException extends RuntimeException { private HttpStatus status; diff --git a/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java b/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java index 4547acf4..dcc62143 100644 --- a/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java +++ b/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java @@ -3,7 +3,7 @@ import com.genius.todoffin.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -12,9 +12,11 @@ @RequiredArgsConstructor public class BusinessExceptionHandler { @ExceptionHandler(BusinessException.class) - protected CommonResponse globalBusinessExceptionHandler(BusinessException e) { + protected ResponseEntity globalBusinessExceptionHandler(BusinessException e) { log.info("[ERROR]" + e.getMessage(), e); - return new CommonResponse(HttpStatus.BAD_REQUEST, e.getMessage()); + return ResponseEntity.badRequest().body( + new CommonResponse(e.getStatus(), e.getMessage()) + ); } } From 61d873c68b7441a487732328f2e34a7c778c9fd6 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 2 Jan 2024 00:57:13 +0900 Subject: [PATCH 037/234] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EA=B0=9D=EC=B2=B4=EC=97=90=20=EA=B0=92=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todoffin/util/exception/BusinessExceptionHandler.java | 2 +- .../genius/todoffin/util/response/dto/CommonResponse.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java b/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java index dcc62143..31faa710 100644 --- a/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java +++ b/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java @@ -13,7 +13,7 @@ public class BusinessExceptionHandler { @ExceptionHandler(BusinessException.class) protected ResponseEntity globalBusinessExceptionHandler(BusinessException e) { - log.info("[ERROR]" + e.getMessage(), e); + log.error("[ERROR]" + e.getMessage(), e); return ResponseEntity.badRequest().body( new CommonResponse(e.getStatus(), e.getMessage()) diff --git a/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java b/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java index 86d1bfce..7cddc8e5 100644 --- a/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java +++ b/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java @@ -13,5 +13,12 @@ @NoArgsConstructor public class CommonResponse { private HttpStatus code; + private int resultCode; private String message; + + public CommonResponse(HttpStatus code, String message) { + this.code = code; + this.resultCode = code.value(); + this.message = message; + } } \ No newline at end of file From f03d1f7742cc4dd13a756903a1446188752b90b2 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 2 Jan 2024 01:06:37 +0900 Subject: [PATCH 038/234] Update issue templates --- .github/ISSUE_TEMPLATE/feature_request.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9e92dd98..3ed523c0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -8,15 +8,15 @@ assignees: '' --- ### Issue 타입(하나 이상의 Issue 타입을 선택해주세요) -- [X] 기능 추가 -- [ ] 기능 삭제 -- [ ] 버그 리포트 -- [ ] 버그 수정 -- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 +☑ 기능 추가 +□ 기능 삭제 +□ 버그 리포트 +□ 버그 수정 +□ 의존성, 환경 변수, 빌드 관련 코드 업데이트 ### 상세 내용 #### 어떤 기능인가요? -\> 추가하려는 기능에 대해 간결하게 설명해주세요 +> 추가하려는 기능에 대해 간결하게 설명해주세요 #### 작업 상세 내용 - [ ] TO DO From 7f1fdd0d98afbd9c87845532fc4e1e766af3d5d2 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 2 Jan 2024 11:55:45 +0900 Subject: [PATCH 039/234] =?UTF-8?q?chore:=20jwt=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 4dfc2f74..f999a9c1 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,9 @@ dependencies { // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + //JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' From 9dfc46d82297b3bbfc9c57098b57b1f7d0d81d03 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Wed, 3 Jan 2024 12:17:29 +0900 Subject: [PATCH 040/234] =?UTF-8?q?feat:=20Security=20Context=EC=97=90=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A5=BC=20=EB=8B=B4=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UserPrincipal가 DefaultOAuth2User 상속 -> OAuth2User 인터페이스 상속으로 변경 - UserPrincipal 클래스에 UserDetails 인터페이스 상속 - 인터페이스 상속으로 인한 메서드 구현 - OAuth2User의 getName(): email 반환 - UserDetails의 getUsername(): User PK를 String 형으로 반환 --- .../security/domain/UserPrincipal.java | 58 +++++++++++++++++-- .../service/CustomUserDetailsService.java | 27 +++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/service/CustomUserDetailsService.java diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java index a7715cb7..84b17944 100644 --- a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java @@ -1,25 +1,73 @@ package com.genius.todoffin.security.domain; import com.genius.todoffin.user.domain.User; +import java.util.Collection; import java.util.Collections; import java.util.Map; import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.user.OAuth2User; @Getter -public class UserPrincipal extends DefaultOAuth2User { +public class UserPrincipal implements UserDetails, OAuth2User { + private User user; + private String nameAttributeKey; + private Map attributes; + private Collection authorities; + + public UserPrincipal(User user) { + this.user = user; + this.authorities = Collections.singletonList(new SimpleGrantedAuthority(user.getRole().getKey())); + } public UserPrincipal(User user, Map attributes, String nameAttributeKey) { - super(Collections.singletonList(new SimpleGrantedAuthority(user.getRole().getKey())), - attributes, - nameAttributeKey); this.user = user; + this.authorities = Collections.singletonList(new SimpleGrantedAuthority(user.getRole().getKey())); + this.attributes = attributes; + this.nameAttributeKey = nameAttributeKey; } + /** + * OAuth2User method implements + */ @Override public String getName() { return user.getEmail(); } + + /** + * UserDetails method implements + */ + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return String.valueOf(user.getId()); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } } diff --git a/src/main/java/com/genius/todoffin/security/service/CustomUserDetailsService.java b/src/main/java/com/genius/todoffin/security/service/CustomUserDetailsService.java new file mode 100644 index 00000000..2000ef22 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/service/CustomUserDetailsService.java @@ -0,0 +1,27 @@ +package com.genius.todoffin.security.service; + +import static com.genius.todoffin.util.exception.ErrorCode.MEMBER_NOT_FOUND; + +import com.genius.todoffin.security.domain.UserPrincipal; +import com.genius.todoffin.user.domain.User; +import com.genius.todoffin.user.repository.UserRepository; +import com.genius.todoffin.util.exception.BusinessException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findById(Long.valueOf(username)) + .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); + + return new UserPrincipal(user); + } +} From d71bfe4970ac3df816e4b4774edee3c87095f588 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Wed, 3 Jan 2024 12:19:15 +0900 Subject: [PATCH 041/234] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genius/todoffin/security/handler/OAuth2SuccessHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index 4ffa458c..789f7ded 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -33,9 +33,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo User user = userRepository.findByEmail(email) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - Role role = user.getRole(); - String redirectUrl = getRedirectUrlByRole(role, email); + String redirectUrl = getRedirectUrlByRole(user.getRole(), email); getRedirectStrategy().sendRedirect(request, response, redirectUrl); } From 6de4dbfc9dbd764729afea474ddd659636e5d955 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Wed, 3 Jan 2024 14:50:44 +0900 Subject: [PATCH 042/234] =?UTF-8?q?feat:=20refresh=20token=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service/JwtTokenUtil.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/security/service/JwtTokenUtil.java diff --git a/src/main/java/com/genius/todoffin/security/service/JwtTokenUtil.java b/src/main/java/com/genius/todoffin/security/service/JwtTokenUtil.java new file mode 100644 index 00000000..01d67358 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/service/JwtTokenUtil.java @@ -0,0 +1,58 @@ +package com.genius.todoffin.security.service; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class JwtTokenUtil { + private final String ACCESS_SECRET; + private final long ACCESS_EXPIRATION; + private final String REFRESH_SECRET; + private final long REFRESH_EXPIRATION; + + + public JwtTokenUtil( + @Value("${jwt.access-secret}") String ACCESS_SECRET, + @Value("${jwt.refresh-secret}") String REFRESH_SECRET, + @Value("${jwt.access-expiration}") long ACCESS_EXPIRATION, + @Value("${jwt.refresh-expiration}") long REFRESH_EXPIRATION + ) { + this.ACCESS_SECRET = ACCESS_SECRET; + this.REFRESH_SECRET = REFRESH_SECRET; + this.ACCESS_EXPIRATION = ACCESS_EXPIRATION; + this.REFRESH_EXPIRATION = REFRESH_EXPIRATION; + } + + + public String generateRefreshToken() { + Long now = System.currentTimeMillis(); + + return Jwts.builder() + .setHeader(createHeader()) + .setIssuedAt(new Date()) + .setExpiration(new Date(now + REFRESH_EXPIRATION)) + .signWith(getSecretKey(REFRESH_SECRET), SignatureAlgorithm.HS512) + .compact(); + } + + private Map createHeader() { + Map header = new HashMap<>(); + header.put("typ", "JWT"); + header.put("alg", "HS512"); + return header; + } + + private Key getSecretKey(String secretKey) { + return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } +} From edb1c2f2048014ab5cda2bb9c690140bdafe0216 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 9 Jan 2024 14:40:43 +0900 Subject: [PATCH 043/234] =?UTF-8?q?feat:=20Github=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Github 소셜로그인 추가 - Github 소셜로그인 추가에 따라, Spring security에서 Authentication을 찾는데에 사용하는 값 변경 --- .../security/constants/OAuthRule.java | 9 ++++--- .../security/constants/ProviderType.java | 1 + .../security/domain/UserPrincipal.java | 2 +- .../handler/OAuth2SuccessHandler.java | 10 ++++---- .../security/info/OAuth2UserInfo.java | 4 +-- .../security/info/OAuth2UserInfoFactory.java | 8 +++--- .../info/impl/FacebookOAuth2UserInfo.java | 25 ------------------- .../info/impl/GithubOAuth2UserInfo.java | 24 ++++++++++++++++++ .../info/impl/GoogleOAuth2UserInfo.java | 8 +++--- .../info/impl/KakaoOAuth2UserInfo.java | 8 +++--- .../info/impl/NaverOAuth2UserInfo.java | 8 +++--- .../service/CustomOAuth2UserService.java | 16 ++++++------ .../com/genius/todoffin/user/domain/User.java | 6 ++--- .../user/repository/UserRepository.java | 6 ++--- .../todoffin/user/service/UserService.java | 6 ++--- .../user/repository/UserRepositoryTest.java | 4 +-- .../user/service/UserServiceTest.java | 6 ++--- 17 files changed, 77 insertions(+), 74 deletions(-) delete mode 100644 src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java create mode 100644 src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java diff --git a/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java b/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java index 25adc75b..7828d449 100644 --- a/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java +++ b/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java @@ -6,11 +6,14 @@ @Getter @RequiredArgsConstructor public enum OAuthRule { + COMMON_USER_KEY("email"), + + GITHUB_PROVIDER_ID("id"), + GITHUB_USER_IDENTIFIER("login"), + KAKAO_PROVIDER_ID("id"), NAVER_PROVIDER_ID("id"), - GOOGLE_PROVIDER_ID("sub"), - FACEBOOK_PROVIDER_ID("id"), - EMAIL_KEY("email"); + GOOGLE_PROVIDER_ID("sub"); private final String value; } diff --git a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java b/src/main/java/com/genius/todoffin/security/constants/ProviderType.java index eedef71b..38d46c36 100644 --- a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java +++ b/src/main/java/com/genius/todoffin/security/constants/ProviderType.java @@ -3,6 +3,7 @@ import java.util.Arrays; public enum ProviderType { + GITHUB, KAKAO, NAVER, GOOGLE, diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java index 84b17944..4b810ffc 100644 --- a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java @@ -35,7 +35,7 @@ public UserPrincipal(User user, Map attributes, String nameAttri */ @Override public String getName() { - return user.getEmail(); + return user.getIdentifier(); } /** diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index 789f7ded..98f3997b 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -1,6 +1,6 @@ package com.genius.todoffin.security.handler; -import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthRule.COMMON_USER_KEY; import com.genius.todoffin.user.domain.Role; import com.genius.todoffin.user.domain.User; @@ -29,19 +29,19 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); - String email = oAuth2User.getName(); + String identifier = oAuth2User.getName(); - User user = userRepository.findByEmail(email) + User user = userRepository.findByIdentifier(identifier) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - String redirectUrl = getRedirectUrlByRole(user.getRole(), email); + String redirectUrl = getRedirectUrlByRole(user.getRole(), identifier); getRedirectStrategy().sendRedirect(request, response, redirectUrl); } private String getRedirectUrlByRole(Role role, String email) { if (role == Role.NOT_REGISTERED) { return UriComponentsBuilder.fromUriString(SIGNUP_URL) - .queryParam(EMAIL_KEY.getValue(), email) + .queryParam(COMMON_USER_KEY.getValue(), email) .build() .toUriString(); } diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java index 8589fafa..d8c01f9e 100644 --- a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java @@ -10,7 +10,7 @@ public abstract class OAuth2UserInfo { protected Map attributes; - public abstract String getProviderId(); + public abstract String getProviderCode(); - public abstract String getEmail(); + public abstract String getUserIdentifier(); } diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java index 07324de8..c59c8c73 100644 --- a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java +++ b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java @@ -1,7 +1,7 @@ package com.genius.todoffin.security.info; import com.genius.todoffin.security.constants.ProviderType; -import com.genius.todoffin.security.info.impl.FacebookOAuth2UserInfo; +import com.genius.todoffin.security.info.impl.GithubOAuth2UserInfo; import com.genius.todoffin.security.info.impl.GoogleOAuth2UserInfo; import com.genius.todoffin.security.info.impl.KakaoOAuth2UserInfo; import com.genius.todoffin.security.info.impl.NaverOAuth2UserInfo; @@ -11,6 +11,9 @@ public class OAuth2UserInfoFactory { public static OAuth2UserInfo getOAuth2UserInfo(ProviderType providerType, Map attributes) { switch (providerType) { + case GITHUB -> { + return new GithubOAuth2UserInfo(attributes); + } case KAKAO -> { return new KakaoOAuth2UserInfo(attributes); } @@ -20,9 +23,6 @@ public static OAuth2UserInfo getOAuth2UserInfo(ProviderType providerType, Map { return new GoogleOAuth2UserInfo(attributes); } - case FACEBOOK -> { - return new FacebookOAuth2UserInfo(attributes); - } } throw new OAuth2AuthenticationException("INVALID PROVIDER TYPE"); } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java deleted file mode 100644 index 15ec98b4..00000000 --- a/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.genius.todoffin.security.info.impl; - -import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; -import static com.genius.todoffin.security.constants.OAuthRule.FACEBOOK_PROVIDER_ID; - -import com.genius.todoffin.security.info.OAuth2UserInfo; -import java.util.Map; - -public class FacebookOAuth2UserInfo extends OAuth2UserInfo { - - public FacebookOAuth2UserInfo(Map attributes) { - super(attributes); - } - - @Override - public String getProviderId() { - return (String) attributes.get(FACEBOOK_PROVIDER_ID.getValue()); - } - - @Override - public String getEmail() { - return (String) attributes.get(EMAIL_KEY.getValue()); - } -} - diff --git a/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java new file mode 100644 index 00000000..08f0b948 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java @@ -0,0 +1,24 @@ +package com.genius.todoffin.security.info.impl; + +import static com.genius.todoffin.security.constants.OAuthRule.GITHUB_PROVIDER_ID; +import static com.genius.todoffin.security.constants.OAuthRule.GITHUB_USER_IDENTIFIER; + +import com.genius.todoffin.security.info.OAuth2UserInfo; +import java.util.Map; + +public class GithubOAuth2UserInfo extends OAuth2UserInfo { + + public GithubOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getProviderCode() { + return (String) attributes.get(GITHUB_PROVIDER_ID.getValue()); + } + + @Override + public String getUserIdentifier() { + return (String) attributes.get(GITHUB_USER_IDENTIFIER.getValue()); + } +} diff --git a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java index 46ed0519..be5fb8c6 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java @@ -1,7 +1,7 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthRule.COMMON_USER_KEY; import static com.genius.todoffin.security.constants.OAuthRule.GOOGLE_PROVIDER_ID; import com.genius.todoffin.security.info.OAuth2UserInfo; @@ -14,12 +14,12 @@ public GoogleOAuth2UserInfo(Map attributes) { } @Override - public String getProviderId() { + public String getProviderCode() { return (String) attributes.get(GOOGLE_PROVIDER_ID.getValue()); } @Override - public String getEmail() { - return (String) attributes.get(EMAIL_KEY.getValue()); + public String getUserIdentifier() { + return (String) attributes.get(COMMON_USER_KEY.getValue()); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java index c18fa0a2..3dc68701 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java @@ -1,6 +1,6 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthRule.COMMON_USER_KEY; import static com.genius.todoffin.security.constants.OAuthRule.KAKAO_PROVIDER_ID; import com.genius.todoffin.security.info.OAuth2UserInfo; @@ -16,12 +16,12 @@ public KakaoOAuth2UserInfo(Map attributes) { } @Override - public String getProviderId() { + public String getProviderCode() { return providerId; } @Override - public String getEmail() { - return (String) attributes.get(EMAIL_KEY.getValue()); + public String getUserIdentifier() { + return (String) attributes.get(COMMON_USER_KEY.getValue()); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java index 986010e0..38293b5c 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java @@ -1,7 +1,7 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; +import static com.genius.todoffin.security.constants.OAuthRule.COMMON_USER_KEY; import static com.genius.todoffin.security.constants.OAuthRule.NAVER_PROVIDER_ID; import com.genius.todoffin.security.info.OAuth2UserInfo; @@ -15,12 +15,12 @@ public NaverOAuth2UserInfo(Map attributes) { } @Override - public String getProviderId() { + public String getProviderCode() { return (String) attributes.get(NAVER_PROVIDER_ID.getValue()); } @Override - public String getEmail() { - return (String) attributes.get(EMAIL_KEY.getValue()); + public String getUserIdentifier() { + return (String) attributes.get(COMMON_USER_KEY.getValue()); } } diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index e68cf9ab..fc4bcb3c 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -37,26 +37,26 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint() .getUserNameAttributeName(); - // 서비스를 구분하는 코드 - String providerId = userRequest.getClientRegistration().getRegistrationId(); + // 서비스를 구분하는 코드 ex) Github, Naver + String providerCode = userRequest.getClientRegistration().getRegistrationId(); - ProviderType providerType = ProviderType.from(providerId); + ProviderType providerType = ProviderType.from(providerCode); Map attributes = oAuth2User.getAttributes(); OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, attributes); - String email = oAuth2UserInfo.getEmail(); + String userIdentifier = oAuth2UserInfo.getUserIdentifier(); - User user = getUser(email, providerType); + User user = getUser(userIdentifier, providerType); return new UserPrincipal(user, attributes, userNameAttributeName); } - private User getUser(String email, ProviderType providerType) { - Optional optionalUser = userRepository.findByOAuthInfo(email, providerType); + private User getUser(String userIdentifier, ProviderType providerType) { + Optional optionalUser = userRepository.findByOAuthInfo(userIdentifier, providerType); if (optionalUser.isEmpty()) { User unregisteredUser = User.builder() - .email(email) + .identifier(userIdentifier) .role(Role.NOT_REGISTERED) .provider(providerType) .build(); diff --git a/src/main/java/com/genius/todoffin/user/domain/User.java b/src/main/java/com/genius/todoffin/user/domain/User.java index 2a186217..f0dbc3ec 100644 --- a/src/main/java/com/genius/todoffin/user/domain/User.java +++ b/src/main/java/com/genius/todoffin/user/domain/User.java @@ -29,7 +29,7 @@ public class User extends BaseTimeEntity { private ProviderType provider; @NotNull - private String email; + private String identifier; @Enumerated(EnumType.STRING) private Role role; @@ -43,10 +43,10 @@ public class User extends BaseTimeEntity { @Builder - public User(ProviderType provider, String email, Role role, String nickname, String information, + public User(ProviderType provider, String identifier, Role role, String nickname, String information, String interest) { this.provider = provider; - this.email = email; + this.identifier = identifier; this.role = role; this.nickname = nickname; this.information = information; diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java index 9892016d..55efa5ba 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -8,8 +8,8 @@ import org.springframework.data.repository.query.Param; public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); + Optional findByIdentifier(String identifier); - @Query("select u from User u where u.email = :email and u.provider = :provider") - Optional findByOAuthInfo(@Param("email") String email, @Param("provider") ProviderType provider); + @Query("select u from User u where u.identifier = :identifier and u.provider = :provider") + Optional findByOAuthInfo(@Param("identifier") String identifier, @Param("provider") ProviderType provider); } diff --git a/src/main/java/com/genius/todoffin/user/service/UserService.java b/src/main/java/com/genius/todoffin/user/service/UserService.java index 7179194a..6b4ea157 100644 --- a/src/main/java/com/genius/todoffin/user/service/UserService.java +++ b/src/main/java/com/genius/todoffin/user/service/UserService.java @@ -24,14 +24,14 @@ public User findUserById(Long id) { .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); } - public User findUserByEmail(String email) { - return userRepository.findByEmail(email) + public User findUserByIdentifier(String email) { + return userRepository.findByIdentifier(email) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); } @Transactional public Long signup(SignupRequest requestUser) { - User targetUser = findUserByEmail(requestUser.email()); + User targetUser = findUserByIdentifier(requestUser.email()); //TODO: Converter 클래스 만들어서 적용하기 String interest = String.join(",", requestUser.interest()); diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java index c5bb5fe9..9f291b4a 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -32,7 +32,7 @@ class UserRepositoryTest { //then assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); - assertThat(savedUser.getEmail()).isEqualTo(foundUser.getEmail()); + assertThat(savedUser.getIdentifier()).isEqualTo(foundUser.getIdentifier()); assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); } @@ -52,7 +52,7 @@ class UserRepositoryTest { //then assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); - assertThat(savedUser.getEmail()).isEqualTo(foundUser.getEmail()); + assertThat(savedUser.getIdentifier()).isEqualTo(foundUser.getIdentifier()); assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); } diff --git a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java index 525fd240..8df5c574 100644 --- a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java @@ -7,6 +7,7 @@ import com.genius.todoffin.user.domain.User; import com.genius.todoffin.user.dto.SignupRequest; import com.genius.todoffin.user.repository.UserRepository; +import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -34,7 +35,7 @@ public void should_matchValues_when_signupUser() { .email(email) .nickname("nickname") .information("information") - .interest("interest") + .interest(List.of("관심사1", "관심사2")) .build(); //when @@ -42,9 +43,8 @@ public void should_matchValues_when_signupUser() { Long signupUserId = userService.signup(signupRequest); User foundUser = userService.findUserById(signupUserId); - //then - assertThat(user.getEmail()).isEqualTo(foundUser.getEmail()); + assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); assertThat(user.getProvider()).isEqualTo(foundUser.getProvider()); assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); From 2b93f74ed713bb4142925d1da07b817fcb49186f Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 11 Jan 2024 00:53:31 +0900 Subject: [PATCH 044/234] =?UTF-8?q?feat:=20JWT=20=EB=B0=9C=EA=B8=89=20API?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C,=20access=20&=20refresh=20tok?= =?UTF-8?q?en=20=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JwtAuthorizationFilter 세부 구현 필요 - JwtService 내의 매직 넘버 처리 필요 --- build.gradle | 2 + .../security/config/SecurityConfig.java | 8 ++- .../security/controller/AuthController.java | 38 +++++++++++ .../todoffin/security/dto/TokenRequest.java | 4 ++ .../filter/JwtAuthenticationFilter.java | 21 ++++++ .../security/service/JwtGenerator.java | 64 +++++++++++++++++++ .../todoffin/security/service/JwtService.java | 49 ++++++++++++++ .../security/service/JwtTokenUtil.java | 58 ----------------- .../todoffin/user/service/UserService.java | 4 +- .../user/service/UserServiceTest.java | 4 +- 10 files changed, 189 insertions(+), 63 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/controller/AuthController.java create mode 100644 src/main/java/com/genius/todoffin/security/dto/TokenRequest.java create mode 100644 src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/genius/todoffin/security/service/JwtGenerator.java create mode 100644 src/main/java/com/genius/todoffin/security/service/JwtService.java delete mode 100644 src/main/java/com/genius/todoffin/security/service/JwtTokenUtil.java diff --git a/build.gradle b/build.gradle index f999a9c1..e9c65466 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,8 @@ dependencies { //JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.10.7' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.7' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 007d175e..64c657aa 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -1,6 +1,7 @@ package com.genius.todoffin.security.config; +import com.genius.todoffin.security.filter.JwtAuthenticationFilter; import com.genius.todoffin.security.handler.OAuth2SuccessHandler; import com.genius.todoffin.security.service.CustomOAuth2UserService; import jakarta.servlet.http.HttpServletRequest; @@ -13,6 +14,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsUtils; @@ -22,6 +24,7 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { + private static final String ALLOWED_ORIGIN = "http://localhost:5173"; private static final String PERMIT_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomOAuth2UserService customOAuthService; @@ -35,7 +38,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @Override public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(Collections.singletonList("http://localhost:5173")); + config.setAllowedOrigins(Collections.singletonList(ALLOWED_ORIGIN)); config.setAllowedMethods(Collections.singletonList("*")); config.setAllowCredentials(true); config.setAllowedHeaders(Collections.singletonList("*")); @@ -58,6 +61,9 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { .sessionManagement(configurer -> configurer .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // JWT 검증 필터 추가 + .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) + // OAuth 로그인 설정 .oauth2Login() .successHandler(successHandler) diff --git a/src/main/java/com/genius/todoffin/security/controller/AuthController.java b/src/main/java/com/genius/todoffin/security/controller/AuthController.java new file mode 100644 index 00000000..3a9d8110 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/controller/AuthController.java @@ -0,0 +1,38 @@ +package com.genius.todoffin.security.controller; + +import static com.genius.todoffin.util.exception.SuccessCode.SUCCESS; + +import com.genius.todoffin.security.dto.TokenRequest; +import com.genius.todoffin.security.service.JwtService; +import com.genius.todoffin.user.domain.User; +import com.genius.todoffin.user.service.UserService; +import com.genius.todoffin.util.response.dto.CommonResponse; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class AuthController { + private final UserService userService; + private final JwtService jwtService; + + @PostMapping("/auth") + public ResponseEntity generateToken(HttpServletResponse response, + @RequestBody TokenRequest tokenRequest) { + User requestUser = userService.findUserById(tokenRequest.userId()); + jwtService.generateAccessToken(response, requestUser); + jwtService.generateRefreshToken(response); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } +} diff --git a/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java b/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java new file mode 100644 index 00000000..408d6ee2 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java @@ -0,0 +1,4 @@ +package com.genius.todoffin.security.dto; + +public record TokenRequest(long userId) { +} diff --git a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java new file mode 100644 index 00000000..f10a3aa4 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java @@ -0,0 +1,21 @@ +package com.genius.todoffin.security.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.web.filter.OncePerRequestFilter; + +public class JwtAuthenticationFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + //TODO: request의 URI가 PERMIT_URI에 속하는지 확인 + + //TODO: + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java new file mode 100644 index 00000000..d10fa394 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java @@ -0,0 +1,64 @@ +package com.genius.todoffin.security.service; + +import com.genius.todoffin.user.domain.User; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Base64; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class JwtGenerator { + + public String generateAccessToken(final String ACCESS_SECRET, final long ACCESS_EXPIRATION, User requestUser) { + Long now = System.currentTimeMillis(); + + return Jwts.builder() + .setHeader(createHeader()) + .setClaims(createClaims(requestUser)) + .setSubject(String.valueOf(requestUser.getId())) + .setExpiration(new Date(now + ACCESS_EXPIRATION)) + .signWith(getSigningKey(ACCESS_SECRET), SignatureAlgorithm.HS256) + .compact(); + } + + public String generateRefreshToken(final String REFRESH_SECRET, final long REFRESH_EXPIRATION) { + Long now = System.currentTimeMillis(); + + return Jwts.builder() + .setHeader(createHeader()) + .setExpiration(new Date(now + REFRESH_EXPIRATION)) + .signWith(getSigningKey(REFRESH_SECRET), SignatureAlgorithm.HS256) + .compact(); + } + + private Map createHeader() { + Map header = new HashMap<>(); + header.put("typ", "JWT"); + header.put("alg", "HS512"); + return header; + } + + private Map createClaims(User requestUser) { + Map claims = new HashMap<>(); + claims.put("ID", requestUser.getId()); + claims.put("Role", requestUser.getRole()); + return claims; + } + + public Key getSigningKey(String secretKey) { + String encodedKey = encodeToBase64(secretKey); + return Keys.hmacShaKeyFor(encodedKey.getBytes(StandardCharsets.UTF_8)); + } + + private String encodeToBase64(String secretKey) { + return Base64.getEncoder().encodeToString(secretKey.getBytes()); + } +} diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java new file mode 100644 index 00000000..6ef7df74 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -0,0 +1,49 @@ +package com.genius.todoffin.security.service; + +import com.genius.todoffin.user.domain.User; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +@Slf4j +public class JwtService { + private final JwtGenerator jwtGenerator; + + @Value("${jwt.access-secret}") + private String ACCESS_SECRET; + @Value("${jwt.refresh-secret}") + private String REFRESH_SECRET; + @Value("${jwt.access-expiration}") + private long ACCESS_EXPIRATION; + @Value("${jwt.refresh-expiration}") + private long REFRESH_EXPIRATION; + + + public void generateAccessToken(HttpServletResponse response, User requestUser) { + String accessToken = jwtGenerator.generateAccessToken(ACCESS_SECRET, ACCESS_EXPIRATION, requestUser); + ResponseCookie cookie = setTokenToCookie("access-token", accessToken, ACCESS_EXPIRATION / 1000); + response.addHeader("Set-Cookie", cookie.toString()); + } + + public void generateRefreshToken(HttpServletResponse response) { + String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET, REFRESH_EXPIRATION); + ResponseCookie cookie = setTokenToCookie("refresh-token", refreshToken, REFRESH_EXPIRATION / 1000); + response.addHeader("Set-Cookie", cookie.toString()); + } + + private ResponseCookie setTokenToCookie(String tokenType, String token, long maxAgeSeconds) { + return ResponseCookie.from(tokenType, token) + .path("/") + .maxAge(maxAgeSeconds) + .httpOnly(true) + .secure(true) + .build(); + } +} diff --git a/src/main/java/com/genius/todoffin/security/service/JwtTokenUtil.java b/src/main/java/com/genius/todoffin/security/service/JwtTokenUtil.java deleted file mode 100644 index 01d67358..00000000 --- a/src/main/java/com/genius/todoffin/security/service/JwtTokenUtil.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.genius.todoffin.security.service; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -@Slf4j -public class JwtTokenUtil { - private final String ACCESS_SECRET; - private final long ACCESS_EXPIRATION; - private final String REFRESH_SECRET; - private final long REFRESH_EXPIRATION; - - - public JwtTokenUtil( - @Value("${jwt.access-secret}") String ACCESS_SECRET, - @Value("${jwt.refresh-secret}") String REFRESH_SECRET, - @Value("${jwt.access-expiration}") long ACCESS_EXPIRATION, - @Value("${jwt.refresh-expiration}") long REFRESH_EXPIRATION - ) { - this.ACCESS_SECRET = ACCESS_SECRET; - this.REFRESH_SECRET = REFRESH_SECRET; - this.ACCESS_EXPIRATION = ACCESS_EXPIRATION; - this.REFRESH_EXPIRATION = REFRESH_EXPIRATION; - } - - - public String generateRefreshToken() { - Long now = System.currentTimeMillis(); - - return Jwts.builder() - .setHeader(createHeader()) - .setIssuedAt(new Date()) - .setExpiration(new Date(now + REFRESH_EXPIRATION)) - .signWith(getSecretKey(REFRESH_SECRET), SignatureAlgorithm.HS512) - .compact(); - } - - private Map createHeader() { - Map header = new HashMap<>(); - header.put("typ", "JWT"); - header.put("alg", "HS512"); - return header; - } - - private Key getSecretKey(String secretKey) { - return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); - } -} diff --git a/src/main/java/com/genius/todoffin/user/service/UserService.java b/src/main/java/com/genius/todoffin/user/service/UserService.java index 6b4ea157..1198dfc3 100644 --- a/src/main/java/com/genius/todoffin/user/service/UserService.java +++ b/src/main/java/com/genius/todoffin/user/service/UserService.java @@ -24,8 +24,8 @@ public User findUserById(Long id) { .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); } - public User findUserByIdentifier(String email) { - return userRepository.findByIdentifier(email) + public User findUserByIdentifier(String identifier) { + return userRepository.findByIdentifier(identifier) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); } diff --git a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java index 8df5c574..d6632fa0 100644 --- a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java @@ -39,7 +39,7 @@ public void should_matchValues_when_signupUser() { .build(); //when - User user = userService.findUserByEmail(email); + User user = userService.findUserByIdentifier(email); Long signupUserId = userService.signup(signupRequest); User foundUser = userService.findUserById(signupUserId); @@ -56,7 +56,7 @@ private void saveUnsignedUser() { userRepository.save(User.builder() .role(Role.NOT_REGISTERED) .provider(ProviderType.NAVER) - .email("test@naver.com") + .identifier("test@naver.com") .build()); } } \ No newline at end of file From 8be9964397d99e5054019c5068074248d11d83ed Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 11 Jan 2024 01:11:58 +0900 Subject: [PATCH 045/234] =?UTF-8?q?refactor:=20CorsConfigurationSource=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/CustomCorsConfigurationSource.java | 25 +++++++++++++++++++ .../security/config/SecurityConfig.java | 22 +++------------- 2 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/config/CustomCorsConfigurationSource.java diff --git a/src/main/java/com/genius/todoffin/security/config/CustomCorsConfigurationSource.java b/src/main/java/com/genius/todoffin/security/config/CustomCorsConfigurationSource.java new file mode 100644 index 00000000..088bccd3 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/config/CustomCorsConfigurationSource.java @@ -0,0 +1,25 @@ +package com.genius.todoffin.security.config; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; + +@Component +public class CustomCorsConfigurationSource implements CorsConfigurationSource { + private static final String ALLOWED_ORIGIN = "http://localhost:5173"; + private static final List ALLOWED_METHODS = List.of("POST", "GET", "PATCH", "OPTIONS", "DELETE"); + + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(Collections.singletonList(ALLOWED_ORIGIN)); + config.setAllowedMethods(ALLOWED_METHODS); + config.setAllowCredentials(true); + config.setAllowedHeaders(Collections.singletonList("*")); + config.setMaxAge(3600L); + return config; + } +} diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 64c657aa..1ac24b66 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -4,8 +4,6 @@ import com.genius.todoffin.security.filter.JwtAuthenticationFilter; import com.genius.todoffin.security.handler.OAuth2SuccessHandler; import com.genius.todoffin.security.service.CustomOAuth2UserService; -import jakarta.servlet.http.HttpServletRequest; -import java.util.Collections; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,8 +13,6 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsUtils; @Configuration @@ -24,29 +20,17 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - private static final String ALLOWED_ORIGIN = "http://localhost:5173"; private static final String PERMIT_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; + private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; private final OAuth2SuccessHandler successHandler; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.cors(corsCustomizer -> corsCustomizer.configurationSource( - new CorsConfigurationSource() { - @Override - public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { - CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(Collections.singletonList(ALLOWED_ORIGIN)); - config.setAllowedMethods(Collections.singletonList("*")); - config.setAllowCredentials(true); - config.setAllowedHeaders(Collections.singletonList("*")); - config.setMaxAge(3600L); - return config; - } - } - ) + http.cors(corsCustomizer -> corsCustomizer + .configurationSource(customCorsConfigurationSource) ) .csrf().disable() .httpBasic().disable() From f42e400b1e49713dc062f6df8682ffe188f36447 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 11 Jan 2024 01:25:55 +0900 Subject: [PATCH 046/234] =?UTF-8?q?refactor:=20filterChain=EC=9D=84=20Secu?= =?UTF-8?q?rity=206=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=9E=8C=EB=8B=A4?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 1ac24b66..b68c20bb 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -2,6 +2,7 @@ import com.genius.todoffin.security.filter.JwtAuthenticationFilter; +import com.genius.todoffin.security.handler.OAuth2FailureHandler; import com.genius.todoffin.security.handler.OAuth2SuccessHandler; import com.genius.todoffin.security.service.CustomOAuth2UserService; import lombok.RequiredArgsConstructor; @@ -10,6 +11,9 @@ import org.springframework.core.annotation.Order; 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.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -25,6 +29,7 @@ public class SecurityConfig { private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; private final OAuth2SuccessHandler successHandler; + private final OAuth2FailureHandler failureHandler; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -32,10 +37,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors(corsCustomizer -> corsCustomizer .configurationSource(customCorsConfigurationSource) ) - .csrf().disable() - .httpBasic().disable() - .formLogin().disable() - .anonymous().and() + .csrf(CsrfConfigurer::disable) + .httpBasic(HttpBasicConfigurer::disable) + .formLogin(FormLoginConfigurer::disable) .authorizeHttpRequests(request -> request .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .requestMatchers(PERMIT_URI).permitAll() @@ -49,12 +53,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // OAuth 로그인 설정 - .oauth2Login() - .successHandler(successHandler) - .authorizationEndpoint() - - .and() - .userInfoEndpoint().userService(customOAuthService); + .oauth2Login(customConfigurer -> customConfigurer + .successHandler(successHandler) + .failureHandler(failureHandler) + .userInfoEndpoint(endpointConfig -> endpointConfig.userService(customOAuthService)) + ); return http.build(); } From 1f6dddf88be7aab31b948da4da939b72f70e0cf9 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:48:33 +0900 Subject: [PATCH 047/234] =?UTF-8?q?refactor:=20OAuth2=EC=99=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=EB=90=9C=20=EC=83=81=EC=88=98=EB=A5=BC=20=ED=95=98?= =?UTF-8?q?=EB=82=98=EC=9D=98=20enum=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EC=97=90=20=EB=AA=A8=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 4 +-- .../security/constants/OAuthRule.java | 19 ------------- .../security/constants/ProviderInfo.java | 27 +++++++++++++++++++ .../security/constants/ProviderType.java | 20 -------------- .../security/info/OAuth2UserInfoFactory.java | 6 ++--- .../info/impl/GithubOAuth2UserInfo.java | 7 +++-- .../info/impl/GoogleOAuth2UserInfo.java | 7 +++-- .../info/impl/KakaoOAuth2UserInfo.java | 10 +++---- .../info/impl/NaverOAuth2UserInfo.java | 10 +++---- .../service/CustomOAuth2UserService.java | 14 +++++----- .../com/genius/todoffin/user/domain/User.java | 8 +++--- .../user/repository/UserRepository.java | 5 ++-- .../user/repository/UserRepositoryTest.java | 20 +++++++------- .../user/service/UserServiceTest.java | 6 ++--- 14 files changed, 73 insertions(+), 90 deletions(-) delete mode 100644 src/main/java/com/genius/todoffin/security/constants/OAuthRule.java create mode 100644 src/main/java/com/genius/todoffin/security/constants/ProviderInfo.java delete mode 100644 src/main/java/com/genius/todoffin/security/constants/ProviderType.java diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index b68c20bb..65cef2e3 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -24,7 +24,7 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - private static final String PERMIT_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; + private static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; @@ -42,7 +42,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .formLogin(FormLoginConfigurer::disable) .authorizeHttpRequests(request -> request .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() - .requestMatchers(PERMIT_URI).permitAll() + .requestMatchers(PERMITTED_URI).permitAll() .anyRequest().hasAnyRole(PERMITTED_ROLES)) // JWT 사용으로 인한 세션 미사용 diff --git a/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java b/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java deleted file mode 100644 index 7828d449..00000000 --- a/src/main/java/com/genius/todoffin/security/constants/OAuthRule.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.genius.todoffin.security.constants; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum OAuthRule { - COMMON_USER_KEY("email"), - - GITHUB_PROVIDER_ID("id"), - GITHUB_USER_IDENTIFIER("login"), - - KAKAO_PROVIDER_ID("id"), - NAVER_PROVIDER_ID("id"), - GOOGLE_PROVIDER_ID("sub"); - - private final String value; -} diff --git a/src/main/java/com/genius/todoffin/security/constants/ProviderInfo.java b/src/main/java/com/genius/todoffin/security/constants/ProviderInfo.java new file mode 100644 index 00000000..8389ea3f --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/constants/ProviderInfo.java @@ -0,0 +1,27 @@ +package com.genius.todoffin.security.constants; + +import java.util.Arrays; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum ProviderInfo { + GITHUB(null, "id", "login"), + KAKAO("kakao_account", "id", "email"), + NAVER("response", "id", "email"), + GOOGLE(null, "sub", "email"); + + private final String attributeKey; + private final String providerCode; + private final String identifier; + + public static ProviderInfo from(String provider) { + String upperCastedProvider = provider.toUpperCase(); + + return Arrays.stream(ProviderInfo.values()) + .filter(item -> item.name().equals(upperCastedProvider)) + .findFirst() + .orElseThrow(); + } +} diff --git a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java b/src/main/java/com/genius/todoffin/security/constants/ProviderType.java deleted file mode 100644 index 38d46c36..00000000 --- a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.genius.todoffin.security.constants; - -import java.util.Arrays; - -public enum ProviderType { - GITHUB, - KAKAO, - NAVER, - GOOGLE, - FACEBOOK; - - public static ProviderType from(String provider) { - String upperCastedProvider = provider.toUpperCase(); - - return Arrays.stream(ProviderType.values()) - .filter(item -> item.name().equals(upperCastedProvider)) - .findFirst() - .orElseThrow(); - } -} diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java index c59c8c73..dbb19669 100644 --- a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java +++ b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java @@ -1,6 +1,6 @@ package com.genius.todoffin.security.info; -import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.security.constants.ProviderInfo; import com.genius.todoffin.security.info.impl.GithubOAuth2UserInfo; import com.genius.todoffin.security.info.impl.GoogleOAuth2UserInfo; import com.genius.todoffin.security.info.impl.KakaoOAuth2UserInfo; @@ -9,8 +9,8 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; public class OAuth2UserInfoFactory { - public static OAuth2UserInfo getOAuth2UserInfo(ProviderType providerType, Map attributes) { - switch (providerType) { + public static OAuth2UserInfo getOAuth2UserInfo(ProviderInfo providerInfo, Map attributes) { + switch (providerInfo) { case GITHUB -> { return new GithubOAuth2UserInfo(attributes); } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java index 08f0b948..b52fa73d 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java @@ -1,7 +1,6 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthRule.GITHUB_PROVIDER_ID; -import static com.genius.todoffin.security.constants.OAuthRule.GITHUB_USER_IDENTIFIER; +import static com.genius.todoffin.security.constants.ProviderInfo.GITHUB; import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; @@ -14,11 +13,11 @@ public GithubOAuth2UserInfo(Map attributes) { @Override public String getProviderCode() { - return (String) attributes.get(GITHUB_PROVIDER_ID.getValue()); + return (String) attributes.get(GITHUB.getProviderCode()); } @Override public String getUserIdentifier() { - return (String) attributes.get(GITHUB_USER_IDENTIFIER.getValue()); + return (String) attributes.get(GITHUB.getIdentifier()); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java index be5fb8c6..03aeb2be 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java @@ -1,8 +1,7 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthRule.COMMON_USER_KEY; -import static com.genius.todoffin.security.constants.OAuthRule.GOOGLE_PROVIDER_ID; +import static com.genius.todoffin.security.constants.ProviderInfo.GOOGLE; import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; @@ -15,11 +14,11 @@ public GoogleOAuth2UserInfo(Map attributes) { @Override public String getProviderCode() { - return (String) attributes.get(GOOGLE_PROVIDER_ID.getValue()); + return (String) attributes.get(GOOGLE.getProviderCode()); } @Override public String getUserIdentifier() { - return (String) attributes.get(COMMON_USER_KEY.getValue()); + return (String) attributes.get(GOOGLE.getIdentifier()); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java index 3dc68701..df9d9e6e 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java @@ -1,18 +1,16 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthRule.COMMON_USER_KEY; -import static com.genius.todoffin.security.constants.OAuthRule.KAKAO_PROVIDER_ID; +import static com.genius.todoffin.security.constants.ProviderInfo.KAKAO; import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; public class KakaoOAuth2UserInfo extends OAuth2UserInfo { - private final static String ATTRIBUTE_KEY = "kakao_account"; private String providerId; public KakaoOAuth2UserInfo(Map attributes) { - super((Map) attributes.get(ATTRIBUTE_KEY)); - this.providerId = String.valueOf(attributes.get(KAKAO_PROVIDER_ID.getValue())); + super((Map) attributes.get(KAKAO.getAttributeKey())); + this.providerId = String.valueOf(attributes.get(KAKAO.getIdentifier())); } @Override @@ -22,6 +20,6 @@ public String getProviderCode() { @Override public String getUserIdentifier() { - return (String) attributes.get(COMMON_USER_KEY.getValue()); + return (String) attributes.get(KAKAO.getProviderCode()); } } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java index 38293b5c..b86cfb4e 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java +++ b/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java @@ -1,26 +1,24 @@ package com.genius.todoffin.security.info.impl; -import static com.genius.todoffin.security.constants.OAuthRule.COMMON_USER_KEY; -import static com.genius.todoffin.security.constants.OAuthRule.NAVER_PROVIDER_ID; +import static com.genius.todoffin.security.constants.ProviderInfo.NAVER; import com.genius.todoffin.security.info.OAuth2UserInfo; import java.util.Map; public class NaverOAuth2UserInfo extends OAuth2UserInfo { - private final static String ATTRIBUTE_KEY = "response"; public NaverOAuth2UserInfo(Map attributes) { - super((Map) attributes.get(ATTRIBUTE_KEY)); + super((Map) attributes.get(NAVER.getAttributeKey())); } @Override public String getProviderCode() { - return (String) attributes.get(NAVER_PROVIDER_ID.getValue()); + return (String) attributes.get(NAVER.getProviderCode()); } @Override public String getUserIdentifier() { - return (String) attributes.get(COMMON_USER_KEY.getValue()); + return (String) attributes.get(NAVER.getIdentifier()); } } diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index fc4bcb3c..82dca220 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -1,6 +1,6 @@ package com.genius.todoffin.security.service; -import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.security.constants.ProviderInfo; import com.genius.todoffin.security.domain.UserPrincipal; import com.genius.todoffin.security.info.OAuth2UserInfo; import com.genius.todoffin.security.info.OAuth2UserInfoFactory; @@ -40,25 +40,25 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic // 서비스를 구분하는 코드 ex) Github, Naver String providerCode = userRequest.getClientRegistration().getRegistrationId(); - ProviderType providerType = ProviderType.from(providerCode); + ProviderInfo providerInfo = ProviderInfo.from(providerCode); Map attributes = oAuth2User.getAttributes(); - OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, attributes); + OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerInfo, attributes); String userIdentifier = oAuth2UserInfo.getUserIdentifier(); - User user = getUser(userIdentifier, providerType); + User user = getUser(userIdentifier, providerInfo); return new UserPrincipal(user, attributes, userNameAttributeName); } - private User getUser(String userIdentifier, ProviderType providerType) { - Optional optionalUser = userRepository.findByOAuthInfo(userIdentifier, providerType); + private User getUser(String userIdentifier, ProviderInfo providerInfo) { + Optional optionalUser = userRepository.findByOAuthInfo(userIdentifier, providerInfo); if (optionalUser.isEmpty()) { User unregisteredUser = User.builder() .identifier(userIdentifier) .role(Role.NOT_REGISTERED) - .provider(providerType) + .provider(providerInfo) .build(); return userRepository.save(unregisteredUser); } diff --git a/src/main/java/com/genius/todoffin/user/domain/User.java b/src/main/java/com/genius/todoffin/user/domain/User.java index f0dbc3ec..d51e7587 100644 --- a/src/main/java/com/genius/todoffin/user/domain/User.java +++ b/src/main/java/com/genius/todoffin/user/domain/User.java @@ -2,7 +2,7 @@ import com.genius.todoffin.common.domain.BaseTimeEntity; -import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.security.constants.ProviderInfo; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -26,7 +26,7 @@ public class User extends BaseTimeEntity { @NotNull @Enumerated(EnumType.STRING) - private ProviderType provider; + private ProviderInfo providerInfo; @NotNull private String identifier; @@ -43,9 +43,9 @@ public class User extends BaseTimeEntity { @Builder - public User(ProviderType provider, String identifier, Role role, String nickname, String information, + public User(ProviderInfo providerInfo, String identifier, Role role, String nickname, String information, String interest) { - this.provider = provider; + this.providerInfo = providerInfo; this.identifier = identifier; this.role = role; this.nickname = nickname; diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java index 55efa5ba..2675aa76 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -1,6 +1,6 @@ package com.genius.todoffin.user.repository; -import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.security.constants.ProviderInfo; import com.genius.todoffin.user.domain.User; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,5 +11,6 @@ public interface UserRepository extends JpaRepository { Optional findByIdentifier(String identifier); @Query("select u from User u where u.identifier = :identifier and u.provider = :provider") - Optional findByOAuthInfo(@Param("identifier") String identifier, @Param("provider") ProviderType provider); + Optional findByOAuthInfo(@Param("identifier") String identifier, + @Param("provider") ProviderInfo providerInfo); } diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java index 9f291b4a..47b8204b 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.security.constants.ProviderInfo; import com.genius.todoffin.user.domain.Role; import com.genius.todoffin.user.domain.User; import org.junit.jupiter.api.DisplayName; @@ -22,9 +22,9 @@ class UserRepositoryTest { public void email을_통해_저장한_User_객체를_찾을수있다() { //given String email = "test@naver.com"; - ProviderType provider = ProviderType.GOOGLE; + ProviderInfo providerInfo = ProviderInfo.GOOGLE; String nickname = "test_nickname"; - User user = getUnsavedUser(email, provider, nickname); + User user = getUnsavedUser(email, providerInfo, nickname); //when User savedUser = userRepository.save(user); @@ -33,7 +33,7 @@ class UserRepositoryTest { //then assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); assertThat(savedUser.getIdentifier()).isEqualTo(foundUser.getIdentifier()); - assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); + assertThat(savedUser.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); } @@ -42,26 +42,26 @@ class UserRepositoryTest { public void email_provider를_통해_저장한_User_객체를_찾을수있다() { //given String email = "test@naver.com"; - ProviderType provider = ProviderType.GOOGLE; + ProviderInfo providerInfo = ProviderInfo.GOOGLE; String nickname = "test_nickname"; - User user = getUnsavedUser(email, provider, nickname); + User user = getUnsavedUser(email, providerInfo, nickname); //when User savedUser = userRepository.save(user); - User foundUser = userRepository.findByOAuthInfo(email, provider).get(); + User foundUser = userRepository.findByOAuthInfo(email, providerInfo).get(); //then assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); assertThat(savedUser.getIdentifier()).isEqualTo(foundUser.getIdentifier()); - assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); + assertThat(savedUser.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); } - private User getUnsavedUser(String email, ProviderType provider, String nickname) { + private User getUnsavedUser(String email, ProviderInfo providerInfo, String nickname) { return User.builder() .email(email) - .provider(provider) + .provider(providerInfo) .role(Role.USER) .nickname(nickname) .build(); diff --git a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java index d6632fa0..faf5c672 100644 --- a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.todoffin.security.constants.ProviderType; +import com.genius.todoffin.security.constants.ProviderInfo; import com.genius.todoffin.user.domain.Role; import com.genius.todoffin.user.domain.User; import com.genius.todoffin.user.dto.SignupRequest; @@ -46,7 +46,7 @@ public void should_matchValues_when_signupUser() { //then assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); - assertThat(user.getProvider()).isEqualTo(foundUser.getProvider()); + assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); } @@ -55,7 +55,7 @@ public void should_matchValues_when_signupUser() { private void saveUnsignedUser() { userRepository.save(User.builder() .role(Role.NOT_REGISTERED) - .provider(ProviderType.NAVER) + .provider(ProviderInfo.NAVER) .identifier("test@naver.com") .build()); } From ebec72d9cae6dc93c79bbf3afdfcff94c084fc3a Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:56:35 +0900 Subject: [PATCH 048/234] =?UTF-8?q?fix:=20OAuthRule=20enum=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=88=98=EC=A0=95=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todoffin/security/handler/OAuth2SuccessHandler.java | 6 ++---- .../todoffin/security/service/CustomOAuth2UserService.java | 2 +- .../com/genius/todoffin/user/repository/UserRepository.java | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index 98f3997b..14516b07 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -1,7 +1,5 @@ package com.genius.todoffin.security.handler; -import static com.genius.todoffin.security.constants.OAuthRule.COMMON_USER_KEY; - import com.genius.todoffin.user.domain.Role; import com.genius.todoffin.user.domain.User; import com.genius.todoffin.user.repository.UserRepository; @@ -38,10 +36,10 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo getRedirectStrategy().sendRedirect(request, response, redirectUrl); } - private String getRedirectUrlByRole(Role role, String email) { + private String getRedirectUrlByRole(Role role, String identifier) { if (role == Role.NOT_REGISTERED) { return UriComponentsBuilder.fromUriString(SIGNUP_URL) - .queryParam(COMMON_USER_KEY.getValue(), email) + .queryParam("identifier", identifier) .build() .toUriString(); } diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index 82dca220..e6ad978f 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -58,7 +58,7 @@ private User getUser(String userIdentifier, ProviderInfo providerInfo) { User unregisteredUser = User.builder() .identifier(userIdentifier) .role(Role.NOT_REGISTERED) - .provider(providerInfo) + .providerInfo(providerInfo) .build(); return userRepository.save(unregisteredUser); } diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java index 2675aa76..d5b83b16 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -10,7 +10,7 @@ public interface UserRepository extends JpaRepository { Optional findByIdentifier(String identifier); - @Query("select u from User u where u.identifier = :identifier and u.provider = :provider") + @Query("select u from User u where u.identifier = :identifier and u.providerInfo = :providerInfo") Optional findByOAuthInfo(@Param("identifier") String identifier, - @Param("provider") ProviderInfo providerInfo); + @Param("providerInfo") ProviderInfo providerInfo); } From 9398af7f9304eff4587fa099d140cead98652580 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:54:37 +0900 Subject: [PATCH 049/234] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9A=94=EC=B2=AD=20DTO=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/genius/todoffin/user/dto/SignupRequest.java | 2 +- src/main/java/com/genius/todoffin/user/service/UserService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java b/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java index f5de661e..205b5c21 100644 --- a/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java +++ b/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java @@ -5,7 +5,7 @@ @Builder public record SignupRequest( - String email, + String identifier, String nickname, String information, List interest diff --git a/src/main/java/com/genius/todoffin/user/service/UserService.java b/src/main/java/com/genius/todoffin/user/service/UserService.java index 1198dfc3..3f2a742f 100644 --- a/src/main/java/com/genius/todoffin/user/service/UserService.java +++ b/src/main/java/com/genius/todoffin/user/service/UserService.java @@ -31,7 +31,7 @@ public User findUserByIdentifier(String identifier) { @Transactional public Long signup(SignupRequest requestUser) { - User targetUser = findUserByIdentifier(requestUser.email()); + User targetUser = findUserByIdentifier(requestUser.identifier()); //TODO: Converter 클래스 만들어서 적용하기 String interest = String.join(",", requestUser.interest()); From bb2852482ff076106c27187a3dd6d1a006e21df8 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 14 Jan 2024 14:30:32 +0900 Subject: [PATCH 050/234] =?UTF-8?q?chore:=20mongoDB=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index e9c65466..b6f12d6f 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,9 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.10.7' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.7' + // MongoDB + implementation 'org.springframework.data:spring-data-mongodb:4.2.0' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' From 921e387c72c436980afe372ef4dbdc924aadf839 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 14 Jan 2024 14:37:28 +0900 Subject: [PATCH 051/234] =?UTF-8?q?refactor:=20JWT=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=8B=9C,=20=EC=A0=84=EB=8B=AC=ED=95=98=EB=8A=94=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=A0=95=EB=B3=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자의 PK를 보내는 방법에서 Identifier 정보 전달로 변경 --- .../com/genius/todoffin/security/controller/AuthController.java | 2 +- .../java/com/genius/todoffin/security/dto/TokenRequest.java | 2 +- .../java/com/genius/todoffin/security/service/JwtGenerator.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/controller/AuthController.java b/src/main/java/com/genius/todoffin/security/controller/AuthController.java index 3a9d8110..4540e077 100644 --- a/src/main/java/com/genius/todoffin/security/controller/AuthController.java +++ b/src/main/java/com/genius/todoffin/security/controller/AuthController.java @@ -27,7 +27,7 @@ public class AuthController { @PostMapping("/auth") public ResponseEntity generateToken(HttpServletResponse response, @RequestBody TokenRequest tokenRequest) { - User requestUser = userService.findUserById(tokenRequest.userId()); + User requestUser = userService.findUserByIdentifier(tokenRequest.identifier()); jwtService.generateAccessToken(response, requestUser); jwtService.generateRefreshToken(response); diff --git a/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java b/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java index 408d6ee2..44866158 100644 --- a/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java +++ b/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java @@ -1,4 +1,4 @@ package com.genius.todoffin.security.dto; -public record TokenRequest(long userId) { +public record TokenRequest(String identifier) { } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java index d10fa394..8b4e5a62 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java @@ -48,7 +48,7 @@ private Map createHeader() { private Map createClaims(User requestUser) { Map claims = new HashMap<>(); - claims.put("ID", requestUser.getId()); + claims.put("Identifier", requestUser.getIdentifier()); claims.put("Role", requestUser.getRole()); return claims; } From bd259b4fd9e5d439c9e383575e549723bbaa4d42 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 14 Jan 2024 15:18:08 +0900 Subject: [PATCH 052/234] =?UTF-8?q?feat:=20MongoDB=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- src/main/java/com/genius/todoffin/TodoffinApplication.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b6f12d6f..c641862f 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.7' // MongoDB - implementation 'org.springframework.data:spring-data-mongodb:4.2.0' + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' diff --git a/src/main/java/com/genius/todoffin/TodoffinApplication.java b/src/main/java/com/genius/todoffin/TodoffinApplication.java index a08c43d4..49f05c62 100644 --- a/src/main/java/com/genius/todoffin/TodoffinApplication.java +++ b/src/main/java/com/genius/todoffin/TodoffinApplication.java @@ -3,9 +3,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication @EnableJpaAuditing +@EnableMongoRepositories public class TodoffinApplication { public static void main(String[] args) { SpringApplication.run(TodoffinApplication.class, args); From b684f925c84e38bd50cc33bfd59d575badfc9704 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 14 Jan 2024 15:19:22 +0900 Subject: [PATCH 053/234] =?UTF-8?q?feat:=20Token=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A5=BC=20=EB=8B=B4=EC=9D=84=20=ED=81=B4=EB=9E=98=EC=8A=A4/?= =?UTF-8?q?=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genius/todoffin/security/domain/Token.java | 14 ++++++++++++++ .../security/repository/TokenRepository.java | 9 +++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/main/java/com/genius/todoffin/security/domain/Token.java create mode 100644 src/main/java/com/genius/todoffin/security/repository/TokenRepository.java diff --git a/src/main/java/com/genius/todoffin/security/domain/Token.java b/src/main/java/com/genius/todoffin/security/domain/Token.java new file mode 100644 index 00000000..e76b0f43 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/domain/Token.java @@ -0,0 +1,14 @@ +package com.genius.todoffin.security.domain; + +import jakarta.persistence.Id; +import lombok.Getter; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "Token") +@Getter +public class Token { + @Id + private Long id; + + private String token; +} diff --git a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java b/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java new file mode 100644 index 00000000..9de5482d --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java @@ -0,0 +1,9 @@ +package com.genius.todoffin.security.repository; + +import com.genius.todoffin.security.domain.Token; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TokenRepository extends MongoRepository { +} From 4ad0ea05a841b901864ef20ab1e03db9c9f20028 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 14 Jan 2024 16:13:45 +0900 Subject: [PATCH 054/234] =?UTF-8?q?feat:=20refresh-token=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=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 - MongoDB에 refresh token을 저장하는 로직 구현 --- .../security/controller/AuthController.java | 2 +- .../com/genius/todoffin/security/domain/Token.java | 13 ++++++++++--- .../security/repository/TokenRepository.java | 2 -- .../todoffin/security/service/JwtService.java | 7 ++++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/controller/AuthController.java b/src/main/java/com/genius/todoffin/security/controller/AuthController.java index 4540e077..b13a96f2 100644 --- a/src/main/java/com/genius/todoffin/security/controller/AuthController.java +++ b/src/main/java/com/genius/todoffin/security/controller/AuthController.java @@ -29,7 +29,7 @@ public ResponseEntity generateToken(HttpServletResponse response @RequestBody TokenRequest tokenRequest) { User requestUser = userService.findUserByIdentifier(tokenRequest.identifier()); jwtService.generateAccessToken(response, requestUser); - jwtService.generateRefreshToken(response); + jwtService.generateRefreshToken(response, requestUser); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/todoffin/security/domain/Token.java b/src/main/java/com/genius/todoffin/security/domain/Token.java index e76b0f43..fcc8d594 100644 --- a/src/main/java/com/genius/todoffin/security/domain/Token.java +++ b/src/main/java/com/genius/todoffin/security/domain/Token.java @@ -1,14 +1,21 @@ package com.genius.todoffin.security.domain; -import jakarta.persistence.Id; +import lombok.Builder; import lombok.Getter; import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.MongoId; @Document(collection = "Token") @Getter public class Token { - @Id - private Long id; + @MongoId + private String identifier; private String token; + + @Builder + public Token(String identifier, String token) { + this.identifier = identifier; + this.token = token; + } } diff --git a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java b/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java index 9de5482d..bec6e76b 100644 --- a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java +++ b/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java @@ -2,8 +2,6 @@ import com.genius.todoffin.security.domain.Token; import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.stereotype.Repository; -@Repository public interface TokenRepository extends MongoRepository { } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java index 6ef7df74..2db9d404 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -1,5 +1,7 @@ package com.genius.todoffin.security.service; +import com.genius.todoffin.security.domain.Token; +import com.genius.todoffin.security.repository.TokenRepository; import com.genius.todoffin.user.domain.User; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -15,6 +17,7 @@ @Slf4j public class JwtService { private final JwtGenerator jwtGenerator; + private final TokenRepository tokenRepository; @Value("${jwt.access-secret}") private String ACCESS_SECRET; @@ -32,10 +35,12 @@ public void generateAccessToken(HttpServletResponse response, User requestUser) response.addHeader("Set-Cookie", cookie.toString()); } - public void generateRefreshToken(HttpServletResponse response) { + public void generateRefreshToken(HttpServletResponse response, User requestUser) { String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET, REFRESH_EXPIRATION); ResponseCookie cookie = setTokenToCookie("refresh-token", refreshToken, REFRESH_EXPIRATION / 1000); response.addHeader("Set-Cookie", cookie.toString()); + + tokenRepository.save(new Token(requestUser.getIdentifier(), refreshToken)); } private ResponseCookie setTokenToCookie(String tokenType, String token, long maxAgeSeconds) { From f96fd8b8e7e05149611e5a32b7be9bfbaaada32a Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 14 Jan 2024 19:47:24 +0900 Subject: [PATCH 055/234] =?UTF-8?q?refactor:=20JWT=EC=97=90=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=90=A0=20enum=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todoffin/security/constants/JwtRule.java | 15 +++++++++++++++ .../todoffin/security/service/JwtService.java | 12 ++++++++---- .../genius/todoffin/util/exception/ErrorCode.java | 7 ++++++- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/constants/JwtRule.java diff --git a/src/main/java/com/genius/todoffin/security/constants/JwtRule.java b/src/main/java/com/genius/todoffin/security/constants/JwtRule.java new file mode 100644 index 00000000..30d0d228 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/constants/JwtRule.java @@ -0,0 +1,15 @@ +package com.genius.todoffin.security.constants; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum JwtRule { + JWT_ISSUE_HEADER("Set-Cookie"), + JWT_RESOLVE_HEADER("Cookie"), + ACCESS_PREFIX("access"), + REFRESH_PREFIX("refresh"); + + private final String value; +} diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java index 2db9d404..45ec5c35 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -1,5 +1,9 @@ package com.genius.todoffin.security.service; +import static com.genius.todoffin.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.todoffin.security.constants.JwtRule.JWT_ISSUE_HEADER; +import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; + import com.genius.todoffin.security.domain.Token; import com.genius.todoffin.security.repository.TokenRepository; import com.genius.todoffin.user.domain.User; @@ -31,14 +35,14 @@ public class JwtService { public void generateAccessToken(HttpServletResponse response, User requestUser) { String accessToken = jwtGenerator.generateAccessToken(ACCESS_SECRET, ACCESS_EXPIRATION, requestUser); - ResponseCookie cookie = setTokenToCookie("access-token", accessToken, ACCESS_EXPIRATION / 1000); - response.addHeader("Set-Cookie", cookie.toString()); + ResponseCookie cookie = setTokenToCookie(ACCESS_PREFIX.getValue(), accessToken, ACCESS_EXPIRATION / 1000); + response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); } public void generateRefreshToken(HttpServletResponse response, User requestUser) { String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET, REFRESH_EXPIRATION); - ResponseCookie cookie = setTokenToCookie("refresh-token", refreshToken, REFRESH_EXPIRATION / 1000); - response.addHeader("Set-Cookie", cookie.toString()); + ResponseCookie cookie = setTokenToCookie(REFRESH_PREFIX.getValue(), refreshToken, REFRESH_EXPIRATION / 1000); + response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); tokenRepository.save(new Token(requestUser.getIdentifier(), refreshToken)); } diff --git a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java index 54ef3c80..c8a94d1a 100644 --- a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java @@ -8,7 +8,12 @@ @RequiredArgsConstructor public enum ErrorCode { - MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."); + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."), + INVALID_EXPIRED_JWT(HttpStatus.BAD_REQUEST, "이미 만료된 JWT 입니다."), + INVALID_MALFORMED_JWT(HttpStatus.BAD_REQUEST, "JWT의 구조가 유효하지 않습니다."), + INVALID_CLAIM_JWT(HttpStatus.BAD_REQUEST, "JWT의 Claim이 유효하지 않습니다."), + UNSUPPORTED_JWT(HttpStatus.BAD_REQUEST, "지원하지 않는 JWT 형식입니다."), + INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."); private final HttpStatus status; private final String message; From 64094f3848cc380f03370e0483d699b1dde88b55 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 14 Jan 2024 23:39:51 +0900 Subject: [PATCH 056/234] =?UTF-8?q?feat:=20JWT=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RTR 로직 구현 - 전반적인 리팩토링 필요 --- build.gradle | 4 +- .../security/config/SecurityConfig.java | 9 +- .../filter/JwtAuthenticationFilter.java | 43 +++++++++- .../security/repository/TokenRepository.java | 2 +- .../security/service/JwtGenerator.java | 3 +- .../todoffin/security/service/JwtService.java | 86 ++++++++++++++++++- .../user/controller/UserController.java | 10 +++ 7 files changed, 148 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index c641862f..436cde71 100644 --- a/build.gradle +++ b/build.gradle @@ -34,8 +34,8 @@ dependencies { //JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.10.7' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.10.7' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' // MongoDB implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java index 65cef2e3..4fd3f053 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java @@ -5,6 +5,8 @@ import com.genius.todoffin.security.handler.OAuth2FailureHandler; import com.genius.todoffin.security.handler.OAuth2SuccessHandler; import com.genius.todoffin.security.service.CustomOAuth2UserService; +import com.genius.todoffin.security.service.JwtService; +import com.genius.todoffin.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -24,10 +26,12 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - private static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; + public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; + private final JwtService jwtService; + private final UserService userService; private final OAuth2SuccessHandler successHandler; private final OAuth2FailureHandler failureHandler; @@ -50,7 +54,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // JWT 검증 필터 추가 - .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new JwtAuthenticationFilter(jwtService, userService), + UsernamePasswordAuthenticationFilter.class) // OAuth 로그인 설정 .oauth2Login(customConfigurer -> customConfigurer diff --git a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java index f10a3aa4..ca65f3c6 100644 --- a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java @@ -1,21 +1,60 @@ package com.genius.todoffin.security.filter; +import static com.genius.todoffin.security.config.SecurityConfig.PERMITTED_URI; + +import com.genius.todoffin.security.constants.JwtRule; +import com.genius.todoffin.security.service.JwtService; +import com.genius.todoffin.user.domain.User; +import com.genius.todoffin.user.service.UserService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Arrays; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; +@RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final JwtService jwtService; + private final UserService userService; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - //TODO: request의 URI가 PERMIT_URI에 속하는지 확인 + if (isPermittedURI(request.getRequestURI())) { + SecurityContextHolder.getContext().setAuthentication(null); + filterChain.doFilter(request, response); + return; + } + + String accessToken = jwtService.resolveTokenFromCookie(request, JwtRule.ACCESS_PREFIX); + if (jwtService.validateAccessToken(accessToken)) { + Authentication authentication = jwtService.getAuthentication(accessToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + return; + } - //TODO: + String refreshToken = jwtService.resolveTokenFromCookie(request, JwtRule.REFRESH_PREFIX); + String identifier = jwtService.getIdentifierFromToken(refreshToken); + if (jwtService.validateRefreshToken(refreshToken, identifier)) { + User user = userService.findUserByIdentifier(identifier); + jwtService.generateAccessToken(response, user); + jwtService.generateRefreshToken(response, user); + } + Authentication authentication = jwtService.getAuthentication(accessToken); + SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(request, response); } + + private boolean isPermittedURI(String requestURI) { + return Arrays.stream(PERMITTED_URI) + .anyMatch(permitted -> permitted.contains(requestURI)); + } } diff --git a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java b/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java index bec6e76b..6844137c 100644 --- a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java +++ b/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java @@ -3,5 +3,5 @@ import com.genius.todoffin.security.domain.Token; import org.springframework.data.mongodb.repository.MongoRepository; -public interface TokenRepository extends MongoRepository { +public interface TokenRepository extends MongoRepository { } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java index 8b4e5a62..13321af9 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java @@ -29,11 +29,12 @@ public String generateAccessToken(final String ACCESS_SECRET, final long ACCESS_ .compact(); } - public String generateRefreshToken(final String REFRESH_SECRET, final long REFRESH_EXPIRATION) { + public String generateRefreshToken(final String REFRESH_SECRET, final long REFRESH_EXPIRATION, User requestUser) { Long now = System.currentTimeMillis(); return Jwts.builder() .setHeader(createHeader()) + .setSubject(requestUser.getIdentifier()) .setExpiration(new Date(now + REFRESH_EXPIRATION)) .signWith(getSigningKey(REFRESH_SECRET), SignatureAlgorithm.HS256) .compact(); diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java index 45ec5c35..5b142428 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -3,15 +3,34 @@ import static com.genius.todoffin.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.todoffin.security.constants.JwtRule.JWT_ISSUE_HEADER; import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.todoffin.util.exception.ErrorCode.INVALID_CLAIM_JWT; +import static com.genius.todoffin.util.exception.ErrorCode.INVALID_EXPIRED_JWT; +import static com.genius.todoffin.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.todoffin.util.exception.ErrorCode.INVALID_MALFORMED_JWT; +import static com.genius.todoffin.util.exception.ErrorCode.UNSUPPORTED_JWT; +import com.genius.todoffin.security.constants.JwtRule; import com.genius.todoffin.security.domain.Token; import com.genius.todoffin.security.repository.TokenRepository; import com.genius.todoffin.user.domain.User; +import com.genius.todoffin.util.exception.BusinessException; +import io.jsonwebtoken.ClaimJwtException; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.security.Key; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseCookie; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +39,7 @@ @RequiredArgsConstructor @Slf4j public class JwtService { + private final CustomUserDetailsService customUserDetailsService; private final JwtGenerator jwtGenerator; private final TokenRepository tokenRepository; @@ -39,8 +59,9 @@ public void generateAccessToken(HttpServletResponse response, User requestUser) response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); } + @Transactional public void generateRefreshToken(HttpServletResponse response, User requestUser) { - String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET, REFRESH_EXPIRATION); + String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET, REFRESH_EXPIRATION, requestUser); ResponseCookie cookie = setTokenToCookie(REFRESH_PREFIX.getValue(), refreshToken, REFRESH_EXPIRATION / 1000); response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); @@ -55,4 +76,67 @@ private ResponseCookie setTokenToCookie(String tokenType, String token, long max .secure(true) .build(); } + + public boolean validateAccessToken(String token) { + Key signingKey = jwtGenerator.getSigningKey(ACCESS_SECRET); + return validateToken(token, signingKey); + } + + public boolean validateRefreshToken(String token, String identifier) { + Key signingKey = jwtGenerator.getSigningKey(REFRESH_SECRET); + return validateToken(token, signingKey) && tokenRepository.existsById(identifier); + } + + public String resolveTokenFromCookie(HttpServletRequest request, JwtRule tokenType) { + Cookie[] cookies = request.getCookies(); + if (tokenType == ACCESS_PREFIX) { + return cookies[0].getValue(); + } + return cookies[1].getValue(); + } + + public Authentication getAuthentication(String token) { + Key key = jwtGenerator.getSigningKey(ACCESS_SECRET); + UserDetails principal = customUserDetailsService.loadUserByUsername(getUserPk(token, key)); + return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities()); + } + + private String getUserPk(String token, Key secretKey) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + } + + public String getIdentifierFromToken(String refreshToken) { + return Jwts.parserBuilder() + .setSigningKey(REFRESH_SECRET) + .build() + .parseClaimsJws(refreshToken) + .getBody() + .getSubject(); + } + + private boolean validateToken(String token, Key secretKey) { + try { + Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token); + return true; + } catch (ExpiredJwtException e) { + log.error(INVALID_EXPIRED_JWT.getMessage()); + return false; + } catch (MalformedJwtException e) { + throw new BusinessException(INVALID_MALFORMED_JWT); + } catch (ClaimJwtException e) { + throw new BusinessException(INVALID_CLAIM_JWT); + } catch (UnsupportedJwtException e) { + throw new BusinessException(UNSUPPORTED_JWT); + } catch (JwtException e) { + throw new BusinessException(INVALID_JWT); + } + } } diff --git a/src/main/java/com/genius/todoffin/user/controller/UserController.java b/src/main/java/com/genius/todoffin/user/controller/UserController.java index f40e4d11..14cddfe5 100644 --- a/src/main/java/com/genius/todoffin/user/controller/UserController.java +++ b/src/main/java/com/genius/todoffin/user/controller/UserController.java @@ -1,12 +1,14 @@ package com.genius.todoffin.user.controller; import static com.genius.todoffin.util.exception.SuccessCode.CREATED; +import static com.genius.todoffin.util.exception.SuccessCode.SUCCESS; import com.genius.todoffin.user.dto.SignupRequest; import com.genius.todoffin.user.service.UserService; import com.genius.todoffin.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,4 +27,12 @@ public ResponseEntity signup(@RequestBody SignupRequest signupRe new CommonResponse(CREATED.getStatus(), CREATED.getMessage()) ); } + + @GetMapping("/test") + public ResponseEntity test() { + System.out.println("test api success"); + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } } From a3f87848bee19b15caf3dfe040c453137fcc6b66 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 15 Jan 2024 01:46:08 +0900 Subject: [PATCH 057/234] =?UTF-8?q?refactor:=20JwtUtil=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/JwtAuthenticationFilter.java | 20 +++-- .../security/service/JwtGenerator.java | 20 +---- .../todoffin/security/service/JwtService.java | 86 +++++++------------ .../todoffin/security/service/JwtUtil.java | 72 ++++++++++++++++ .../todoffin/util/exception/ErrorCode.java | 3 +- 5 files changed, 119 insertions(+), 82 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/service/JwtUtil.java diff --git a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java index ca65f3c6..1892d8b8 100644 --- a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java @@ -1,8 +1,9 @@ package com.genius.todoffin.security.filter; import static com.genius.todoffin.security.config.SecurityConfig.PERMITTED_URI; +import static com.genius.todoffin.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; -import com.genius.todoffin.security.constants.JwtRule; import com.genius.todoffin.security.service.JwtService; import com.genius.todoffin.user.domain.User; import com.genius.todoffin.user.service.UserService; @@ -32,24 +33,22 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - String accessToken = jwtService.resolveTokenFromCookie(request, JwtRule.ACCESS_PREFIX); + String accessToken = jwtService.resolveTokenFromCookie(request, ACCESS_PREFIX); if (jwtService.validateAccessToken(accessToken)) { - Authentication authentication = jwtService.getAuthentication(accessToken); - SecurityContextHolder.getContext().setAuthentication(authentication); + setAuthenticationToContext(accessToken); filterChain.doFilter(request, response); return; } - String refreshToken = jwtService.resolveTokenFromCookie(request, JwtRule.REFRESH_PREFIX); - String identifier = jwtService.getIdentifierFromToken(refreshToken); + String refreshToken = jwtService.resolveTokenFromCookie(request, REFRESH_PREFIX); + String identifier = jwtService.getIdentifierFromRefresh(refreshToken); if (jwtService.validateRefreshToken(refreshToken, identifier)) { User user = userService.findUserByIdentifier(identifier); jwtService.generateAccessToken(response, user); jwtService.generateRefreshToken(response, user); } - Authentication authentication = jwtService.getAuthentication(accessToken); - SecurityContextHolder.getContext().setAuthentication(authentication); + setAuthenticationToContext(accessToken); filterChain.doFilter(request, response); } @@ -57,4 +56,9 @@ private boolean isPermittedURI(String requestURI) { return Arrays.stream(PERMITTED_URI) .anyMatch(permitted -> permitted.contains(requestURI)); } + + private void setAuthenticationToContext(String accessToken) { + Authentication authentication = jwtService.getAuthentication(accessToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + } } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java index 13321af9..cdad8fac 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java @@ -3,10 +3,7 @@ import com.genius.todoffin.user.domain.User; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import java.nio.charset.StandardCharsets; import java.security.Key; -import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -17,7 +14,7 @@ @Slf4j public class JwtGenerator { - public String generateAccessToken(final String ACCESS_SECRET, final long ACCESS_EXPIRATION, User requestUser) { + public String generateAccessToken(final Key ACCESS_SECRET, final long ACCESS_EXPIRATION, User requestUser) { Long now = System.currentTimeMillis(); return Jwts.builder() @@ -25,18 +22,18 @@ public String generateAccessToken(final String ACCESS_SECRET, final long ACCESS_ .setClaims(createClaims(requestUser)) .setSubject(String.valueOf(requestUser.getId())) .setExpiration(new Date(now + ACCESS_EXPIRATION)) - .signWith(getSigningKey(ACCESS_SECRET), SignatureAlgorithm.HS256) + .signWith(ACCESS_SECRET, SignatureAlgorithm.HS256) .compact(); } - public String generateRefreshToken(final String REFRESH_SECRET, final long REFRESH_EXPIRATION, User requestUser) { + public String generateRefreshToken(final Key REFRESH_SECRET, final long REFRESH_EXPIRATION, User requestUser) { Long now = System.currentTimeMillis(); return Jwts.builder() .setHeader(createHeader()) .setSubject(requestUser.getIdentifier()) .setExpiration(new Date(now + REFRESH_EXPIRATION)) - .signWith(getSigningKey(REFRESH_SECRET), SignatureAlgorithm.HS256) + .signWith(REFRESH_SECRET, SignatureAlgorithm.HS256) .compact(); } @@ -53,13 +50,4 @@ private Map createClaims(User requestUser) { claims.put("Role", requestUser.getRole()); return claims; } - - public Key getSigningKey(String secretKey) { - String encodedKey = encodeToBase64(secretKey); - return Keys.hmacShaKeyFor(encodedKey.getBytes(StandardCharsets.UTF_8)); - } - - private String encodeToBase64(String secretKey) { - return Base64.getEncoder().encodeToString(secretKey.getBytes()); - } } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java index 5b142428..502da4bd 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -3,28 +3,16 @@ import static com.genius.todoffin.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.todoffin.security.constants.JwtRule.JWT_ISSUE_HEADER; import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; -import static com.genius.todoffin.util.exception.ErrorCode.INVALID_CLAIM_JWT; -import static com.genius.todoffin.util.exception.ErrorCode.INVALID_EXPIRED_JWT; -import static com.genius.todoffin.util.exception.ErrorCode.INVALID_JWT; -import static com.genius.todoffin.util.exception.ErrorCode.INVALID_MALFORMED_JWT; -import static com.genius.todoffin.util.exception.ErrorCode.UNSUPPORTED_JWT; import com.genius.todoffin.security.constants.JwtRule; import com.genius.todoffin.security.domain.Token; import com.genius.todoffin.security.repository.TokenRepository; import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.util.exception.BusinessException; -import io.jsonwebtoken.ClaimJwtException; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.UnsupportedJwtException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.security.Key; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseCookie; @@ -36,32 +24,43 @@ @Service @Transactional(readOnly = true) -@RequiredArgsConstructor @Slf4j public class JwtService { private final CustomUserDetailsService customUserDetailsService; private final JwtGenerator jwtGenerator; + private final JwtUtil jwtUtil; private final TokenRepository tokenRepository; - @Value("${jwt.access-secret}") - private String ACCESS_SECRET; - @Value("${jwt.refresh-secret}") - private String REFRESH_SECRET; - @Value("${jwt.access-expiration}") - private long ACCESS_EXPIRATION; - @Value("${jwt.refresh-expiration}") - private long REFRESH_EXPIRATION; - + private final Key ACCESS_SECRET_KEY; + private final Key REFRESH_SECRET_KEY; + private final long ACCESS_EXPIRATION; + private final long REFRESH_EXPIRATION; + + public JwtService(CustomUserDetailsService customUserDetailsService, JwtGenerator jwtGenerator, + JwtUtil jwtUtil, TokenRepository tokenRepository, + @Value("${jwt.access-secret}") String ACCESS_SECRET_KEY, + @Value("${jwt.refresh-secret}") String REFRESH_SECRET_KEY, + @Value("${jwt.access-expiration}") long ACCESS_EXPIRATION, + @Value("${jwt.refresh-expiration}") long REFRESH_EXPIRATION) { + this.customUserDetailsService = customUserDetailsService; + this.jwtGenerator = jwtGenerator; + this.jwtUtil = jwtUtil; + this.tokenRepository = tokenRepository; + this.ACCESS_SECRET_KEY = jwtUtil.getSigningKey(ACCESS_SECRET_KEY); + this.REFRESH_SECRET_KEY = jwtUtil.getSigningKey(REFRESH_SECRET_KEY); + this.ACCESS_EXPIRATION = ACCESS_EXPIRATION; + this.REFRESH_EXPIRATION = REFRESH_EXPIRATION; + } public void generateAccessToken(HttpServletResponse response, User requestUser) { - String accessToken = jwtGenerator.generateAccessToken(ACCESS_SECRET, ACCESS_EXPIRATION, requestUser); + String accessToken = jwtGenerator.generateAccessToken(ACCESS_SECRET_KEY, ACCESS_EXPIRATION, requestUser); ResponseCookie cookie = setTokenToCookie(ACCESS_PREFIX.getValue(), accessToken, ACCESS_EXPIRATION / 1000); response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); } @Transactional public void generateRefreshToken(HttpServletResponse response, User requestUser) { - String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET, REFRESH_EXPIRATION, requestUser); + String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET_KEY, REFRESH_EXPIRATION, requestUser); ResponseCookie cookie = setTokenToCookie(REFRESH_PREFIX.getValue(), refreshToken, REFRESH_EXPIRATION / 1000); response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); @@ -78,26 +77,20 @@ private ResponseCookie setTokenToCookie(String tokenType, String token, long max } public boolean validateAccessToken(String token) { - Key signingKey = jwtGenerator.getSigningKey(ACCESS_SECRET); - return validateToken(token, signingKey); + return jwtUtil.validateToken(token, ACCESS_SECRET_KEY); } public boolean validateRefreshToken(String token, String identifier) { - Key signingKey = jwtGenerator.getSigningKey(REFRESH_SECRET); - return validateToken(token, signingKey) && tokenRepository.existsById(identifier); + return jwtUtil.validateToken(token, REFRESH_SECRET_KEY) && tokenRepository.existsById(identifier); } public String resolveTokenFromCookie(HttpServletRequest request, JwtRule tokenType) { Cookie[] cookies = request.getCookies(); - if (tokenType == ACCESS_PREFIX) { - return cookies[0].getValue(); - } - return cookies[1].getValue(); + return jwtUtil.resolveTokenFromCookie(cookies, tokenType); } public Authentication getAuthentication(String token) { - Key key = jwtGenerator.getSigningKey(ACCESS_SECRET); - UserDetails principal = customUserDetailsService.loadUserByUsername(getUserPk(token, key)); + UserDetails principal = customUserDetailsService.loadUserByUsername(getUserPk(token, ACCESS_SECRET_KEY)); return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities()); } @@ -110,33 +103,12 @@ private String getUserPk(String token, Key secretKey) { .getSubject(); } - public String getIdentifierFromToken(String refreshToken) { + public String getIdentifierFromRefresh(String refreshToken) { return Jwts.parserBuilder() - .setSigningKey(REFRESH_SECRET) + .setSigningKey(REFRESH_SECRET_KEY) .build() .parseClaimsJws(refreshToken) .getBody() .getSubject(); } - - private boolean validateToken(String token, Key secretKey) { - try { - Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token); - return true; - } catch (ExpiredJwtException e) { - log.error(INVALID_EXPIRED_JWT.getMessage()); - return false; - } catch (MalformedJwtException e) { - throw new BusinessException(INVALID_MALFORMED_JWT); - } catch (ClaimJwtException e) { - throw new BusinessException(INVALID_CLAIM_JWT); - } catch (UnsupportedJwtException e) { - throw new BusinessException(UNSUPPORTED_JWT); - } catch (JwtException e) { - throw new BusinessException(INVALID_JWT); - } - } } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtUtil.java b/src/main/java/com/genius/todoffin/security/service/JwtUtil.java new file mode 100644 index 00000000..f2a4e0e5 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/service/JwtUtil.java @@ -0,0 +1,72 @@ +package com.genius.todoffin.security.service; + +import static com.genius.todoffin.util.exception.ErrorCode.INVALID_CLAIM_JWT; +import static com.genius.todoffin.util.exception.ErrorCode.INVALID_EXPIRED_JWT; +import static com.genius.todoffin.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.todoffin.util.exception.ErrorCode.INVALID_MALFORMED_JWT; +import static com.genius.todoffin.util.exception.ErrorCode.TOKEN_NOT_FOUND; +import static com.genius.todoffin.util.exception.ErrorCode.UNSUPPORTED_JWT; + +import com.genius.todoffin.security.constants.JwtRule; +import com.genius.todoffin.util.exception.BusinessException; +import io.jsonwebtoken.ClaimJwtException; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Arrays; +import java.util.Base64; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class JwtUtil { + + public boolean validateToken(String token, Key secretKey) { + try { + Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token); + return true; + } catch (ExpiredJwtException e) { + log.error(INVALID_EXPIRED_JWT.getMessage()); + return false; + } catch (MalformedJwtException e) { + throw new BusinessException(INVALID_MALFORMED_JWT); + } catch (ClaimJwtException e) { + throw new BusinessException(INVALID_CLAIM_JWT); + } catch (UnsupportedJwtException e) { + throw new BusinessException(UNSUPPORTED_JWT); + } catch (JwtException e) { + throw new BusinessException(INVALID_JWT); + } + } + + public String resolveTokenFromCookie(Cookie[] cookies, JwtRule tokenType) { + return Arrays.stream(cookies) + .filter(cookie -> cookie.getName().equals(tokenType.getValue())) + .findFirst() + .map(String::valueOf) + .orElseThrow(() -> new BusinessException(TOKEN_NOT_FOUND)); + } + + public Key getSigningKey(String secretKey) { + String encodedKey = encodeToBase64(secretKey); + return Keys.hmacShaKeyFor(encodedKey.getBytes(StandardCharsets.UTF_8)); + } + + private String encodeToBase64(String secretKey) { + return Base64.getEncoder().encodeToString(secretKey.getBytes()); + } +} diff --git a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java index c8a94d1a..6ccc868d 100644 --- a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java @@ -13,7 +13,8 @@ public enum ErrorCode { INVALID_MALFORMED_JWT(HttpStatus.BAD_REQUEST, "JWT의 구조가 유효하지 않습니다."), INVALID_CLAIM_JWT(HttpStatus.BAD_REQUEST, "JWT의 Claim이 유효하지 않습니다."), UNSUPPORTED_JWT(HttpStatus.BAD_REQUEST, "지원하지 않는 JWT 형식입니다."), - INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."); + INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."), + TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."); private final HttpStatus status; private final String message; From cfba09dbb126867bf49471dfcebda89d8d027dd0 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 15 Jan 2024 02:31:15 +0900 Subject: [PATCH 058/234] =?UTF-8?q?feat:=20logout=20API=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 --- .../security/controller/AuthController.java | 13 +++++++++++++ .../security/filter/JwtAuthenticationFilter.java | 5 ++++- .../todoffin/security/service/JwtService.java | 10 ++++++++++ .../genius/todoffin/security/service/JwtUtil.java | 9 ++++++++- .../todoffin/user/controller/UserController.java | 10 ---------- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/controller/AuthController.java b/src/main/java/com/genius/todoffin/security/controller/AuthController.java index b13a96f2..dd7946b2 100644 --- a/src/main/java/com/genius/todoffin/security/controller/AuthController.java +++ b/src/main/java/com/genius/todoffin/security/controller/AuthController.java @@ -2,6 +2,7 @@ import static com.genius.todoffin.util.exception.SuccessCode.SUCCESS; +import com.genius.todoffin.security.domain.UserPrincipal; import com.genius.todoffin.security.dto.TokenRequest; import com.genius.todoffin.security.service.JwtService; import com.genius.todoffin.user.domain.User; @@ -11,6 +12,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -35,4 +37,15 @@ public ResponseEntity generateToken(HttpServletResponse response new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); } + + @PostMapping("/logout") + public ResponseEntity logout(HttpServletResponse response) { + UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() + .getPrincipal(); + jwtService.logout(userPrincipal.getUser(), response); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } } diff --git a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java index 1892d8b8..6842a69b 100644 --- a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java @@ -54,7 +54,10 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse private boolean isPermittedURI(String requestURI) { return Arrays.stream(PERMITTED_URI) - .anyMatch(permitted -> permitted.contains(requestURI)); + .anyMatch(permitted -> { + String replace = permitted.replace("*", ""); + return replace.contains(requestURI); + }); } private void setAuthenticationToContext(String accessToken) { diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java index 502da4bd..3820d1bd 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -111,4 +111,14 @@ public String getIdentifierFromRefresh(String refreshToken) { .getBody() .getSubject(); } + + public void logout(User requestUser, HttpServletResponse response) { + tokenRepository.deleteById(requestUser.getIdentifier()); + + Cookie accessCookie = jwtUtil.resetToken(ACCESS_PREFIX); + Cookie refreshCookie = jwtUtil.resetToken(REFRESH_PREFIX); + + response.addCookie(accessCookie); + response.addCookie(refreshCookie); + } } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtUtil.java b/src/main/java/com/genius/todoffin/security/service/JwtUtil.java index f2a4e0e5..d8b0f1d1 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtUtil.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtUtil.java @@ -57,7 +57,7 @@ public String resolveTokenFromCookie(Cookie[] cookies, JwtRule tokenType) { return Arrays.stream(cookies) .filter(cookie -> cookie.getName().equals(tokenType.getValue())) .findFirst() - .map(String::valueOf) + .map(Cookie::getValue) .orElseThrow(() -> new BusinessException(TOKEN_NOT_FOUND)); } @@ -69,4 +69,11 @@ public Key getSigningKey(String secretKey) { private String encodeToBase64(String secretKey) { return Base64.getEncoder().encodeToString(secretKey.getBytes()); } + + public Cookie resetToken(JwtRule tokenType) { + Cookie cookie = new Cookie(tokenType.getValue(), null); + cookie.setMaxAge(0); + cookie.setPath("/"); + return cookie; + } } diff --git a/src/main/java/com/genius/todoffin/user/controller/UserController.java b/src/main/java/com/genius/todoffin/user/controller/UserController.java index 14cddfe5..f40e4d11 100644 --- a/src/main/java/com/genius/todoffin/user/controller/UserController.java +++ b/src/main/java/com/genius/todoffin/user/controller/UserController.java @@ -1,14 +1,12 @@ package com.genius.todoffin.user.controller; import static com.genius.todoffin.util.exception.SuccessCode.CREATED; -import static com.genius.todoffin.util.exception.SuccessCode.SUCCESS; import com.genius.todoffin.user.dto.SignupRequest; import com.genius.todoffin.user.service.UserService; import com.genius.todoffin.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,12 +25,4 @@ public ResponseEntity signup(@RequestBody SignupRequest signupRe new CommonResponse(CREATED.getStatus(), CREATED.getMessage()) ); } - - @GetMapping("/test") - public ResponseEntity test() { - System.out.println("test api success"); - return ResponseEntity.ok().body( - new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) - ); - } } From 48eb1aca2ee19c2aec82bdf56560357bcad07dce Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:01:41 +0900 Subject: [PATCH 059/234] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=ED=99=95=EC=9D=B8=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/JwtAuthenticationFilter.java | 2 +- .../user/controller/UserController.java | 11 +++++++++ .../user/repository/UserRepository.java | 2 ++ .../todoffin/user/service/UserService.java | 14 ++++++++--- .../todoffin/util/exception/ErrorCode.java | 3 +++ .../util/response/dto/SingleResponse.java | 6 +++++ .../user/repository/UserRepositoryTest.java | 23 +++++++++++++++---- .../user/service/UserServiceTest.java | 4 ++-- 8 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java index 6842a69b..34ca475c 100644 --- a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java @@ -56,7 +56,7 @@ private boolean isPermittedURI(String requestURI) { return Arrays.stream(PERMITTED_URI) .anyMatch(permitted -> { String replace = permitted.replace("*", ""); - return replace.contains(requestURI); + return requestURI.contains(replace); }); } diff --git a/src/main/java/com/genius/todoffin/user/controller/UserController.java b/src/main/java/com/genius/todoffin/user/controller/UserController.java index f40e4d11..d3db3fad 100644 --- a/src/main/java/com/genius/todoffin/user/controller/UserController.java +++ b/src/main/java/com/genius/todoffin/user/controller/UserController.java @@ -1,15 +1,18 @@ package com.genius.todoffin.user.controller; import static com.genius.todoffin.util.exception.SuccessCode.CREATED; +import static com.genius.todoffin.util.exception.SuccessCode.SUCCESS; import com.genius.todoffin.user.dto.SignupRequest; import com.genius.todoffin.user.service.UserService; import com.genius.todoffin.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -18,6 +21,14 @@ public class UserController { private final UserService userService; + @GetMapping("/auth/check-nickname") + public ResponseEntity checkNicknameDuplicate(@RequestParam(value = "nickname") String nickname) { + userService.isNicknameDuplicate(nickname); + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } + @PostMapping("/auth/signup") public ResponseEntity signup(@RequestBody SignupRequest signupRequest) { userService.signup(signupRequest); diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java index d5b83b16..3eca1ad0 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -10,6 +10,8 @@ public interface UserRepository extends JpaRepository { Optional findByIdentifier(String identifier); + Optional findByNickname(String nickname); + @Query("select u from User u where u.identifier = :identifier and u.providerInfo = :providerInfo") Optional findByOAuthInfo(@Param("identifier") String identifier, @Param("providerInfo") ProviderInfo providerInfo); diff --git a/src/main/java/com/genius/todoffin/user/service/UserService.java b/src/main/java/com/genius/todoffin/user/service/UserService.java index 3f2a742f..df273a7a 100644 --- a/src/main/java/com/genius/todoffin/user/service/UserService.java +++ b/src/main/java/com/genius/todoffin/user/service/UserService.java @@ -1,11 +1,13 @@ package com.genius.todoffin.user.service; +import static com.genius.todoffin.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.todoffin.util.exception.ErrorCode.MEMBER_NOT_FOUND; + import com.genius.todoffin.user.domain.Role; import com.genius.todoffin.user.domain.User; import com.genius.todoffin.user.dto.SignupRequest; import com.genius.todoffin.user.repository.UserRepository; import com.genius.todoffin.util.exception.BusinessException; -import com.genius.todoffin.util.exception.ErrorCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -21,12 +23,12 @@ public class UserService { public User findUserById(Long id) { return userRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); } public User findUserByIdentifier(String identifier) { return userRepository.findByIdentifier(identifier) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); } @Transactional @@ -43,4 +45,10 @@ public Long signup(SignupRequest requestUser) { return targetUser.getId(); } + + public void isNicknameDuplicate(String nickname) { + if (userRepository.findByNickname(nickname).isPresent()) { + throw new BusinessException(DUPLICATED_NICKNAME); + } + } } diff --git a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java index 6ccc868d..1afa545e 100644 --- a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java @@ -9,11 +9,14 @@ public enum ErrorCode { MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."), + DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "이미 존재하는 닉네임입니다"), + INVALID_EXPIRED_JWT(HttpStatus.BAD_REQUEST, "이미 만료된 JWT 입니다."), INVALID_MALFORMED_JWT(HttpStatus.BAD_REQUEST, "JWT의 구조가 유효하지 않습니다."), INVALID_CLAIM_JWT(HttpStatus.BAD_REQUEST, "JWT의 Claim이 유효하지 않습니다."), UNSUPPORTED_JWT(HttpStatus.BAD_REQUEST, "지원하지 않는 JWT 형식입니다."), INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."), + TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."); private final HttpStatus status; diff --git a/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java b/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java index 74c08ff6..3339817e 100644 --- a/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java +++ b/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java @@ -2,6 +2,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; @Getter @RequiredArgsConstructor @@ -11,4 +12,9 @@ public class SingleResponse extends CommonResponse { public SingleResponse(T data) { this.data = data; } + + public SingleResponse(HttpStatus status, String message, T data) { + super(status, message); + this.data = data; + } } diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java index 47b8204b..f72965d7 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -1,5 +1,6 @@ package com.genius.todoffin.user.repository; +import static com.genius.todoffin.security.constants.ProviderInfo.GITHUB; import static org.assertj.core.api.Assertions.assertThat; import com.genius.todoffin.security.constants.ProviderInfo; @@ -28,7 +29,7 @@ class UserRepositoryTest { //when User savedUser = userRepository.save(user); - User foundUser = userRepository.findByEmail(email).get(); + User foundUser = userRepository.findByIdentifier(email).get(); //then assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); @@ -57,11 +58,25 @@ class UserRepositoryTest { assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); } + @Test + @DisplayName("User nickname 중복 방지 테스트") + public void checkNicknameDuplicate() { + //given + User user1 = getUnsavedUser("SSung023", GITHUB, "nickname"); + + //when + User savedUser = userRepository.save(user1); + User nickname = userRepository.findByNickname("nickname").get(); + + //then + assertThat(nickname).isEqualTo(savedUser); + } + - private User getUnsavedUser(String email, ProviderInfo providerInfo, String nickname) { + private User getUnsavedUser(String identifier, ProviderInfo providerInfo, String nickname) { return User.builder() - .email(email) - .provider(providerInfo) + .identifier(identifier) + .providerInfo(providerInfo) .role(Role.USER) .nickname(nickname) .build(); diff --git a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java index faf5c672..6d3c1437 100644 --- a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java @@ -32,7 +32,7 @@ public void should_matchValues_when_signupUser() { String email = "test@naver.com"; saveUnsignedUser(); SignupRequest signupRequest = SignupRequest.builder() - .email(email) + .identifier(email) .nickname("nickname") .information("information") .interest(List.of("관심사1", "관심사2")) @@ -55,7 +55,7 @@ public void should_matchValues_when_signupUser() { private void saveUnsignedUser() { userRepository.save(User.builder() .role(Role.NOT_REGISTERED) - .provider(ProviderInfo.NAVER) + .providerInfo(ProviderInfo.NAVER) .identifier("test@naver.com") .build()); } From 69f008dab7d216ee2ce8f9c6eedf4c676da242a2 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 15 Jan 2024 14:30:10 +0900 Subject: [PATCH 060/234] =?UTF-8?q?refactor:=20JWT=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=82=B4=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 변수명 적절하게 수정 2. TokenStatus enum 도입 및 적용 3. 메서드 추출 --- .../security/constants/TokenStatus.java | 12 +++++++++ .../filter/JwtAuthenticationFilter.java | 18 ++++++++----- .../security/service/JwtGenerator.java | 16 +++++------ .../todoffin/security/service/JwtService.java | 18 ++++++++----- .../todoffin/security/service/JwtUtil.java | 27 ++++++------------- 5 files changed, 50 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/constants/TokenStatus.java diff --git a/src/main/java/com/genius/todoffin/security/constants/TokenStatus.java b/src/main/java/com/genius/todoffin/security/constants/TokenStatus.java new file mode 100644 index 00000000..09c70708 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/constants/TokenStatus.java @@ -0,0 +1,12 @@ +package com.genius.todoffin.security.constants; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum TokenStatus { + AUTHENTICATED, + EXPIRED, + INVALID +} diff --git a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java index 34ca475c..1d74d249 100644 --- a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java @@ -41,15 +41,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } String refreshToken = jwtService.resolveTokenFromCookie(request, REFRESH_PREFIX); - String identifier = jwtService.getIdentifierFromRefresh(refreshToken); - if (jwtService.validateRefreshToken(refreshToken, identifier)) { - User user = userService.findUserByIdentifier(identifier); - jwtService.generateAccessToken(response, user); + User user = findUserByRefreshToken(refreshToken); + + if (jwtService.validateRefreshToken(refreshToken, user.getIdentifier())) { + String reissuedAccessToken = jwtService.generateAccessToken(response, user); jwtService.generateRefreshToken(response, user); - } - setAuthenticationToContext(accessToken); - filterChain.doFilter(request, response); + setAuthenticationToContext(reissuedAccessToken); + } } private boolean isPermittedURI(String requestURI) { @@ -60,6 +59,11 @@ private boolean isPermittedURI(String requestURI) { }); } + private User findUserByRefreshToken(String refreshToken) { + String identifier = jwtService.getIdentifierFromRefresh(refreshToken); + return userService.findUserByIdentifier(identifier); + } + private void setAuthenticationToContext(String accessToken) { Authentication authentication = jwtService.getAuthentication(accessToken); SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java index cdad8fac..2f201d0b 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java @@ -14,24 +14,24 @@ @Slf4j public class JwtGenerator { - public String generateAccessToken(final Key ACCESS_SECRET, final long ACCESS_EXPIRATION, User requestUser) { + public String generateAccessToken(final Key ACCESS_SECRET, final long ACCESS_EXPIRATION, User user) { Long now = System.currentTimeMillis(); return Jwts.builder() .setHeader(createHeader()) - .setClaims(createClaims(requestUser)) - .setSubject(String.valueOf(requestUser.getId())) + .setClaims(createClaims(user)) + .setSubject(String.valueOf(user.getId())) .setExpiration(new Date(now + ACCESS_EXPIRATION)) .signWith(ACCESS_SECRET, SignatureAlgorithm.HS256) .compact(); } - public String generateRefreshToken(final Key REFRESH_SECRET, final long REFRESH_EXPIRATION, User requestUser) { + public String generateRefreshToken(final Key REFRESH_SECRET, final long REFRESH_EXPIRATION, User user) { Long now = System.currentTimeMillis(); return Jwts.builder() .setHeader(createHeader()) - .setSubject(requestUser.getIdentifier()) + .setSubject(user.getIdentifier()) .setExpiration(new Date(now + REFRESH_EXPIRATION)) .signWith(REFRESH_SECRET, SignatureAlgorithm.HS256) .compact(); @@ -44,10 +44,10 @@ private Map createHeader() { return header; } - private Map createClaims(User requestUser) { + private Map createClaims(User user) { Map claims = new HashMap<>(); - claims.put("Identifier", requestUser.getIdentifier()); - claims.put("Role", requestUser.getRole()); + claims.put("Identifier", user.getIdentifier()); + claims.put("Role", user.getRole()); return claims; } } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java index 3820d1bd..27619514 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -5,6 +5,7 @@ import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; import com.genius.todoffin.security.constants.JwtRule; +import com.genius.todoffin.security.constants.TokenStatus; import com.genius.todoffin.security.domain.Token; import com.genius.todoffin.security.repository.TokenRepository; import com.genius.todoffin.user.domain.User; @@ -52,10 +53,12 @@ public JwtService(CustomUserDetailsService customUserDetailsService, JwtGenerato this.REFRESH_EXPIRATION = REFRESH_EXPIRATION; } - public void generateAccessToken(HttpServletResponse response, User requestUser) { + public String generateAccessToken(HttpServletResponse response, User requestUser) { String accessToken = jwtGenerator.generateAccessToken(ACCESS_SECRET_KEY, ACCESS_EXPIRATION, requestUser); ResponseCookie cookie = setTokenToCookie(ACCESS_PREFIX.getValue(), accessToken, ACCESS_EXPIRATION / 1000); response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); + + return accessToken; } @Transactional @@ -67,8 +70,8 @@ public void generateRefreshToken(HttpServletResponse response, User requestUser) tokenRepository.save(new Token(requestUser.getIdentifier(), refreshToken)); } - private ResponseCookie setTokenToCookie(String tokenType, String token, long maxAgeSeconds) { - return ResponseCookie.from(tokenType, token) + private ResponseCookie setTokenToCookie(String tokenPrefix, String token, long maxAgeSeconds) { + return ResponseCookie.from(tokenPrefix, token) .path("/") .maxAge(maxAgeSeconds) .httpOnly(true) @@ -77,16 +80,17 @@ private ResponseCookie setTokenToCookie(String tokenType, String token, long max } public boolean validateAccessToken(String token) { - return jwtUtil.validateToken(token, ACCESS_SECRET_KEY); + return jwtUtil.getTokenStatus(token, ACCESS_SECRET_KEY) == TokenStatus.AUTHENTICATED; } public boolean validateRefreshToken(String token, String identifier) { - return jwtUtil.validateToken(token, REFRESH_SECRET_KEY) && tokenRepository.existsById(identifier); + boolean isRefreshValid = jwtUtil.getTokenStatus(token, REFRESH_SECRET_KEY) == TokenStatus.AUTHENTICATED; + return isRefreshValid && tokenRepository.existsById(identifier); } - public String resolveTokenFromCookie(HttpServletRequest request, JwtRule tokenType) { + public String resolveTokenFromCookie(HttpServletRequest request, JwtRule tokenPrefix) { Cookie[] cookies = request.getCookies(); - return jwtUtil.resolveTokenFromCookie(cookies, tokenType); + return jwtUtil.resolveTokenFromCookie(cookies, tokenPrefix); } public Authentication getAuthentication(String token) { diff --git a/src/main/java/com/genius/todoffin/security/service/JwtUtil.java b/src/main/java/com/genius/todoffin/security/service/JwtUtil.java index d8b0f1d1..585473c7 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtUtil.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtUtil.java @@ -1,20 +1,15 @@ package com.genius.todoffin.security.service; -import static com.genius.todoffin.util.exception.ErrorCode.INVALID_CLAIM_JWT; import static com.genius.todoffin.util.exception.ErrorCode.INVALID_EXPIRED_JWT; import static com.genius.todoffin.util.exception.ErrorCode.INVALID_JWT; -import static com.genius.todoffin.util.exception.ErrorCode.INVALID_MALFORMED_JWT; import static com.genius.todoffin.util.exception.ErrorCode.TOKEN_NOT_FOUND; -import static com.genius.todoffin.util.exception.ErrorCode.UNSUPPORTED_JWT; import com.genius.todoffin.security.constants.JwtRule; +import com.genius.todoffin.security.constants.TokenStatus; import com.genius.todoffin.util.exception.BusinessException; -import io.jsonwebtoken.ClaimJwtException; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.security.Keys; import jakarta.servlet.http.Cookie; import java.nio.charset.StandardCharsets; @@ -32,30 +27,24 @@ @RequiredArgsConstructor public class JwtUtil { - public boolean validateToken(String token, Key secretKey) { + public TokenStatus getTokenStatus(String token, Key secretKey) { try { Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token); - return true; + return TokenStatus.AUTHENTICATED; } catch (ExpiredJwtException e) { log.error(INVALID_EXPIRED_JWT.getMessage()); - return false; - } catch (MalformedJwtException e) { - throw new BusinessException(INVALID_MALFORMED_JWT); - } catch (ClaimJwtException e) { - throw new BusinessException(INVALID_CLAIM_JWT); - } catch (UnsupportedJwtException e) { - throw new BusinessException(UNSUPPORTED_JWT); + return TokenStatus.EXPIRED; } catch (JwtException e) { throw new BusinessException(INVALID_JWT); } } - public String resolveTokenFromCookie(Cookie[] cookies, JwtRule tokenType) { + public String resolveTokenFromCookie(Cookie[] cookies, JwtRule tokenPrefix) { return Arrays.stream(cookies) - .filter(cookie -> cookie.getName().equals(tokenType.getValue())) + .filter(cookie -> cookie.getName().equals(tokenPrefix.getValue())) .findFirst() .map(Cookie::getValue) .orElseThrow(() -> new BusinessException(TOKEN_NOT_FOUND)); @@ -70,8 +59,8 @@ private String encodeToBase64(String secretKey) { return Base64.getEncoder().encodeToString(secretKey.getBytes()); } - public Cookie resetToken(JwtRule tokenType) { - Cookie cookie = new Cookie(tokenType.getValue(), null); + public Cookie resetToken(JwtRule tokenPrefix) { + Cookie cookie = new Cookie(tokenPrefix.getValue(), null); cookie.setMaxAge(0); cookie.setPath("/"); return cookie; From 061b7d472c85732abdb21e07446e989adbee7363 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:25:30 +0900 Subject: [PATCH 061/234] Update pull_request_template.md --- .github/pull_request_template.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c88fcb5e..377295fb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,11 @@ ### PR 타입(하나 이상의 PR 타입을 선택해주세요) -- [ ] 기능 추가 +☑ 기능 추가 -- [ ] 기능 삭제 +□ 기능 삭제 -- [ ] 버그 수정 +□ 버그 수정 -- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 +□ 의존성, 환경 변수, 빌드 관련 코드 업데이트
From ef0e5a065a9ad1e6ad782e3b6439d453cc3722c5 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:09:16 +0900 Subject: [PATCH 062/234] =?UTF-8?q?feat:=20refresh=20token=20=ED=83=88?= =?UTF-8?q?=EC=B7=A8=20=EA=B0=90=EC=A7=80=20=EB=A1=9C=EC=A7=81=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 1. 요청받은 Refresh-token과 DB에 저장된 토큰이 불일치 시 토큰 탈취됨을 감지하는 로직 구현 2. 토큰 탈취 감지 시, 강제 로그아웃 실행 3. 관련 테스트코드 추가 --- .../todoffin/security/domain/Token.java | 8 +- .../filter/JwtAuthenticationFilter.java | 6 +- .../security/repository/TokenRepository.java | 1 + .../todoffin/security/service/JwtService.java | 1 + .../security/service/TokenService.java | 30 +++++++ .../security/service/TokenServiceTest.java | 81 +++++++++++++++++++ 6 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/security/service/TokenService.java create mode 100644 src/test/java/com/genius/todoffin/security/service/TokenServiceTest.java diff --git a/src/main/java/com/genius/todoffin/security/domain/Token.java b/src/main/java/com/genius/todoffin/security/domain/Token.java index fcc8d594..8ad631f9 100644 --- a/src/main/java/com/genius/todoffin/security/domain/Token.java +++ b/src/main/java/com/genius/todoffin/security/domain/Token.java @@ -2,13 +2,15 @@ import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; -import org.springframework.data.mongodb.core.mapping.MongoId; -@Document(collection = "Token") @Getter +@Document(collection = "Token") +@NoArgsConstructor public class Token { - @MongoId + @Id private String identifier; private String token; diff --git a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java index 1d74d249..64d883ae 100644 --- a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java @@ -48,14 +48,18 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse jwtService.generateRefreshToken(response, user); setAuthenticationToContext(reissuedAccessToken); + filterChain.doFilter(request, response); + return; } + + jwtService.logout(user, response); } private boolean isPermittedURI(String requestURI) { return Arrays.stream(PERMITTED_URI) .anyMatch(permitted -> { String replace = permitted.replace("*", ""); - return requestURI.contains(replace); + return requestURI.contains(replace) || replace.contains(requestURI); }); } diff --git a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java b/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java index 6844137c..f528de52 100644 --- a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java +++ b/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java @@ -4,4 +4,5 @@ import org.springframework.data.mongodb.repository.MongoRepository; public interface TokenRepository extends MongoRepository { + Token findByIdentifier(String identifier); } diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java index 27619514..5b6103f3 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -85,6 +85,7 @@ public boolean validateAccessToken(String token) { public boolean validateRefreshToken(String token, String identifier) { boolean isRefreshValid = jwtUtil.getTokenStatus(token, REFRESH_SECRET_KEY) == TokenStatus.AUTHENTICATED; + return isRefreshValid && tokenRepository.existsById(identifier); } diff --git a/src/main/java/com/genius/todoffin/security/service/TokenService.java b/src/main/java/com/genius/todoffin/security/service/TokenService.java new file mode 100644 index 00000000..08231433 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/service/TokenService.java @@ -0,0 +1,30 @@ +package com.genius.todoffin.security.service; + +import com.genius.todoffin.security.domain.Token; +import com.genius.todoffin.security.repository.TokenRepository; +import com.genius.todoffin.util.exception.BusinessException; +import com.genius.todoffin.util.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class TokenService { + private final TokenRepository tokenRepository; + + public Token findTokenByIdentifier(String identifier) { + return tokenRepository.findById(identifier) + .orElseThrow(() -> new BusinessException(ErrorCode.TOKEN_NOT_FOUND)); + } + + public boolean isRefreshHijacked(String identifier, String refreshToken) { + Token token = findTokenByIdentifier(identifier); + return token.getToken().equals(refreshToken); + } + + +} diff --git a/src/test/java/com/genius/todoffin/security/service/TokenServiceTest.java b/src/test/java/com/genius/todoffin/security/service/TokenServiceTest.java new file mode 100644 index 00000000..59273be9 --- /dev/null +++ b/src/test/java/com/genius/todoffin/security/service/TokenServiceTest.java @@ -0,0 +1,81 @@ +package com.genius.todoffin.security.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.todoffin.security.domain.Token; +import com.genius.todoffin.security.repository.TokenRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class TokenServiceTest { + @Autowired + private TokenService tokenService; + + @Autowired + private TokenRepository tokenRepository; + + @Test + @DisplayName("특정 리프레시 토큰을 identifier를 통해 DB에서 값을 조회할 수 있어야 한다.") + public void should_findToken_when_findByIdentifier() { + //given + String identifier = "SSung023"; + String refreshToken = "refresh token example"; + Token token = Token.builder() + .identifier(identifier) + .token(refreshToken) + .build(); + + //when + Token savedToken = tokenRepository.save(token); + Token tokenByIdentifier = tokenService.findTokenByIdentifier(identifier); + + //then + assertThat(savedToken.getIdentifier()).isEqualTo(tokenByIdentifier.getIdentifier()); + assertThat(savedToken.getToken()).isEqualTo(tokenByIdentifier.getToken()); + + } + + @Test + @DisplayName("리프레시 토큰 요청이 들어왔을 때, identifier-token 짝이 맞게 저장되어 있으면 true를 반환한다.") + public void should_returnTrue_when_tokenValid() { + //given + String identifier = "SSung023"; + String refreshToken = "refresh token example"; + Token token = Token.builder() + .identifier(identifier) + .token(refreshToken) + .build(); + + //when + tokenRepository.save(token); + boolean isRefreshHijacked = tokenService.isRefreshHijacked(identifier, refreshToken); + + //then + assertThat(isRefreshHijacked).isTrue(); + } + + @Test + @DisplayName("리프레시 토큰 요청이 들어왔을 때, identifier-token 짝이 맞게 저장되어 있으면 true를 반환한다.") + public void should_returnFalse_when_tokenInvalid() { + //given + String identifier = "SSung023"; + String refreshToken = "refresh token example"; + String fakeRefreshToken = "fake refresh token example"; + Token token = Token.builder() + .identifier(identifier) + .token(refreshToken) + .build(); + + //when + tokenRepository.save(token); + boolean isRefreshHijacked = tokenService.isRefreshHijacked(identifier, fakeRefreshToken); + + //then + assertThat(isRefreshHijacked).isFalse(); + } +} \ No newline at end of file From c93b35e4a4c0156e05de48cb302d8d78072742ae Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Tue, 16 Jan 2024 20:03:32 +0900 Subject: [PATCH 063/234] =?UTF-8?q?[FEAT]=20DB=20Entity=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20(#28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 소셜 로그인 facebook 관련 파일 제거 * feat: DB Entity 개발 - User Entity 수정 - Hits, Topic, Instance, ParticipantInfo Entity 개발 * feat: challenge domain 및 repository 개발 - entity 수정 및 애노테이션 추가 - DB Table 별 repository 추가 - User entity : email -> identifier 로 변경 --- .../todoffin/challenge/domain/Hits.java | 28 ++++++++ .../todoffin/challenge/domain/Instance.java | 65 +++++++++++++++++++ .../todoffin/challenge/domain/JoinResult.java | 8 +++ .../challenge/domain/ParticipantInfo.java | 44 +++++++++++++ .../todoffin/challenge/domain/Progress.java | 8 +++ .../todoffin/challenge/domain/Topic.java | 37 +++++++++++ .../challenge/repository/HitsRepository.java | 7 ++ .../repository/InstanceRepository.java | 7 ++ .../challenge/repository/TopicRepository.java | 7 ++ .../common/domain/BaseTimeEntity.java | 17 ++++- .../security/constants/ProviderType.java | 3 +- .../security/domain/UserPrincipal.java | 2 +- .../handler/OAuth2SuccessHandler.java | 6 +- .../security/info/OAuth2UserInfoFactory.java | 4 -- .../info/impl/FacebookOAuth2UserInfo.java | 25 ------- .../service/CustomOAuth2UserService.java | 8 +-- .../com/genius/todoffin/user/domain/User.java | 48 +++++++------- .../todoffin/user/dto/SignupRequest.java | 2 +- .../user/repository/UserRepository.java | 6 +- .../todoffin/user/service/UserService.java | 6 +- .../user/repository/UserRepositoryTest.java | 22 ++----- .../user/service/UserServiceTest.java | 27 +------- 22 files changed, 276 insertions(+), 111 deletions(-) create mode 100644 src/main/java/com/genius/todoffin/challenge/domain/Hits.java create mode 100644 src/main/java/com/genius/todoffin/challenge/domain/Instance.java create mode 100644 src/main/java/com/genius/todoffin/challenge/domain/JoinResult.java create mode 100644 src/main/java/com/genius/todoffin/challenge/domain/ParticipantInfo.java create mode 100644 src/main/java/com/genius/todoffin/challenge/domain/Progress.java create mode 100644 src/main/java/com/genius/todoffin/challenge/domain/Topic.java create mode 100644 src/main/java/com/genius/todoffin/challenge/repository/HitsRepository.java create mode 100644 src/main/java/com/genius/todoffin/challenge/repository/InstanceRepository.java create mode 100644 src/main/java/com/genius/todoffin/challenge/repository/TopicRepository.java delete mode 100644 src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java diff --git a/src/main/java/com/genius/todoffin/challenge/domain/Hits.java b/src/main/java/com/genius/todoffin/challenge/domain/Hits.java new file mode 100644 index 00000000..d14378c4 --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/domain/Hits.java @@ -0,0 +1,28 @@ +package com.genius.todoffin.challenge.domain; + +import com.genius.todoffin.user.domain.User; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "hits") +public class Hits { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "hits_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "instance_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Instance instance; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private User user; +} diff --git a/src/main/java/com/genius/todoffin/challenge/domain/Instance.java b/src/main/java/com/genius/todoffin/challenge/domain/Instance.java new file mode 100644 index 00000000..ce55119a --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/domain/Instance.java @@ -0,0 +1,65 @@ +package com.genius.todoffin.challenge.domain; + +import com.genius.todoffin.common.domain.BaseTimeEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.Fetch; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DynamicInsert +@Table(name = "instance") +public class Instance extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "instance_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "topic_id") + private Topic topic; + + @OneToMany(mappedBy = "instance") + private List hitsList = new ArrayList<>(); + + @OneToMany(mappedBy = "instance") + private List participantInfoList = new ArrayList<>(); + + private String title; + + private String description; + + private int participants; + + private String tags; + + private int point_per_person; + + @NotNull + @ColumnDefault("0") + private int like_count; + + @NotNull + @Enumerated(EnumType.STRING) + @ColumnDefault("'PRE_ACTIVITY'") + private Progress progress; + + public Instance(String title, String description, int participants, String tags, int point_per_person, int like_count, Progress progress) { + this.title = title; + this.description = description; + this.participants = participants; + this.tags = tags; + this.point_per_person = point_per_person; + this.like_count = like_count; + this.progress = progress; + } +} diff --git a/src/main/java/com/genius/todoffin/challenge/domain/JoinResult.java b/src/main/java/com/genius/todoffin/challenge/domain/JoinResult.java new file mode 100644 index 00000000..c40c9f83 --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/domain/JoinResult.java @@ -0,0 +1,8 @@ +package com.genius.todoffin.challenge.domain; + +import lombok.Getter; + +@Getter +public enum JoinResult { + FAIL, SUCCESS +} diff --git a/src/main/java/com/genius/todoffin/challenge/domain/ParticipantInfo.java b/src/main/java/com/genius/todoffin/challenge/domain/ParticipantInfo.java new file mode 100644 index 00000000..97962828 --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/domain/ParticipantInfo.java @@ -0,0 +1,44 @@ +package com.genius.todoffin.challenge.domain; + +import com.genius.todoffin.user.domain.User; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DynamicInsert +@Table(name = "participantInfo") +public class ParticipantInfo { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "participantInfo_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "instance_id") + private Instance instance; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @NotNull + @Column(name = "join_status") + @ColumnDefault("0") + private Boolean joinStatus; + + @Enumerated(EnumType.STRING) + @Column(name = "join_result") + private JoinResult joinResult; + + public ParticipantInfo(Boolean joinStatus, JoinResult joinResult) { + this.joinStatus = joinStatus; + this.joinResult = joinResult; + } +} diff --git a/src/main/java/com/genius/todoffin/challenge/domain/Progress.java b/src/main/java/com/genius/todoffin/challenge/domain/Progress.java new file mode 100644 index 00000000..46c5574d --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/domain/Progress.java @@ -0,0 +1,8 @@ +package com.genius.todoffin.challenge.domain; + +import lombok.Getter; + +@Getter +public enum Progress { + PRE_ACTIVITY, ACTIVITY, DONE +} diff --git a/src/main/java/com/genius/todoffin/challenge/domain/Topic.java b/src/main/java/com/genius/todoffin/challenge/domain/Topic.java new file mode 100644 index 00000000..f90d955d --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/domain/Topic.java @@ -0,0 +1,37 @@ +package com.genius.todoffin.challenge.domain; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "topic") +public class Topic { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "topic_id") + private Long id; + + private String title; + + private String description; + + private String tags; + + private int point_per_person; + + @OneToMany(mappedBy = "topic") + private List instanceList; + + public Topic(String title, String description, String tags, int point_per_person) { + this.title = title; + this.description = description; + this.tags = tags; + this.point_per_person = point_per_person; + } +} diff --git a/src/main/java/com/genius/todoffin/challenge/repository/HitsRepository.java b/src/main/java/com/genius/todoffin/challenge/repository/HitsRepository.java new file mode 100644 index 00000000..1736d65c --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/repository/HitsRepository.java @@ -0,0 +1,7 @@ +package com.genius.todoffin.challenge.repository; + +import com.genius.todoffin.challenge.domain.Hits; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HitsRepository extends JpaRepository { +} diff --git a/src/main/java/com/genius/todoffin/challenge/repository/InstanceRepository.java b/src/main/java/com/genius/todoffin/challenge/repository/InstanceRepository.java new file mode 100644 index 00000000..ae4951aa --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/repository/InstanceRepository.java @@ -0,0 +1,7 @@ +package com.genius.todoffin.challenge.repository; + +import com.genius.todoffin.challenge.domain.Instance; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface InstanceRepository extends JpaRepository { +} diff --git a/src/main/java/com/genius/todoffin/challenge/repository/TopicRepository.java b/src/main/java/com/genius/todoffin/challenge/repository/TopicRepository.java new file mode 100644 index 00000000..675463a2 --- /dev/null +++ b/src/main/java/com/genius/todoffin/challenge/repository/TopicRepository.java @@ -0,0 +1,7 @@ +package com.genius.todoffin.challenge.repository; + +import com.genius.todoffin.challenge.domain.Topic; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TopicRepository extends JpaRepository { +} diff --git a/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java b/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java index c331380b..60a4ea73 100644 --- a/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java +++ b/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java @@ -5,6 +5,8 @@ import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; + +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.ToString; import org.springframework.data.annotation.CreatedDate; @@ -17,12 +19,25 @@ @EntityListeners(AuditingEntityListener.class) public class BaseTimeEntity { + // User 테이블 @CreatedDate - @Column(updatable = false) + @Column(name = "created_at", updatable = false) private LocalDateTime createdDate; @LastModifiedDate + @Column(name = "updated_at") private LocalDateTime modifiedDate; + + @Column(name = "deleted_at") private LocalDateTime deletedDate; + + + // ====================================================== + // Instance 테이블 + @Column(name = "started_at") + private LocalDateTime startedDate; + + @Column(name = "completed_at") + private LocalDateTime completedDate; } \ No newline at end of file diff --git a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java b/src/main/java/com/genius/todoffin/security/constants/ProviderType.java index eedef71b..ee4cbcd2 100644 --- a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java +++ b/src/main/java/com/genius/todoffin/security/constants/ProviderType.java @@ -5,8 +5,7 @@ public enum ProviderType { KAKAO, NAVER, - GOOGLE, - FACEBOOK; + GOOGLE; public static ProviderType from(String provider) { String upperCastedProvider = provider.toUpperCase(); diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java index a7715cb7..947f061a 100644 --- a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java @@ -20,6 +20,6 @@ public UserPrincipal(User user, Map attributes, String nameAttri @Override public String getName() { - return user.getEmail(); + return user.getIdentifier(); } } diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java index 4ffa458c..71ae64b7 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java @@ -29,13 +29,13 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); - String email = oAuth2User.getName(); + String identifier = oAuth2User.getName(); - User user = userRepository.findByEmail(email) + User user = userRepository.findByIdentifier(identifier) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); Role role = user.getRole(); - String redirectUrl = getRedirectUrlByRole(role, email); + String redirectUrl = getRedirectUrlByRole(role, identifier); getRedirectStrategy().sendRedirect(request, response, redirectUrl); } diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java index 07324de8..a3e268ec 100644 --- a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java +++ b/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java @@ -1,7 +1,6 @@ package com.genius.todoffin.security.info; import com.genius.todoffin.security.constants.ProviderType; -import com.genius.todoffin.security.info.impl.FacebookOAuth2UserInfo; import com.genius.todoffin.security.info.impl.GoogleOAuth2UserInfo; import com.genius.todoffin.security.info.impl.KakaoOAuth2UserInfo; import com.genius.todoffin.security.info.impl.NaverOAuth2UserInfo; @@ -20,9 +19,6 @@ public static OAuth2UserInfo getOAuth2UserInfo(ProviderType providerType, Map { return new GoogleOAuth2UserInfo(attributes); } - case FACEBOOK -> { - return new FacebookOAuth2UserInfo(attributes); - } } throw new OAuth2AuthenticationException("INVALID PROVIDER TYPE"); } diff --git a/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java b/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java deleted file mode 100644 index 15ec98b4..00000000 --- a/src/main/java/com/genius/todoffin/security/info/impl/FacebookOAuth2UserInfo.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.genius.todoffin.security.info.impl; - -import static com.genius.todoffin.security.constants.OAuthRule.EMAIL_KEY; -import static com.genius.todoffin.security.constants.OAuthRule.FACEBOOK_PROVIDER_ID; - -import com.genius.todoffin.security.info.OAuth2UserInfo; -import java.util.Map; - -public class FacebookOAuth2UserInfo extends OAuth2UserInfo { - - public FacebookOAuth2UserInfo(Map attributes) { - super(attributes); - } - - @Override - public String getProviderId() { - return (String) attributes.get(FACEBOOK_PROVIDER_ID.getValue()); - } - - @Override - public String getEmail() { - return (String) attributes.get(EMAIL_KEY.getValue()); - } -} - diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java index e68cf9ab..1f574c7f 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java @@ -51,14 +51,14 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic return new UserPrincipal(user, attributes, userNameAttributeName); } - private User getUser(String email, ProviderType providerType) { - Optional optionalUser = userRepository.findByOAuthInfo(email, providerType); + private User getUser(String identifier, ProviderType providerType) { + Optional optionalUser = userRepository.findByOAuthInfo(identifier, providerType); if (optionalUser.isEmpty()) { User unregisteredUser = User.builder() - .email(email) + // .email(email) .role(Role.NOT_REGISTERED) - .provider(providerType) + .providerInfo(providerType) .build(); return userRepository.save(unregisteredUser); } diff --git a/src/main/java/com/genius/todoffin/user/domain/User.java b/src/main/java/com/genius/todoffin/user/domain/User.java index 2a186217..2cc9bada 100644 --- a/src/main/java/com/genius/todoffin/user/domain/User.java +++ b/src/main/java/com/genius/todoffin/user/domain/User.java @@ -1,23 +1,20 @@ package com.genius.todoffin.user.domain; - +import com.genius.todoffin.challenge.domain.Hits; +import com.genius.todoffin.challenge.domain.ParticipantInfo; import com.genius.todoffin.common.domain.BaseTimeEntity; import com.genius.todoffin.security.constants.ProviderType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; @Entity @Getter -@RequiredArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "user") public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -26,31 +23,38 @@ public class User extends BaseTimeEntity { @NotNull @Enumerated(EnumType.STRING) - private ProviderType provider; + private ProviderType providerInfo; @NotNull - private String email; + private String identifier; + @NotNull @Enumerated(EnumType.STRING) private Role role; - @Column(unique = true, length = 16) + @Column(unique = true, length = 10) private String nickname; - @Column(length = 160) - private String information; private String interest; + @Column(length = 100) + private String information; + + @OneToMany(mappedBy = "user") + private List hitsList = new ArrayList<>(); + + @OneToMany(mappedBy = "user") + private List participantInfoList = new ArrayList<>(); + @Builder - public User(ProviderType provider, String email, Role role, String nickname, String information, - String interest) { - this.provider = provider; - this.email = email; + public User(ProviderType providerInfo, String identifier, Role role, String nickname, String interest, String information) { + this.providerInfo = providerInfo; + this.identifier = identifier; this.role = role; this.nickname = nickname; - this.information = information; this.interest = interest; + this.information = information; } public void updateUser(String nickname, String information, String interest) { diff --git a/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java b/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java index f5de661e..205b5c21 100644 --- a/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java +++ b/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java @@ -5,7 +5,7 @@ @Builder public record SignupRequest( - String email, + String identifier, String nickname, String information, List interest diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java index 9892016d..46ea8c28 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/todoffin/user/repository/UserRepository.java @@ -8,8 +8,8 @@ import org.springframework.data.repository.query.Param; public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); + Optional findByIdentifier(String identifier); - @Query("select u from User u where u.email = :email and u.provider = :provider") - Optional findByOAuthInfo(@Param("email") String email, @Param("provider") ProviderType provider); + @Query("select u from User u where u.identifier = :identifier and u.providerInfo = :provider") + Optional findByOAuthInfo(@Param("identifier") String identifier, @Param("provider") ProviderType provider); } diff --git a/src/main/java/com/genius/todoffin/user/service/UserService.java b/src/main/java/com/genius/todoffin/user/service/UserService.java index 7179194a..8acf601a 100644 --- a/src/main/java/com/genius/todoffin/user/service/UserService.java +++ b/src/main/java/com/genius/todoffin/user/service/UserService.java @@ -24,14 +24,14 @@ public User findUserById(Long id) { .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); } - public User findUserByEmail(String email) { - return userRepository.findByEmail(email) + public User findUserByEmail(String identifier) { + return userRepository.findByIdentifier(identifier) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); } @Transactional public Long signup(SignupRequest requestUser) { - User targetUser = findUserByEmail(requestUser.email()); + User targetUser = findUserByEmail(requestUser.identifier()); //TODO: Converter 클래스 만들어서 적용하기 String interest = String.join(",", requestUser.interest()); diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java index c5bb5fe9..64298fbc 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java @@ -20,21 +20,7 @@ class UserRepositoryTest { @Test @DisplayName("email을 통해 저장한 User 객체 찾은 후, 검증") public void email을_통해_저장한_User_객체를_찾을수있다() { - //given - String email = "test@naver.com"; - ProviderType provider = ProviderType.GOOGLE; - String nickname = "test_nickname"; - User user = getUnsavedUser(email, provider, nickname); - //when - User savedUser = userRepository.save(user); - User foundUser = userRepository.findByEmail(email).get(); - - //then - assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); - assertThat(savedUser.getEmail()).isEqualTo(foundUser.getEmail()); - assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); - assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); } @Test @@ -52,16 +38,16 @@ class UserRepositoryTest { //then assertThat(savedUser.getId()).isEqualTo(foundUser.getId()); - assertThat(savedUser.getEmail()).isEqualTo(foundUser.getEmail()); - assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); + //assertThat(savedUser.getEmail()).isEqualTo(foundUser.getEmail()); + //assertThat(savedUser.getProvider()).isEqualTo(foundUser.getProvider()); assertThat(savedUser.getNickname()).isEqualTo(foundUser.getNickname()); } private User getUnsavedUser(String email, ProviderType provider, String nickname) { return User.builder() - .email(email) - .provider(provider) + //.email(email) + .providerInfo(provider) .role(Role.USER) .nickname(nickname) .build(); diff --git a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java index 525fd240..e1e71e0d 100644 --- a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java @@ -27,36 +27,15 @@ class UserServiceTest { @Test @DisplayName("특정 사용자 가입 테스트") public void should_matchValues_when_signupUser() { - //given - String email = "test@naver.com"; - saveUnsignedUser(); - SignupRequest signupRequest = SignupRequest.builder() - .email(email) - .nickname("nickname") - .information("information") - .interest("interest") - .build(); - - //when - User user = userService.findUserByEmail(email); - - Long signupUserId = userService.signup(signupRequest); - User foundUser = userService.findUserById(signupUserId); - - //then - assertThat(user.getEmail()).isEqualTo(foundUser.getEmail()); - assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); - assertThat(user.getProvider()).isEqualTo(foundUser.getProvider()); - assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); - assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); + } private void saveUnsignedUser() { userRepository.save(User.builder() .role(Role.NOT_REGISTERED) - .provider(ProviderType.NAVER) - .email("test@naver.com") + .providerInfo(ProviderType.NAVER) + // .email("test@naver.com") .build()); } } \ No newline at end of file From 0101d4a87f3262feae0b0fa2518bd773702d8026 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:03:41 +0900 Subject: [PATCH 064/234] =?UTF-8?q?test:=20JwtService=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/domain/CustomCsrfToken.java | 20 +++ .../todoffin/security/service/JwtService.java | 3 +- .../security/service/JwtServiceTest.java | 143 ++++++++++++++++++ 3 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/genius/todoffin/security/domain/CustomCsrfToken.java create mode 100644 src/test/java/com/genius/todoffin/security/service/JwtServiceTest.java diff --git a/src/main/java/com/genius/todoffin/security/domain/CustomCsrfToken.java b/src/main/java/com/genius/todoffin/security/domain/CustomCsrfToken.java new file mode 100644 index 00000000..b9ef2142 --- /dev/null +++ b/src/main/java/com/genius/todoffin/security/domain/CustomCsrfToken.java @@ -0,0 +1,20 @@ +package com.genius.todoffin.security.domain; + +import org.springframework.security.web.csrf.CsrfToken; + +public class CustomCsrfToken implements CsrfToken { + @Override + public String getHeaderName() { + return null; + } + + @Override + public String getParameterName() { + return null; + } + + @Override + public String getToken() { + return null; + } +} diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/todoffin/security/service/JwtService.java index 5b6103f3..2ef6f9e1 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/todoffin/security/service/JwtService.java @@ -62,12 +62,13 @@ public String generateAccessToken(HttpServletResponse response, User requestUser } @Transactional - public void generateRefreshToken(HttpServletResponse response, User requestUser) { + public String generateRefreshToken(HttpServletResponse response, User requestUser) { String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET_KEY, REFRESH_EXPIRATION, requestUser); ResponseCookie cookie = setTokenToCookie(REFRESH_PREFIX.getValue(), refreshToken, REFRESH_EXPIRATION / 1000); response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); tokenRepository.save(new Token(requestUser.getIdentifier(), refreshToken)); + return refreshToken; } private ResponseCookie setTokenToCookie(String tokenPrefix, String token, long maxAgeSeconds) { diff --git a/src/test/java/com/genius/todoffin/security/service/JwtServiceTest.java b/src/test/java/com/genius/todoffin/security/service/JwtServiceTest.java new file mode 100644 index 00000000..03ab1ac9 --- /dev/null +++ b/src/test/java/com/genius/todoffin/security/service/JwtServiceTest.java @@ -0,0 +1,143 @@ +package com.genius.todoffin.security.service; + +import static com.genius.todoffin.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.todoffin.security.constants.ProviderInfo; +import com.genius.todoffin.user.domain.Role; +import com.genius.todoffin.user.domain.User; +import com.genius.todoffin.user.repository.UserRepository; +import com.genius.todoffin.util.exception.BusinessException; +import jakarta.servlet.http.Cookie; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional(readOnly = true) +@Slf4j +@ActiveProfiles({"jwt"}) +class JwtServiceTest { + @Autowired + private JwtService jwtService; + @Autowired + private UserRepository userRepository; + + @Test + @DisplayName("사용자 정보를 받아서 access-token을 생성할 수 있다.") + public void should_generateAccess_when_passUserInfo() { + //given + User user = getSavedUser(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + //when + String accessToken = jwtService.generateAccessToken(response, user); + Cookie cookie = response.getCookies()[0]; + + //then + assertThat(cookie.getValue()).isEqualTo(accessToken); + assertThat(cookie.getSecure()).isTrue(); + assertThat(cookie.getPath()).isEqualTo("/"); + } + + @Test + @DisplayName("사용자 정보를 받아서 유효한 refresh-token를 생성할 수 있다.") + public void should_generateRefresh_when_passUserInfo() { + //given + User user = getSavedUser(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + //when + String refreshToken = jwtService.generateRefreshToken(response, user); + Cookie cookie = response.getCookies()[0]; + + //then + assertThat(cookie.getValue()).isEqualTo(refreshToken); + assertThat(cookie.getSecure()).isTrue(); + assertThat(cookie.getPath()).isEqualTo("/"); + } + + @Test + @DisplayName("생성한 access-token이 유효하다면 true를 반환한다.") + public void should_returnTrue_when_accessTokenIsValid() { + //given + User user = getSavedUser(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + //when + String accessToken = jwtService.generateAccessToken(response, user); + boolean isValid = jwtService.validateAccessToken(accessToken); + + //then + assertThat(isValid).isTrue(); + } + + @Test + @DisplayName("생성한 access-token가 유효하지 않다면 false를 반환한다.") + public void should_returnFalse_when_accessTokenIsInvalid() { + //given + + //when + String accessToken = "fake access token"; + + //then + assertThatThrownBy(() -> jwtService.validateAccessToken(accessToken)) + .isInstanceOf(BusinessException.class); + } + + @Test + @DisplayName("Cookie에서 access-token을 추출할 수 있다.") + public void should_extractAccessToken_when_passTokenType() { + //given + User user = getSavedUser(); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + //when + String accessToken = jwtService.generateAccessToken(response, user); + + request.setCookies(new Cookie(ACCESS_PREFIX.getValue(), accessToken)); + String resolvedToken = jwtService.resolveTokenFromCookie(request, ACCESS_PREFIX); + + //then + assertThat(accessToken).isEqualTo(resolvedToken); + } + + @Test + @DisplayName("Cookie에서 refresh-token을 추출할 수 있다.") + public void should_extractRefreshToken_when_passTokenType() { + //given + User user = getSavedUser(); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + //when + String refreshToken = jwtService.generateRefreshToken(response, user); + + request.setCookies(new Cookie(REFRESH_PREFIX.getValue(), refreshToken)); + String resolvedToken = jwtService.resolveTokenFromCookie(request, REFRESH_PREFIX); + + //then + assertThat(refreshToken).isEqualTo(resolvedToken); + } + + + private User getSavedUser() { + return userRepository.save(User.builder() + .providerInfo(ProviderInfo.GITHUB) + .nickname("nickname") + .identifier("identifier") + .role(Role.USER) + .interest("interest1,interest2") + .information("information") + .build()); + } +} \ No newline at end of file From 204b043da7ea421155219f71d07ce43b4a72a638 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:36:46 +0900 Subject: [PATCH 065/234] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=9D=B4=EB=A6=84=EC=9D=84=20GitGet=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- settings.gradle | 2 +- .../GitgetApplication.java} | 6 +++--- .../challenge/domain/Hits.java | 4 ++-- .../challenge/domain/Instance.java | 8 ++++---- .../challenge/domain/JoinResult.java | 2 +- .../challenge/domain/ParticipantInfo.java | 4 ++-- .../challenge/domain/Progress.java | 2 +- .../challenge/domain/Topic.java | 2 +- .../challenge/repository/HitsRepository.java | 4 ++-- .../repository/InstanceRepository.java | 4 ++-- .../challenge/repository/TopicRepository.java | 4 ++-- .../common/domain/BaseTimeEntity.java | 3 +-- .../config/CustomCorsConfigurationSource.java | 2 +- .../security/config/SecurityConfig.java | 14 +++++++------- .../security/constants/JwtRule.java | 2 +- .../security/constants/ProviderInfo.java | 2 +- .../security/constants/ProviderType.java | 2 +- .../security/constants/TokenStatus.java | 2 +- .../security/controller/AuthController.java | 16 ++++++++-------- .../security/domain/CustomCsrfToken.java | 2 +- .../security/domain/Token.java | 2 +- .../security/domain/UserPrincipal.java | 4 ++-- .../security/dto/TokenRequest.java | 2 +- .../filter/JwtAuthenticationFilter.java | 14 +++++++------- .../security/handler/OAuth2FailureHandler.java | 2 +- .../security/handler/OAuth2SuccessHandler.java | 12 ++++++------ .../security/info/OAuth2UserInfo.java | 2 +- .../security/info/OAuth2UserInfoFactory.java | 12 ++++++------ .../info/impl/GithubOAuth2UserInfo.java | 6 +++--- .../info/impl/GoogleOAuth2UserInfo.java | 6 +++--- .../info/impl/KakaoOAuth2UserInfo.java | 6 +++--- .../info/impl/NaverOAuth2UserInfo.java | 6 +++--- .../security/repository/TokenRepository.java | 4 ++-- .../service/CustomOAuth2UserService.java | 16 ++++++++-------- .../service/CustomUserDetailsService.java | 12 ++++++------ .../security/service/JwtGenerator.java | 4 ++-- .../security/service/JwtService.java | 18 +++++++++--------- .../security/service/JwtUtil.java | 14 +++++++------- .../security/service/TokenService.java | 10 +++++----- .../user/controller/UserController.java | 12 ++++++------ .../{todoffin => gitget}/user/domain/Role.java | 2 +- .../{todoffin => gitget}/user/domain/User.java | 10 +++++----- .../user/dto/SignupRequest.java | 2 +- .../user/repository/UserRepository.java | 6 +++--- .../user/service/UserService.java | 16 ++++++++-------- .../util/config/AppConfig.java | 6 +++--- .../util/config/SwaggerConfig.java | 2 +- .../util/exception/BusinessException.java | 2 +- .../exception/BusinessExceptionHandler.java | 4 ++-- .../util/exception/ErrorCode.java | 4 ++-- .../util/exception/GlobalExceptionHandler.java | 4 ++-- .../util/exception/SuccessCode.java | 2 +- .../util/formatter/CommonPattern.java | 2 +- .../util/formatter/LocalDateFormatter.java | 2 +- .../util/formatter/LocalDateTimeFormatter.java | 2 +- .../util/response/dto/CommonResponse.java | 2 +- .../util/response/dto/ListResponse.java | 2 +- .../util/response/dto/PagingResponse.java | 2 +- .../util/response/dto/SingleResponse.java | 2 +- .../util/response/dto/SlicingResponse.java | 2 +- .../GitgetApplicationTests.java} | 10 +++++----- .../security/service/JwtServiceTest.java | 16 ++++++++-------- .../security/service/TokenServiceTest.java | 6 +++--- .../user/repository/UserRepositoryTest.java | 10 +++++----- .../user/service/UserServiceTest.java | 12 ++++++------ 65 files changed, 190 insertions(+), 191 deletions(-) rename src/main/java/com/genius/{todoffin/TodoffinApplication.java => gitget/GitgetApplication.java} (75%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/domain/Hits.java (87%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/domain/Instance.java (87%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/domain/JoinResult.java (62%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/domain/ParticipantInfo.java (92%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/domain/Progress.java (66%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/domain/Topic.java (94%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/repository/HitsRepository.java (57%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/repository/InstanceRepository.java (57%) rename src/main/java/com/genius/{todoffin => gitget}/challenge/repository/TopicRepository.java (57%) rename src/main/java/com/genius/{todoffin => gitget}/common/domain/BaseTimeEntity.java (92%) rename src/main/java/com/genius/{todoffin => gitget}/security/config/CustomCorsConfigurationSource.java (95%) rename src/main/java/com/genius/{todoffin => gitget}/security/config/SecurityConfig.java (87%) rename src/main/java/com/genius/{todoffin => gitget}/security/constants/JwtRule.java (85%) rename src/main/java/com/genius/{todoffin => gitget}/security/constants/ProviderInfo.java (93%) rename src/main/java/com/genius/{todoffin => gitget}/security/constants/ProviderType.java (89%) rename src/main/java/com/genius/{todoffin => gitget}/security/constants/TokenStatus.java (77%) rename src/main/java/com/genius/{todoffin => gitget}/security/controller/AuthController.java (79%) rename src/main/java/com/genius/{todoffin => gitget}/security/domain/CustomCsrfToken.java (88%) rename src/main/java/com/genius/{todoffin => gitget}/security/domain/Token.java (91%) rename src/main/java/com/genius/{todoffin => gitget}/security/domain/UserPrincipal.java (95%) rename src/main/java/com/genius/{todoffin => gitget}/security/dto/TokenRequest.java (54%) rename src/main/java/com/genius/{todoffin => gitget}/security/filter/JwtAuthenticationFilter.java (86%) rename src/main/java/com/genius/{todoffin => gitget}/security/handler/OAuth2FailureHandler.java (96%) rename src/main/java/com/genius/{todoffin => gitget}/security/handler/OAuth2SuccessHandler.java (86%) rename src/main/java/com/genius/{todoffin => gitget}/security/info/OAuth2UserInfo.java (87%) rename src/main/java/com/genius/{todoffin => gitget}/security/info/OAuth2UserInfoFactory.java (67%) rename src/main/java/com/genius/{todoffin => gitget}/security/info/impl/GithubOAuth2UserInfo.java (70%) rename src/main/java/com/genius/{todoffin => gitget}/security/info/impl/GoogleOAuth2UserInfo.java (70%) rename src/main/java/com/genius/{todoffin => gitget}/security/info/impl/KakaoOAuth2UserInfo.java (75%) rename src/main/java/com/genius/{todoffin => gitget}/security/info/impl/NaverOAuth2UserInfo.java (73%) rename src/main/java/com/genius/{todoffin => gitget}/security/repository/TokenRepository.java (66%) rename src/main/java/com/genius/{todoffin => gitget}/security/service/CustomOAuth2UserService.java (85%) rename src/main/java/com/genius/{todoffin => gitget}/security/service/CustomUserDetailsService.java (68%) rename src/main/java/com/genius/{todoffin => gitget}/security/service/JwtGenerator.java (94%) rename src/main/java/com/genius/{todoffin => gitget}/security/service/JwtService.java (90%) rename src/main/java/com/genius/{todoffin => gitget}/security/service/JwtUtil.java (81%) rename src/main/java/com/genius/{todoffin => gitget}/security/service/TokenService.java (73%) rename src/main/java/com/genius/{todoffin => gitget}/user/controller/UserController.java (78%) rename src/main/java/com/genius/{todoffin => gitget}/user/domain/Role.java (89%) rename src/main/java/com/genius/{todoffin => gitget}/user/domain/User.java (88%) rename src/main/java/com/genius/{todoffin => gitget}/user/dto/SignupRequest.java (84%) rename src/main/java/com/genius/{todoffin => gitget}/user/repository/UserRepository.java (81%) rename src/main/java/com/genius/{todoffin => gitget}/user/service/UserService.java (75%) rename src/main/java/com/genius/{todoffin => gitget}/util/config/AppConfig.java (69%) rename src/main/java/com/genius/{todoffin => gitget}/util/config/SwaggerConfig.java (92%) rename src/main/java/com/genius/{todoffin => gitget}/util/exception/BusinessException.java (93%) rename src/main/java/com/genius/{todoffin => gitget}/util/exception/BusinessExceptionHandler.java (86%) rename src/main/java/com/genius/{todoffin => gitget}/util/exception/ErrorCode.java (95%) rename src/main/java/com/genius/{todoffin => gitget}/util/exception/GlobalExceptionHandler.java (88%) rename src/main/java/com/genius/{todoffin => gitget}/util/exception/SuccessCode.java (91%) rename src/main/java/com/genius/{todoffin => gitget}/util/formatter/CommonPattern.java (91%) rename src/main/java/com/genius/{todoffin => gitget}/util/formatter/LocalDateFormatter.java (92%) rename src/main/java/com/genius/{todoffin => gitget}/util/formatter/LocalDateTimeFormatter.java (94%) rename src/main/java/com/genius/{todoffin => gitget}/util/response/dto/CommonResponse.java (91%) rename src/main/java/com/genius/{todoffin => gitget}/util/response/dto/ListResponse.java (88%) rename src/main/java/com/genius/{todoffin => gitget}/util/response/dto/PagingResponse.java (86%) rename src/main/java/com/genius/{todoffin => gitget}/util/response/dto/SingleResponse.java (90%) rename src/main/java/com/genius/{todoffin => gitget}/util/response/dto/SlicingResponse.java (81%) rename src/test/java/com/genius/{todoffin/TodoffinApplicationTests.java => gitget/GitgetApplicationTests.java} (54%) rename src/test/java/com/genius/{todoffin => gitget}/security/service/JwtServiceTest.java (91%) rename src/test/java/com/genius/{todoffin => gitget}/security/service/TokenServiceTest.java (94%) rename src/test/java/com/genius/{todoffin => gitget}/user/repository/UserRepositoryTest.java (91%) rename src/test/java/com/genius/{todoffin => gitget}/user/service/UserServiceTest.java (86%) diff --git a/settings.gradle b/settings.gradle index c39c0ad4..d94797e2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'todoffin' +rootProject.name = 'gitget' diff --git a/src/main/java/com/genius/todoffin/TodoffinApplication.java b/src/main/java/com/genius/gitget/GitgetApplication.java similarity index 75% rename from src/main/java/com/genius/todoffin/TodoffinApplication.java rename to src/main/java/com/genius/gitget/GitgetApplication.java index 49f05c62..50dce880 100644 --- a/src/main/java/com/genius/todoffin/TodoffinApplication.java +++ b/src/main/java/com/genius/gitget/GitgetApplication.java @@ -1,4 +1,4 @@ -package com.genius.todoffin; +package com.genius.gitget; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -8,8 +8,8 @@ @SpringBootApplication @EnableJpaAuditing @EnableMongoRepositories -public class TodoffinApplication { +public class GitgetApplication { public static void main(String[] args) { - SpringApplication.run(TodoffinApplication.class, args); + SpringApplication.run(GitgetApplication.class, args); } } diff --git a/src/main/java/com/genius/todoffin/challenge/domain/Hits.java b/src/main/java/com/genius/gitget/challenge/domain/Hits.java similarity index 87% rename from src/main/java/com/genius/todoffin/challenge/domain/Hits.java rename to src/main/java/com/genius/gitget/challenge/domain/Hits.java index d14378c4..4037f370 100644 --- a/src/main/java/com/genius/todoffin/challenge/domain/Hits.java +++ b/src/main/java/com/genius/gitget/challenge/domain/Hits.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.challenge.domain; +package com.genius.gitget.challenge.domain; -import com.genius.todoffin.user.domain.User; +import com.genius.gitget.user.domain.User; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; diff --git a/src/main/java/com/genius/todoffin/challenge/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/domain/Instance.java similarity index 87% rename from src/main/java/com/genius/todoffin/challenge/domain/Instance.java rename to src/main/java/com/genius/gitget/challenge/domain/Instance.java index ce55119a..381954ed 100644 --- a/src/main/java/com/genius/todoffin/challenge/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/domain/Instance.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.challenge.domain; +package com.genius.gitget.challenge.domain; -import com.genius.todoffin.common.domain.BaseTimeEntity; +import com.genius.gitget.common.domain.BaseTimeEntity; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; @@ -8,7 +8,6 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; -import org.hibernate.annotations.Fetch; import java.util.ArrayList; import java.util.List; @@ -53,7 +52,8 @@ public class Instance extends BaseTimeEntity { @ColumnDefault("'PRE_ACTIVITY'") private Progress progress; - public Instance(String title, String description, int participants, String tags, int point_per_person, int like_count, Progress progress) { + public Instance(String title, String description, int participants, String tags, int point_per_person, + int like_count, Progress progress) { this.title = title; this.description = description; this.participants = participants; diff --git a/src/main/java/com/genius/todoffin/challenge/domain/JoinResult.java b/src/main/java/com/genius/gitget/challenge/domain/JoinResult.java similarity index 62% rename from src/main/java/com/genius/todoffin/challenge/domain/JoinResult.java rename to src/main/java/com/genius/gitget/challenge/domain/JoinResult.java index c40c9f83..545e8cee 100644 --- a/src/main/java/com/genius/todoffin/challenge/domain/JoinResult.java +++ b/src/main/java/com/genius/gitget/challenge/domain/JoinResult.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.challenge.domain; +package com.genius.gitget.challenge.domain; import lombok.Getter; diff --git a/src/main/java/com/genius/todoffin/challenge/domain/ParticipantInfo.java b/src/main/java/com/genius/gitget/challenge/domain/ParticipantInfo.java similarity index 92% rename from src/main/java/com/genius/todoffin/challenge/domain/ParticipantInfo.java rename to src/main/java/com/genius/gitget/challenge/domain/ParticipantInfo.java index 97962828..dba97653 100644 --- a/src/main/java/com/genius/todoffin/challenge/domain/ParticipantInfo.java +++ b/src/main/java/com/genius/gitget/challenge/domain/ParticipantInfo.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.challenge.domain; +package com.genius.gitget.challenge.domain; -import com.genius.todoffin.user.domain.User; +import com.genius.gitget.user.domain.User; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; diff --git a/src/main/java/com/genius/todoffin/challenge/domain/Progress.java b/src/main/java/com/genius/gitget/challenge/domain/Progress.java similarity index 66% rename from src/main/java/com/genius/todoffin/challenge/domain/Progress.java rename to src/main/java/com/genius/gitget/challenge/domain/Progress.java index 46c5574d..2a128e46 100644 --- a/src/main/java/com/genius/todoffin/challenge/domain/Progress.java +++ b/src/main/java/com/genius/gitget/challenge/domain/Progress.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.challenge.domain; +package com.genius.gitget.challenge.domain; import lombok.Getter; diff --git a/src/main/java/com/genius/todoffin/challenge/domain/Topic.java b/src/main/java/com/genius/gitget/challenge/domain/Topic.java similarity index 94% rename from src/main/java/com/genius/todoffin/challenge/domain/Topic.java rename to src/main/java/com/genius/gitget/challenge/domain/Topic.java index f90d955d..0096ca1d 100644 --- a/src/main/java/com/genius/todoffin/challenge/domain/Topic.java +++ b/src/main/java/com/genius/gitget/challenge/domain/Topic.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.challenge.domain; +package com.genius.gitget.challenge.domain; import jakarta.persistence.*; import lombok.AccessLevel; diff --git a/src/main/java/com/genius/todoffin/challenge/repository/HitsRepository.java b/src/main/java/com/genius/gitget/challenge/repository/HitsRepository.java similarity index 57% rename from src/main/java/com/genius/todoffin/challenge/repository/HitsRepository.java rename to src/main/java/com/genius/gitget/challenge/repository/HitsRepository.java index 1736d65c..397e23d2 100644 --- a/src/main/java/com/genius/todoffin/challenge/repository/HitsRepository.java +++ b/src/main/java/com/genius/gitget/challenge/repository/HitsRepository.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.challenge.repository; +package com.genius.gitget.challenge.repository; -import com.genius.todoffin.challenge.domain.Hits; +import com.genius.gitget.challenge.domain.Hits; import org.springframework.data.jpa.repository.JpaRepository; public interface HitsRepository extends JpaRepository { diff --git a/src/main/java/com/genius/todoffin/challenge/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/challenge/repository/InstanceRepository.java similarity index 57% rename from src/main/java/com/genius/todoffin/challenge/repository/InstanceRepository.java rename to src/main/java/com/genius/gitget/challenge/repository/InstanceRepository.java index ae4951aa..1ebec3f2 100644 --- a/src/main/java/com/genius/todoffin/challenge/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/challenge/repository/InstanceRepository.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.challenge.repository; +package com.genius.gitget.challenge.repository; -import com.genius.todoffin.challenge.domain.Instance; +import com.genius.gitget.challenge.domain.Instance; import org.springframework.data.jpa.repository.JpaRepository; public interface InstanceRepository extends JpaRepository { diff --git a/src/main/java/com/genius/todoffin/challenge/repository/TopicRepository.java b/src/main/java/com/genius/gitget/challenge/repository/TopicRepository.java similarity index 57% rename from src/main/java/com/genius/todoffin/challenge/repository/TopicRepository.java rename to src/main/java/com/genius/gitget/challenge/repository/TopicRepository.java index 675463a2..6a90c36d 100644 --- a/src/main/java/com/genius/todoffin/challenge/repository/TopicRepository.java +++ b/src/main/java/com/genius/gitget/challenge/repository/TopicRepository.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.challenge.repository; +package com.genius.gitget.challenge.repository; -import com.genius.todoffin.challenge.domain.Topic; +import com.genius.gitget.challenge.domain.Topic; import org.springframework.data.jpa.repository.JpaRepository; public interface TopicRepository extends JpaRepository { diff --git a/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java b/src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java similarity index 92% rename from src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java rename to src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java index 60a4ea73..e8677d84 100644 --- a/src/main/java/com/genius/todoffin/common/domain/BaseTimeEntity.java +++ b/src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.common.domain; +package com.genius.gitget.common.domain; import jakarta.persistence.Column; @@ -6,7 +6,6 @@ import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; -import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.ToString; import org.springframework.data.annotation.CreatedDate; diff --git a/src/main/java/com/genius/todoffin/security/config/CustomCorsConfigurationSource.java b/src/main/java/com/genius/gitget/security/config/CustomCorsConfigurationSource.java similarity index 95% rename from src/main/java/com/genius/todoffin/security/config/CustomCorsConfigurationSource.java rename to src/main/java/com/genius/gitget/security/config/CustomCorsConfigurationSource.java index 088bccd3..1ce8cadf 100644 --- a/src/main/java/com/genius/todoffin/security/config/CustomCorsConfigurationSource.java +++ b/src/main/java/com/genius/gitget/security/config/CustomCorsConfigurationSource.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.config; +package com.genius.gitget.security.config; import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; diff --git a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/security/config/SecurityConfig.java similarity index 87% rename from src/main/java/com/genius/todoffin/security/config/SecurityConfig.java rename to src/main/java/com/genius/gitget/security/config/SecurityConfig.java index 4fd3f053..51688363 100644 --- a/src/main/java/com/genius/todoffin/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/security/config/SecurityConfig.java @@ -1,12 +1,12 @@ -package com.genius.todoffin.security.config; +package com.genius.gitget.security.config; -import com.genius.todoffin.security.filter.JwtAuthenticationFilter; -import com.genius.todoffin.security.handler.OAuth2FailureHandler; -import com.genius.todoffin.security.handler.OAuth2SuccessHandler; -import com.genius.todoffin.security.service.CustomOAuth2UserService; -import com.genius.todoffin.security.service.JwtService; -import com.genius.todoffin.user.service.UserService; +import com.genius.gitget.security.filter.JwtAuthenticationFilter; +import com.genius.gitget.security.handler.OAuth2FailureHandler; +import com.genius.gitget.security.handler.OAuth2SuccessHandler; +import com.genius.gitget.security.service.CustomOAuth2UserService; +import com.genius.gitget.security.service.JwtService; +import com.genius.gitget.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/genius/todoffin/security/constants/JwtRule.java b/src/main/java/com/genius/gitget/security/constants/JwtRule.java similarity index 85% rename from src/main/java/com/genius/todoffin/security/constants/JwtRule.java rename to src/main/java/com/genius/gitget/security/constants/JwtRule.java index 30d0d228..32e8c286 100644 --- a/src/main/java/com/genius/todoffin/security/constants/JwtRule.java +++ b/src/main/java/com/genius/gitget/security/constants/JwtRule.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.constants; +package com.genius.gitget.security.constants; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/todoffin/security/constants/ProviderInfo.java b/src/main/java/com/genius/gitget/security/constants/ProviderInfo.java similarity index 93% rename from src/main/java/com/genius/todoffin/security/constants/ProviderInfo.java rename to src/main/java/com/genius/gitget/security/constants/ProviderInfo.java index 8389ea3f..2acdfaba 100644 --- a/src/main/java/com/genius/todoffin/security/constants/ProviderInfo.java +++ b/src/main/java/com/genius/gitget/security/constants/ProviderInfo.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.constants; +package com.genius.gitget.security.constants; import java.util.Arrays; import lombok.Getter; diff --git a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java b/src/main/java/com/genius/gitget/security/constants/ProviderType.java similarity index 89% rename from src/main/java/com/genius/todoffin/security/constants/ProviderType.java rename to src/main/java/com/genius/gitget/security/constants/ProviderType.java index ee4cbcd2..e35e9c5b 100644 --- a/src/main/java/com/genius/todoffin/security/constants/ProviderType.java +++ b/src/main/java/com/genius/gitget/security/constants/ProviderType.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.constants; +package com.genius.gitget.security.constants; import java.util.Arrays; diff --git a/src/main/java/com/genius/todoffin/security/constants/TokenStatus.java b/src/main/java/com/genius/gitget/security/constants/TokenStatus.java similarity index 77% rename from src/main/java/com/genius/todoffin/security/constants/TokenStatus.java rename to src/main/java/com/genius/gitget/security/constants/TokenStatus.java index 09c70708..b32273c5 100644 --- a/src/main/java/com/genius/todoffin/security/constants/TokenStatus.java +++ b/src/main/java/com/genius/gitget/security/constants/TokenStatus.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.constants; +package com.genius.gitget.security.constants; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/todoffin/security/controller/AuthController.java b/src/main/java/com/genius/gitget/security/controller/AuthController.java similarity index 79% rename from src/main/java/com/genius/todoffin/security/controller/AuthController.java rename to src/main/java/com/genius/gitget/security/controller/AuthController.java index dd7946b2..e1701398 100644 --- a/src/main/java/com/genius/todoffin/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/security/controller/AuthController.java @@ -1,13 +1,13 @@ -package com.genius.todoffin.security.controller; +package com.genius.gitget.security.controller; -import static com.genius.todoffin.util.exception.SuccessCode.SUCCESS; +import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; -import com.genius.todoffin.security.domain.UserPrincipal; -import com.genius.todoffin.security.dto.TokenRequest; -import com.genius.todoffin.security.service.JwtService; -import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.user.service.UserService; -import com.genius.todoffin.util.response.dto.CommonResponse; +import com.genius.gitget.security.domain.UserPrincipal; +import com.genius.gitget.security.dto.TokenRequest; +import com.genius.gitget.security.service.JwtService; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.service.UserService; +import com.genius.gitget.util.response.dto.CommonResponse; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/genius/todoffin/security/domain/CustomCsrfToken.java b/src/main/java/com/genius/gitget/security/domain/CustomCsrfToken.java similarity index 88% rename from src/main/java/com/genius/todoffin/security/domain/CustomCsrfToken.java rename to src/main/java/com/genius/gitget/security/domain/CustomCsrfToken.java index b9ef2142..71b44b51 100644 --- a/src/main/java/com/genius/todoffin/security/domain/CustomCsrfToken.java +++ b/src/main/java/com/genius/gitget/security/domain/CustomCsrfToken.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.domain; +package com.genius.gitget.security.domain; import org.springframework.security.web.csrf.CsrfToken; diff --git a/src/main/java/com/genius/todoffin/security/domain/Token.java b/src/main/java/com/genius/gitget/security/domain/Token.java similarity index 91% rename from src/main/java/com/genius/todoffin/security/domain/Token.java rename to src/main/java/com/genius/gitget/security/domain/Token.java index 8ad631f9..f08c45be 100644 --- a/src/main/java/com/genius/todoffin/security/domain/Token.java +++ b/src/main/java/com/genius/gitget/security/domain/Token.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.domain; +package com.genius.gitget.security.domain; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java b/src/main/java/com/genius/gitget/security/domain/UserPrincipal.java similarity index 95% rename from src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java rename to src/main/java/com/genius/gitget/security/domain/UserPrincipal.java index 4b810ffc..801c285f 100644 --- a/src/main/java/com/genius/todoffin/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/gitget/security/domain/UserPrincipal.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.security.domain; +package com.genius.gitget.security.domain; -import com.genius.todoffin.user.domain.User; +import com.genius.gitget.user.domain.User; import java.util.Collection; import java.util.Collections; import java.util.Map; diff --git a/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java b/src/main/java/com/genius/gitget/security/dto/TokenRequest.java similarity index 54% rename from src/main/java/com/genius/todoffin/security/dto/TokenRequest.java rename to src/main/java/com/genius/gitget/security/dto/TokenRequest.java index 44866158..9da8e8a7 100644 --- a/src/main/java/com/genius/todoffin/security/dto/TokenRequest.java +++ b/src/main/java/com/genius/gitget/security/dto/TokenRequest.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.dto; +package com.genius.gitget.security.dto; public record TokenRequest(String identifier) { } diff --git a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/gitget/security/filter/JwtAuthenticationFilter.java similarity index 86% rename from src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java rename to src/main/java/com/genius/gitget/security/filter/JwtAuthenticationFilter.java index 64d883ae..5f43c83c 100644 --- a/src/main/java/com/genius/todoffin/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/gitget/security/filter/JwtAuthenticationFilter.java @@ -1,12 +1,12 @@ -package com.genius.todoffin.security.filter; +package com.genius.gitget.security.filter; -import static com.genius.todoffin.security.config.SecurityConfig.PERMITTED_URI; -import static com.genius.todoffin.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.security.config.SecurityConfig.PERMITTED_URI; +import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; -import com.genius.todoffin.security.service.JwtService; -import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.user.service.UserService; +import com.genius.gitget.security.service.JwtService; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.service.UserService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java b/src/main/java/com/genius/gitget/security/handler/OAuth2FailureHandler.java similarity index 96% rename from src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java rename to src/main/java/com/genius/gitget/security/handler/OAuth2FailureHandler.java index 35de4bba..e7ba9e60 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/genius/gitget/security/handler/OAuth2FailureHandler.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.handler; +package com.genius.gitget.security.handler; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/gitget/security/handler/OAuth2SuccessHandler.java similarity index 86% rename from src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java rename to src/main/java/com/genius/gitget/security/handler/OAuth2SuccessHandler.java index 14516b07..3b02e341 100644 --- a/src/main/java/com/genius/todoffin/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/gitget/security/handler/OAuth2SuccessHandler.java @@ -1,10 +1,10 @@ -package com.genius.todoffin.security.handler; +package com.genius.gitget.security.handler; -import com.genius.todoffin.user.domain.Role; -import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.user.repository.UserRepository; -import com.genius.todoffin.util.exception.BusinessException; -import com.genius.todoffin.util.exception.ErrorCode; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.util.exception.ErrorCode; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/OAuth2UserInfo.java similarity index 87% rename from src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java rename to src/main/java/com/genius/gitget/security/info/OAuth2UserInfo.java index d8c01f9e..8ac649bb 100644 --- a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfo.java +++ b/src/main/java/com/genius/gitget/security/info/OAuth2UserInfo.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.security.info; +package com.genius.gitget.security.info; import java.util.Map; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java b/src/main/java/com/genius/gitget/security/info/OAuth2UserInfoFactory.java similarity index 67% rename from src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java rename to src/main/java/com/genius/gitget/security/info/OAuth2UserInfoFactory.java index dbb19669..0ecd0456 100644 --- a/src/main/java/com/genius/todoffin/security/info/OAuth2UserInfoFactory.java +++ b/src/main/java/com/genius/gitget/security/info/OAuth2UserInfoFactory.java @@ -1,10 +1,10 @@ -package com.genius.todoffin.security.info; +package com.genius.gitget.security.info; -import com.genius.todoffin.security.constants.ProviderInfo; -import com.genius.todoffin.security.info.impl.GithubOAuth2UserInfo; -import com.genius.todoffin.security.info.impl.GoogleOAuth2UserInfo; -import com.genius.todoffin.security.info.impl.KakaoOAuth2UserInfo; -import com.genius.todoffin.security.info.impl.NaverOAuth2UserInfo; +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.security.info.impl.GithubOAuth2UserInfo; +import com.genius.gitget.security.info.impl.GoogleOAuth2UserInfo; +import com.genius.gitget.security.info.impl.KakaoOAuth2UserInfo; +import com.genius.gitget.security.info.impl.NaverOAuth2UserInfo; import java.util.Map; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; diff --git a/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/impl/GithubOAuth2UserInfo.java similarity index 70% rename from src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java rename to src/main/java/com/genius/gitget/security/info/impl/GithubOAuth2UserInfo.java index b52fa73d..39ee1ef3 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/GithubOAuth2UserInfo.java +++ b/src/main/java/com/genius/gitget/security/info/impl/GithubOAuth2UserInfo.java @@ -1,8 +1,8 @@ -package com.genius.todoffin.security.info.impl; +package com.genius.gitget.security.info.impl; -import static com.genius.todoffin.security.constants.ProviderInfo.GITHUB; +import static com.genius.gitget.security.constants.ProviderInfo.GITHUB; -import com.genius.todoffin.security.info.OAuth2UserInfo; +import com.genius.gitget.security.info.OAuth2UserInfo; import java.util.Map; public class GithubOAuth2UserInfo extends OAuth2UserInfo { diff --git a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/impl/GoogleOAuth2UserInfo.java similarity index 70% rename from src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java rename to src/main/java/com/genius/gitget/security/info/impl/GoogleOAuth2UserInfo.java index 03aeb2be..7a03b8af 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/GoogleOAuth2UserInfo.java +++ b/src/main/java/com/genius/gitget/security/info/impl/GoogleOAuth2UserInfo.java @@ -1,9 +1,9 @@ -package com.genius.todoffin.security.info.impl; +package com.genius.gitget.security.info.impl; -import static com.genius.todoffin.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; -import com.genius.todoffin.security.info.OAuth2UserInfo; +import com.genius.gitget.security.info.OAuth2UserInfo; import java.util.Map; public class GoogleOAuth2UserInfo extends OAuth2UserInfo { diff --git a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/impl/KakaoOAuth2UserInfo.java similarity index 75% rename from src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java rename to src/main/java/com/genius/gitget/security/info/impl/KakaoOAuth2UserInfo.java index df9d9e6e..54b301dd 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/KakaoOAuth2UserInfo.java +++ b/src/main/java/com/genius/gitget/security/info/impl/KakaoOAuth2UserInfo.java @@ -1,8 +1,8 @@ -package com.genius.todoffin.security.info.impl; +package com.genius.gitget.security.info.impl; -import static com.genius.todoffin.security.constants.ProviderInfo.KAKAO; +import static com.genius.gitget.security.constants.ProviderInfo.KAKAO; -import com.genius.todoffin.security.info.OAuth2UserInfo; +import com.genius.gitget.security.info.OAuth2UserInfo; import java.util.Map; public class KakaoOAuth2UserInfo extends OAuth2UserInfo { diff --git a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/impl/NaverOAuth2UserInfo.java similarity index 73% rename from src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java rename to src/main/java/com/genius/gitget/security/info/impl/NaverOAuth2UserInfo.java index b86cfb4e..0b443194 100644 --- a/src/main/java/com/genius/todoffin/security/info/impl/NaverOAuth2UserInfo.java +++ b/src/main/java/com/genius/gitget/security/info/impl/NaverOAuth2UserInfo.java @@ -1,9 +1,9 @@ -package com.genius.todoffin.security.info.impl; +package com.genius.gitget.security.info.impl; -import static com.genius.todoffin.security.constants.ProviderInfo.NAVER; +import static com.genius.gitget.security.constants.ProviderInfo.NAVER; -import com.genius.todoffin.security.info.OAuth2UserInfo; +import com.genius.gitget.security.info.OAuth2UserInfo; import java.util.Map; public class NaverOAuth2UserInfo extends OAuth2UserInfo { diff --git a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java b/src/main/java/com/genius/gitget/security/repository/TokenRepository.java similarity index 66% rename from src/main/java/com/genius/todoffin/security/repository/TokenRepository.java rename to src/main/java/com/genius/gitget/security/repository/TokenRepository.java index f528de52..67e9b07d 100644 --- a/src/main/java/com/genius/todoffin/security/repository/TokenRepository.java +++ b/src/main/java/com/genius/gitget/security/repository/TokenRepository.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.security.repository; +package com.genius.gitget.security.repository; -import com.genius.todoffin.security.domain.Token; +import com.genius.gitget.security.domain.Token; import org.springframework.data.mongodb.repository.MongoRepository; public interface TokenRepository extends MongoRepository { diff --git a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/gitget/security/service/CustomOAuth2UserService.java similarity index 85% rename from src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java rename to src/main/java/com/genius/gitget/security/service/CustomOAuth2UserService.java index e6ad978f..9ba22809 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/gitget/security/service/CustomOAuth2UserService.java @@ -1,12 +1,12 @@ -package com.genius.todoffin.security.service; +package com.genius.gitget.security.service; -import com.genius.todoffin.security.constants.ProviderInfo; -import com.genius.todoffin.security.domain.UserPrincipal; -import com.genius.todoffin.security.info.OAuth2UserInfo; -import com.genius.todoffin.security.info.OAuth2UserInfoFactory; -import com.genius.todoffin.user.domain.Role; -import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.user.repository.UserRepository; +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.security.domain.UserPrincipal; +import com.genius.gitget.security.info.OAuth2UserInfo; +import com.genius.gitget.security.info.OAuth2UserInfoFactory; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.repository.UserRepository; import jakarta.transaction.Transactional; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/com/genius/todoffin/security/service/CustomUserDetailsService.java b/src/main/java/com/genius/gitget/security/service/CustomUserDetailsService.java similarity index 68% rename from src/main/java/com/genius/todoffin/security/service/CustomUserDetailsService.java rename to src/main/java/com/genius/gitget/security/service/CustomUserDetailsService.java index 2000ef22..7ed9388a 100644 --- a/src/main/java/com/genius/todoffin/security/service/CustomUserDetailsService.java +++ b/src/main/java/com/genius/gitget/security/service/CustomUserDetailsService.java @@ -1,11 +1,11 @@ -package com.genius.todoffin.security.service; +package com.genius.gitget.security.service; -import static com.genius.todoffin.util.exception.ErrorCode.MEMBER_NOT_FOUND; +import static com.genius.gitget.util.exception.ErrorCode.MEMBER_NOT_FOUND; -import com.genius.todoffin.security.domain.UserPrincipal; -import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.user.repository.UserRepository; -import com.genius.todoffin.util.exception.BusinessException; +import com.genius.gitget.security.domain.UserPrincipal; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.util.exception.BusinessException; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; diff --git a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java b/src/main/java/com/genius/gitget/security/service/JwtGenerator.java similarity index 94% rename from src/main/java/com/genius/todoffin/security/service/JwtGenerator.java rename to src/main/java/com/genius/gitget/security/service/JwtGenerator.java index 2f201d0b..a141f292 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtGenerator.java +++ b/src/main/java/com/genius/gitget/security/service/JwtGenerator.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.security.service; +package com.genius.gitget.security.service; -import com.genius.todoffin.user.domain.User; +import com.genius.gitget.user.domain.User; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.security.Key; diff --git a/src/main/java/com/genius/todoffin/security/service/JwtService.java b/src/main/java/com/genius/gitget/security/service/JwtService.java similarity index 90% rename from src/main/java/com/genius/todoffin/security/service/JwtService.java rename to src/main/java/com/genius/gitget/security/service/JwtService.java index 2ef6f9e1..336b614b 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtService.java +++ b/src/main/java/com/genius/gitget/security/service/JwtService.java @@ -1,14 +1,14 @@ -package com.genius.todoffin.security.service; +package com.genius.gitget.security.service; -import static com.genius.todoffin.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.todoffin.security.constants.JwtRule.JWT_ISSUE_HEADER; -import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.gitget.security.constants.JwtRule.JWT_ISSUE_HEADER; +import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; -import com.genius.todoffin.security.constants.JwtRule; -import com.genius.todoffin.security.constants.TokenStatus; -import com.genius.todoffin.security.domain.Token; -import com.genius.todoffin.security.repository.TokenRepository; -import com.genius.todoffin.user.domain.User; +import com.genius.gitget.security.constants.JwtRule; +import com.genius.gitget.security.constants.TokenStatus; +import com.genius.gitget.security.domain.Token; +import com.genius.gitget.security.repository.TokenRepository; +import com.genius.gitget.user.domain.User; import io.jsonwebtoken.Jwts; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/genius/todoffin/security/service/JwtUtil.java b/src/main/java/com/genius/gitget/security/service/JwtUtil.java similarity index 81% rename from src/main/java/com/genius/todoffin/security/service/JwtUtil.java rename to src/main/java/com/genius/gitget/security/service/JwtUtil.java index 585473c7..083a544c 100644 --- a/src/main/java/com/genius/todoffin/security/service/JwtUtil.java +++ b/src/main/java/com/genius/gitget/security/service/JwtUtil.java @@ -1,12 +1,12 @@ -package com.genius.todoffin.security.service; +package com.genius.gitget.security.service; -import static com.genius.todoffin.util.exception.ErrorCode.INVALID_EXPIRED_JWT; -import static com.genius.todoffin.util.exception.ErrorCode.INVALID_JWT; -import static com.genius.todoffin.util.exception.ErrorCode.TOKEN_NOT_FOUND; +import static com.genius.gitget.util.exception.ErrorCode.INVALID_EXPIRED_JWT; +import static com.genius.gitget.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.gitget.util.exception.ErrorCode.TOKEN_NOT_FOUND; -import com.genius.todoffin.security.constants.JwtRule; -import com.genius.todoffin.security.constants.TokenStatus; -import com.genius.todoffin.util.exception.BusinessException; +import com.genius.gitget.security.constants.JwtRule; +import com.genius.gitget.security.constants.TokenStatus; +import com.genius.gitget.util.exception.BusinessException; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; diff --git a/src/main/java/com/genius/todoffin/security/service/TokenService.java b/src/main/java/com/genius/gitget/security/service/TokenService.java similarity index 73% rename from src/main/java/com/genius/todoffin/security/service/TokenService.java rename to src/main/java/com/genius/gitget/security/service/TokenService.java index 08231433..bb5f4035 100644 --- a/src/main/java/com/genius/todoffin/security/service/TokenService.java +++ b/src/main/java/com/genius/gitget/security/service/TokenService.java @@ -1,9 +1,9 @@ -package com.genius.todoffin.security.service; +package com.genius.gitget.security.service; -import com.genius.todoffin.security.domain.Token; -import com.genius.todoffin.security.repository.TokenRepository; -import com.genius.todoffin.util.exception.BusinessException; -import com.genius.todoffin.util.exception.ErrorCode; +import com.genius.gitget.security.domain.Token; +import com.genius.gitget.security.repository.TokenRepository; +import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.util.exception.ErrorCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/genius/todoffin/user/controller/UserController.java b/src/main/java/com/genius/gitget/user/controller/UserController.java similarity index 78% rename from src/main/java/com/genius/todoffin/user/controller/UserController.java rename to src/main/java/com/genius/gitget/user/controller/UserController.java index d3db3fad..df0cf343 100644 --- a/src/main/java/com/genius/todoffin/user/controller/UserController.java +++ b/src/main/java/com/genius/gitget/user/controller/UserController.java @@ -1,11 +1,11 @@ -package com.genius.todoffin.user.controller; +package com.genius.gitget.user.controller; -import static com.genius.todoffin.util.exception.SuccessCode.CREATED; -import static com.genius.todoffin.util.exception.SuccessCode.SUCCESS; +import static com.genius.gitget.util.exception.SuccessCode.CREATED; +import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; -import com.genius.todoffin.user.dto.SignupRequest; -import com.genius.todoffin.user.service.UserService; -import com.genius.todoffin.util.response.dto.CommonResponse; +import com.genius.gitget.user.dto.SignupRequest; +import com.genius.gitget.user.service.UserService; +import com.genius.gitget.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/genius/todoffin/user/domain/Role.java b/src/main/java/com/genius/gitget/user/domain/Role.java similarity index 89% rename from src/main/java/com/genius/todoffin/user/domain/Role.java rename to src/main/java/com/genius/gitget/user/domain/Role.java index dd0e2528..7afe444b 100644 --- a/src/main/java/com/genius/todoffin/user/domain/Role.java +++ b/src/main/java/com/genius/gitget/user/domain/Role.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.user.domain; +package com.genius.gitget.user.domain; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/todoffin/user/domain/User.java b/src/main/java/com/genius/gitget/user/domain/User.java similarity index 88% rename from src/main/java/com/genius/todoffin/user/domain/User.java rename to src/main/java/com/genius/gitget/user/domain/User.java index 327480d0..6e6d0fcc 100644 --- a/src/main/java/com/genius/todoffin/user/domain/User.java +++ b/src/main/java/com/genius/gitget/user/domain/User.java @@ -1,9 +1,9 @@ -package com.genius.todoffin.user.domain; +package com.genius.gitget.user.domain; -import com.genius.todoffin.challenge.domain.Hits; -import com.genius.todoffin.challenge.domain.ParticipantInfo; -import com.genius.todoffin.common.domain.BaseTimeEntity; -import com.genius.todoffin.security.constants.ProviderInfo; +import com.genius.gitget.challenge.domain.Hits; +import com.genius.gitget.challenge.domain.ParticipantInfo; +import com.genius.gitget.common.domain.BaseTimeEntity; +import com.genius.gitget.security.constants.ProviderInfo; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java b/src/main/java/com/genius/gitget/user/dto/SignupRequest.java similarity index 84% rename from src/main/java/com/genius/todoffin/user/dto/SignupRequest.java rename to src/main/java/com/genius/gitget/user/dto/SignupRequest.java index 205b5c21..79d3b963 100644 --- a/src/main/java/com/genius/todoffin/user/dto/SignupRequest.java +++ b/src/main/java/com/genius/gitget/user/dto/SignupRequest.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.user.dto; +package com.genius.gitget.user.dto; import java.util.List; import lombok.Builder; diff --git a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java b/src/main/java/com/genius/gitget/user/repository/UserRepository.java similarity index 81% rename from src/main/java/com/genius/todoffin/user/repository/UserRepository.java rename to src/main/java/com/genius/gitget/user/repository/UserRepository.java index ab2d8802..11128387 100644 --- a/src/main/java/com/genius/todoffin/user/repository/UserRepository.java +++ b/src/main/java/com/genius/gitget/user/repository/UserRepository.java @@ -1,7 +1,7 @@ -package com.genius.todoffin.user.repository; +package com.genius.gitget.user.repository; -import com.genius.todoffin.security.constants.ProviderInfo; -import com.genius.todoffin.user.domain.User; +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.user.domain.User; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/genius/todoffin/user/service/UserService.java b/src/main/java/com/genius/gitget/user/service/UserService.java similarity index 75% rename from src/main/java/com/genius/todoffin/user/service/UserService.java rename to src/main/java/com/genius/gitget/user/service/UserService.java index df273a7a..22a1c0b6 100644 --- a/src/main/java/com/genius/todoffin/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/user/service/UserService.java @@ -1,13 +1,13 @@ -package com.genius.todoffin.user.service; +package com.genius.gitget.user.service; -import static com.genius.todoffin.util.exception.ErrorCode.DUPLICATED_NICKNAME; -import static com.genius.todoffin.util.exception.ErrorCode.MEMBER_NOT_FOUND; +import static com.genius.gitget.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.gitget.util.exception.ErrorCode.MEMBER_NOT_FOUND; -import com.genius.todoffin.user.domain.Role; -import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.user.dto.SignupRequest; -import com.genius.todoffin.user.repository.UserRepository; -import com.genius.todoffin.util.exception.BusinessException; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.dto.SignupRequest; +import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.util.exception.BusinessException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/genius/todoffin/util/config/AppConfig.java b/src/main/java/com/genius/gitget/util/config/AppConfig.java similarity index 69% rename from src/main/java/com/genius/todoffin/util/config/AppConfig.java rename to src/main/java/com/genius/gitget/util/config/AppConfig.java index 5baef352..d7d79a8e 100644 --- a/src/main/java/com/genius/todoffin/util/config/AppConfig.java +++ b/src/main/java/com/genius/gitget/util/config/AppConfig.java @@ -1,7 +1,7 @@ -package com.genius.todoffin.util.config; +package com.genius.gitget.util.config; -import com.genius.todoffin.util.formatter.LocalDateFormatter; -import com.genius.todoffin.util.formatter.LocalDateTimeFormatter; +import com.genius.gitget.util.formatter.LocalDateFormatter; +import com.genius.gitget.util.formatter.LocalDateTimeFormatter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/genius/todoffin/util/config/SwaggerConfig.java b/src/main/java/com/genius/gitget/util/config/SwaggerConfig.java similarity index 92% rename from src/main/java/com/genius/todoffin/util/config/SwaggerConfig.java rename to src/main/java/com/genius/gitget/util/config/SwaggerConfig.java index f95dfbb7..a968aa00 100644 --- a/src/main/java/com/genius/todoffin/util/config/SwaggerConfig.java +++ b/src/main/java/com/genius/gitget/util/config/SwaggerConfig.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.config; +package com.genius.gitget.util.config; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; diff --git a/src/main/java/com/genius/todoffin/util/exception/BusinessException.java b/src/main/java/com/genius/gitget/util/exception/BusinessException.java similarity index 93% rename from src/main/java/com/genius/todoffin/util/exception/BusinessException.java rename to src/main/java/com/genius/gitget/util/exception/BusinessException.java index 15f09cac..468cc619 100644 --- a/src/main/java/com/genius/todoffin/util/exception/BusinessException.java +++ b/src/main/java/com/genius/gitget/util/exception/BusinessException.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.exception; +package com.genius.gitget.util.exception; import lombok.Getter; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java b/src/main/java/com/genius/gitget/util/exception/BusinessExceptionHandler.java similarity index 86% rename from src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java rename to src/main/java/com/genius/gitget/util/exception/BusinessExceptionHandler.java index 31faa710..7956bfd6 100644 --- a/src/main/java/com/genius/todoffin/util/exception/BusinessExceptionHandler.java +++ b/src/main/java/com/genius/gitget/util/exception/BusinessExceptionHandler.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.util.exception; +package com.genius.gitget.util.exception; -import com.genius.todoffin.util.response.dto.CommonResponse; +import com.genius.gitget.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/util/exception/ErrorCode.java similarity index 95% rename from src/main/java/com/genius/todoffin/util/exception/ErrorCode.java rename to src/main/java/com/genius/gitget/util/exception/ErrorCode.java index 1afa545e..018f996c 100644 --- a/src/main/java/com/genius/todoffin/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/util/exception/ErrorCode.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.exception; +package com.genius.gitget.util.exception; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -16,7 +16,7 @@ public enum ErrorCode { INVALID_CLAIM_JWT(HttpStatus.BAD_REQUEST, "JWT의 Claim이 유효하지 않습니다."), UNSUPPORTED_JWT(HttpStatus.BAD_REQUEST, "지원하지 않는 JWT 형식입니다."), INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."), - + TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."); private final HttpStatus status; diff --git a/src/main/java/com/genius/todoffin/util/exception/GlobalExceptionHandler.java b/src/main/java/com/genius/gitget/util/exception/GlobalExceptionHandler.java similarity index 88% rename from src/main/java/com/genius/todoffin/util/exception/GlobalExceptionHandler.java rename to src/main/java/com/genius/gitget/util/exception/GlobalExceptionHandler.java index d4104d3b..bbc94f45 100644 --- a/src/main/java/com/genius/todoffin/util/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/genius/gitget/util/exception/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ -package com.genius.todoffin.util.exception; +package com.genius.gitget.util.exception; -import com.genius.todoffin.util.response.dto.CommonResponse; +import com.genius.gitget.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/genius/todoffin/util/exception/SuccessCode.java b/src/main/java/com/genius/gitget/util/exception/SuccessCode.java similarity index 91% rename from src/main/java/com/genius/todoffin/util/exception/SuccessCode.java rename to src/main/java/com/genius/gitget/util/exception/SuccessCode.java index 93e5c4cd..48a7d4e9 100644 --- a/src/main/java/com/genius/todoffin/util/exception/SuccessCode.java +++ b/src/main/java/com/genius/gitget/util/exception/SuccessCode.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.exception; +package com.genius.gitget.util.exception; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/todoffin/util/formatter/CommonPattern.java b/src/main/java/com/genius/gitget/util/formatter/CommonPattern.java similarity index 91% rename from src/main/java/com/genius/todoffin/util/formatter/CommonPattern.java rename to src/main/java/com/genius/gitget/util/formatter/CommonPattern.java index 6e1086ba..c5cc4fa1 100644 --- a/src/main/java/com/genius/todoffin/util/formatter/CommonPattern.java +++ b/src/main/java/com/genius/gitget/util/formatter/CommonPattern.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.formatter; +package com.genius.gitget.util.formatter; public final class CommonPattern { public static final String MANAGER_ID_PATTERN = "^(?=.*[a-z])(?=.*\\d)[a-z\\d]{8,16}$"; diff --git a/src/main/java/com/genius/todoffin/util/formatter/LocalDateFormatter.java b/src/main/java/com/genius/gitget/util/formatter/LocalDateFormatter.java similarity index 92% rename from src/main/java/com/genius/todoffin/util/formatter/LocalDateFormatter.java rename to src/main/java/com/genius/gitget/util/formatter/LocalDateFormatter.java index 2aa6b887..700ddd56 100644 --- a/src/main/java/com/genius/todoffin/util/formatter/LocalDateFormatter.java +++ b/src/main/java/com/genius/gitget/util/formatter/LocalDateFormatter.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.formatter; +package com.genius.gitget.util.formatter; import java.time.LocalDate; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/com/genius/todoffin/util/formatter/LocalDateTimeFormatter.java b/src/main/java/com/genius/gitget/util/formatter/LocalDateTimeFormatter.java similarity index 94% rename from src/main/java/com/genius/todoffin/util/formatter/LocalDateTimeFormatter.java rename to src/main/java/com/genius/gitget/util/formatter/LocalDateTimeFormatter.java index 8f0f3314..1a5c52b5 100644 --- a/src/main/java/com/genius/todoffin/util/formatter/LocalDateTimeFormatter.java +++ b/src/main/java/com/genius/gitget/util/formatter/LocalDateTimeFormatter.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.formatter; +package com.genius.gitget.util.formatter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java b/src/main/java/com/genius/gitget/util/response/dto/CommonResponse.java similarity index 91% rename from src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java rename to src/main/java/com/genius/gitget/util/response/dto/CommonResponse.java index 7cddc8e5..2c4d5510 100644 --- a/src/main/java/com/genius/todoffin/util/response/dto/CommonResponse.java +++ b/src/main/java/com/genius/gitget/util/response/dto/CommonResponse.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.response.dto; +package com.genius.gitget.util.response.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/genius/todoffin/util/response/dto/ListResponse.java b/src/main/java/com/genius/gitget/util/response/dto/ListResponse.java similarity index 88% rename from src/main/java/com/genius/todoffin/util/response/dto/ListResponse.java rename to src/main/java/com/genius/gitget/util/response/dto/ListResponse.java index e3e3032a..3ab6a67e 100644 --- a/src/main/java/com/genius/todoffin/util/response/dto/ListResponse.java +++ b/src/main/java/com/genius/gitget/util/response/dto/ListResponse.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.response.dto; +package com.genius.gitget.util.response.dto; import java.util.List; import lombok.Getter; diff --git a/src/main/java/com/genius/todoffin/util/response/dto/PagingResponse.java b/src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java similarity index 86% rename from src/main/java/com/genius/todoffin/util/response/dto/PagingResponse.java rename to src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java index 37d574d3..87b59908 100644 --- a/src/main/java/com/genius/todoffin/util/response/dto/PagingResponse.java +++ b/src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.response.dto; +package com.genius.gitget.util.response.dto; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java b/src/main/java/com/genius/gitget/util/response/dto/SingleResponse.java similarity index 90% rename from src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java rename to src/main/java/com/genius/gitget/util/response/dto/SingleResponse.java index 3339817e..69c6e899 100644 --- a/src/main/java/com/genius/todoffin/util/response/dto/SingleResponse.java +++ b/src/main/java/com/genius/gitget/util/response/dto/SingleResponse.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.response.dto; +package com.genius.gitget.util.response.dto; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/todoffin/util/response/dto/SlicingResponse.java b/src/main/java/com/genius/gitget/util/response/dto/SlicingResponse.java similarity index 81% rename from src/main/java/com/genius/todoffin/util/response/dto/SlicingResponse.java rename to src/main/java/com/genius/gitget/util/response/dto/SlicingResponse.java index ad556c73..4b3fd984 100644 --- a/src/main/java/com/genius/todoffin/util/response/dto/SlicingResponse.java +++ b/src/main/java/com/genius/gitget/util/response/dto/SlicingResponse.java @@ -1,4 +1,4 @@ -package com.genius.todoffin.util.response.dto; +package com.genius.gitget.util.response.dto; import org.springframework.data.domain.Slice; diff --git a/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java b/src/test/java/com/genius/gitget/GitgetApplicationTests.java similarity index 54% rename from src/test/java/com/genius/todoffin/TodoffinApplicationTests.java rename to src/test/java/com/genius/gitget/GitgetApplicationTests.java index 0774dfab..7e1b17f5 100644 --- a/src/test/java/com/genius/todoffin/TodoffinApplicationTests.java +++ b/src/test/java/com/genius/gitget/GitgetApplicationTests.java @@ -1,13 +1,13 @@ -package com.genius.todoffin; +package com.genius.gitget; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class TodoffinApplicationTests { +class GitgetApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/src/test/java/com/genius/todoffin/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java similarity index 91% rename from src/test/java/com/genius/todoffin/security/service/JwtServiceTest.java rename to src/test/java/com/genius/gitget/security/service/JwtServiceTest.java index 03ab1ac9..7aa43199 100644 --- a/src/test/java/com/genius/todoffin/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java @@ -1,15 +1,15 @@ -package com.genius.todoffin.security.service; +package com.genius.gitget.security.service; -import static com.genius.todoffin.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.todoffin.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.genius.todoffin.security.constants.ProviderInfo; -import com.genius.todoffin.user.domain.Role; -import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.user.repository.UserRepository; -import com.genius.todoffin.util.exception.BusinessException; +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.util.exception.BusinessException; import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/todoffin/security/service/TokenServiceTest.java b/src/test/java/com/genius/gitget/security/service/TokenServiceTest.java similarity index 94% rename from src/test/java/com/genius/todoffin/security/service/TokenServiceTest.java rename to src/test/java/com/genius/gitget/security/service/TokenServiceTest.java index 59273be9..d9030d55 100644 --- a/src/test/java/com/genius/todoffin/security/service/TokenServiceTest.java +++ b/src/test/java/com/genius/gitget/security/service/TokenServiceTest.java @@ -1,9 +1,9 @@ -package com.genius.todoffin.security.service; +package com.genius.gitget.security.service; import static org.assertj.core.api.Assertions.assertThat; -import com.genius.todoffin.security.domain.Token; -import com.genius.todoffin.security.repository.TokenRepository; +import com.genius.gitget.security.domain.Token; +import com.genius.gitget.security.repository.TokenRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java similarity index 91% rename from src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java rename to src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java index f72965d7..b92eaf9f 100644 --- a/src/test/java/com/genius/todoffin/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java @@ -1,11 +1,11 @@ -package com.genius.todoffin.user.repository; +package com.genius.gitget.user.repository; -import static com.genius.todoffin.security.constants.ProviderInfo.GITHUB; +import static com.genius.gitget.security.constants.ProviderInfo.GITHUB; import static org.assertj.core.api.Assertions.assertThat; -import com.genius.todoffin.security.constants.ProviderInfo; -import com.genius.todoffin.user.domain.Role; -import com.genius.todoffin.user.domain.User; +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/user/service/UserServiceTest.java similarity index 86% rename from src/test/java/com/genius/todoffin/user/service/UserServiceTest.java rename to src/test/java/com/genius/gitget/user/service/UserServiceTest.java index 6d3c1437..00384c2c 100644 --- a/src/test/java/com/genius/todoffin/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/user/service/UserServiceTest.java @@ -1,12 +1,12 @@ -package com.genius.todoffin.user.service; +package com.genius.gitget.user.service; import static org.assertj.core.api.Assertions.assertThat; -import com.genius.todoffin.security.constants.ProviderInfo; -import com.genius.todoffin.user.domain.Role; -import com.genius.todoffin.user.domain.User; -import com.genius.todoffin.user.dto.SignupRequest; -import com.genius.todoffin.user.repository.UserRepository; +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.dto.SignupRequest; +import com.genius.gitget.user.repository.UserRepository; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; From da5bf8b7d93d19647c882a13c8ee2e2ec4c9deaa Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Fri, 19 Jan 2024 15:01:39 +0900 Subject: [PATCH 066/234] =?UTF-8?q?[REFACTOR]=20DB=20entity=20=EB=A6=AC?= =?UTF-8?q?=ED=8E=99=ED=86=A0=EB=A7=81=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 소셜 로그인 facebook 관련 파일 제거 * feat: DB Entity 개발 - User Entity 수정 - Hits, Topic, Instance, ParticipantInfo Entity 개발 * feat: challenge domain 및 repository 개발 - entity 수정 및 애노테이션 추가 - DB Table 별 repository 추가 - User entity : email -> identifier 로 변경 * refactor: 엔티티와 파일 리펙토링 - entity refactoring - 프로젝트 구조 변경 * chore: P6Spy query logging 의존성 추가 * test: 기능별 도메인 생성 테스트 코드 추가 * test: User 회원 추가/조회/수정/삭제 테스트 코드 작성 완료 * feat: User와 Instance 다대다 연관관계 편의 메서드 작성 - 테스트 코드 작성 완료 * feat: User와 Instance 다대다 연관관계 편의 메서드 작성 - 2 - 관심목록, 인스턴스에 참여한 유저 정보 - 테스트 완료 - 하나의 인스턴스가 가지는 Hits 테이블의 컬럼 갯수를 조회함으로서 Instance 테이블의 like_count 컬럼을 제거해도 됨. -> 리펙토링 예정 * refactor: 불필요한 entity 제거 * feat: 토픽과 인스턴스 연관관계 편의 메서드 작성 * test: 기존에 작성한 불필요한 코드 제거 및 테스트 --- build.gradle | 3 + .../genius/gitget/challenge/domain/Hits.java | 28 ----- .../gitget/challenge/domain/JoinResult.java | 8 -- .../challenge/domain/ParticipantInfo.java | 44 -------- .../gitget/common/domain/BaseTimeEntity.java | 11 -- .../com/genius/gitget/hits/domain/Hits.java | 70 ++++++++++++ .../repository/HitsRepository.java | 4 +- .../domain/Instance.java | 42 +++++--- .../domain/Progress.java | 2 +- .../repository/InstanceRepository.java | 4 +- .../participantinfo/domain/JoinResult.java | 8 ++ .../participantinfo/domain/JoinStatus.java | 5 + .../domain/ParticipantInfo.java | 76 +++++++++++++ .../repository/ParticipantInfoRepository.java | 7 ++ .../{challenge => topic}/domain/Topic.java | 17 ++- .../repository/TopicRepository.java | 4 +- .../com/genius/gitget/user/domain/User.java | 24 ++--- .../java/com/genius/gitget/hits/HitsTest.java | 76 +++++++++++++ .../instance/InstanceRepositoryTest.java | 32 ++++++ .../ParticipantInfoRepositoryTest.java | 99 +++++++++++++++++ .../gitget/topic/TopicRepositoryTest.java | 27 +++++ .../genius/gitget/user/domain/UserTest.java | 100 ++++++++++++++++++ .../user/repository/UserRepositoryTest.java | 2 + 23 files changed, 565 insertions(+), 128 deletions(-) delete mode 100644 src/main/java/com/genius/gitget/challenge/domain/Hits.java delete mode 100644 src/main/java/com/genius/gitget/challenge/domain/JoinResult.java delete mode 100644 src/main/java/com/genius/gitget/challenge/domain/ParticipantInfo.java create mode 100644 src/main/java/com/genius/gitget/hits/domain/Hits.java rename src/main/java/com/genius/gitget/{challenge => hits}/repository/HitsRepository.java (58%) rename src/main/java/com/genius/gitget/{challenge => instance}/domain/Instance.java (62%) rename src/main/java/com/genius/gitget/{challenge => instance}/domain/Progress.java (67%) rename src/main/java/com/genius/gitget/{challenge => instance}/repository/InstanceRepository.java (58%) create mode 100644 src/main/java/com/genius/gitget/participantinfo/domain/JoinResult.java create mode 100644 src/main/java/com/genius/gitget/participantinfo/domain/JoinStatus.java create mode 100644 src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java create mode 100644 src/main/java/com/genius/gitget/participantinfo/repository/ParticipantInfoRepository.java rename src/main/java/com/genius/gitget/{challenge => topic}/domain/Topic.java (71%) rename src/main/java/com/genius/gitget/{challenge => topic}/repository/TopicRepository.java (58%) create mode 100644 src/test/java/com/genius/gitget/hits/HitsTest.java create mode 100644 src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/participantInfo/ParticipantInfoRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/user/domain/UserTest.java diff --git a/build.gradle b/build.gradle index 436cde71..6b48a617 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,9 @@ dependencies { // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + // query log 띄우기 시작 + implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' + // query log 띄우기 끝 //JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' diff --git a/src/main/java/com/genius/gitget/challenge/domain/Hits.java b/src/main/java/com/genius/gitget/challenge/domain/Hits.java deleted file mode 100644 index 4037f370..00000000 --- a/src/main/java/com/genius/gitget/challenge/domain/Hits.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.genius.gitget.challenge.domain; - -import com.genius.gitget.user.domain.User; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "hits") -public class Hits { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "hits_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "instance_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Instance instance; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - private User user; -} diff --git a/src/main/java/com/genius/gitget/challenge/domain/JoinResult.java b/src/main/java/com/genius/gitget/challenge/domain/JoinResult.java deleted file mode 100644 index 545e8cee..00000000 --- a/src/main/java/com/genius/gitget/challenge/domain/JoinResult.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.genius.gitget.challenge.domain; - -import lombok.Getter; - -@Getter -public enum JoinResult { - FAIL, SUCCESS -} diff --git a/src/main/java/com/genius/gitget/challenge/domain/ParticipantInfo.java b/src/main/java/com/genius/gitget/challenge/domain/ParticipantInfo.java deleted file mode 100644 index dba97653..00000000 --- a/src/main/java/com/genius/gitget/challenge/domain/ParticipantInfo.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.genius.gitget.challenge.domain; - -import com.genius.gitget.user.domain.User; -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.DynamicInsert; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DynamicInsert -@Table(name = "participantInfo") -public class ParticipantInfo { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "participantInfo_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "instance_id") - private Instance instance; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - - @NotNull - @Column(name = "join_status") - @ColumnDefault("0") - private Boolean joinStatus; - - @Enumerated(EnumType.STRING) - @Column(name = "join_result") - private JoinResult joinResult; - - public ParticipantInfo(Boolean joinStatus, JoinResult joinResult) { - this.joinStatus = joinStatus; - this.joinResult = joinResult; - } -} diff --git a/src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java b/src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java index e8677d84..c87a3fa8 100644 --- a/src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java +++ b/src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java @@ -1,6 +1,5 @@ package com.genius.gitget.common.domain; - import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; @@ -27,16 +26,6 @@ public class BaseTimeEntity { @Column(name = "updated_at") private LocalDateTime modifiedDate; - @Column(name = "deleted_at") private LocalDateTime deletedDate; - - - // ====================================================== - // Instance 테이블 - @Column(name = "started_at") - private LocalDateTime startedDate; - - @Column(name = "completed_at") - private LocalDateTime completedDate; } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/hits/domain/Hits.java b/src/main/java/com/genius/gitget/hits/domain/Hits.java new file mode 100644 index 00000000..7844c531 --- /dev/null +++ b/src/main/java/com/genius/gitget/hits/domain/Hits.java @@ -0,0 +1,70 @@ +package com.genius.gitget.hits.domain; + +import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.user.domain.User; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "hits") +@EntityListeners(AuditingEntityListener.class) +public class Hits { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "hits_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "instance_id") + private Instance instance; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + public Hits(User user, Instance instance) { + this.instance = instance; + this.user = user; + setUserAndInstance(user, instance); + } + + @CreatedDate + @Column(name = "liked_at", unique = false) + private LocalDateTime likedAt; // 찜하기 누른 시각 + + /*== 연관관계 편의 메서드 ==*/ + public void setUserAndInstance(User user, Instance instance) { + addHitsForUser(user); + addHitsForInstance(instance); + setUser(user); + setInstance(instance); + } + + private void setUser(User user) { + this.user = user; + } + + private void addHitsForUser(User user) { + if (!(user.getHitsList().contains(this))) { + user.getHitsList().add(this); + } + } + + private void setInstance(Instance instance) { + this.instance = instance; + } + + private void addHitsForInstance(Instance instance) { + if (!(instance.getHitsList().contains(this))) { + instance.getHitsList().add(this); + } + } +} diff --git a/src/main/java/com/genius/gitget/challenge/repository/HitsRepository.java b/src/main/java/com/genius/gitget/hits/repository/HitsRepository.java similarity index 58% rename from src/main/java/com/genius/gitget/challenge/repository/HitsRepository.java rename to src/main/java/com/genius/gitget/hits/repository/HitsRepository.java index 397e23d2..bb60b559 100644 --- a/src/main/java/com/genius/gitget/challenge/repository/HitsRepository.java +++ b/src/main/java/com/genius/gitget/hits/repository/HitsRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.challenge.repository; +package com.genius.gitget.hits.repository; -import com.genius.gitget.challenge.domain.Hits; +import com.genius.gitget.hits.domain.Hits; import org.springframework.data.jpa.repository.JpaRepository; public interface HitsRepository extends JpaRepository { diff --git a/src/main/java/com/genius/gitget/challenge/domain/Instance.java b/src/main/java/com/genius/gitget/instance/domain/Instance.java similarity index 62% rename from src/main/java/com/genius/gitget/challenge/domain/Instance.java rename to src/main/java/com/genius/gitget/instance/domain/Instance.java index 381954ed..9eb85ab6 100644 --- a/src/main/java/com/genius/gitget/challenge/domain/Instance.java +++ b/src/main/java/com/genius/gitget/instance/domain/Instance.java @@ -1,6 +1,8 @@ -package com.genius.gitget.challenge.domain; +package com.genius.gitget.instance.domain; -import com.genius.gitget.common.domain.BaseTimeEntity; +import com.genius.gitget.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.hits.domain.Hits; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; @@ -9,6 +11,7 @@ import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -17,22 +20,22 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @DynamicInsert @Table(name = "instance") -public class Instance extends BaseTimeEntity { +public class Instance { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "instance_id") private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "topic_id") - private Topic topic; - @OneToMany(mappedBy = "instance") private List hitsList = new ArrayList<>(); @OneToMany(mappedBy = "instance") private List participantInfoList = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "topic_id") + private Topic topic; + private String title; private String description; @@ -43,23 +46,34 @@ public class Instance extends BaseTimeEntity { private int point_per_person; - @NotNull - @ColumnDefault("0") - private int like_count; - @NotNull @Enumerated(EnumType.STRING) @ColumnDefault("'PRE_ACTIVITY'") private Progress progress; - public Instance(String title, String description, int participants, String tags, int point_per_person, - int like_count, Progress progress) { + @Column(name = "started_at") + private LocalDateTime startedDate; + + @Column(name = "completed_at") + private LocalDateTime completedDate; + + + public Instance(String title, String description, int participants, String tags, int point_per_person, Progress progress, LocalDateTime startedDate, LocalDateTime completedDate) { this.title = title; this.description = description; this.participants = participants; this.tags = tags; this.point_per_person = point_per_person; - this.like_count = like_count; this.progress = progress; + this.startedDate = startedDate; + this.completedDate = completedDate; + } + + //== 연관관계 편의 메서드 ==// + public void setTopic(Topic topic) { + this.topic = topic; + if (!topic.getInstanceList().contains(this)) { + topic.getInstanceList().add(this); + } } } diff --git a/src/main/java/com/genius/gitget/challenge/domain/Progress.java b/src/main/java/com/genius/gitget/instance/domain/Progress.java similarity index 67% rename from src/main/java/com/genius/gitget/challenge/domain/Progress.java rename to src/main/java/com/genius/gitget/instance/domain/Progress.java index 2a128e46..d2f38aba 100644 --- a/src/main/java/com/genius/gitget/challenge/domain/Progress.java +++ b/src/main/java/com/genius/gitget/instance/domain/Progress.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.domain; +package com.genius.gitget.instance.domain; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/challenge/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java similarity index 58% rename from src/main/java/com/genius/gitget/challenge/repository/InstanceRepository.java rename to src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java index 1ebec3f2..e0f44ff6 100644 --- a/src/main/java/com/genius/gitget/challenge/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.challenge.repository; +package com.genius.gitget.instance.repository; -import com.genius.gitget.challenge.domain.Instance; +import com.genius.gitget.instance.domain.Instance; import org.springframework.data.jpa.repository.JpaRepository; public interface InstanceRepository extends JpaRepository { diff --git a/src/main/java/com/genius/gitget/participantinfo/domain/JoinResult.java b/src/main/java/com/genius/gitget/participantinfo/domain/JoinResult.java new file mode 100644 index 00000000..c42015dd --- /dev/null +++ b/src/main/java/com/genius/gitget/participantinfo/domain/JoinResult.java @@ -0,0 +1,8 @@ +package com.genius.gitget.participantinfo.domain; + +import lombok.Getter; + +@Getter +public enum JoinResult { + FAIL, SUCCESS, PROCESSING +} diff --git a/src/main/java/com/genius/gitget/participantinfo/domain/JoinStatus.java b/src/main/java/com/genius/gitget/participantinfo/domain/JoinStatus.java new file mode 100644 index 00000000..44c3f952 --- /dev/null +++ b/src/main/java/com/genius/gitget/participantinfo/domain/JoinStatus.java @@ -0,0 +1,5 @@ +package com.genius.gitget.participantinfo.domain; + +public enum JoinStatus { + NO, YES +} diff --git a/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java b/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java new file mode 100644 index 00000000..cbc85dc7 --- /dev/null +++ b/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java @@ -0,0 +1,76 @@ +package com.genius.gitget.participantinfo.domain; + +import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.user.domain.User; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DynamicInsert +@Table(name = "participantInfo") +public class ParticipantInfo { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "participantInfo_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "instance_id") + private Instance instance; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @Enumerated(EnumType.STRING) + @Column(name = "join_status") + @NotNull + @ColumnDefault("'YES'") + private JoinStatus joinStatus; + + @Enumerated(EnumType.STRING) + @Column(name = "join_result") + private JoinResult joinResult; + + @Builder + public ParticipantInfo(JoinStatus joinStatus, JoinResult joinResult) { + this.joinStatus = joinStatus; + this.joinResult = joinResult; + } + + /*== 연관관계 편의 메서드 ==*/ + public void setUserAndInstance(User user, Instance instance) { + addParticipantInfoForUser(user); + addParticipantInfoForInstance(instance); + setUser(user); + setInstance(instance); + } + + private void setUser(User user) { + this.user = user; + } + + private void addParticipantInfoForUser(User user) { + if(!(user.getParticipantInfoList().contains(this))) { + user.getParticipantInfoList().add(this); + } + } + + private void setInstance(Instance instance) { + this.instance = instance; + } + + private void addParticipantInfoForInstance(Instance instance) { + if(!(instance.getParticipantInfoList().contains(this))) { + instance.getParticipantInfoList().add(this); + } + } +} diff --git a/src/main/java/com/genius/gitget/participantinfo/repository/ParticipantInfoRepository.java b/src/main/java/com/genius/gitget/participantinfo/repository/ParticipantInfoRepository.java new file mode 100644 index 00000000..ae539f12 --- /dev/null +++ b/src/main/java/com/genius/gitget/participantinfo/repository/ParticipantInfoRepository.java @@ -0,0 +1,7 @@ +package com.genius.gitget.participantinfo.repository; + +import com.genius.gitget.participantinfo.domain.ParticipantInfo; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ParticipantInfoRepository extends JpaRepository { +} diff --git a/src/main/java/com/genius/gitget/challenge/domain/Topic.java b/src/main/java/com/genius/gitget/topic/domain/Topic.java similarity index 71% rename from src/main/java/com/genius/gitget/challenge/domain/Topic.java rename to src/main/java/com/genius/gitget/topic/domain/Topic.java index 0096ca1d..226792d9 100644 --- a/src/main/java/com/genius/gitget/challenge/domain/Topic.java +++ b/src/main/java/com/genius/gitget/topic/domain/Topic.java @@ -1,5 +1,6 @@ -package com.genius.gitget.challenge.domain; +package com.genius.gitget.topic.domain; +import com.genius.gitget.instance.domain.Instance; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -17,6 +18,9 @@ public class Topic { @Column(name = "topic_id") private Long id; + @OneToMany(mappedBy = "topic") + private List instanceList; + private String title; private String description; @@ -25,13 +29,18 @@ public class Topic { private int point_per_person; - @OneToMany(mappedBy = "topic") - private List instanceList; - public Topic(String title, String description, String tags, int point_per_person) { this.title = title; this.description = description; this.tags = tags; this.point_per_person = point_per_person; } + + //== 연관관계 편의 메서드 ==// + public void setInstance(Instance instance) { + instanceList.add(instance); + if (instance.getTopic() != this) { + instance.setTopic(this); + } + } } diff --git a/src/main/java/com/genius/gitget/challenge/repository/TopicRepository.java b/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java similarity index 58% rename from src/main/java/com/genius/gitget/challenge/repository/TopicRepository.java rename to src/main/java/com/genius/gitget/topic/repository/TopicRepository.java index 6a90c36d..1a429d02 100644 --- a/src/main/java/com/genius/gitget/challenge/repository/TopicRepository.java +++ b/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.challenge.repository; +package com.genius.gitget.topic.repository; -import com.genius.gitget.challenge.domain.Topic; +import com.genius.gitget.topic.domain.Topic; import org.springframework.data.jpa.repository.JpaRepository; public interface TopicRepository extends JpaRepository { diff --git a/src/main/java/com/genius/gitget/user/domain/User.java b/src/main/java/com/genius/gitget/user/domain/User.java index 6e6d0fcc..fde4805c 100644 --- a/src/main/java/com/genius/gitget/user/domain/User.java +++ b/src/main/java/com/genius/gitget/user/domain/User.java @@ -1,7 +1,11 @@ package com.genius.gitget.user.domain; -import com.genius.gitget.challenge.domain.Hits; -import com.genius.gitget.challenge.domain.ParticipantInfo; +import com.genius.gitget.hits.domain.Hits; +import com.genius.gitget.participantinfo.domain.ParticipantInfo; +import jakarta.validation.constraints.NotNull; + +import java.util.ArrayList; +import java.util.List; import com.genius.gitget.common.domain.BaseTimeEntity; import com.genius.gitget.security.constants.ProviderInfo; import jakarta.persistence.Column; @@ -13,9 +17,6 @@ import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import java.util.ArrayList; -import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -31,6 +32,12 @@ public class User extends BaseTimeEntity { @Column(name = "user_id") private Long id; + @OneToMany(mappedBy = "user") + private List hitsList = new ArrayList<>(); + + @OneToMany(mappedBy = "user") + private List participantInfoList = new ArrayList<>(); + @NotNull @Enumerated(EnumType.STRING) private ProviderInfo providerInfo; @@ -50,13 +57,6 @@ public class User extends BaseTimeEntity { @Column(length = 100) private String information; - @OneToMany(mappedBy = "user") - private List hitsList = new ArrayList<>(); - - @OneToMany(mappedBy = "user") - private List participantInfoList = new ArrayList<>(); - - @Builder public User(ProviderInfo providerInfo, String identifier, Role role, String nickname, String information, String interest) { diff --git a/src/test/java/com/genius/gitget/hits/HitsTest.java b/src/test/java/com/genius/gitget/hits/HitsTest.java new file mode 100644 index 00000000..d74c6156 --- /dev/null +++ b/src/test/java/com/genius/gitget/hits/HitsTest.java @@ -0,0 +1,76 @@ +package com.genius.gitget.hits; + +import com.genius.gitget.hits.domain.Hits; +import com.genius.gitget.hits.repository.HitsRepository; +import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.instance.domain.Progress; +import com.genius.gitget.instance.repository.InstanceRepository; +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.repository.UserRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.security.constants.ProviderType.NAVER; +import static com.genius.gitget.user.domain.Role.ADMIN; +import static com.genius.gitget.user.domain.Role.USER; + +@SpringBootTest +@Transactional +@Rollback(value = false) + +public class HitsTest { + + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + HitsRepository hitsRepository; + + private User user1, user2; + private Instance instance1; + + @BeforeEach + public void setup() { + user1 = User.builder().identifier("neo5188@gmail.com") + .providerInfo(ProviderInfo.NAVER) + .nickname("kimdozzi") + .information("백엔드") + .interest("운동") + .role(ADMIN) + .build(); + + user2 = User.builder().identifier("ssang23@naver.com") + .providerInfo(GOOGLE) + .nickname("SEONG") + .information("프론트엔드") + .interest("영화") + .role(USER) + .build(); + instance1 = new Instance("1일 1커밋", "챌린지 세부사항입니다." ,10, "BE, CS", + 100, Progress.ACTIVITY, LocalDateTime.now(), LocalDateTime.now().plusDays(3)); + + userRepository.save(user1); + userRepository.save(user2); + instanceRepository.save(instance1); + } + + @Test + public void 사용자는_챌린지의_인스턴스를_관심목록에_저장한다() { + Hits like = new Hits(user1, instance1); + hitsRepository.save(like); + + int likeCount = instance1.getHitsList().size(); + Assertions.assertEquals(1, likeCount); + + } +} diff --git a/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java new file mode 100644 index 00000000..bd095228 --- /dev/null +++ b/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java @@ -0,0 +1,32 @@ +package com.genius.gitget.instance; + +import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.instance.domain.Progress; +import com.genius.gitget.instance.repository.InstanceRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@SpringBootTest +@Transactional +@Rollback(value = false) +public class InstanceRepositoryTest { + + @Autowired + private InstanceRepository instanceRepository; + + @Test + public void 인스턴스_저장() { + Instance instance = new Instance("1일 1커밋", "챌린지 세부사항입니다." ,10, "BE, CS", + 100, Progress.ACTIVITY, LocalDateTime.now(), LocalDateTime.now().plusDays(3)); + + Instance savedInstance = instanceRepository.save(instance); + + Assertions.assertThat(instance.getId()).isEqualTo(savedInstance.getId()); + } +} diff --git a/src/test/java/com/genius/gitget/participantInfo/ParticipantInfoRepositoryTest.java b/src/test/java/com/genius/gitget/participantInfo/ParticipantInfoRepositoryTest.java new file mode 100644 index 00000000..0c5c6c0a --- /dev/null +++ b/src/test/java/com/genius/gitget/participantInfo/ParticipantInfoRepositoryTest.java @@ -0,0 +1,99 @@ +package com.genius.gitget.participantInfo; + +import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.instance.domain.Progress; +import com.genius.gitget.instance.repository.InstanceRepository; +import com.genius.gitget.participantinfo.domain.JoinResult; +import com.genius.gitget.participantinfo.domain.JoinStatus; +import com.genius.gitget.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.participantinfo.repository.ParticipantInfoRepository; + +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.repository.UserRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.security.constants.ProviderInfo.NAVER; +import static com.genius.gitget.user.domain.Role.ADMIN; +import static com.genius.gitget.user.domain.Role.USER; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +@Transactional +@Rollback(value = false) +public class ParticipantInfoRepositoryTest { + + @Autowired + private ParticipantInfoRepository participantInfoRepository; + @Autowired + private UserRepository userRepository; + @Autowired + private InstanceRepository instanceRepository; + + + @Test + public void 참여자_정보_저장() { + ParticipantInfo participantInfo = new ParticipantInfo(JoinStatus.YES, JoinResult.SUCCESS); + + ParticipantInfo savedInfo = participantInfoRepository.save(participantInfo); + + Assertions.assertThat(participantInfo.getId()).isEqualTo(savedInfo.getId()); + } + + @Test + public void 회원과_챌린지_인스턴스_양방향_연관관계_편의_메서드_테스트() { + // given + User user1 = userA(); + User user2 = userB(); + userRepository.save(user1); + userRepository.save(user2); + + // when + + Instance instance1 = instanceA(); + + ParticipantInfo participantInfo1 = new ParticipantInfo(JoinStatus.YES, JoinResult.FAIL); + participantInfo1.setUserAndInstance(user1, instance1); + participantInfoRepository.save(participantInfo1); + + ParticipantInfo participantInfo2 = new ParticipantInfo(JoinStatus.YES, JoinResult.SUCCESS); + participantInfo2.setUserAndInstance(user2, instance1); + participantInfoRepository.save(participantInfo2); + + instanceRepository.save(instance1); + + // then + assertEquals(1, userRepository.findByIdentifier("neo5188@gmail.com").get().getParticipantInfoList().size()); + } + + private User userA() { + return User.builder().identifier("neo5188@gmail.com") + .providerInfo(NAVER) + .nickname("kimdozzi") + .information("백엔드") + .interest("운동") + .role(ADMIN) + .build(); + } + + private User userB() { + return User.builder().identifier("ssang23@naver.com") + .providerInfo(GOOGLE) + .nickname("SEONG") + .information("프론트엔드") + .interest("영화") + .role(USER) + .build(); + } + private Instance instanceA() { + return new Instance("1일 1커밋", "챌린지 세부사항입니다." ,10, "BE, CS", + 100, Progress.ACTIVITY, LocalDateTime.now(), LocalDateTime.now().plusDays(3)); + } +} diff --git a/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java b/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java new file mode 100644 index 00000000..b8c06342 --- /dev/null +++ b/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java @@ -0,0 +1,27 @@ +package com.genius.gitget.topic; + +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Rollback(value = false) +public class TopicRepositoryTest { + + @Autowired + TopicRepository topicRepository; + + @Test + public void 토픽_저장() { + Topic topic = new Topic("1일 1커밋", "챌린지입니다.", "BE, CS", 500); + Topic savedTopic = topicRepository.save(topic); + + Assertions.assertThat(topic.getTitle()).isEqualTo(savedTopic.getTitle()); + } +} diff --git a/src/test/java/com/genius/gitget/user/domain/UserTest.java b/src/test/java/com/genius/gitget/user/domain/UserTest.java new file mode 100644 index 00000000..cbd91687 --- /dev/null +++ b/src/test/java/com/genius/gitget/user/domain/UserTest.java @@ -0,0 +1,100 @@ +package com.genius.gitget.user.domain; + +import com.genius.gitget.user.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; + +import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.security.constants.ProviderInfo.NAVER; +import static com.genius.gitget.user.domain.Role.ADMIN; +import static com.genius.gitget.user.domain.Role.USER; +import static org.assertj.core.api.Assertions.*; +import static org.xmlunit.util.Linqy.count; + +@SpringBootTest +@Transactional +public class UserTest { + + @Autowired + private UserRepository userRepository; + + @Test + public void 사용자_추가() { + User user = User.builder().identifier("neo5188@gmail.com") + .providerInfo(NAVER) + .nickname("kimdozzi") + .information("백엔드") + .interest("운동") + .role(ADMIN) + .build(); + + User savedUser = userRepository.save(user); + assertThat(savedUser.getId()).isEqualTo(user.getId()); + } + + @Test + public void 사용자_목록_조회() { + User savedUser1 = userRepository.save(userA()); + User savedUser2 = userRepository.save(userB()); + + List users = userRepository.findAll(); + for (User user : users) { + System.out.println("user = " + user); + } + assertThat(count(users)).isEqualTo(2); + assertThat(savedUser1).isNotSameAs(savedUser2); + } + + @Test + public void 사용자_정보_수정() { + User savedUser1 = userRepository.save(userA()); + + String nickName = "zzanggu"; + String information = "This is updated info !!"; + String interest = "This is interest!!"; + + savedUser1.updateUser(nickName, information, interest); + User savedUser = userRepository.save(savedUser1); + + assertThat(nickName).isEqualTo(savedUser.getNickname()); + + System.out.println("savedUser.getNickname() = " + savedUser.getNickname()); + System.out.println("savedUser.getIdentifier() = " + savedUser.getIdentifier()); + + } + + @Test + public void 사용자_삭제() { + User savedUser1 = userRepository.save(userA()); + User savedUser2 = userRepository.save(userB()); + + userRepository.deleteAll(); + + List users = userRepository.findAll(); + + assertThat(users.size()).isEqualTo(0); + } + + private User userA() { + return User.builder().identifier("neo5188@gmail.com") + .providerInfo(NAVER) + .nickname("kimdozzi") + .information("백엔드") + .interest("운동") + .role(ADMIN) + .build(); + } + + private User userB() { + return User.builder().identifier("ssang23@naver.com") + .providerInfo(GOOGLE) + .nickname("SEONG") + .information("프론트엔드") + .interest("영화") + .role(USER) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java index b92eaf9f..ff2e1d6c 100644 --- a/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java @@ -10,10 +10,12 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; @SpringBootTest @Transactional +@Rollback(value = false) class UserRepositoryTest { @Autowired private UserRepository userRepository; From da4a86f539863d3a99abffb721b36a2a185093eb Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Fri, 19 Jan 2024 17:55:28 +0900 Subject: [PATCH 067/234] =?UTF-8?q?refactor:=20=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/genius/gitget/topic/domain/Topic.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/genius/gitget/topic/domain/Topic.java b/src/main/java/com/genius/gitget/topic/domain/Topic.java index 226792d9..25c5c65a 100644 --- a/src/main/java/com/genius/gitget/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/topic/domain/Topic.java @@ -36,6 +36,7 @@ public Topic(String title, String description, String tags, int point_per_person this.point_per_person = point_per_person; } + //== 연관관계 편의 메서드 ==// public void setInstance(Instance instance) { instanceList.add(instance); @@ -43,4 +44,5 @@ public void setInstance(Instance instance) { instance.setTopic(this); } } + } From 70fda414ec1e8da9e68e703b28eb5c0d85d8fc5e Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 22 Jan 2024 02:46:16 +0900 Subject: [PATCH 068/234] =?UTF-8?q?feat:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EC=9D=B8=EC=A6=9D=20=EA=B0=9D=EC=B2=B4=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EB=B0=9B=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AuthControllerTest.java | 65 +++++++++++++++++++ .../gitget/util/WithMockCustomUser.java | 24 +++++++ ...hMockCustomUserSecurityContextFactory.java | 52 +++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java create mode 100644 src/test/java/com/genius/gitget/util/WithMockCustomUser.java create mode 100644 src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java diff --git a/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java b/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java new file mode 100644 index 00000000..0bf68e16 --- /dev/null +++ b/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java @@ -0,0 +1,65 @@ +package com.genius.gitget.security.controller; + +import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.genius.gitget.security.domain.UserPrincipal; +import com.genius.gitget.security.service.JwtService; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.util.WithMockCustomUser; +import jakarta.servlet.http.Cookie; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest +@Transactional +@Slf4j +public class AuthControllerTest { + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + + @Autowired + JwtService jwtService; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @DisplayName("anotation test") + @WithMockCustomUser(role = Role.USER) + public void test() throws Exception { + //given + UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() + .getPrincipal(); + User user = userPrincipal.getUser(); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + String accessCookie = jwtService.generateAccessToken(httpServletResponse, user); + Cookie cookie = new Cookie(ACCESS_PREFIX.getValue(), accessCookie); + + //when&then + mockMvc.perform(get("/api/test") + .cookie(cookie)) + .andExpect(status().isOk()); + } +} diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUser.java b/src/test/java/com/genius/gitget/util/WithMockCustomUser.java new file mode 100644 index 00000000..d528a25f --- /dev/null +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUser.java @@ -0,0 +1,24 @@ +package com.genius.gitget.util; + +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.user.domain.Role; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.springframework.security.test.context.support.WithSecurityContext; + +@Retention(value = RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) +public @interface WithMockCustomUser { + + ProviderInfo providerInfo() default ProviderInfo.GITHUB; + + String identifier() default "identifier"; + + String nickname() default "nickname"; + + String interest() default "BE,FE"; + + String information() default "information"; + + Role role() default Role.USER; +} diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java new file mode 100644 index 00000000..e9156eb9 --- /dev/null +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java @@ -0,0 +1,52 @@ +package com.genius.gitget.util; + +import com.genius.gitget.security.service.CustomUserDetailsService; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.dto.SignupRequest; +import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.user.service.UserService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +@RequiredArgsConstructor +@Slf4j +public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory { + private final UserRepository userRepository; + private final UserService userService; + private final CustomUserDetailsService customUserDetailsService; + + @Override + public SecurityContext createSecurityContext(WithMockCustomUser customUser) { + User user = User.builder() + .providerInfo(customUser.providerInfo()) + .identifier(customUser.identifier()) + .role(Role.NOT_REGISTERED) + .build(); + + SignupRequest signupRequest = SignupRequest.builder() + .identifier(customUser.identifier()) + .interest(List.of("FE", "BE")) + .nickname(customUser.nickname()) + .information(customUser.information()) + .build(); + + userRepository.save(user); + Long signupId = userService.signup(signupRequest); + + UserDetails principal = customUserDetailsService.loadUserByUsername(String.valueOf(signupId)); + Authentication auth = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), + principal.getAuthorities()); + + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(auth); + return securityContext; + } +} From 5b1c149da4e82c4c81e873199a10611d23a13030 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:57:47 +0900 Subject: [PATCH 069/234] =?UTF-8?q?feat:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EC=9D=B4=EC=9A=A9=ED=95=A0=20JWT=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=83=9D=EC=84=B1=20=EC=9C=A0=ED=8B=B8=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/controller/AuthController.java | 9 +++++ .../controller/AuthControllerTest.java | 18 ++------- .../com/genius/gitget/util/TokenTestUtil.java | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 src/test/java/com/genius/gitget/util/TokenTestUtil.java diff --git a/src/main/java/com/genius/gitget/security/controller/AuthController.java b/src/main/java/com/genius/gitget/security/controller/AuthController.java index e1701398..32e51b38 100644 --- a/src/main/java/com/genius/gitget/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/security/controller/AuthController.java @@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -48,4 +49,12 @@ public ResponseEntity logout(HttpServletResponse response) { new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); } + + @GetMapping("/test") + public ResponseEntity test() { + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), "TEST 성공") + ); + } } diff --git a/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java b/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java index 0bf68e16..8ae2a8d9 100644 --- a/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java +++ b/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java @@ -1,14 +1,11 @@ package com.genius.gitget.security.controller; -import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.security.domain.UserPrincipal; -import com.genius.gitget.security.service.JwtService; import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; +import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; @@ -17,8 +14,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +28,7 @@ public class AuthControllerTest { WebApplicationContext context; @Autowired - JwtService jwtService; + TokenTestUtil tokenTestUtil; @BeforeEach public void setup() { @@ -48,14 +43,7 @@ public void setup() { @WithMockCustomUser(role = Role.USER) public void test() throws Exception { //given - UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() - .getPrincipal(); - User user = userPrincipal.getUser(); - - MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); - - String accessCookie = jwtService.generateAccessToken(httpServletResponse, user); - Cookie cookie = new Cookie(ACCESS_PREFIX.getValue(), accessCookie); + Cookie cookie = tokenTestUtil.createAccessCookie(); //when&then mockMvc.perform(get("/api/test") diff --git a/src/test/java/com/genius/gitget/util/TokenTestUtil.java b/src/test/java/com/genius/gitget/util/TokenTestUtil.java new file mode 100644 index 00000000..2214676f --- /dev/null +++ b/src/test/java/com/genius/gitget/util/TokenTestUtil.java @@ -0,0 +1,40 @@ +package com.genius.gitget.util; + +import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; + +import com.genius.gitget.security.domain.UserPrincipal; +import com.genius.gitget.security.service.JwtService; +import com.genius.gitget.user.domain.User; +import jakarta.servlet.http.Cookie; +import lombok.RequiredArgsConstructor; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class TokenTestUtil { + private final JwtService jwtService; + + public Cookie createAccessCookie() { + UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() + .getPrincipal(); + User user = userPrincipal.getUser(); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + String accessCookie = jwtService.generateAccessToken(httpServletResponse, user); + return new Cookie(ACCESS_PREFIX.getValue(), accessCookie); + } + + public Cookie createRefreshCookie() { + UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() + .getPrincipal(); + User user = userPrincipal.getUser(); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + String refreshCookie = jwtService.generateRefreshToken(httpServletResponse, user); + return new Cookie(ACCESS_PREFIX.getValue(), refreshCookie); + } +} From beccdca33dbbe36b9aa060817b72920f0b26b465 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:05:27 +0900 Subject: [PATCH 070/234] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80/?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=A0=80=EC=9E=A5=ED=95=A0=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미지/파일을 저장할 엔티티 및 enum 클래스 작성 --- .../genius/gitget/file/domain/FileType.java | 15 +++++++ .../com/genius/gitget/file/domain/Files.java | 45 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/main/java/com/genius/gitget/file/domain/FileType.java create mode 100644 src/main/java/com/genius/gitget/file/domain/Files.java diff --git a/src/main/java/com/genius/gitget/file/domain/FileType.java b/src/main/java/com/genius/gitget/file/domain/FileType.java new file mode 100644 index 00000000..321fbb4e --- /dev/null +++ b/src/main/java/com/genius/gitget/file/domain/FileType.java @@ -0,0 +1,15 @@ +package com.genius.gitget.file.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum FileType { + PROFILE("profile"), + TOPIC("topic"), + INSTANCE("instance"), + PET("pet"); + + private final String path; +} diff --git a/src/main/java/com/genius/gitget/file/domain/Files.java b/src/main/java/com/genius/gitget/file/domain/Files.java new file mode 100644 index 00000000..637e6f46 --- /dev/null +++ b/src/main/java/com/genius/gitget/file/domain/Files.java @@ -0,0 +1,45 @@ +package com.genius.gitget.file.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Files { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "attachment_id") + private Long id; + + //TODO: User 연관관계 설정 필요(Profile) + //TODO: Topic 연관관계 설정 필요 + //TODO: Instance 연관관계 설정 필요 + //TODO: 추후 PET쪽과 연관관계 설정 필요 + + @Enumerated(value = EnumType.STRING) + private FileType fileType; + + private String originalFilename; + + private String savedFilename; + + private String fileURI; + + @Builder + public Files(FileType fileType, String originalFilename, String savedFilename, String fileURI) { + this.fileType = fileType; + this.originalFilename = originalFilename; + this.savedFilename = savedFilename; + this.fileURI = fileURI; + } +} From c513a3ee39ac6b4aea10a3783e7fb4c813ad2694 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:06:12 +0900 Subject: [PATCH 071/234] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80/?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/repository/FilesRepository.java | 7 ++++ .../file/repository/FilesRepositoryTest.java | 40 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/main/java/com/genius/gitget/file/repository/FilesRepository.java create mode 100644 src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java diff --git a/src/main/java/com/genius/gitget/file/repository/FilesRepository.java b/src/main/java/com/genius/gitget/file/repository/FilesRepository.java new file mode 100644 index 00000000..1b5a38a6 --- /dev/null +++ b/src/main/java/com/genius/gitget/file/repository/FilesRepository.java @@ -0,0 +1,7 @@ +package com.genius.gitget.file.repository; + +import com.genius.gitget.file.domain.Files; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FilesRepository extends JpaRepository { +} diff --git a/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java b/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java new file mode 100644 index 00000000..9d974223 --- /dev/null +++ b/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java @@ -0,0 +1,40 @@ +package com.genius.gitget.file.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.file.domain.FileType; +import com.genius.gitget.file.domain.Files; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class FilesRepositoryTest { + @Autowired + private FilesRepository filesRepository; + + @Test + @DisplayName("File 엔티티 저장한 후, PK를 통해 해당 엔티티를 찾을 수 있다.") + public void fileSaveTest() { + //given + Files files = Files.builder() + .fileType(FileType.TOPIC) + .savedFilename("saved file name") + .originalFilename("original file name") + .fileURI("file uri") + .build(); + + //when + Files savedFiles = filesRepository.save(files); + + //then + assertThat(savedFiles.getFileType()).isEqualTo(files.getFileType()); + assertThat(savedFiles.getSavedFilename()).isEqualTo(files.getSavedFilename()); + assertThat(savedFiles.getOriginalFilename()).isEqualTo(files.getOriginalFilename()); + assertThat(savedFiles.getFileURI()).isEqualTo(files.getFileURI()); + } + +} \ No newline at end of file From 4ca9006aeaf03af28867c5bb60db0b37e3abb368 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:33:56 +0900 Subject: [PATCH 072/234] =?UTF-8?q?feat:=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC,=20=EC=A0=80=EC=9E=A5=ED=95=A0=20File=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20FileUtil=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 전달받은 파일의 유효성 검사, 저장할 File을 생성하는 로직을 FileUtil 클래스에 구현 - FileUtil 관련 서비스 코드 작성 - Files 클래스에 BaseTimeEntity 상속 --- .../com/genius/gitget/file/domain/Files.java | 3 +- .../genius/gitget/file/service/FileUtil.java | 59 +++++++++ .../gitget/util/exception/ErrorCode.java | 6 +- .../gitget/file/service/FileUtilTest.java | 122 ++++++++++++++++++ 4 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/genius/gitget/file/service/FileUtil.java create mode 100644 src/test/java/com/genius/gitget/file/service/FileUtilTest.java diff --git a/src/main/java/com/genius/gitget/file/domain/Files.java b/src/main/java/com/genius/gitget/file/domain/Files.java index 637e6f46..cf8d6c4d 100644 --- a/src/main/java/com/genius/gitget/file/domain/Files.java +++ b/src/main/java/com/genius/gitget/file/domain/Files.java @@ -1,5 +1,6 @@ package com.genius.gitget.file.domain; +import com.genius.gitget.common.domain.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -15,7 +16,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Files { +public class Files extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "attachment_id") diff --git a/src/main/java/com/genius/gitget/file/service/FileUtil.java b/src/main/java/com/genius/gitget/file/service/FileUtil.java new file mode 100644 index 00000000..4f4d13b0 --- /dev/null +++ b/src/main/java/com/genius/gitget/file/service/FileUtil.java @@ -0,0 +1,59 @@ +package com.genius.gitget.file.service; + +import static com.genius.gitget.util.exception.ErrorCode.IMAGE_NOT_EXIST; +import static com.genius.gitget.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; + +import com.genius.gitget.util.exception.BusinessException; +import java.io.File; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +@Component +public class FileUtil { + private final List validExtensions = List.of("jpg", "jpeg", "png", "gif"); + private final String uploadPath; + + public FileUtil(@Value("${file.upload.path}") String uploadPath) { + this.uploadPath = uploadPath; + } + + public File getTargetFile(MultipartFile file) { + //TODO: file의 타입에 따라 저장하는 경로 다르게 하는 로직 추가 + + String originalFilename = file.getOriginalFilename(); + String savedFilename = getSavedFilename(originalFilename); + + return new File(uploadPath + savedFilename); + } + + public void validateFile(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + + if (originalFilename == null || Objects.equals(originalFilename, "")) { + throw new BusinessException(IMAGE_NOT_EXIST); + } + + String extension = extractExtension(originalFilename); + if (validExtensions.stream() + .noneMatch(ex -> ex.equals(extension))) { + throw new BusinessException(NOT_SUPPORTED_EXTENSION); + } + } + + + private String getSavedFilename(String originalFilename) { + String uuid = UUID.randomUUID().toString(); + String extension = extractExtension(originalFilename); + + return uuid + "." + extension; + } + + private String extractExtension(String filename) { + int index = filename.lastIndexOf("."); + return filename.substring(index + 1).toLowerCase(); + } +} diff --git a/src/main/java/com/genius/gitget/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/util/exception/ErrorCode.java index 018f996c..a9a052eb 100644 --- a/src/main/java/com/genius/gitget/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/util/exception/ErrorCode.java @@ -17,7 +17,11 @@ public enum ErrorCode { UNSUPPORTED_JWT(HttpStatus.BAD_REQUEST, "지원하지 않는 JWT 형식입니다."), INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."), - TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."); + TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."), + + IMAGE_NOT_EXIST(HttpStatus.BAD_REQUEST, "Image가 존재하지 않습니다."), + NOT_SUPPORTED_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 확장자입니다."), + NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, ""); private final HttpStatus status; private final String message; diff --git a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java new file mode 100644 index 00000000..739cba07 --- /dev/null +++ b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java @@ -0,0 +1,122 @@ +package com.genius.gitget.file.service; + +import static com.genius.gitget.util.exception.ErrorCode.IMAGE_NOT_EXIST; +import static com.genius.gitget.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.util.exception.BusinessException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.multipart.MultipartFile; + +@SpringBootTest +@ActiveProfiles({"file"}) +class FileUtilTest { + @Autowired + private FileUtil fileUtil; + + @Value("${file.upload.path}") + private String uploadPath; + + @Test + @DisplayName("file을 전달받았을 때, originFilename가 null일 때 예외를 발생해야 한다.") + public void should_throwException_when_originFilenameIsNull() { + //given + MultipartFile multipartFile = getTestMultiPartFile(null); + + //when&then + assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(IMAGE_NOT_EXIST.getMessage()); + } + + @Test + @DisplayName("file을 전달받았을 때, originFilename이 비어있다면 예외를 발생해야 한다.") + public void should_throwException_when_originFilenameIsBlank() { + //given + MultipartFile multipartFile = getTestMultiPartFile(""); + + //when&then + assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(IMAGE_NOT_EXIST.getMessage()); + } + + @Test + @DisplayName("file을 전달받았을 때, 지원하는 확장자가 아니라면 예외를 발생해야 한다.") + public void should_throwException_when_notSupportedExtension() { + //given + MultipartFile multipartFile = getTestMultiPartFile("sky.pdf"); + + //when&then + assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(NOT_SUPPORTED_EXTENSION.getMessage()); + } + + @Test + @DisplayName("유효한 file을 전달받았을 때, 반환받은 File 객체에는 upload path가 포함되어 있어야 한다.") + public void should_returnTargetFileInstance_when_passValidFile() { + //given + MultipartFile multipartFile = getTestMultiPartFile("sky.png"); + + //when + File targetFile = fileUtil.getTargetFile(multipartFile); + + //then + assertThat(targetFile.toString()).contains(uploadPath); + } + + + private MultipartFile getTestMultiPartFile(String originalFilename) { + return new MultipartFile() { + @Override + public String getName() { + return "image"; + } + + @Override + public String getOriginalFilename() { + return originalFilename; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public long getSize() { + return 0; + } + + @Override + public byte[] getBytes() throws IOException { + return new byte[0]; + } + + @Override + public InputStream getInputStream() throws IOException { + return null; + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + + } + }; + } +} \ No newline at end of file From d04d40df208c56d70d7540b06a86060c1b7ae94c Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:34:52 +0900 Subject: [PATCH 073/234] =?UTF-8?q?feat:=20=EC=A0=80=EC=9E=A5=EC=86=8C,=20?= =?UTF-8?q?DB=EC=97=90=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A5=BC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genius/gitget/file/domain/FileType.java | 19 +++++-- .../com/genius/gitget/file/dto/UploadDTO.java | 11 ++++ .../genius/gitget/file/service/FileUtil.java | 17 ++++--- .../gitget/file/service/FilesService.java | 51 +++++++++++++++++++ .../gitget/file/service/FileUtilTest.java | 5 +- .../gitget/file/service/FilesServiceTest.java | 5 ++ 6 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/genius/gitget/file/dto/UploadDTO.java create mode 100644 src/main/java/com/genius/gitget/file/service/FilesService.java create mode 100644 src/test/java/com/genius/gitget/file/service/FilesServiceTest.java diff --git a/src/main/java/com/genius/gitget/file/domain/FileType.java b/src/main/java/com/genius/gitget/file/domain/FileType.java index 321fbb4e..62e226a9 100644 --- a/src/main/java/com/genius/gitget/file/domain/FileType.java +++ b/src/main/java/com/genius/gitget/file/domain/FileType.java @@ -1,15 +1,26 @@ package com.genius.gitget.file.domain; +import static com.genius.gitget.util.exception.ErrorCode.NOT_SUPPORTED_IMAGE_TYPE; + +import com.genius.gitget.util.exception.BusinessException; +import java.util.Arrays; import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Getter public enum FileType { - PROFILE("profile"), - TOPIC("topic"), - INSTANCE("instance"), - PET("pet"); + PROFILE("profile/"), + TOPIC("topic/"), + INSTANCE("instance/"), + PET("pet/"); private final String path; + + public static FileType fineType(String targetType) { + return Arrays.stream(FileType.values()) + .filter(type -> type.path.contains(targetType)) + .findFirst() + .orElseThrow(() -> new BusinessException(NOT_SUPPORTED_IMAGE_TYPE)); + } } diff --git a/src/main/java/com/genius/gitget/file/dto/UploadDTO.java b/src/main/java/com/genius/gitget/file/dto/UploadDTO.java new file mode 100644 index 00000000..a83dbc97 --- /dev/null +++ b/src/main/java/com/genius/gitget/file/dto/UploadDTO.java @@ -0,0 +1,11 @@ +package com.genius.gitget.file.dto; + +import com.genius.gitget.file.domain.FileType; +import lombok.Builder; + +@Builder +public record UploadDTO(FileType fileType, + String originalFilename, + String savedFilename, + String fileURI) { +} diff --git a/src/main/java/com/genius/gitget/file/service/FileUtil.java b/src/main/java/com/genius/gitget/file/service/FileUtil.java index 4f4d13b0..06a6c381 100644 --- a/src/main/java/com/genius/gitget/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/file/service/FileUtil.java @@ -3,8 +3,9 @@ import static com.genius.gitget.util.exception.ErrorCode.IMAGE_NOT_EXIST; import static com.genius.gitget.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; +import com.genius.gitget.file.domain.FileType; +import com.genius.gitget.file.dto.UploadDTO; import com.genius.gitget.util.exception.BusinessException; -import java.io.File; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -21,13 +22,17 @@ public FileUtil(@Value("${file.upload.path}") String uploadPath) { this.uploadPath = uploadPath; } - public File getTargetFile(MultipartFile file) { - //TODO: file의 타입에 따라 저장하는 경로 다르게 하는 로직 추가 - + public UploadDTO getUploadInfo(MultipartFile file, String typeStr) { String originalFilename = file.getOriginalFilename(); String savedFilename = getSavedFilename(originalFilename); + FileType fileType = FileType.fineType(typeStr); - return new File(uploadPath + savedFilename); + return UploadDTO.builder() + .fileType(fileType) + .originalFilename(originalFilename) + .savedFilename(savedFilename) + .fileURI(uploadPath + fileType.getPath() + savedFilename) + .build(); } public void validateFile(MultipartFile file) { @@ -45,7 +50,7 @@ public void validateFile(MultipartFile file) { } - private String getSavedFilename(String originalFilename) { + public String getSavedFilename(String originalFilename) { String uuid = UUID.randomUUID().toString(); String extension = extractExtension(originalFilename); diff --git a/src/main/java/com/genius/gitget/file/service/FilesService.java b/src/main/java/com/genius/gitget/file/service/FilesService.java new file mode 100644 index 00000000..825526c1 --- /dev/null +++ b/src/main/java/com/genius/gitget/file/service/FilesService.java @@ -0,0 +1,51 @@ +package com.genius.gitget.file.service; + +import com.genius.gitget.file.domain.Files; +import com.genius.gitget.file.dto.UploadDTO; +import com.genius.gitget.file.repository.FilesRepository; +import java.io.File; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class FilesService { + private final FileUtil fileUtil; + private final FilesRepository filesRepository; + + + @Transactional + public Long uploadFile(MultipartFile receivedFile, String typeStr) throws IOException { + fileUtil.validateFile(receivedFile); + + UploadDTO uploadDTO = fileUtil.getUploadInfo(receivedFile, typeStr); + + saveFile(receivedFile, uploadDTO.fileURI()); + + Files file = Files.builder() + .originalFilename(uploadDTO.originalFilename()) + .savedFilename(uploadDTO.savedFilename()) + .fileType(uploadDTO.fileType()) + .fileURI(uploadDTO.fileURI()) + .build(); + + Files savedFile = filesRepository.save(file); + return savedFile.getId(); + } + + private void saveFile(MultipartFile receivedFile, String fileURI) throws IOException { + File targetFile = new File(fileURI); + + if (!targetFile.exists()) { + targetFile.mkdirs(); + } + receivedFile.transferTo(targetFile); + } + +} diff --git a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java index 739cba07..ca9d7b29 100644 --- a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.genius.gitget.file.dto.UploadDTO; import com.genius.gitget.util.exception.BusinessException; import java.io.File; import java.io.IOException; @@ -69,10 +70,10 @@ public void should_returnTargetFileInstance_when_passValidFile() { MultipartFile multipartFile = getTestMultiPartFile("sky.png"); //when - File targetFile = fileUtil.getTargetFile(multipartFile); + UploadDTO uploadDTO = fileUtil.getUploadInfo(multipartFile, "profile"); //then - assertThat(targetFile.toString()).contains(uploadPath); + assertThat(uploadDTO.fileURI()).contains(uploadPath); } diff --git a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java new file mode 100644 index 00000000..d1311f9a --- /dev/null +++ b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java @@ -0,0 +1,5 @@ +package com.genius.gitget.file.service; + +class FilesServiceTest { + +} \ No newline at end of file From 8a235d40eb5364ad4992108f6d3225e9ce3b4394 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:35:26 +0900 Subject: [PATCH 074/234] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=EB=A5=BC=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=9E=84=EC=8B=9C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/controller/FilesController.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/java/com/genius/gitget/file/controller/FilesController.java diff --git a/src/main/java/com/genius/gitget/file/controller/FilesController.java b/src/main/java/com/genius/gitget/file/controller/FilesController.java new file mode 100644 index 00000000..f0e9d479 --- /dev/null +++ b/src/main/java/com/genius/gitget/file/controller/FilesController.java @@ -0,0 +1,35 @@ +package com.genius.gitget.file.controller; + +import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.file.service.FilesService; +import com.genius.gitget.util.response.dto.CommonResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/auth/file") +public class FilesController { + private final FilesService filesService; + + @PostMapping + public ResponseEntity addImageTestCode( + @RequestPart(value = "image") MultipartFile image, + @RequestPart(value = "type") String type) throws IOException { + + filesService.uploadFile(image, type); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } +} From 077a466c9fe52d1459f622eeb115d5460e8069d8 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:58:05 +0900 Subject: [PATCH 075/234] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genius/gitget/file/controller/FilesController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/genius/gitget/file/controller/FilesController.java b/src/main/java/com/genius/gitget/file/controller/FilesController.java index f0e9d479..38e10c15 100644 --- a/src/main/java/com/genius/gitget/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/file/controller/FilesController.java @@ -3,7 +3,7 @@ import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.file.service.FilesService; -import com.genius.gitget.util.response.dto.CommonResponse; +import com.genius.gitget.util.response.dto.SingleResponse; import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,19 +17,19 @@ @Slf4j @RestController @RequiredArgsConstructor -@RequestMapping("/api/auth/file") +@RequestMapping("/api/file") public class FilesController { private final FilesService filesService; @PostMapping - public ResponseEntity addImageTestCode( + public ResponseEntity> uploadImage( @RequestPart(value = "image") MultipartFile image, @RequestPart(value = "type") String type) throws IOException { - filesService.uploadFile(image, type); + Long savedFileId = filesService.uploadFile(image, type); return ResponseEntity.ok().body( - new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), savedFileId) ); } } From a1014bc447c985f10cf9014cf07623c1c4896d3c Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:30:13 +0900 Subject: [PATCH 076/234] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC(=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80)=20=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/controller/FilesController.java | 23 +++++++++++++++---- .../genius/gitget/file/dto/FileResponse.java | 4 ++++ .../gitget/file/service/FilesService.java | 15 ++++++++++-- .../gitget/file/service/FilesServiceTest.java | 18 +++++++++++++++ 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/genius/gitget/file/dto/FileResponse.java diff --git a/src/main/java/com/genius/gitget/file/controller/FilesController.java b/src/main/java/com/genius/gitget/file/controller/FilesController.java index 38e10c15..9fd07b7d 100644 --- a/src/main/java/com/genius/gitget/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/file/controller/FilesController.java @@ -1,13 +1,19 @@ package com.genius.gitget.file.controller; -import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; +import static com.genius.gitget.util.exception.SuccessCode.CREATED; +import com.genius.gitget.file.dto.FileResponse; import com.genius.gitget.file.service.FilesService; import com.genius.gitget.util.response.dto.SingleResponse; import java.io.IOException; +import java.net.MalformedURLException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; @@ -22,14 +28,23 @@ public class FilesController { private final FilesService filesService; @PostMapping - public ResponseEntity> uploadImage( + public ResponseEntity> uploadImage( @RequestPart(value = "image") MultipartFile image, @RequestPart(value = "type") String type) throws IOException { - Long savedFileId = filesService.uploadFile(image, type); + FileResponse fileResponse = filesService.uploadFile(image, type); return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), savedFileId) + new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), fileResponse) ); } + + @GetMapping(value = {"/{fileId}"}, + produces = MediaType.IMAGE_JPEG_VALUE) + public ResponseEntity downloadImage(@PathVariable(name = "fileId") Long fileId) + throws MalformedURLException { + + Resource urlResource = filesService.getFile(fileId); + return ResponseEntity.ok(urlResource); + } } diff --git a/src/main/java/com/genius/gitget/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/file/dto/FileResponse.java new file mode 100644 index 00000000..ee802236 --- /dev/null +++ b/src/main/java/com/genius/gitget/file/dto/FileResponse.java @@ -0,0 +1,4 @@ +package com.genius.gitget.file.dto; + +public record FileResponse(Long fileId) { +} diff --git a/src/main/java/com/genius/gitget/file/service/FilesService.java b/src/main/java/com/genius/gitget/file/service/FilesService.java index 825526c1..87d3e6a9 100644 --- a/src/main/java/com/genius/gitget/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/file/service/FilesService.java @@ -1,12 +1,17 @@ package com.genius.gitget.file.service; import com.genius.gitget.file.domain.Files; +import com.genius.gitget.file.dto.FileResponse; import com.genius.gitget.file.dto.UploadDTO; import com.genius.gitget.file.repository.FilesRepository; +import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.util.exception.ErrorCode; import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -21,7 +26,7 @@ public class FilesService { @Transactional - public Long uploadFile(MultipartFile receivedFile, String typeStr) throws IOException { + public FileResponse uploadFile(MultipartFile receivedFile, String typeStr) throws IOException { fileUtil.validateFile(receivedFile); UploadDTO uploadDTO = fileUtil.getUploadInfo(receivedFile, typeStr); @@ -36,7 +41,7 @@ public Long uploadFile(MultipartFile receivedFile, String typeStr) throws IOExce .build(); Files savedFile = filesRepository.save(file); - return savedFile.getId(); + return new FileResponse(savedFile.getId()); } private void saveFile(MultipartFile receivedFile, String fileURI) throws IOException { @@ -48,4 +53,10 @@ private void saveFile(MultipartFile receivedFile, String fileURI) throws IOExcep receivedFile.transferTo(targetFile); } + public UrlResource getFile(Long fileId) throws MalformedURLException { + Files files = filesRepository.findById(fileId) + .orElseThrow(() -> new BusinessException(ErrorCode.IMAGE_NOT_EXIST)); + + return new UrlResource("file:" + files.getFileURI()); + } } diff --git a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java index d1311f9a..b60de121 100644 --- a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java +++ b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java @@ -1,5 +1,23 @@ package com.genius.gitget.file.service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional class FilesServiceTest { + @Autowired + private FilesService filesService; + + @Test + @DisplayName("") + public void (){ + //given + + //when + + //then + } } \ No newline at end of file From 061171e13bf48328f6f5bc94b3a9dc5899cab431 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Wed, 24 Jan 2024 00:37:56 +0900 Subject: [PATCH 077/234] =?UTF-8?q?[FEAT]=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B0=9C=EB=B0=9C=20(#38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] Admin 페이지 Instance , Topic API 개발 (#33) * feat: Topic Controller 개발 * feat: Topic Domain 비즈니스 로직 개발 * feat: Topic Service 개발 * test: topic rest api 테스트 코드 작성 - jwt 관련 오류로 인해 테스트 불가 -> 해결 방안 모색 중 * feat: 전체 Topic 조회 페이징과 정렬 * feat: Instance API 개발 - DTO 활용 - BusinessException 활용 * refactor: topic controller & service DTO 도입 * refactor: topic, instance paging 리팩토링 * refactor: 리펙토링 - participant_count entity 제거 -> participantInfo list size로 해결 가능 - 테스트 코드 작성 - 코드 리펙토링 등 * refactor: admin page refactoring - 연관관계 편의 메서드 수정 - DTO 수정 - API Response 재정의 - util의 ErrorCode Enum 추가 - PagingResponse 추가 * [REFACTOR] 어드민 페이지 리펙토링 (#36) * feat: Topic Controller 개발 * feat: Topic Domain 비즈니스 로직 개발 * feat: Topic Service 개발 * test: topic rest api 테스트 코드 작성 - jwt 관련 오류로 인해 테스트 불가 -> 해결 방안 모색 중 * feat: 전체 Topic 조회 페이징과 정렬 * feat: Instance API 개발 - DTO 활용 - BusinessException 활용 * refactor: topic controller & service DTO 도입 * refactor: topic, instance paging 리팩토링 * refactor: 리펙토링 - participant_count entity 제거 -> participantInfo list size로 해결 가능 - 테스트 코드 작성 - 코드 리펙토링 등 * refactor: admin page refactoring - 연관관계 편의 메서드 수정 - DTO 수정 - API Response 재정의 - util의 ErrorCode Enum 추가 - PagingResponse 추가 * [FEAT] 어드민 페이지 postman API 테스트 (#37) * feat: Topic Controller 개발 * feat: Topic Domain 비즈니스 로직 개발 * feat: Topic Service 개발 * test: topic rest api 테스트 코드 작성 - jwt 관련 오류로 인해 테스트 불가 -> 해결 방안 모색 중 * feat: 전체 Topic 조회 페이징과 정렬 * feat: Instance API 개발 - DTO 활용 - BusinessException 활용 * refactor: topic controller & service DTO 도입 * refactor: topic, instance paging 리팩토링 * refactor: 리펙토링 - participant_count entity 제거 -> participantInfo list size로 해결 가능 - 테스트 코드 작성 - 코드 리펙토링 등 * refactor: admin page refactoring - 연관관계 편의 메서드 수정 - DTO 수정 - API Response 재정의 - util의 ErrorCode Enum 추가 - PagingResponse 추가 * test: postman api test - test 수행 중 발견한 수정사항 해결 --------- Co-authored-by: HEY <50323157+SSung023@users.noreply.github.com> --- .../com/genius/gitget/hits/domain/Hits.java | 12 +- .../controller/InstanceController.java | 72 +++++++ .../gitget/instance/domain/Instance.java | 25 ++- .../instance/dto/InstanceCreateRequest.java | 16 ++ .../instance/dto/InstanceDetailResponse.java | 18 ++ .../instance/dto/InstancePagingResponse.java | 13 ++ .../instance/dto/InstanceUpdateRequest.java | 12 ++ .../repository/InstanceRepository.java | 7 + .../instance/service/InstanceService.java | 89 +++++++++ .../domain/ParticipantInfo.java | 12 +- .../security/config/SecurityConfig.java | 4 +- .../topic/controller/TopicController.java | 73 ++++++++ .../com/genius/gitget/topic/domain/Topic.java | 26 ++- .../gitget/topic/dto/TopicCreateRequest.java | 11 ++ .../gitget/topic/dto/TopicDetailResponse.java | 12 ++ .../gitget/topic/dto/TopicPagingResponse.java | 8 + .../gitget/topic/dto/TopicUpdateRequest.java | 11 ++ .../topic/repository/TopicRepository.java | 6 + .../gitget/topic/service/TopicService.java | 71 +++++++ .../gitget/util/exception/ErrorCode.java | 6 + .../util/response/dto/PagingResponse.java | 6 + .../genius/gitget/GitgetApplicationTests.java | 1 + .../java/com/genius/gitget/hits/HitsTest.java | 28 ++- .../instance/InstanceRepositoryTest.java | 32 ---- .../java/com/genius/gitget/mock/MockTest.java | 21 +++ .../ParticipantInfoRepositoryTest.java | 99 ---------- .../gitget/topic/TopicControllerTest.java | 176 ++++++++++++++++++ .../gitget/topic/TopicRepositoryTest.java | 27 --- 28 files changed, 698 insertions(+), 196 deletions(-) create mode 100644 src/main/java/com/genius/gitget/instance/controller/InstanceController.java create mode 100644 src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java create mode 100644 src/main/java/com/genius/gitget/instance/dto/InstanceDetailResponse.java create mode 100644 src/main/java/com/genius/gitget/instance/dto/InstancePagingResponse.java create mode 100644 src/main/java/com/genius/gitget/instance/dto/InstanceUpdateRequest.java create mode 100644 src/main/java/com/genius/gitget/instance/service/InstanceService.java create mode 100644 src/main/java/com/genius/gitget/topic/controller/TopicController.java create mode 100644 src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java create mode 100644 src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java create mode 100644 src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java create mode 100644 src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java create mode 100644 src/main/java/com/genius/gitget/topic/service/TopicService.java delete mode 100644 src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/mock/MockTest.java delete mode 100644 src/test/java/com/genius/gitget/participantInfo/ParticipantInfoRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/topic/TopicControllerTest.java delete mode 100644 src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java diff --git a/src/main/java/com/genius/gitget/hits/domain/Hits.java b/src/main/java/com/genius/gitget/hits/domain/Hits.java index 7844c531..a2b30c1a 100644 --- a/src/main/java/com/genius/gitget/hits/domain/Hits.java +++ b/src/main/java/com/genius/gitget/hits/domain/Hits.java @@ -44,27 +44,19 @@ public Hits(User user, Instance instance) { public void setUserAndInstance(User user, Instance instance) { addHitsForUser(user); addHitsForInstance(instance); - setUser(user); - setInstance(instance); - } - - private void setUser(User user) { - this.user = user; } private void addHitsForUser(User user) { if (!(user.getHitsList().contains(this))) { user.getHitsList().add(this); } - } - - private void setInstance(Instance instance) { - this.instance = instance; + this.user = user; } private void addHitsForInstance(Instance instance) { if (!(instance.getHitsList().contains(this))) { instance.getHitsList().add(this); } + this.instance = instance; } } diff --git a/src/main/java/com/genius/gitget/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/instance/controller/InstanceController.java new file mode 100644 index 00000000..e9cb0df0 --- /dev/null +++ b/src/main/java/com/genius/gitget/instance/controller/InstanceController.java @@ -0,0 +1,72 @@ +package com.genius.gitget.instance.controller; + +import com.genius.gitget.instance.dto.InstanceCreateRequest; +import com.genius.gitget.instance.dto.InstanceDetailResponse; +import com.genius.gitget.instance.dto.InstancePagingResponse; +import com.genius.gitget.instance.dto.InstanceUpdateRequest; +import com.genius.gitget.instance.service.InstanceService; +import com.genius.gitget.util.exception.SuccessCode; +import com.genius.gitget.util.response.dto.CommonResponse; +import com.genius.gitget.util.response.dto.PagingResponse; +import com.genius.gitget.util.response.dto.SingleResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/admin/instance") +@RequiredArgsConstructor +public class InstanceController { + private final InstanceService instanceService; + + // 인스턴스 리스트 조회 + @GetMapping + public ResponseEntity> getAllInstances(@PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id")Pageable pageable) { + Page instances = instanceService.getAllInstances(pageable); + + return ResponseEntity.ok().body( + new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), instances) + ); + } + + // 인스턴스 단건 조회 + @GetMapping("/{id}") + public ResponseEntity> getInstanceById(@PathVariable Long id) { + InstanceDetailResponse instanceDetails = instanceService.getInstanceById(id); + return ResponseEntity.ok().body( + new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), instanceDetails) + ); + } + + // 인스턴스 생성 + @PostMapping + public ResponseEntity createInstance(@RequestBody @Valid InstanceCreateRequest instanceCreateRequest) { + instanceService.createInstance(instanceCreateRequest); + return ResponseEntity.ok().body( + new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) + ); + } + + // 인스턴스 수정 + @PatchMapping("/{id}") + public ResponseEntity updateInstance(@PathVariable Long id, @RequestBody @Valid InstanceUpdateRequest instanceUpdateRequest) { + instanceService.updateInstance(id, instanceUpdateRequest); + return ResponseEntity.ok().body( + new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) + ); + } + + // 인스턴스 삭제 + @DeleteMapping("/{id}") + public ResponseEntity deleteInstance(@PathVariable Long id) { + instanceService.deleteInstance(id); + return ResponseEntity.ok().body( + new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/instance/domain/Instance.java b/src/main/java/com/genius/gitget/instance/domain/Instance.java index 9eb85ab6..8699172c 100644 --- a/src/main/java/com/genius/gitget/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/instance/domain/Instance.java @@ -6,6 +6,7 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; @@ -40,15 +41,13 @@ public class Instance { private String description; - private int participants; - private String tags; - private int point_per_person; + private int pointPerPerson; @NotNull @Enumerated(EnumType.STRING) - @ColumnDefault("'PRE_ACTIVITY'") + // @Column(columnDefinition = "varchar(255) default 'PRE_ACTIVITY'") private Progress progress; @Column(name = "started_at") @@ -57,18 +56,28 @@ public class Instance { @Column(name = "completed_at") private LocalDateTime completedDate; - - public Instance(String title, String description, int participants, String tags, int point_per_person, Progress progress, LocalDateTime startedDate, LocalDateTime completedDate) { + @Builder + public Instance(String title, String description, String tags, int pointPerPerson, Progress progress, LocalDateTime startedDate, LocalDateTime completedDate) { this.title = title; this.description = description; - this.participants = participants; this.tags = tags; - this.point_per_person = point_per_person; + this.pointPerPerson = pointPerPerson; this.progress = progress; this.startedDate = startedDate; this.completedDate = completedDate; } + public void updateInstance(String description, int pointPerPerson, LocalDateTime startedDate, LocalDateTime completedDate) { + this.description = description; + this.pointPerPerson = pointPerPerson; + this.startedDate = startedDate; + this.completedDate = completedDate; + } + + public int getJoinPeopleCount() { + return participantInfoList.size(); + } + //== 연관관계 편의 메서드 ==// public void setTopic(Topic topic) { this.topic = topic; diff --git a/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java b/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java new file mode 100644 index 00000000..72e610cf --- /dev/null +++ b/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java @@ -0,0 +1,16 @@ +package com.genius.gitget.instance.dto; + +import java.time.LocalDateTime; + +public record InstanceCreateRequest( + Long topicId, + String title, + String tags, + String description, + //이미지 + //유의사항 + int pointPerPerson, + LocalDateTime startedAt, + LocalDateTime completedAt +) { +} diff --git a/src/main/java/com/genius/gitget/instance/dto/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/instance/dto/InstanceDetailResponse.java new file mode 100644 index 00000000..b38ac6ae --- /dev/null +++ b/src/main/java/com/genius/gitget/instance/dto/InstanceDetailResponse.java @@ -0,0 +1,18 @@ +package com.genius.gitget.instance.dto; + +import java.time.LocalDateTime; + +public record InstanceDetailResponse( + Long topicId, + Long instanceId, + String title, + String description, + int pointPerPerson, + String tags, + // 이미지 + // 유의사항 + LocalDateTime startedAt, + LocalDateTime completedAt +) { + +} diff --git a/src/main/java/com/genius/gitget/instance/dto/InstancePagingResponse.java b/src/main/java/com/genius/gitget/instance/dto/InstancePagingResponse.java new file mode 100644 index 00000000..36c21ac8 --- /dev/null +++ b/src/main/java/com/genius/gitget/instance/dto/InstancePagingResponse.java @@ -0,0 +1,13 @@ +package com.genius.gitget.instance.dto; + +import java.time.LocalDateTime; + +public record InstancePagingResponse( + Long topicId, + Long instanceId, + String title, + // 이미지 + LocalDateTime startedAt, + LocalDateTime completedAt +) { +} diff --git a/src/main/java/com/genius/gitget/instance/dto/InstanceUpdateRequest.java b/src/main/java/com/genius/gitget/instance/dto/InstanceUpdateRequest.java new file mode 100644 index 00000000..1f80083c --- /dev/null +++ b/src/main/java/com/genius/gitget/instance/dto/InstanceUpdateRequest.java @@ -0,0 +1,12 @@ +package com.genius.gitget.instance.dto; + +import java.time.LocalDateTime; + +public record InstanceUpdateRequest ( + Long topicId, + String description, + int pointPerPerson, + LocalDateTime startedAt, + LocalDateTime completedAt +){ +} diff --git a/src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java index e0f44ff6..db8a0051 100644 --- a/src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java @@ -1,7 +1,14 @@ package com.genius.gitget.instance.repository; import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.topic.domain.Topic; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface InstanceRepository extends JpaRepository { + + @Query("select i from Instance i ORDER BY i.id DESC ") + Page findAllById(Pageable pageable); } diff --git a/src/main/java/com/genius/gitget/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/instance/service/InstanceService.java new file mode 100644 index 00000000..9767032b --- /dev/null +++ b/src/main/java/com/genius/gitget/instance/service/InstanceService.java @@ -0,0 +1,89 @@ +package com.genius.gitget.instance.service; + +import com.genius.gitget.instance.domain.Progress; +import com.genius.gitget.instance.dto.InstanceCreateRequest; +import com.genius.gitget.instance.dto.InstanceDetailResponse; +import com.genius.gitget.instance.dto.InstancePagingResponse; +import com.genius.gitget.instance.dto.InstanceUpdateRequest; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.instance.repository.InstanceRepository; +import com.genius.gitget.topic.repository.TopicRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static com.genius.gitget.util.exception.ErrorCode.INSTANCE_NOT_FOUND; +import static com.genius.gitget.util.exception.ErrorCode.TOPIC_NOT_FOUND; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class InstanceService { + + private final InstanceRepository instanceRepository; + private final TopicRepository topicRepository; + + // 인스턴스 생성 + @Transactional + public void createInstance(InstanceCreateRequest instanceCreateRequest) { + Topic topic = topicRepository.findById(instanceCreateRequest.topicId()) + .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); + + Instance instance = Instance.builder() + .description(instanceCreateRequest.description()) + .pointPerPerson(instanceCreateRequest.pointPerPerson()) + .startedDate(instanceCreateRequest.startedAt()) + .completedDate(instanceCreateRequest.completedAt()) + .progress(Progress.PRE_ACTIVITY) + .build(); + + instance.setTopic(topic); + + instanceRepository.save(instance); + } + + + public Page getAllInstances(Pageable pageable) { + Page instances = instanceRepository.findAllById(pageable); + return instances.map(instance -> new InstancePagingResponse(instance.getTopic().getId(), instance.getId(), instance.getTitle(), instance.getStartedDate(), instance.getCompletedDate())); + + } + + // 인스턴스 단건 조회 + public InstanceDetailResponse getInstanceById(Long id) { + Instance instanceDetails = instanceRepository.findById(id).orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); + return new InstanceDetailResponse( + instanceDetails.getTopic().getId(), + instanceDetails.getId(), + instanceDetails.getTitle(), + instanceDetails.getDescription(), + instanceDetails.getPointPerPerson(), + instanceDetails.getTags(), + instanceDetails.getStartedDate(), + instanceDetails.getCompletedDate() + ); + } + + @Transactional + public void deleteInstance(Long id) { + Instance instance = instanceRepository.findById(id) + .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); + instanceRepository.delete(instance); + } + + // 인스턴스 수정 + @Transactional + public void updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest) { + Instance existingInstance = instanceRepository.findById(id) + .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); + + existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.pointPerPerson(), + instanceUpdateRequest.startedAt(), instanceUpdateRequest.completedAt()); + + instanceRepository.save(existingInstance); + } +} diff --git a/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java b/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java index cbc85dc7..cf81bc44 100644 --- a/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java +++ b/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java @@ -50,27 +50,19 @@ public ParticipantInfo(JoinStatus joinStatus, JoinResult joinResult) { public void setUserAndInstance(User user, Instance instance) { addParticipantInfoForUser(user); addParticipantInfoForInstance(instance); - setUser(user); - setInstance(instance); - } - - private void setUser(User user) { - this.user = user; } private void addParticipantInfoForUser(User user) { if(!(user.getParticipantInfoList().contains(this))) { user.getParticipantInfoList().add(this); } - } - - private void setInstance(Instance instance) { - this.instance = instance; + this.user = user; } private void addParticipantInfoForInstance(Instance instance) { if(!(instance.getParticipantInfoList().contains(this))) { instance.getParticipantInfoList().add(this); } + this.instance = instance; } } diff --git a/src/main/java/com/genius/gitget/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/security/config/SecurityConfig.java index 51688363..d0d42085 100644 --- a/src/main/java/com/genius/gitget/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/security/config/SecurityConfig.java @@ -54,8 +54,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // JWT 검증 필터 추가 - .addFilterBefore(new JwtAuthenticationFilter(jwtService, userService), - UsernamePasswordAuthenticationFilter.class) +// .addFilterBefore(new JwtAuthenticationFilter(jwtService, userService), +// UsernamePasswordAuthenticationFilter.class) // OAuth 로그인 설정 .oauth2Login(customConfigurer -> customConfigurer diff --git a/src/main/java/com/genius/gitget/topic/controller/TopicController.java b/src/main/java/com/genius/gitget/topic/controller/TopicController.java new file mode 100644 index 00000000..a93ddfe7 --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/controller/TopicController.java @@ -0,0 +1,73 @@ +package com.genius.gitget.topic.controller; + +import com.genius.gitget.topic.dto.TopicCreateRequest; +import com.genius.gitget.topic.dto.TopicDetailResponse; +import com.genius.gitget.topic.dto.TopicPagingResponse; +import com.genius.gitget.topic.dto.TopicUpdateRequest; +import com.genius.gitget.topic.service.TopicService; +import com.genius.gitget.util.exception.SuccessCode; +import com.genius.gitget.util.response.dto.CommonResponse; +import com.genius.gitget.util.response.dto.PagingResponse; +import com.genius.gitget.util.response.dto.SingleResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin/topic") +public class TopicController { + + private final TopicService topicService; + + // 토픽 리스트 요청 + @GetMapping + public ResponseEntity> getAllTopics(@PageableDefault(size = 5, direction = Sort.Direction.ASC) Pageable pageable) { + Page allTopics = topicService.getAllTopics(pageable); + + return ResponseEntity.ok().body( + new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), allTopics) + ); + } + + // 토픽 상세 정보 요청 + @GetMapping("/{id}") + public ResponseEntity> getTopicById(@PathVariable Long id) { + TopicDetailResponse topicDetail = topicService.getTopicById(id); + return ResponseEntity.ok().body( + new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), topicDetail) + ); + } + + // 토픽 생성 요청 + @PostMapping + public ResponseEntity createTopic(@RequestBody @Valid TopicCreateRequest topicCreateRequest) { + topicService.createTopic(topicCreateRequest); + return ResponseEntity.ok().body( + new CommonResponse(SuccessCode.CREATED.getStatus(), SuccessCode.CREATED.getMessage()) + ); + } + + // 토픽 수정 요청 + @PatchMapping("/{id}") + public ResponseEntity updateTopic(@PathVariable Long id, @RequestBody @Valid TopicUpdateRequest topicUpdateRequest) { + topicService.updateTopic(id, topicUpdateRequest); + return ResponseEntity.ok().body( + new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) + ); + } + + // 토픽 삭제 요청 + @DeleteMapping("/{id}") + public ResponseEntity deleteTopic(@PathVariable Long id) { + topicService.deleteTopic(id); + return ResponseEntity.ok().body( + new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/topic/domain/Topic.java b/src/main/java/com/genius/gitget/topic/domain/Topic.java index 25c5c65a..85104fdc 100644 --- a/src/main/java/com/genius/gitget/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/topic/domain/Topic.java @@ -3,15 +3,18 @@ import com.genius.gitget.instance.domain.Instance; import jakarta.persistence.*; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.ArrayList; import java.util.List; @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "topic") + public class Topic { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -19,7 +22,7 @@ public class Topic { private Long id; @OneToMany(mappedBy = "topic") - private List instanceList; + private List instanceList = new ArrayList<>(); private String title; @@ -27,15 +30,27 @@ public class Topic { private String tags; - private int point_per_person; + private int pointPerPerson; - public Topic(String title, String description, String tags, int point_per_person) { + @Builder + public Topic(String title, String description, String tags, int pointPerPerson) { this.title = title; this.description = description; this.tags = tags; - this.point_per_person = point_per_person; + this.pointPerPerson = pointPerPerson; } + //== 비즈니스 로직 ==// + public void updateExistInstance(String description) { + this.description = description; + } + + public void createInstance(String title, String description, String tags, int pointPerPerson) { + this.title = title; + this.description = description; + this.tags = tags; + this.pointPerPerson = pointPerPerson; + } //== 연관관계 편의 메서드 ==// public void setInstance(Instance instance) { @@ -44,5 +59,4 @@ public void setInstance(Instance instance) { instance.setTopic(this); } } - -} +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java b/src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java new file mode 100644 index 00000000..eb064f25 --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java @@ -0,0 +1,11 @@ +package com.genius.gitget.topic.dto; + +public record TopicCreateRequest( + String title, + String tags, + String description, + int pointPerPerson + // 이미지 + // 유의사항 +) { +} diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java b/src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java new file mode 100644 index 00000000..b8b2e61d --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java @@ -0,0 +1,12 @@ +package com.genius.gitget.topic.dto; + +public record TopicDetailResponse( + Long topicId, + String title, + String tags, + String description, + // 이미지 + // 유의사항 + int pointPerPerson +) { +} diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java b/src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java new file mode 100644 index 00000000..7e50ca80 --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java @@ -0,0 +1,8 @@ +package com.genius.gitget.topic.dto; + + +public record TopicPagingResponse( + Long topicId, + String title +) { +} diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java b/src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java new file mode 100644 index 00000000..fab38395 --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java @@ -0,0 +1,11 @@ +package com.genius.gitget.topic.dto; + +public record TopicUpdateRequest( + String title, + String tags, + String description, + int pointPerPerson + // 이미지 + // 유의사항 +) { +} diff --git a/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java b/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java index 1a429d02..b4bcbc4e 100644 --- a/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java +++ b/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java @@ -1,7 +1,13 @@ package com.genius.gitget.topic.repository; +import com.genius.gitget.instance.domain.Instance; import com.genius.gitget.topic.domain.Topic; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface TopicRepository extends JpaRepository { + @Query("select t from Topic t ORDER BY t.id DESC ") + Page findAllById(Pageable pageable); } diff --git a/src/main/java/com/genius/gitget/topic/service/TopicService.java b/src/main/java/com/genius/gitget/topic/service/TopicService.java new file mode 100644 index 00000000..f3ebafa9 --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/service/TopicService.java @@ -0,0 +1,71 @@ +package com.genius.gitget.topic.service; + +import com.genius.gitget.topic.dto.TopicDetailResponse; +import com.genius.gitget.topic.dto.TopicUpdateRequest; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.dto.TopicCreateRequest; +import com.genius.gitget.topic.dto.TopicPagingResponse; +import com.genius.gitget.topic.repository.TopicRepository; +import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.util.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class TopicService { + private final TopicRepository topicRepository; + + // 토픽 리스트 요청 + public Page getAllTopics(Pageable pageable) { + Page topics = topicRepository.findAllById(pageable); + return topics.map(topic -> new TopicPagingResponse(topic.getId(), topic.getTitle())); + } + + // 토픽 상세정보 요청 + public TopicDetailResponse getTopicById(Long id) { + Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + return new TopicDetailResponse(topic.getId(), topic.getTitle(), topic.getTags(), topic.getDescription(), topic.getPointPerPerson()); + } + + // 토픽 생성 요청 + @Transactional + public void createTopic(TopicCreateRequest topicCreateRequest) { + Topic topic = Topic.builder() + Topic.builder() + .title(topicCreateRequest.title()) + .description(topicCreateRequest.description()) + .tags(topicCreateRequest.tags()) + .pointPerPerson(topicCreateRequest.pointPerPerson()) + // 이미지 + // 유의사항 + .build(); + topicRepository.save(topic); + + } + + @Transactional + public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest) { + Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + + // 서버에서 한번 더 검사 + boolean hasInstance = !topic.getInstanceList().isEmpty(); + if (hasInstance) { + topic.updateExistInstance(topicUpdateRequest.description()); + } else { + topic.createInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), topicUpdateRequest.tags(), topicUpdateRequest.pointPerPerson()); + } + topicRepository.save(topic); + } + + // 토픽 삭제 요청 + @Transactional + public void deleteTopic(Long id) { + Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + topicRepository.delete(topic); + } +} diff --git a/src/main/java/com/genius/gitget/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/util/exception/ErrorCode.java index 018f996c..3794fbae 100644 --- a/src/main/java/com/genius/gitget/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/util/exception/ErrorCode.java @@ -8,6 +8,10 @@ @RequiredArgsConstructor public enum ErrorCode { + TOPIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 토픽을 찾을 수 없습니다."), + + INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."), DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "이미 존재하는 닉네임입니다"), @@ -19,6 +23,8 @@ public enum ErrorCode { TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."); + + private final HttpStatus status; private final String message; } diff --git a/src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java b/src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java index 87b59908..49e7754a 100644 --- a/src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java +++ b/src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; @Getter @RequiredArgsConstructor @@ -12,4 +13,9 @@ public class PagingResponse extends CommonResponse { public PagingResponse(Page data) { this.data = data; } + + public PagingResponse(HttpStatus status, String message, Page data) { + super(status, message); + this.data = data; + } } diff --git a/src/test/java/com/genius/gitget/GitgetApplicationTests.java b/src/test/java/com/genius/gitget/GitgetApplicationTests.java index 7e1b17f5..e578a2f4 100644 --- a/src/test/java/com/genius/gitget/GitgetApplicationTests.java +++ b/src/test/java/com/genius/gitget/GitgetApplicationTests.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; + @SpringBootTest class GitgetApplicationTests { diff --git a/src/test/java/com/genius/gitget/hits/HitsTest.java b/src/test/java/com/genius/gitget/hits/HitsTest.java index d74c6156..de248138 100644 --- a/src/test/java/com/genius/gitget/hits/HitsTest.java +++ b/src/test/java/com/genius/gitget/hits/HitsTest.java @@ -6,6 +6,8 @@ import com.genius.gitget.instance.domain.Progress; import com.genius.gitget.instance.repository.InstanceRepository; import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.user.domain.User; import com.genius.gitget.user.repository.UserRepository; import org.junit.jupiter.api.Assertions; @@ -35,9 +37,12 @@ public class HitsTest { InstanceRepository instanceRepository; @Autowired HitsRepository hitsRepository; + @Autowired + TopicRepository topicRepository; private User user1, user2; private Instance instance1; + private Topic topic1; @BeforeEach public void setup() { @@ -56,11 +61,30 @@ public void setup() { .interest("영화") .role(USER) .build(); - instance1 = new Instance("1일 1커밋", "챌린지 세부사항입니다." ,10, "BE, CS", - 100, Progress.ACTIVITY, LocalDateTime.now(), LocalDateTime.now().plusDays(3)); + + instance1 = Instance.builder() + .title("1일 1커밋") + .description("챌린지 세부사항입니다.") + .point_per_person(10) + .tags("BE, CS") + .progress(Progress.ACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + topic1 = Topic.builder() + .title("1일 1커밋") + .description("간단한 설명란") + .point_per_person(300) + .tags("BE, CS") + .build(); userRepository.save(user1); userRepository.save(user2); + + topicRepository.save(topic1); + topic1.setInstance(instance1); + instance1.setTopic(topic1); instanceRepository.save(instance1); } diff --git a/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java deleted file mode 100644 index bd095228..00000000 --- a/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.genius.gitget.instance; - -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.instance.domain.Progress; -import com.genius.gitget.instance.repository.InstanceRepository; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; - -@SpringBootTest -@Transactional -@Rollback(value = false) -public class InstanceRepositoryTest { - - @Autowired - private InstanceRepository instanceRepository; - - @Test - public void 인스턴스_저장() { - Instance instance = new Instance("1일 1커밋", "챌린지 세부사항입니다." ,10, "BE, CS", - 100, Progress.ACTIVITY, LocalDateTime.now(), LocalDateTime.now().plusDays(3)); - - Instance savedInstance = instanceRepository.save(instance); - - Assertions.assertThat(instance.getId()).isEqualTo(savedInstance.getId()); - } -} diff --git a/src/test/java/com/genius/gitget/mock/MockTest.java b/src/test/java/com/genius/gitget/mock/MockTest.java new file mode 100644 index 00000000..d254bbf9 --- /dev/null +++ b/src/test/java/com/genius/gitget/mock/MockTest.java @@ -0,0 +1,21 @@ +package com.genius.gitget.mock; + + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.mockito.Mockito.*; + +@SpringBootTest +public class MockTest { + + @Test + public void Mock_Test() { + List mockList = mock(List.class); + when(mockList.get(anyInt())).thenReturn("first"); + System.out.println(mockList.get(999)); + verify(mockList).get(anyInt()); + } +} diff --git a/src/test/java/com/genius/gitget/participantInfo/ParticipantInfoRepositoryTest.java b/src/test/java/com/genius/gitget/participantInfo/ParticipantInfoRepositoryTest.java deleted file mode 100644 index 0c5c6c0a..00000000 --- a/src/test/java/com/genius/gitget/participantInfo/ParticipantInfoRepositoryTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.genius.gitget.participantInfo; - -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.instance.domain.Progress; -import com.genius.gitget.instance.repository.InstanceRepository; -import com.genius.gitget.participantinfo.domain.JoinResult; -import com.genius.gitget.participantinfo.domain.JoinStatus; -import com.genius.gitget.participantinfo.domain.ParticipantInfo; -import com.genius.gitget.participantinfo.repository.ParticipantInfoRepository; - -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.repository.UserRepository; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; - -import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; -import static com.genius.gitget.security.constants.ProviderInfo.NAVER; -import static com.genius.gitget.user.domain.Role.ADMIN; -import static com.genius.gitget.user.domain.Role.USER; -import static org.junit.jupiter.api.Assertions.assertEquals; - -@SpringBootTest -@Transactional -@Rollback(value = false) -public class ParticipantInfoRepositoryTest { - - @Autowired - private ParticipantInfoRepository participantInfoRepository; - @Autowired - private UserRepository userRepository; - @Autowired - private InstanceRepository instanceRepository; - - - @Test - public void 참여자_정보_저장() { - ParticipantInfo participantInfo = new ParticipantInfo(JoinStatus.YES, JoinResult.SUCCESS); - - ParticipantInfo savedInfo = participantInfoRepository.save(participantInfo); - - Assertions.assertThat(participantInfo.getId()).isEqualTo(savedInfo.getId()); - } - - @Test - public void 회원과_챌린지_인스턴스_양방향_연관관계_편의_메서드_테스트() { - // given - User user1 = userA(); - User user2 = userB(); - userRepository.save(user1); - userRepository.save(user2); - - // when - - Instance instance1 = instanceA(); - - ParticipantInfo participantInfo1 = new ParticipantInfo(JoinStatus.YES, JoinResult.FAIL); - participantInfo1.setUserAndInstance(user1, instance1); - participantInfoRepository.save(participantInfo1); - - ParticipantInfo participantInfo2 = new ParticipantInfo(JoinStatus.YES, JoinResult.SUCCESS); - participantInfo2.setUserAndInstance(user2, instance1); - participantInfoRepository.save(participantInfo2); - - instanceRepository.save(instance1); - - // then - assertEquals(1, userRepository.findByIdentifier("neo5188@gmail.com").get().getParticipantInfoList().size()); - } - - private User userA() { - return User.builder().identifier("neo5188@gmail.com") - .providerInfo(NAVER) - .nickname("kimdozzi") - .information("백엔드") - .interest("운동") - .role(ADMIN) - .build(); - } - - private User userB() { - return User.builder().identifier("ssang23@naver.com") - .providerInfo(GOOGLE) - .nickname("SEONG") - .information("프론트엔드") - .interest("영화") - .role(USER) - .build(); - } - private Instance instanceA() { - return new Instance("1일 1커밋", "챌린지 세부사항입니다." ,10, "BE, CS", - 100, Progress.ACTIVITY, LocalDateTime.now(), LocalDateTime.now().plusDays(3)); - } -} diff --git a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java new file mode 100644 index 00000000..905113c4 --- /dev/null +++ b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java @@ -0,0 +1,176 @@ +package com.genius.gitget.topic; + +import com.genius.gitget.hits.repository.HitsRepository; +import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.instance.domain.Progress; +import com.genius.gitget.instance.repository.InstanceRepository; +import com.genius.gitget.participantinfo.domain.JoinResult; +import com.genius.gitget.participantinfo.domain.JoinStatus; +import com.genius.gitget.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.participantinfo.repository.ParticipantInfoRepository; +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.security.service.CustomOAuth2UserService; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import com.genius.gitget.topic.service.TopicService; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.*; +import org.springframework.http.MediaType; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.user.domain.Role.ADMIN; +import static com.genius.gitget.user.domain.Role.USER; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@Rollback(value = false) +public class TopicControllerTest { + + @BeforeEach + public void setup() { + user1 = User.builder().identifier("neo5188@gmail.com") + .providerInfo(ProviderInfo.NAVER) + .nickname("kimdozzi") + .information("백엔드") + .interest("운동") + .role(ADMIN) + .build(); + + user2 = User.builder().identifier("ssang23@naver.com") + .providerInfo(GOOGLE) + .nickname("SEONG") + .information("프론트엔드") + .interest("영화") + .role(USER) + .build(); + + instance1 = Instance.builder() + .title("1일 1커밋") + .description("챌린지 세부사항입니다.") + .point_per_person(10) + .tags("BE, CS") + .progress(Progress.ACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + topic1 = Topic.builder() + .title("1일 1커밋") + .description("간단한 설명란") + .point_per_person(300) + .tags("BE, CS") + .build(); + + topic2 = Topic.builder() + .title("1일 2커밋") + .description("간단한 설명란") + .point_per_person(300) + .tags("BE, CS") + .build(); + + + participantInfo1 = ParticipantInfo.builder() + .joinResult(JoinResult.PROCESSING) + .joinStatus(JoinStatus.YES) + .build(); + + participantInfo2 = ParticipantInfo.builder() + .joinResult(JoinResult.SUCCESS) + .joinStatus(JoinStatus.YES) + .build(); + + + userRepository.save(user1); + userRepository.save(user2); + + topicRepository.save(topic1); + topicRepository.save(topic2); + + topic1.setInstance(instance1); + instance1.setTopic(topic1); + instanceRepository.save(instance1); + + participantInfo1.setUserAndInstance(user1, instance1); + participantInfoRepository.save(participantInfo1); + participantInfo2.setUserAndInstance(user2, instance1); + participantInfoRepository.save(participantInfo2); + + } + + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + HitsRepository hitsRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + ParticipantInfoRepository participantInfoRepository; + + @Autowired + private MockMvc mockMvc; + + @MockBean + private TopicService topicService; + + protected MediaType contentType = + new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), StandardCharsets.UTF_8); + + private User user1, user2; + private Instance instance1; + private Topic topic1, topic2; + private ParticipantInfo participantInfo1; + private ParticipantInfo participantInfo2; + + + @Test + public void 토픽_조회() throws Exception { + Pageable pageable = PageRequest.of(0, 5, Sort.Direction.DESC, "id"); + List topics = Arrays.asList(topic1, topic2); + PageImpl topicPage = new PageImpl<>(topics, pageable, topics.size()); + + when(topicService.getAllTopics(pageable)).thenReturn(topicPage); + + for (Topic topic : topicPage) { + System.out.println("topic.getInstanceList() = " + topic.getInstanceList()); + System.out.println("topic.getTitle() = " + topic.getTitle()); + } + + System.out.println("topics.size() = " + topics.size()); + + // When & Then + mockMvc.perform(get("/api/admin/topic?page=0&size=5") + .contentType(contentType)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content", hasSize(2))) + .andExpect(jsonPath("$.content[0].title").value("1일 1커밋")) + .andExpect(jsonPath("$.content[0].tags").value("BE, CS")) + .andExpect(jsonPath("$.content[0].description").value("간단한 설명란")) + .andExpect(jsonPath("$.content[0].point_per_person").value(300)) + .andExpect(jsonPath("$.content[1].title").value("1일 2커밋")) + .andExpect(jsonPath("$.content[1].tags").value("BE, CS")) + .andExpect(jsonPath("$.content[1].description").value("간단한 설명란")) + .andExpect(jsonPath("$.content[1].point_per_person").value(300)); + } +} diff --git a/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java b/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java deleted file mode 100644 index b8c06342..00000000 --- a/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.genius.gitget.topic; - -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest -@Transactional -@Rollback(value = false) -public class TopicRepositoryTest { - - @Autowired - TopicRepository topicRepository; - - @Test - public void 토픽_저장() { - Topic topic = new Topic("1일 1커밋", "챌린지입니다.", "BE, CS", 500); - Topic savedTopic = topicRepository.save(topic); - - Assertions.assertThat(topic.getTitle()).isEqualTo(savedTopic.getTitle()); - } -} From 32ca58aa9da64d644921bbdd2eb515214ab8cc7c Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Wed, 24 Jan 2024 01:22:25 +0900 Subject: [PATCH 078/234] =?UTF-8?q?feat:=20Files=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genius/gitget/file/domain/Files.java | 5 +-- .../gitget/instance/domain/Instance.java | 39 ++++++++++++++----- .../com/genius/gitget/topic/domain/Topic.java | 25 ++++++++++-- .../gitget/topic/service/TopicService.java | 11 +++--- .../com/genius/gitget/user/domain/User.java | 22 ++++++++--- 5 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/genius/gitget/file/domain/Files.java b/src/main/java/com/genius/gitget/file/domain/Files.java index cf8d6c4d..d0c8493b 100644 --- a/src/main/java/com/genius/gitget/file/domain/Files.java +++ b/src/main/java/com/genius/gitget/file/domain/Files.java @@ -19,12 +19,9 @@ public class Files extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "attachment_id") + @Column(name = "files_id") private Long id; - //TODO: User 연관관계 설정 필요(Profile) - //TODO: Topic 연관관계 설정 필요 - //TODO: Instance 연관관계 설정 필요 //TODO: 추후 PET쪽과 연관관계 설정 필요 @Enumerated(value = EnumType.STRING) diff --git a/src/main/java/com/genius/gitget/instance/domain/Instance.java b/src/main/java/com/genius/gitget/instance/domain/Instance.java index 8699172c..665325e0 100644 --- a/src/main/java/com/genius/gitget/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/instance/domain/Instance.java @@ -1,21 +1,32 @@ package com.genius.gitget.instance.domain; +import com.genius.gitget.file.domain.Files; +import com.genius.gitget.hits.domain.Hits; import com.genius.gitget.participantinfo.domain.ParticipantInfo; import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.hits.domain.Hits; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -27,6 +38,10 @@ public class Instance { @Column(name = "instance_id") private Long id; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "files_id") + private Files files; + @OneToMany(mappedBy = "instance") private List hitsList = new ArrayList<>(); @@ -57,7 +72,8 @@ public class Instance { private LocalDateTime completedDate; @Builder - public Instance(String title, String description, String tags, int pointPerPerson, Progress progress, LocalDateTime startedDate, LocalDateTime completedDate) { + public Instance(String title, String description, String tags, int pointPerPerson, Progress progress, + LocalDateTime startedDate, LocalDateTime completedDate) { this.title = title; this.description = description; this.tags = tags; @@ -67,7 +83,8 @@ public Instance(String title, String description, String tags, int pointPerPerso this.completedDate = completedDate; } - public void updateInstance(String description, int pointPerPerson, LocalDateTime startedDate, LocalDateTime completedDate) { + public void updateInstance(String description, int pointPerPerson, LocalDateTime startedDate, + LocalDateTime completedDate) { this.description = description; this.pointPerPerson = pointPerPerson; this.startedDate = startedDate; @@ -85,4 +102,8 @@ public void setTopic(Topic topic) { topic.getInstanceList().add(this); } } + + public void setFiles(Files files) { + this.files = files; + } } diff --git a/src/main/java/com/genius/gitget/topic/domain/Topic.java b/src/main/java/com/genius/gitget/topic/domain/Topic.java index 85104fdc..8ac5c719 100644 --- a/src/main/java/com/genius/gitget/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/topic/domain/Topic.java @@ -1,15 +1,24 @@ package com.genius.gitget.topic.domain; +import com.genius.gitget.file.domain.Files; import com.genius.gitget.instance.domain.Instance; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -21,6 +30,10 @@ public class Topic { @Column(name = "topic_id") private Long id; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "files_id") + private Files files; + @OneToMany(mappedBy = "topic") private List instanceList = new ArrayList<>(); @@ -59,4 +72,8 @@ public void setInstance(Instance instance) { instance.setTopic(this); } } + + public void setFiles(Files files) { + this.files = files; + } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/topic/service/TopicService.java b/src/main/java/com/genius/gitget/topic/service/TopicService.java index f3ebafa9..704ece86 100644 --- a/src/main/java/com/genius/gitget/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/topic/service/TopicService.java @@ -1,10 +1,10 @@ package com.genius.gitget.topic.service; -import com.genius.gitget.topic.dto.TopicDetailResponse; -import com.genius.gitget.topic.dto.TopicUpdateRequest; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.dto.TopicCreateRequest; +import com.genius.gitget.topic.dto.TopicDetailResponse; import com.genius.gitget.topic.dto.TopicPagingResponse; +import com.genius.gitget.topic.dto.TopicUpdateRequest; import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.util.exception.BusinessException; import com.genius.gitget.util.exception.ErrorCode; @@ -29,14 +29,14 @@ public Page getAllTopics(Pageable pageable) { // 토픽 상세정보 요청 public TopicDetailResponse getTopicById(Long id) { Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - return new TopicDetailResponse(topic.getId(), topic.getTitle(), topic.getTags(), topic.getDescription(), topic.getPointPerPerson()); + return new TopicDetailResponse(topic.getId(), topic.getTitle(), topic.getTags(), topic.getDescription(), + topic.getPointPerPerson()); } // 토픽 생성 요청 @Transactional public void createTopic(TopicCreateRequest topicCreateRequest) { Topic topic = Topic.builder() - Topic.builder() .title(topicCreateRequest.title()) .description(topicCreateRequest.description()) .tags(topicCreateRequest.tags()) @@ -57,7 +57,8 @@ public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest) { if (hasInstance) { topic.updateExistInstance(topicUpdateRequest.description()); } else { - topic.createInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), topicUpdateRequest.tags(), topicUpdateRequest.pointPerPerson()); + topic.createInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), + topicUpdateRequest.tags(), topicUpdateRequest.pointPerPerson()); } topicRepository.save(topic); } diff --git a/src/main/java/com/genius/gitget/user/domain/User.java b/src/main/java/com/genius/gitget/user/domain/User.java index fde4805c..c18b4d47 100644 --- a/src/main/java/com/genius/gitget/user/domain/User.java +++ b/src/main/java/com/genius/gitget/user/domain/User.java @@ -1,22 +1,25 @@ package com.genius.gitget.user.domain; +import com.genius.gitget.common.domain.BaseTimeEntity; +import com.genius.gitget.file.domain.Files; import com.genius.gitget.hits.domain.Hits; import com.genius.gitget.participantinfo.domain.ParticipantInfo; -import jakarta.validation.constraints.NotNull; - -import java.util.ArrayList; -import java.util.List; -import com.genius.gitget.common.domain.BaseTimeEntity; import com.genius.gitget.security.constants.ProviderInfo; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -32,6 +35,10 @@ public class User extends BaseTimeEntity { @Column(name = "user_id") private Long id; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "files_id") + private Files files; + @OneToMany(mappedBy = "user") private List hitsList = new ArrayList<>(); @@ -77,4 +84,9 @@ public void updateUser(String nickname, String information, String interest) { public void updateRole(Role role) { this.role = role; } + + //=== 연관관계 편의 메서드 ===// + public void setFiles(Files files) { + this.files = files; + } } From a120e651f47371ee7d809823a6e8fd3c1ecd01a6 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Wed, 24 Jan 2024 20:48:02 +0900 Subject: [PATCH 079/234] 24 feat admin topic api (#41) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Topic Controller 개발 * feat: Topic Domain 비즈니스 로직 개발 * feat: Topic Service 개발 * test: topic rest api 테스트 코드 작성 - jwt 관련 오류로 인해 테스트 불가 -> 해결 방안 모색 중 * feat: 전체 Topic 조회 페이징과 정렬 * feat: Instance API 개발 - DTO 활용 - BusinessException 활용 * refactor: topic controller & service DTO 도입 * refactor: topic, instance paging 리팩토링 * refactor: 리펙토링 - participant_count entity 제거 -> participantInfo list size로 해결 가능 - 테스트 코드 작성 - 코드 리펙토링 등 * refactor: admin page refactoring - 연관관계 편의 메서드 수정 - DTO 수정 - API Response 재정의 - util의 ErrorCode Enum 추가 - PagingResponse 추가 * test: postman api test - test 수행 중 발견한 수정사항 해결 * test: test 완료 --- .../java/com/genius/gitget/instance/domain/Instance.java | 5 +---- .../gitget/instance/dto/InstanceCreateRequest.java | 4 ++-- src/main/java/com/genius/gitget/topic/domain/Topic.java | 9 +-------- .../com/genius/gitget/topic/service/TopicService.java | 1 - 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/genius/gitget/instance/domain/Instance.java b/src/main/java/com/genius/gitget/instance/domain/Instance.java index 8699172c..406fdcc5 100644 --- a/src/main/java/com/genius/gitget/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/instance/domain/Instance.java @@ -5,10 +5,7 @@ import com.genius.gitget.hits.domain.Hits; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; diff --git a/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java b/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java index 72e610cf..5a7e980c 100644 --- a/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java +++ b/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java @@ -7,8 +7,8 @@ public record InstanceCreateRequest( String title, String tags, String description, - //이미지 - //유의사항 + // TODO 이미지 + // TODO 유의사항 int pointPerPerson, LocalDateTime startedAt, LocalDateTime completedAt diff --git a/src/main/java/com/genius/gitget/topic/domain/Topic.java b/src/main/java/com/genius/gitget/topic/domain/Topic.java index 85104fdc..28d78514 100644 --- a/src/main/java/com/genius/gitget/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/topic/domain/Topic.java @@ -32,6 +32,7 @@ public class Topic { private int pointPerPerson; + @Builder public Topic(String title, String description, String tags, int pointPerPerson) { this.title = title; @@ -51,12 +52,4 @@ public void createInstance(String title, String description, String tags, int po this.tags = tags; this.pointPerPerson = pointPerPerson; } - - //== 연관관계 편의 메서드 ==// - public void setInstance(Instance instance) { - instanceList.add(instance); - if (instance.getTopic() != this) { - instance.setTopic(this); - } - } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/topic/service/TopicService.java b/src/main/java/com/genius/gitget/topic/service/TopicService.java index f3ebafa9..953ca56e 100644 --- a/src/main/java/com/genius/gitget/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/topic/service/TopicService.java @@ -36,7 +36,6 @@ public TopicDetailResponse getTopicById(Long id) { @Transactional public void createTopic(TopicCreateRequest topicCreateRequest) { Topic topic = Topic.builder() - Topic.builder() .title(topicCreateRequest.title()) .description(topicCreateRequest.description()) .tags(topicCreateRequest.tags()) From f9a0e3320212a76c1fc33f291c4146b93d81963a Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 25 Jan 2024 18:09:04 +0900 Subject: [PATCH 080/234] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9224e4f0..d7b078a4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,28 +1,28 @@ --- name: Bug report about: Create a report to help us improve -title: '' +title: "[BUG]" labels: '' assignees: '' --- ### Issue 타입(하나 이상의 Issue 타입을 선택해주세요) -- [ ] 기능 추가 -- [ ] 기능 삭제 -- [x] 버그 리포트 -- [ ] 버그 수정 -- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 +□ 기능 추가 +□ 기능 삭제 +☑ 버그 리포트 +□ 버그 수정 +□ 의존성, 환경 변수, 빌드 관련 코드 업데이트 ### 상세 내용 #### 어떤 버그인가요? -\> 어떤 버그인지 간결하게 설명해주세요 +> 어떤 버그인지 간결하게 설명해주세요 #### 어떤 상황에서 발생한 버그인가요? -\> (가능하면) Given-When-Then 형식으로 서술해주세요 +> (가능하면) Given-When-Then 형식으로 서술해주세요 #### 예상 결과 -\> 예상했던 정상적인 결과가 어떤 것이었는지 설명해주세요 +> 예상했던 정상적인 결과가 어떤 것이었는지 설명해주세요 ### 라벨 - 예상 소요 시간: `E: 1h` From 42f6e36ae95189025342ee0730485d7c6e37cd20 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 25 Jan 2024 21:43:28 +0900 Subject: [PATCH 081/234] =?UTF-8?q?!HOTFIX:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=ED=95=AB=ED=94=BD=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 회원가입 완료 후 반환하는 Response 객체의 구조 변경 --- .../genius/gitget/security/config/SecurityConfig.java | 4 ++-- .../gitget/security/controller/AuthController.java | 6 +++--- .../java/com/genius/gitget/security/dto/TokenDTO.java | 4 ++++ .../com/genius/gitget/security/dto/TokenRequest.java | 4 ---- .../genius/gitget/user/controller/UserController.java | 10 +++++++--- 5 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/genius/gitget/security/dto/TokenDTO.java delete mode 100644 src/main/java/com/genius/gitget/security/dto/TokenRequest.java diff --git a/src/main/java/com/genius/gitget/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/security/config/SecurityConfig.java index d0d42085..51688363 100644 --- a/src/main/java/com/genius/gitget/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/security/config/SecurityConfig.java @@ -54,8 +54,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // JWT 검증 필터 추가 -// .addFilterBefore(new JwtAuthenticationFilter(jwtService, userService), -// UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new JwtAuthenticationFilter(jwtService, userService), + UsernamePasswordAuthenticationFilter.class) // OAuth 로그인 설정 .oauth2Login(customConfigurer -> customConfigurer diff --git a/src/main/java/com/genius/gitget/security/controller/AuthController.java b/src/main/java/com/genius/gitget/security/controller/AuthController.java index 32e51b38..fd378ea8 100644 --- a/src/main/java/com/genius/gitget/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/security/controller/AuthController.java @@ -3,7 +3,7 @@ import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.security.domain.UserPrincipal; -import com.genius.gitget.security.dto.TokenRequest; +import com.genius.gitget.security.dto.TokenDTO; import com.genius.gitget.security.service.JwtService; import com.genius.gitget.user.domain.User; import com.genius.gitget.user.service.UserService; @@ -29,8 +29,8 @@ public class AuthController { @PostMapping("/auth") public ResponseEntity generateToken(HttpServletResponse response, - @RequestBody TokenRequest tokenRequest) { - User requestUser = userService.findUserByIdentifier(tokenRequest.identifier()); + @RequestBody TokenDTO tokenDTO) { + User requestUser = userService.findUserByIdentifier(tokenDTO.identifier()); jwtService.generateAccessToken(response, requestUser); jwtService.generateRefreshToken(response, requestUser); diff --git a/src/main/java/com/genius/gitget/security/dto/TokenDTO.java b/src/main/java/com/genius/gitget/security/dto/TokenDTO.java new file mode 100644 index 00000000..00b1d1a3 --- /dev/null +++ b/src/main/java/com/genius/gitget/security/dto/TokenDTO.java @@ -0,0 +1,4 @@ +package com.genius.gitget.security.dto; + +public record TokenDTO(String identifier) { +} diff --git a/src/main/java/com/genius/gitget/security/dto/TokenRequest.java b/src/main/java/com/genius/gitget/security/dto/TokenRequest.java deleted file mode 100644 index 9da8e8a7..00000000 --- a/src/main/java/com/genius/gitget/security/dto/TokenRequest.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.genius.gitget.security.dto; - -public record TokenRequest(String identifier) { -} diff --git a/src/main/java/com/genius/gitget/user/controller/UserController.java b/src/main/java/com/genius/gitget/user/controller/UserController.java index df0cf343..89fa0d43 100644 --- a/src/main/java/com/genius/gitget/user/controller/UserController.java +++ b/src/main/java/com/genius/gitget/user/controller/UserController.java @@ -3,9 +3,11 @@ import static com.genius.gitget.util.exception.SuccessCode.CREATED; import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.security.dto.TokenDTO; import com.genius.gitget.user.dto.SignupRequest; import com.genius.gitget.user.service.UserService; import com.genius.gitget.util.response.dto.CommonResponse; +import com.genius.gitget.util.response.dto.SingleResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -30,10 +32,12 @@ public ResponseEntity checkNicknameDuplicate(@RequestParam(value } @PostMapping("/auth/signup") - public ResponseEntity signup(@RequestBody SignupRequest signupRequest) { - userService.signup(signupRequest); + public ResponseEntity> signup(@RequestBody SignupRequest signupRequest) { + Long signupUserId = userService.signup(signupRequest); + String identifier = userService.findUserById(signupUserId).getIdentifier(); + return ResponseEntity.ok().body( - new CommonResponse(CREATED.getStatus(), CREATED.getMessage()) + new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), new TokenDTO(identifier)) ); } } From d2f1db695856489b13de47accb700c9432051ff3 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:14:14 +0900 Subject: [PATCH 082/234] =?UTF-8?q?[FIX]=20JWT=20=EC=9E=AC=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20=EA=B4=80=EB=A0=A8=20=EB=B2=84=EA=B7=B8=20=ED=94=BD?= =?UTF-8?q?=EC=8A=A4=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: access-token 재발급 안되는 버그 수정 * fix: 예외 처리 로직 추가 및 무한 리다이렉션 버그 픽스 - Cookie로부터 토큰을 얻을 때, cookie가 비어있을 때 예외 처리 로직 추가 - JWT 토큰 요청 시, 사용자의 권한이 NOT_REGISTERED(가입 이전)이라면 JWT 토큰 발급 거부 로직 추가 - refresh-token이 비어있는 경우 예외 처리 * chore: test 코드 정리 --- .../com/genius/gitget/GitgetApplication.java | 3 +- .../security/config/SecurityConfig.java | 2 +- .../security/controller/AuthController.java | 6 +- .../gitget/security/service/JwtService.java | 17 +++ .../gitget/security/service/JwtUtil.java | 5 +- .../com/genius/gitget/user/domain/User.java | 2 +- .../gitget/util/exception/ErrorCode.java | 4 +- .../gitget/file/service/FileUtilTest.java | 2 + .../gitget/file/service/FilesServiceTest.java | 9 -- .../java/com/genius/gitget/hits/HitsTest.java | 27 ++--- .../security/service/JwtServiceTest.java | 2 +- .../gitget/topic/TopicControllerTest.java | 109 +++++++++--------- .../user/repository/UserRepositoryTest.java | 2 - 13 files changed, 98 insertions(+), 92 deletions(-) diff --git a/src/main/java/com/genius/gitget/GitgetApplication.java b/src/main/java/com/genius/gitget/GitgetApplication.java index 50dce880..41dcb083 100644 --- a/src/main/java/com/genius/gitget/GitgetApplication.java +++ b/src/main/java/com/genius/gitget/GitgetApplication.java @@ -2,10 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; -@SpringBootApplication +@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) @EnableJpaAuditing @EnableMongoRepositories public class GitgetApplication { diff --git a/src/main/java/com/genius/gitget/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/security/config/SecurityConfig.java index 51688363..f30ef09f 100644 --- a/src/main/java/com/genius/gitget/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/security/config/SecurityConfig.java @@ -26,7 +26,7 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**"}; + public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**", "/login"}; private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; diff --git a/src/main/java/com/genius/gitget/security/controller/AuthController.java b/src/main/java/com/genius/gitget/security/controller/AuthController.java index fd378ea8..c227738d 100644 --- a/src/main/java/com/genius/gitget/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/security/controller/AuthController.java @@ -29,8 +29,10 @@ public class AuthController { @PostMapping("/auth") public ResponseEntity generateToken(HttpServletResponse response, - @RequestBody TokenDTO tokenDTO) { - User requestUser = userService.findUserByIdentifier(tokenDTO.identifier()); + @RequestBody TokenRequest tokenRequest) { + User requestUser = userService.findUserByIdentifier(tokenRequest.identifier()); + jwtService.validateUser(requestUser); + jwtService.generateAccessToken(response, requestUser); jwtService.generateRefreshToken(response, requestUser); diff --git a/src/main/java/com/genius/gitget/security/service/JwtService.java b/src/main/java/com/genius/gitget/security/service/JwtService.java index 336b614b..c7e0a461 100644 --- a/src/main/java/com/genius/gitget/security/service/JwtService.java +++ b/src/main/java/com/genius/gitget/security/service/JwtService.java @@ -3,12 +3,17 @@ import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.gitget.security.constants.JwtRule.JWT_ISSUE_HEADER; import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; +import static com.genius.gitget.util.exception.ErrorCode.TOKEN_NOT_FOUND; import com.genius.gitget.security.constants.JwtRule; import com.genius.gitget.security.constants.TokenStatus; import com.genius.gitget.security.domain.Token; import com.genius.gitget.security.repository.TokenRepository; +import com.genius.gitget.user.domain.Role; import com.genius.gitget.user.domain.User; +import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.util.exception.ErrorCode; import io.jsonwebtoken.Jwts; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -53,6 +58,12 @@ public JwtService(CustomUserDetailsService customUserDetailsService, JwtGenerato this.REFRESH_EXPIRATION = REFRESH_EXPIRATION; } + public void validateUser(User requestUser) { + if (requestUser.getRole() == Role.NOT_REGISTERED) { + throw new BusinessException(NOT_AUTHENTICATED_USER); + } + } + public String generateAccessToken(HttpServletResponse response, User requestUser) { String accessToken = jwtGenerator.generateAccessToken(ACCESS_SECRET_KEY, ACCESS_EXPIRATION, requestUser); ResponseCookie cookie = setTokenToCookie(ACCESS_PREFIX.getValue(), accessToken, ACCESS_EXPIRATION / 1000); @@ -92,6 +103,9 @@ public boolean validateRefreshToken(String token, String identifier) { public String resolveTokenFromCookie(HttpServletRequest request, JwtRule tokenPrefix) { Cookie[] cookies = request.getCookies(); + if (cookies == null) { + throw new BusinessException(TOKEN_NOT_FOUND); + } return jwtUtil.resolveTokenFromCookie(cookies, tokenPrefix); } @@ -110,6 +124,9 @@ private String getUserPk(String token, Key secretKey) { } public String getIdentifierFromRefresh(String refreshToken) { + if (refreshToken == null || refreshToken == "") { + throw new BusinessException(ErrorCode.INVALID_JWT); + } return Jwts.parserBuilder() .setSigningKey(REFRESH_SECRET_KEY) .build() diff --git a/src/main/java/com/genius/gitget/security/service/JwtUtil.java b/src/main/java/com/genius/gitget/security/service/JwtUtil.java index 083a544c..0ab4cbcf 100644 --- a/src/main/java/com/genius/gitget/security/service/JwtUtil.java +++ b/src/main/java/com/genius/gitget/security/service/JwtUtil.java @@ -2,7 +2,6 @@ import static com.genius.gitget.util.exception.ErrorCode.INVALID_EXPIRED_JWT; import static com.genius.gitget.util.exception.ErrorCode.INVALID_JWT; -import static com.genius.gitget.util.exception.ErrorCode.TOKEN_NOT_FOUND; import com.genius.gitget.security.constants.JwtRule; import com.genius.gitget.security.constants.TokenStatus; @@ -34,7 +33,7 @@ public TokenStatus getTokenStatus(String token, Key secretKey) { .build() .parseClaimsJws(token); return TokenStatus.AUTHENTICATED; - } catch (ExpiredJwtException e) { + } catch (ExpiredJwtException | IllegalArgumentException e) { log.error(INVALID_EXPIRED_JWT.getMessage()); return TokenStatus.EXPIRED; } catch (JwtException e) { @@ -47,7 +46,7 @@ public String resolveTokenFromCookie(Cookie[] cookies, JwtRule tokenPrefix) { .filter(cookie -> cookie.getName().equals(tokenPrefix.getValue())) .findFirst() .map(Cookie::getValue) - .orElseThrow(() -> new BusinessException(TOKEN_NOT_FOUND)); + .orElse(""); } public Key getSigningKey(String secretKey) { diff --git a/src/main/java/com/genius/gitget/user/domain/User.java b/src/main/java/com/genius/gitget/user/domain/User.java index c18b4d47..b881b01f 100644 --- a/src/main/java/com/genius/gitget/user/domain/User.java +++ b/src/main/java/com/genius/gitget/user/domain/User.java @@ -56,7 +56,7 @@ public class User extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Role role; - @Column(unique = true, length = 10) + @Column(unique = true, length = 20) private String nickname; private String interest; diff --git a/src/main/java/com/genius/gitget/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/util/exception/ErrorCode.java index 50ae839e..9b4b45d3 100644 --- a/src/main/java/com/genius/gitget/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/util/exception/ErrorCode.java @@ -14,6 +14,7 @@ public enum ErrorCode { MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."), DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "이미 존재하는 닉네임입니다"), + NOT_AUTHENTICATED_USER(HttpStatus.BAD_REQUEST, "인증 가능한 사용자가 아닙니다."), INVALID_EXPIRED_JWT(HttpStatus.BAD_REQUEST, "이미 만료된 JWT 입니다."), INVALID_MALFORMED_JWT(HttpStatus.BAD_REQUEST, "JWT의 구조가 유효하지 않습니다."), @@ -25,8 +26,7 @@ public enum ErrorCode { IMAGE_NOT_EXIST(HttpStatus.BAD_REQUEST, "Image가 존재하지 않습니다."), NOT_SUPPORTED_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 확장자입니다."), - NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, ""); - + NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 타입입니다."); private final HttpStatus status; diff --git a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java index ca9d7b29..1d9e7d6b 100644 --- a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java @@ -16,9 +16,11 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @SpringBootTest +@Transactional @ActiveProfiles({"file"}) class FileUtilTest { @Autowired diff --git a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java index b60de121..4d653549 100644 --- a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java +++ b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java @@ -10,14 +10,5 @@ class FilesServiceTest { @Autowired private FilesService filesService; - @Test - @DisplayName("") - public void (){ - //given - //when - - //then - - } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/hits/HitsTest.java b/src/test/java/com/genius/gitget/hits/HitsTest.java index de248138..6a785efb 100644 --- a/src/test/java/com/genius/gitget/hits/HitsTest.java +++ b/src/test/java/com/genius/gitget/hits/HitsTest.java @@ -1,5 +1,9 @@ package com.genius.gitget.hits; +import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.user.domain.Role.ADMIN; +import static com.genius.gitget.user.domain.Role.USER; + import com.genius.gitget.hits.domain.Hits; import com.genius.gitget.hits.repository.HitsRepository; import com.genius.gitget.instance.domain.Instance; @@ -10,25 +14,16 @@ import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.user.domain.User; import com.genius.gitget.user.repository.UserRepository; +import java.time.LocalDateTime; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - -import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; -import static com.genius.gitget.security.constants.ProviderType.NAVER; -import static com.genius.gitget.user.domain.Role.ADMIN; -import static com.genius.gitget.user.domain.Role.USER; - @SpringBootTest @Transactional -@Rollback(value = false) - public class HitsTest { @Autowired @@ -65,7 +60,7 @@ public void setup() { instance1 = Instance.builder() .title("1일 1커밋") .description("챌린지 세부사항입니다.") - .point_per_person(10) + .pointPerPerson(10) .tags("BE, CS") .progress(Progress.ACTIVITY) .startedDate(LocalDateTime.now()) @@ -73,11 +68,11 @@ public void setup() { .build(); topic1 = Topic.builder() - .title("1일 1커밋") - .description("간단한 설명란") - .point_per_person(300) - .tags("BE, CS") - .build(); + .title("1일 1커밋") + .description("간단한 설명란") + .pointPerPerson(300) + .tags("BE, CS") + .build(); userRepository.save(user1); userRepository.save(user2); diff --git a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java index 7aa43199..30b90214 100644 --- a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java @@ -22,7 +22,7 @@ import org.springframework.transaction.annotation.Transactional; @SpringBootTest -@Transactional(readOnly = true) +@Transactional @Slf4j @ActiveProfiles({"jwt"}) class JwtServiceTest { diff --git a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java index 905113c4..d74e4e52 100644 --- a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java @@ -1,5 +1,13 @@ package com.genius.gitget.topic; +import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.user.domain.Role.ADMIN; +import static com.genius.gitget.user.domain.Role.USER; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import com.genius.gitget.hits.repository.HitsRepository; import com.genius.gitget.instance.domain.Instance; import com.genius.gitget.instance.domain.Progress; @@ -9,43 +17,63 @@ import com.genius.gitget.participantinfo.domain.ParticipantInfo; import com.genius.gitget.participantinfo.repository.ParticipantInfoRepository; import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.security.service.CustomOAuth2UserService; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.topic.service.TopicService; import com.genius.gitget.user.domain.User; import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.*; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.MediaType; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.test.annotation.Rollback; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; - -import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; -import static com.genius.gitget.user.domain.Role.ADMIN; -import static com.genius.gitget.user.domain.Role.USER; -import static org.hamcrest.Matchers.hasSize; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import org.springframework.transaction.annotation.Transactional; @SpringBootTest +@Transactional @AutoConfigureMockMvc -@Rollback(value = false) public class TopicControllerTest { + protected MediaType contentType = + new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), + StandardCharsets.UTF_8); + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + HitsRepository hitsRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + ParticipantInfoRepository participantInfoRepository; + @Autowired + private TokenTestUtil tokenTestUtil; + + @Autowired + private MockMvc mockMvc; + + @MockBean + private TopicService topicService; + private User user1, user2; + private Instance instance1; + private Topic topic1, topic2; + private ParticipantInfo participantInfo1; + private ParticipantInfo participantInfo2; + @BeforeEach public void setup() { user1 = User.builder().identifier("neo5188@gmail.com") @@ -67,7 +95,7 @@ public void setup() { instance1 = Instance.builder() .title("1일 1커밋") .description("챌린지 세부사항입니다.") - .point_per_person(10) + .pointPerPerson(10) .tags("BE, CS") .progress(Progress.ACTIVITY) .startedDate(LocalDateTime.now()) @@ -77,29 +105,27 @@ public void setup() { topic1 = Topic.builder() .title("1일 1커밋") .description("간단한 설명란") - .point_per_person(300) + .pointPerPerson(300) .tags("BE, CS") .build(); topic2 = Topic.builder() .title("1일 2커밋") .description("간단한 설명란") - .point_per_person(300) + .pointPerPerson(300) .tags("BE, CS") .build(); - participantInfo1 = ParticipantInfo.builder() .joinResult(JoinResult.PROCESSING) .joinStatus(JoinStatus.YES) - .build(); + .build(); participantInfo2 = ParticipantInfo.builder() .joinResult(JoinResult.SUCCESS) .joinStatus(JoinStatus.YES) .build(); - userRepository.save(user1); userRepository.save(user2); @@ -117,40 +143,14 @@ public void setup() { } - @Autowired - UserRepository userRepository; - @Autowired - InstanceRepository instanceRepository; - @Autowired - HitsRepository hitsRepository; - @Autowired - TopicRepository topicRepository; - @Autowired - ParticipantInfoRepository participantInfoRepository; - - @Autowired - private MockMvc mockMvc; - - @MockBean - private TopicService topicService; - - protected MediaType contentType = - new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), StandardCharsets.UTF_8); - - private User user1, user2; - private Instance instance1; - private Topic topic1, topic2; - private ParticipantInfo participantInfo1; - private ParticipantInfo participantInfo2; - - @Test + @WithMockCustomUser public void 토픽_조회() throws Exception { Pageable pageable = PageRequest.of(0, 5, Sort.Direction.DESC, "id"); List topics = Arrays.asList(topic1, topic2); PageImpl topicPage = new PageImpl<>(topics, pageable, topics.size()); - when(topicService.getAllTopics(pageable)).thenReturn(topicPage); +// when(topicService.getAllTopics(pageable)).thenReturn(topicPage); for (Topic topic : topicPage) { System.out.println("topic.getInstanceList() = " + topic.getInstanceList()); @@ -158,9 +158,10 @@ public void setup() { } System.out.println("topics.size() = " + topics.size()); - + // When & Then mockMvc.perform(get("/api/admin/topic?page=0&size=5") + .cookie(tokenTestUtil.createAccessCookie()) .contentType(contentType)) .andExpect(status().isOk()) .andExpect(jsonPath("$.content", hasSize(2))) diff --git a/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java index ff2e1d6c..b92eaf9f 100644 --- a/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java @@ -10,12 +10,10 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; @SpringBootTest @Transactional -@Rollback(value = false) class UserRepositoryTest { @Autowired private UserRepository userRepository; From 025fd0c5faf4a2d6ab159f4b0a95c8c7cde751a3 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:15:25 +0900 Subject: [PATCH 083/234] =?UTF-8?q?!HOTFIX:=20conflict=20resolve=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genius/gitget/security/controller/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/genius/gitget/security/controller/AuthController.java b/src/main/java/com/genius/gitget/security/controller/AuthController.java index c227738d..e833d81b 100644 --- a/src/main/java/com/genius/gitget/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/security/controller/AuthController.java @@ -29,7 +29,7 @@ public class AuthController { @PostMapping("/auth") public ResponseEntity generateToken(HttpServletResponse response, - @RequestBody TokenRequest tokenRequest) { + @RequestBody TokenDTO tokenRequest) { User requestUser = userService.findUserByIdentifier(tokenRequest.identifier()); jwtService.validateUser(requestUser); From 8929e06e2cbb61717b00f76ae08af526d09a2fee Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:21:42 +0900 Subject: [PATCH 084/234] =?UTF-8?q?[TEST]=20JWT,=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EB=A1=9C=EC=A7=81=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Squashed commit of the following: commit 025fd0c5faf4a2d6ab159f4b0a95c8c7cde751a3 Author: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu Jan 25 23:15:25 2024 +0900 !HOTFIX: conflict resolve 해결 commit d2f1db695856489b13de47accb700c9432051ff3 Author: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu Jan 25 23:14:14 2024 +0900 [FIX] JWT 재발급 관련 버그 픽스 (#45) * fix: access-token 재발급 안되는 버그 수정 * fix: 예외 처리 로직 추가 및 무한 리다이렉션 버그 픽스 - Cookie로부터 토큰을 얻을 때, cookie가 비어있을 때 예외 처리 로직 추가 - JWT 토큰 요청 시, 사용자의 권한이 NOT_REGISTERED(가입 이전)이라면 JWT 토큰 발급 거부 로직 추가 - refresh-token이 비어있는 경우 예외 처리 * chore: test 코드 정리 commit 42f6e36ae95189025342ee0730485d7c6e37cd20 Author: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu Jan 25 21:43:28 2024 +0900 !HOTFIX: 회원가입 기능 핫픽스 - 회원가입 완료 후 반환하는 Response 객체의 구조 변경 commit f9a0e3320212a76c1fc33f291c4146b93d81963a Author: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu Jan 25 18:09:04 2024 +0900 Update issue templates * test: JwtService에 대한 테스트 코드 추가 * test: JWT 테스트 코드 추가 * test: 회원가입 관련 테스트 코드 추가 --- .../gitget/security/service/JwtService.java | 20 ++-- .../security/service/JwtServiceTest.java | 104 ++++++++++++++++++ .../gitget/security/service/JwtUtilTest.java | 33 ++++++ .../user/controller/UserControllerTest.java | 103 +++++++++++++++++ .../gitget/user/service/UserServiceTest.java | 66 ++++++++++- .../com/genius/gitget/util/TokenTestUtil.java | 23 +++- 6 files changed, 339 insertions(+), 10 deletions(-) create mode 100644 src/test/java/com/genius/gitget/security/service/JwtUtilTest.java create mode 100644 src/test/java/com/genius/gitget/user/controller/UserControllerTest.java diff --git a/src/main/java/com/genius/gitget/security/service/JwtService.java b/src/main/java/com/genius/gitget/security/service/JwtService.java index c7e0a461..795372a7 100644 --- a/src/main/java/com/genius/gitget/security/service/JwtService.java +++ b/src/main/java/com/genius/gitget/security/service/JwtService.java @@ -98,7 +98,10 @@ public boolean validateAccessToken(String token) { public boolean validateRefreshToken(String token, String identifier) { boolean isRefreshValid = jwtUtil.getTokenStatus(token, REFRESH_SECRET_KEY) == TokenStatus.AUTHENTICATED; - return isRefreshValid && tokenRepository.existsById(identifier); + Token storedToken = tokenRepository.findByIdentifier(identifier); + boolean isTokenMatched = storedToken.getToken().equals(token); + + return isRefreshValid && isTokenMatched; } public String resolveTokenFromCookie(HttpServletRequest request, JwtRule tokenPrefix) { @@ -124,15 +127,16 @@ private String getUserPk(String token, Key secretKey) { } public String getIdentifierFromRefresh(String refreshToken) { - if (refreshToken == null || refreshToken == "") { + try { + return Jwts.parserBuilder() + .setSigningKey(REFRESH_SECRET_KEY) + .build() + .parseClaimsJws(refreshToken) + .getBody() + .getSubject(); + } catch (Exception e) { throw new BusinessException(ErrorCode.INVALID_JWT); } - return Jwts.parserBuilder() - .setSigningKey(REFRESH_SECRET_KEY) - .build() - .parseClaimsJws(refreshToken) - .getBody() - .getSubject(); } public void logout(User requestUser, HttpServletResponse response) { diff --git a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java index 30b90214..5fb38a88 100644 --- a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java @@ -2,18 +2,28 @@ import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.gitget.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; +import static com.genius.gitget.util.exception.ErrorCode.TOKEN_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.genius.gitget.security.constants.JwtRule; import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.security.repository.TokenRepository; import com.genius.gitget.user.domain.Role; import com.genius.gitget.user.domain.User; import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.util.exception.ErrorCode; import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockHttpServletRequest; @@ -30,6 +40,10 @@ class JwtServiceTest { private JwtService jwtService; @Autowired private UserRepository userRepository; + @Autowired + private TokenRepository tokenRepository; + @Autowired + private TokenTestUtil tokenTestUtil; @Test @DisplayName("사용자 정보를 받아서 access-token을 생성할 수 있다.") @@ -129,6 +143,88 @@ public void should_extractRefreshToken_when_passTokenType() { assertThat(refreshToken).isEqualTo(resolvedToken); } + @Test + @DisplayName("Access-token은 없고 유효한 Refresh-token만 있을 때, Access-token을 추출한다면 \"\"을 반환해야 한다.") + @WithMockCustomUser + public void should_returnBlank_whenOnlyValidRefreshToken() { + //given + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setCookies(tokenTestUtil.createRefreshCookie()); + + //when + JwtRule accessTokenPrefix = ACCESS_PREFIX; + String resolvedToken = jwtService.resolveTokenFromCookie(request, accessTokenPrefix); + + //then + assertThat(resolvedToken).isEqualTo(""); + } + + @Test + @DisplayName("Cookie에 아무런 JWT 토큰이 존재하지 않는다면 예외를 발생해야 한다.") + public void should_throwException_when_noTokens() { + //given + MockHttpServletRequest request = new MockHttpServletRequest(); + + //when + JwtRule refreshTokenPrefix = REFRESH_PREFIX; + + //then + assertThatThrownBy(() -> jwtService.resolveTokenFromCookie(request, refreshTokenPrefix)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(TOKEN_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("사용자가 아직 가입하지 않은 회원이 JWT 발급을 요청한다면, 예외를 발생시킨다.") + public void should_throwException_when_userIsNotRegistered() { + //given + User user = getUnregisteredUser(); + + //when & then + assertThatThrownBy(() -> jwtService.validateUser(user)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(NOT_AUTHENTICATED_USER.getMessage()); + } + + @Test + @DisplayName("Refresh-token으로부터 사용자 식별 정보인 Identifier를 받아올 수 있다.") + @WithMockCustomUser(identifier = "testIdentifier") + public void should_getIdentifier_when_passRefreshToken() { + //given + String refreshToken = tokenTestUtil.createRefreshToken(); + + //when + String identifier = jwtService.getIdentifierFromRefresh(refreshToken); + + //then + assertThat(identifier).isEqualTo("testIdentifier"); + } + + @ParameterizedTest(name = "Refresh-token: {0}") + @DisplayName("Refresh-token이 빈 문자열이거나, 유효하지 않은 문자열이라면 예외가 발생해야 한다.") + @ValueSource(strings = {"invalid refresh token", ""}) + public void should_throwException_when_refreshTokenInvalid(String invalidRefreshToken) { + //given + + //when&then + assertThatThrownBy(() -> jwtService.getIdentifierFromRefresh(invalidRefreshToken)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.INVALID_JWT.getMessage()); + } + + @Test + @DisplayName("Refresh-token이 유효하지 않는다면 예외가 발생한다.") + @WithMockCustomUser + public void should_throwException_when_refreshTokenInvalid() { + //given + String refreshToken = "invalid refresh token"; + + //when + assertThatThrownBy(() -> jwtService.validateRefreshToken(refreshToken, "identifier")) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INVALID_JWT.getMessage()); + } + private User getSavedUser() { return userRepository.save(User.builder() @@ -140,4 +236,12 @@ private User getSavedUser() { .information("information") .build()); } + + private User getUnregisteredUser() { + return userRepository.save(User.builder() + .providerInfo(ProviderInfo.GITHUB) + .identifier("identifier") + .role(Role.NOT_REGISTERED) + .build()); + } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java b/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java new file mode 100644 index 00000000..ce5040bf --- /dev/null +++ b/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java @@ -0,0 +1,33 @@ +package com.genius.gitget.security.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.security.constants.JwtRule; +import jakarta.servlet.http.Cookie; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@Slf4j +@SpringBootTest +class JwtUtilTest { + @Autowired + private JwtUtil jwtUtil; + + @Test + @DisplayName("모든 것이 리셋된 Cookie를 반환받을 수 있다.") + public void should_returnResetCookie() { + //given + + //when + Cookie cookie = jwtUtil.resetToken(JwtRule.ACCESS_PREFIX); + + //then + assertThat(cookie.getMaxAge()).isEqualTo(0); + assertThat(cookie.getPath()).isEqualTo("/"); + assertThat(cookie.getValue()).isNull(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java b/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java new file mode 100644 index 00000000..92d839a0 --- /dev/null +++ b/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java @@ -0,0 +1,103 @@ +package com.genius.gitget.user.controller; + +import static com.genius.gitget.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.user.domain.Role; +import com.genius.gitget.user.domain.User; +import com.genius.gitget.user.repository.UserRepository; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest +@Transactional +@Slf4j +class UserControllerTest { + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + @Autowired + UserRepository userRepository; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @DisplayName("사용자의 닉네임이 중복된다면 400번대를 반환한다.") + public void should_return4XX_when_nicknameDuplicated() throws Exception { + //given + User user = getSavedUser(); + + //when & then + mockMvc.perform(get("/api/auth/check-nickname?nickname=" + user.getNickname())) + .andExpect(status().is4xxClientError()) + .andExpect(jsonPath("$.code").value("BAD_REQUEST")) + .andExpect(jsonPath("$.resultCode").value(DUPLICATED_NICKNAME.getStatus().value())) + .andExpect(jsonPath("$.message").value(DUPLICATED_NICKNAME.getMessage())); + } + + @Test + @DisplayName("사용자의 닉네임이 중복되지 않는다면 200번대를 반환한다.") + public void should_return2XX_when_nicknameNotDuplicated() throws Exception { + mockMvc.perform(get("/api/auth/check-nickname?nickname=" + "nickname")) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.code").value(SUCCESS.getKey())) + .andExpect(jsonPath("$.resultCode").value(SUCCESS.getStatus().value())) + .andExpect(jsonPath("$.message").value(SUCCESS.getMessage())); + } + + @Test + @DisplayName("사용자의 회원가입이 완료되었다면 사용자의 identifier를 전달한다.") + public void should_passIdentifier_when_signupCompleted() throws Exception { + //given + saveUnsignedUser(); + + String requestBody = "{\"identifier\" : \"kimdozzi\",\"nickname\" : \"nickname\",\"interest\" : [\"Backend\", \"Frontend\"],\"information\" : \"hello\"}"; + + //when & then + mockMvc.perform(post("/api/auth/signup") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().is2xxSuccessful()); + } + + + private void saveUnsignedUser() { + userRepository.save(User.builder() + .role(Role.NOT_REGISTERED) + .providerInfo(ProviderInfo.GITHUB) + .identifier("kimdozzi") + .build()); + } + + private User getSavedUser() { + return userRepository.save(User.builder() + .identifier("identifier") + .role(Role.USER) + .information("information") + .interest("interest1,interest2") + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .build()); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/user/service/UserServiceTest.java index 00384c2c..08e03da2 100644 --- a/src/test/java/com/genius/gitget/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/user/service/UserServiceTest.java @@ -1,12 +1,15 @@ package com.genius.gitget.user.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.genius.gitget.security.constants.ProviderInfo; import com.genius.gitget.user.domain.Role; import com.genius.gitget.user.domain.User; import com.genius.gitget.user.dto.SignupRequest; import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.util.exception.ErrorCode; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; @@ -26,7 +29,7 @@ class UserServiceTest { private UserService userService; @Test - @DisplayName("특정 사용자 가입 테스트") + @DisplayName("특정 사용자를 가입한 이후, 사용자를 찾았을 때 가입했을 때 입력한 정보와 일치해야 한다.") public void should_matchValues_when_signupUser() { //given String email = "test@naver.com"; @@ -51,6 +54,56 @@ public void should_matchValues_when_signupUser() { assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); } + @Test + @DisplayName("저장되어 있는 사용자를 PK를 통해 찾을 수 있다.") + public void should_returnUser_when_passPK() { + //given + User user = getSavedUser(); + + //when + User foundUser = userService.findUserById(user.getId()); + + //then + assertThat(user.getId()).isEqualTo(foundUser.getId()); + assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); + assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); + assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); + assertThat(user.getRole()).isEqualTo(foundUser.getRole()); + assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); + assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); + } + + @Test + @DisplayName("저장되어 있는 사용자를 identifier를 통해 찾을 수 있다.") + public void should_returnUser_when_passIdentifier() { + //given + User user = getSavedUser(); + + //when + User foundUser = userService.findUserByIdentifier(user.getIdentifier()); + + //then + assertThat(user.getId()).isEqualTo(foundUser.getId()); + assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); + assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); + assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); + assertThat(user.getRole()).isEqualTo(foundUser.getRole()); + assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); + assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); + } + + @Test + @DisplayName("이미 등록되어 있는 닉네임인 경우 예외가 발생한다.") + public void should_throwException_when_nicknameIsDuplicated() { + //given + User user = getSavedUser(); + + //when & then + assertThatThrownBy(() -> userService.isNicknameDuplicate(user.getNickname())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.DUPLICATED_NICKNAME.getMessage()); + } + private void saveUnsignedUser() { userRepository.save(User.builder() @@ -59,4 +112,15 @@ private void saveUnsignedUser() { .identifier("test@naver.com") .build()); } + + private User getSavedUser() { + return userRepository.save(User.builder() + .identifier("identifier") + .role(Role.USER) + .information("information") + .interest("interest1,interest2") + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .build()); + } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/util/TokenTestUtil.java b/src/test/java/com/genius/gitget/util/TokenTestUtil.java index 2214676f..a449ac14 100644 --- a/src/test/java/com/genius/gitget/util/TokenTestUtil.java +++ b/src/test/java/com/genius/gitget/util/TokenTestUtil.java @@ -1,6 +1,7 @@ package com.genius.gitget.util; import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; import com.genius.gitget.security.domain.UserPrincipal; import com.genius.gitget.security.service.JwtService; @@ -27,6 +28,16 @@ public Cookie createAccessCookie() { return new Cookie(ACCESS_PREFIX.getValue(), accessCookie); } + public String createAccessToken() { + UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() + .getPrincipal(); + User user = userPrincipal.getUser(); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + return jwtService.generateAccessToken(httpServletResponse, user); + } + public Cookie createRefreshCookie() { UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() .getPrincipal(); @@ -35,6 +46,16 @@ public Cookie createRefreshCookie() { MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); String refreshCookie = jwtService.generateRefreshToken(httpServletResponse, user); - return new Cookie(ACCESS_PREFIX.getValue(), refreshCookie); + return new Cookie(REFRESH_PREFIX.getValue(), refreshCookie); + } + + public String createRefreshToken() { + UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() + .getPrincipal(); + User user = userPrincipal.getUser(); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + return jwtService.generateRefreshToken(httpServletResponse, user); } } From 77f54caa2f77b9ce040738b2a2f642b03a84d1d8 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:33:27 +0900 Subject: [PATCH 085/234] =?UTF-8?q?chore:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - admin 패키지: 관리자 관련 기능과 밀접한 기능들 - challenge 패키지: 사용자 관련 기능과 밀접한 기능들 - global 패키지: file, security, util 같은 서비스 전체에 영향을 줄 수 있는 기능들 --- .../topic/controller/TopicController.java | 26 +++++++------- .../{ => admin}/topic/domain/Topic.java | 8 ++--- .../topic/dto/TopicCreateRequest.java | 2 +- .../topic/dto/TopicDetailResponse.java | 2 +- .../topic/dto/TopicPagingResponse.java | 2 +- .../topic/dto/TopicUpdateRequest.java | 2 +- .../topic/repository/TopicRepository.java | 5 ++- .../topic/service/TopicService.java | 18 +++++----- .../{ => challenge}/hits/domain/Hits.java | 13 ++++--- .../hits/repository/HitsRepository.java | 4 +-- .../controller/InstanceController.java | 29 +++++++++------- .../instance/domain/Instance.java | 14 ++++---- .../instance/domain/Progress.java | 2 +- .../instance/dto/InstanceCreateRequest.java | 2 +- .../instance/dto/InstanceDetailResponse.java | 2 +- .../instance/dto/InstancePagingResponse.java | 2 +- .../instance/dto/InstanceUpdateRequest.java | 6 ++-- .../repository/InstanceRepository.java | 5 ++- .../instance/service/InstanceService.java | 34 ++++++++++--------- .../participantinfo/domain/JoinResult.java | 2 +- .../participantinfo/domain/JoinStatus.java | 5 +++ .../domain/ParticipantInfo.java | 10 +++--- .../repository/ParticipantInfoRepository.java | 4 +-- .../user/controller/UserController.java | 16 ++++----- .../{ => challenge}/user/domain/Role.java | 2 +- .../{ => challenge}/user/domain/User.java | 12 +++---- .../user/dto/SignupRequest.java | 2 +- .../user/repository/UserRepository.java | 6 ++-- .../user/service/UserService.java | 16 ++++----- .../file/controller/FilesController.java | 10 +++--- .../{ => global}/file/domain/FileType.java | 6 ++-- .../{ => global}/file/domain/Files.java | 4 +-- .../{ => global}/file/dto/FileResponse.java | 2 +- .../{ => global}/file/dto/UploadDTO.java | 4 +-- .../file/repository/FilesRepository.java | 4 +-- .../{ => global}/file/service/FileUtil.java | 12 +++---- .../file/service/FilesService.java | 16 ++++----- .../config/CustomCorsConfigurationSource.java | 2 +- .../security/config/SecurityConfig.java | 14 ++++---- .../security/constants/JwtRule.java | 2 +- .../security/constants/ProviderInfo.java | 2 +- .../security/constants/ProviderType.java | 2 +- .../security/constants/TokenStatus.java | 2 +- .../security/controller/AuthController.java | 16 ++++----- .../security/domain/CustomCsrfToken.java | 2 +- .../{ => global}/security/domain/Token.java | 2 +- .../security/domain/UserPrincipal.java | 4 +-- .../{ => global}/security/dto/TokenDTO.java | 2 +- .../filter/JwtAuthenticationFilter.java | 17 +++++----- .../handler/OAuth2FailureHandler.java | 2 +- .../handler/OAuth2SuccessHandler.java | 12 +++---- .../security/info/OAuth2UserInfo.java | 2 +- .../security/info/OAuth2UserInfoFactory.java | 12 +++---- .../info/impl/GithubOAuth2UserInfo.java | 22 ++++++++++++ .../info/impl/GoogleOAuth2UserInfo.java | 23 +++++++++++++ .../info/impl/KakaoOAuth2UserInfo.java | 24 +++++++++++++ .../info/impl/NaverOAuth2UserInfo.java | 23 +++++++++++++ .../security/repository/TokenRepository.java | 4 +-- .../service/CustomOAuth2UserService.java | 16 ++++----- .../service/CustomUserDetailsService.java | 12 +++---- .../security/service/JwtGenerator.java | 4 +-- .../security/service/JwtService.java | 32 ++++++++--------- .../security/service/JwtUtil.java | 12 +++---- .../security/service/TokenService.java | 10 +++--- .../{ => global}/util/config/AppConfig.java | 6 ++-- .../util/config/SwaggerConfig.java | 2 +- .../util}/domain/BaseTimeEntity.java | 3 +- .../util/exception/BusinessException.java | 2 +- .../exception/BusinessExceptionHandler.java | 4 +-- .../util/exception/ErrorCode.java | 2 +- .../exception/GlobalExceptionHandler.java | 4 +-- .../util/exception/SuccessCode.java | 2 +- .../util/formatter/CommonPattern.java | 2 +- .../util/formatter/LocalDateFormatter.java | 2 +- .../formatter/LocalDateTimeFormatter.java | 2 +- .../util/response/dto/CommonResponse.java | 2 +- .../util/response/dto/ListResponse.java | 2 +- .../util/response/dto/PagingResponse.java | 2 +- .../util/response/dto/SingleResponse.java | 2 +- .../util/response/dto/SlicingResponse.java | 2 +- .../participantinfo/domain/JoinStatus.java | 5 --- .../info/impl/GithubOAuth2UserInfo.java | 23 ------------- .../info/impl/GoogleOAuth2UserInfo.java | 24 ------------- .../info/impl/KakaoOAuth2UserInfo.java | 25 -------------- .../info/impl/NaverOAuth2UserInfo.java | 24 ------------- .../file/repository/FilesRepositoryTest.java | 5 +-- .../gitget/file/service/FileUtilTest.java | 9 ++--- .../gitget/file/service/FilesServiceTest.java | 1 + .../java/com/genius/gitget/hits/HitsTest.java | 26 +++++++------- .../controller/AuthControllerTest.java | 2 +- .../security/service/JwtServiceTest.java | 29 ++++++++-------- .../gitget/security/service/JwtUtilTest.java | 3 +- .../security/service/TokenServiceTest.java | 5 +-- .../gitget/topic/TopicControllerTest.java | 34 +++++++++---------- .../user/controller/UserControllerTest.java | 12 +++---- .../genius/gitget/user/domain/UserTest.java | 11 +++--- .../user/repository/UserRepositoryTest.java | 9 ++--- .../gitget/user/service/UserServiceTest.java | 15 ++++---- .../com/genius/gitget/util/TokenTestUtil.java | 10 +++--- .../gitget/util/WithMockCustomUser.java | 4 +-- ...hMockCustomUserSecurityContextFactory.java | 12 +++---- 101 files changed, 461 insertions(+), 456 deletions(-) rename src/main/java/com/genius/gitget/{ => admin}/topic/controller/TopicController.java (74%) rename src/main/java/com/genius/gitget/{ => admin}/topic/domain/Topic.java (92%) rename src/main/java/com/genius/gitget/{ => admin}/topic/dto/TopicCreateRequest.java (81%) rename src/main/java/com/genius/gitget/{ => admin}/topic/dto/TopicDetailResponse.java (82%) rename src/main/java/com/genius/gitget/{ => admin}/topic/dto/TopicPagingResponse.java (66%) rename src/main/java/com/genius/gitget/{ => admin}/topic/dto/TopicUpdateRequest.java (81%) rename src/main/java/com/genius/gitget/{ => admin}/topic/repository/TopicRepository.java (73%) rename src/main/java/com/genius/gitget/{ => admin}/topic/service/TopicService.java (82%) rename src/main/java/com/genius/gitget/{ => challenge}/hits/domain/Hits.java (90%) rename src/main/java/com/genius/gitget/{ => challenge}/hits/repository/HitsRepository.java (55%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/controller/InstanceController.java (68%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/domain/Instance.java (90%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/domain/Progress.java (62%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/dto/InstanceCreateRequest.java (86%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/dto/InstanceDetailResponse.java (87%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/dto/InstancePagingResponse.java (82%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/dto/InstanceUpdateRequest.java (66%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/repository/InstanceRepository.java (73%) rename src/main/java/com/genius/gitget/{ => challenge}/instance/service/InstanceService.java (71%) rename src/main/java/com/genius/gitget/{ => challenge}/participantinfo/domain/JoinResult.java (59%) create mode 100644 src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinStatus.java rename src/main/java/com/genius/gitget/{ => challenge}/participantinfo/domain/ParticipantInfo.java (84%) rename src/main/java/com/genius/gitget/{ => challenge}/participantinfo/repository/ParticipantInfoRepository.java (52%) rename src/main/java/com/genius/gitget/{ => challenge}/user/controller/UserController.java (73%) rename src/main/java/com/genius/gitget/{ => challenge}/user/domain/Role.java (87%) rename src/main/java/com/genius/gitget/{ => challenge}/user/domain/User.java (86%) rename src/main/java/com/genius/gitget/{ => challenge}/user/dto/SignupRequest.java (81%) rename src/main/java/com/genius/gitget/{ => challenge}/user/repository/UserRepository.java (79%) rename src/main/java/com/genius/gitget/{ => challenge}/user/service/UserService.java (73%) rename src/main/java/com/genius/gitget/{ => global}/file/controller/FilesController.java (84%) rename src/main/java/com/genius/gitget/{ => global}/file/domain/FileType.java (73%) rename src/main/java/com/genius/gitget/{ => global}/file/domain/Files.java (91%) rename src/main/java/com/genius/gitget/{ => global}/file/dto/FileResponse.java (51%) rename src/main/java/com/genius/gitget/{ => global}/file/dto/UploadDTO.java (69%) rename src/main/java/com/genius/gitget/{ => global}/file/repository/FilesRepository.java (57%) rename src/main/java/com/genius/gitget/{ => global}/file/service/FileUtil.java (83%) rename src/main/java/com/genius/gitget/{ => global}/file/service/FilesService.java (82%) rename src/main/java/com/genius/gitget/{ => global}/security/config/CustomCorsConfigurationSource.java (95%) rename src/main/java/com/genius/gitget/{ => global}/security/config/SecurityConfig.java (86%) rename src/main/java/com/genius/gitget/{ => global}/security/constants/JwtRule.java (84%) rename src/main/java/com/genius/gitget/{ => global}/security/constants/ProviderInfo.java (93%) rename src/main/java/com/genius/gitget/{ => global}/security/constants/ProviderType.java (88%) rename src/main/java/com/genius/gitget/{ => global}/security/constants/TokenStatus.java (76%) rename src/main/java/com/genius/gitget/{ => global}/security/controller/AuthController.java (80%) rename src/main/java/com/genius/gitget/{ => global}/security/domain/CustomCsrfToken.java (87%) rename src/main/java/com/genius/gitget/{ => global}/security/domain/Token.java (90%) rename src/main/java/com/genius/gitget/{ => global}/security/domain/UserPrincipal.java (94%) rename src/main/java/com/genius/gitget/{ => global}/security/dto/TokenDTO.java (50%) rename src/main/java/com/genius/gitget/{ => global}/security/filter/JwtAuthenticationFilter.java (84%) rename src/main/java/com/genius/gitget/{ => global}/security/handler/OAuth2FailureHandler.java (95%) rename src/main/java/com/genius/gitget/{ => global}/security/handler/OAuth2SuccessHandler.java (84%) rename src/main/java/com/genius/gitget/{ => global}/security/info/OAuth2UserInfo.java (85%) rename src/main/java/com/genius/gitget/{ => global}/security/info/OAuth2UserInfoFactory.java (66%) create mode 100644 src/main/java/com/genius/gitget/global/security/info/impl/GithubOAuth2UserInfo.java create mode 100644 src/main/java/com/genius/gitget/global/security/info/impl/GoogleOAuth2UserInfo.java create mode 100644 src/main/java/com/genius/gitget/global/security/info/impl/KakaoOAuth2UserInfo.java create mode 100644 src/main/java/com/genius/gitget/global/security/info/impl/NaverOAuth2UserInfo.java rename src/main/java/com/genius/gitget/{ => global}/security/repository/TokenRepository.java (63%) rename src/main/java/com/genius/gitget/{ => global}/security/service/CustomOAuth2UserService.java (83%) rename src/main/java/com/genius/gitget/{ => global}/security/service/CustomUserDetailsService.java (66%) rename src/main/java/com/genius/gitget/{ => global}/security/service/JwtGenerator.java (94%) rename src/main/java/com/genius/gitget/{ => global}/security/service/JwtService.java (85%) rename src/main/java/com/genius/gitget/{ => global}/security/service/JwtUtil.java (83%) rename src/main/java/com/genius/gitget/{ => global}/security/service/TokenService.java (71%) rename src/main/java/com/genius/gitget/{ => global}/util/config/AppConfig.java (68%) rename src/main/java/com/genius/gitget/{ => global}/util/config/SwaggerConfig.java (92%) rename src/main/java/com/genius/gitget/{common => global/util}/domain/BaseTimeEntity.java (94%) rename src/main/java/com/genius/gitget/{ => global}/util/exception/BusinessException.java (92%) rename src/main/java/com/genius/gitget/{ => global}/util/exception/BusinessExceptionHandler.java (85%) rename src/main/java/com/genius/gitget/{ => global}/util/exception/ErrorCode.java (96%) rename src/main/java/com/genius/gitget/{ => global}/util/exception/GlobalExceptionHandler.java (87%) rename src/main/java/com/genius/gitget/{ => global}/util/exception/SuccessCode.java (90%) rename src/main/java/com/genius/gitget/{ => global}/util/formatter/CommonPattern.java (90%) rename src/main/java/com/genius/gitget/{ => global}/util/formatter/LocalDateFormatter.java (92%) rename src/main/java/com/genius/gitget/{ => global}/util/formatter/LocalDateTimeFormatter.java (93%) rename src/main/java/com/genius/gitget/{ => global}/util/response/dto/CommonResponse.java (90%) rename src/main/java/com/genius/gitget/{ => global}/util/response/dto/ListResponse.java (87%) rename src/main/java/com/genius/gitget/{ => global}/util/response/dto/PagingResponse.java (90%) rename src/main/java/com/genius/gitget/{ => global}/util/response/dto/SingleResponse.java (89%) rename src/main/java/com/genius/gitget/{ => global}/util/response/dto/SlicingResponse.java (80%) delete mode 100644 src/main/java/com/genius/gitget/participantinfo/domain/JoinStatus.java delete mode 100644 src/main/java/com/genius/gitget/security/info/impl/GithubOAuth2UserInfo.java delete mode 100644 src/main/java/com/genius/gitget/security/info/impl/GoogleOAuth2UserInfo.java delete mode 100644 src/main/java/com/genius/gitget/security/info/impl/KakaoOAuth2UserInfo.java delete mode 100644 src/main/java/com/genius/gitget/security/info/impl/NaverOAuth2UserInfo.java diff --git a/src/main/java/com/genius/gitget/topic/controller/TopicController.java b/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java similarity index 74% rename from src/main/java/com/genius/gitget/topic/controller/TopicController.java rename to src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java index a93ddfe7..e33ec350 100644 --- a/src/main/java/com/genius/gitget/topic/controller/TopicController.java +++ b/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java @@ -1,14 +1,14 @@ -package com.genius.gitget.topic.controller; +package com.genius.gitget.admin.topic.controller; -import com.genius.gitget.topic.dto.TopicCreateRequest; -import com.genius.gitget.topic.dto.TopicDetailResponse; -import com.genius.gitget.topic.dto.TopicPagingResponse; -import com.genius.gitget.topic.dto.TopicUpdateRequest; -import com.genius.gitget.topic.service.TopicService; -import com.genius.gitget.util.exception.SuccessCode; -import com.genius.gitget.util.response.dto.CommonResponse; -import com.genius.gitget.util.response.dto.PagingResponse; -import com.genius.gitget.util.response.dto.SingleResponse; +import com.genius.gitget.admin.topic.dto.TopicCreateRequest; +import com.genius.gitget.admin.topic.dto.TopicPagingResponse; +import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; +import com.genius.gitget.admin.topic.dto.TopicDetailResponse; +import com.genius.gitget.admin.topic.service.TopicService; +import com.genius.gitget.global.util.exception.SuccessCode; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.PagingResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -27,7 +27,8 @@ public class TopicController { // 토픽 리스트 요청 @GetMapping - public ResponseEntity> getAllTopics(@PageableDefault(size = 5, direction = Sort.Direction.ASC) Pageable pageable) { + public ResponseEntity> getAllTopics( + @PageableDefault(size = 5, direction = Sort.Direction.ASC) Pageable pageable) { Page allTopics = topicService.getAllTopics(pageable); return ResponseEntity.ok().body( @@ -55,7 +56,8 @@ public ResponseEntity createTopic(@RequestBody @Valid TopicCreat // 토픽 수정 요청 @PatchMapping("/{id}") - public ResponseEntity updateTopic(@PathVariable Long id, @RequestBody @Valid TopicUpdateRequest topicUpdateRequest) { + public ResponseEntity updateTopic(@PathVariable Long id, + @RequestBody @Valid TopicUpdateRequest topicUpdateRequest) { topicService.updateTopic(id, topicUpdateRequest); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/topic/domain/Topic.java b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java similarity index 92% rename from src/main/java/com/genius/gitget/topic/domain/Topic.java rename to src/main/java/com/genius/gitget/admin/topic/domain/Topic.java index 61865ba3..d37f61a4 100644 --- a/src/main/java/com/genius/gitget/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java @@ -1,7 +1,7 @@ -package com.genius.gitget.topic.domain; +package com.genius.gitget.admin.topic.domain; -import com.genius.gitget.file.domain.Files; -import com.genius.gitget.instance.domain.Instance; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.challenge.instance.domain.Instance; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -73,7 +73,7 @@ public void setInstance(Instance instance) { instance.setTopic(this); } } - + public void setFiles(Files files) { this.files = files; } diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java similarity index 81% rename from src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java rename to src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java index eb064f25..183bf254 100644 --- a/src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.topic.dto; +package com.genius.gitget.admin.topic.dto; public record TopicCreateRequest( String title, diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java similarity index 82% rename from src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java rename to src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java index b8b2e61d..768764ca 100644 --- a/src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.topic.dto; +package com.genius.gitget.admin.topic.dto; public record TopicDetailResponse( Long topicId, diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java similarity index 66% rename from src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java rename to src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java index 7e50ca80..02aa8a65 100644 --- a/src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.topic.dto; +package com.genius.gitget.admin.topic.dto; public record TopicPagingResponse( diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java similarity index 81% rename from src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java rename to src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java index fab38395..356a69bf 100644 --- a/src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.topic.dto; +package com.genius.gitget.admin.topic.dto; public record TopicUpdateRequest( String title, diff --git a/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java b/src/main/java/com/genius/gitget/admin/topic/repository/TopicRepository.java similarity index 73% rename from src/main/java/com/genius/gitget/topic/repository/TopicRepository.java rename to src/main/java/com/genius/gitget/admin/topic/repository/TopicRepository.java index b4bcbc4e..4e0b7318 100644 --- a/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java +++ b/src/main/java/com/genius/gitget/admin/topic/repository/TopicRepository.java @@ -1,7 +1,6 @@ -package com.genius.gitget.topic.repository; +package com.genius.gitget.admin.topic.repository; -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.admin.topic.domain.Topic; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/genius/gitget/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java similarity index 82% rename from src/main/java/com/genius/gitget/topic/service/TopicService.java rename to src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index 704ece86..9330921b 100644 --- a/src/main/java/com/genius/gitget/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -1,13 +1,13 @@ -package com.genius.gitget.topic.service; +package com.genius.gitget.admin.topic.service; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.dto.TopicCreateRequest; -import com.genius.gitget.topic.dto.TopicDetailResponse; -import com.genius.gitget.topic.dto.TopicPagingResponse; -import com.genius.gitget.topic.dto.TopicUpdateRequest; -import com.genius.gitget.topic.repository.TopicRepository; -import com.genius.gitget.util.exception.BusinessException; -import com.genius.gitget.util.exception.ErrorCode; +import com.genius.gitget.admin.topic.dto.TopicCreateRequest; +import com.genius.gitget.admin.topic.dto.TopicPagingResponse; +import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.dto.TopicDetailResponse; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/com/genius/gitget/hits/domain/Hits.java b/src/main/java/com/genius/gitget/challenge/hits/domain/Hits.java similarity index 90% rename from src/main/java/com/genius/gitget/hits/domain/Hits.java rename to src/main/java/com/genius/gitget/challenge/hits/domain/Hits.java index a2b30c1a..9b9fd9dc 100644 --- a/src/main/java/com/genius/gitget/hits/domain/Hits.java +++ b/src/main/java/com/genius/gitget/challenge/hits/domain/Hits.java @@ -1,7 +1,7 @@ -package com.genius.gitget.hits.domain; +package com.genius.gitget.challenge.hits.domain; -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.user.domain.User; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.user.domain.User; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -29,6 +29,9 @@ public class Hits { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; + @CreatedDate + @Column(name = "liked_at", unique = false) + private LocalDateTime likedAt; // 찜하기 누른 시각 public Hits(User user, Instance instance) { this.instance = instance; @@ -36,10 +39,6 @@ public Hits(User user, Instance instance) { setUserAndInstance(user, instance); } - @CreatedDate - @Column(name = "liked_at", unique = false) - private LocalDateTime likedAt; // 찜하기 누른 시각 - /*== 연관관계 편의 메서드 ==*/ public void setUserAndInstance(User user, Instance instance) { addHitsForUser(user); diff --git a/src/main/java/com/genius/gitget/hits/repository/HitsRepository.java b/src/main/java/com/genius/gitget/challenge/hits/repository/HitsRepository.java similarity index 55% rename from src/main/java/com/genius/gitget/hits/repository/HitsRepository.java rename to src/main/java/com/genius/gitget/challenge/hits/repository/HitsRepository.java index bb60b559..31919b47 100644 --- a/src/main/java/com/genius/gitget/hits/repository/HitsRepository.java +++ b/src/main/java/com/genius/gitget/challenge/hits/repository/HitsRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.hits.repository; +package com.genius.gitget.challenge.hits.repository; -import com.genius.gitget.hits.domain.Hits; +import com.genius.gitget.challenge.hits.domain.Hits; import org.springframework.data.jpa.repository.JpaRepository; public interface HitsRepository extends JpaRepository { diff --git a/src/main/java/com/genius/gitget/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java similarity index 68% rename from src/main/java/com/genius/gitget/instance/controller/InstanceController.java rename to src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index e9cb0df0..790be4b8 100644 --- a/src/main/java/com/genius/gitget/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -1,14 +1,14 @@ -package com.genius.gitget.instance.controller; +package com.genius.gitget.challenge.instance.controller; -import com.genius.gitget.instance.dto.InstanceCreateRequest; -import com.genius.gitget.instance.dto.InstanceDetailResponse; -import com.genius.gitget.instance.dto.InstancePagingResponse; -import com.genius.gitget.instance.dto.InstanceUpdateRequest; -import com.genius.gitget.instance.service.InstanceService; -import com.genius.gitget.util.exception.SuccessCode; -import com.genius.gitget.util.response.dto.CommonResponse; -import com.genius.gitget.util.response.dto.PagingResponse; -import com.genius.gitget.util.response.dto.SingleResponse; +import com.genius.gitget.challenge.instance.dto.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.InstancePagingResponse; +import com.genius.gitget.challenge.instance.dto.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.global.util.exception.SuccessCode; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.PagingResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -26,7 +26,8 @@ public class InstanceController { // 인스턴스 리스트 조회 @GetMapping - public ResponseEntity> getAllInstances(@PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id")Pageable pageable) { + public ResponseEntity> getAllInstances( + @PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id") Pageable pageable) { Page instances = instanceService.getAllInstances(pageable); return ResponseEntity.ok().body( @@ -45,7 +46,8 @@ public ResponseEntity> getInstanceById(@P // 인스턴스 생성 @PostMapping - public ResponseEntity createInstance(@RequestBody @Valid InstanceCreateRequest instanceCreateRequest) { + public ResponseEntity createInstance( + @RequestBody @Valid InstanceCreateRequest instanceCreateRequest) { instanceService.createInstance(instanceCreateRequest); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) @@ -54,7 +56,8 @@ public ResponseEntity createInstance(@RequestBody @Valid Instanc // 인스턴스 수정 @PatchMapping("/{id}") - public ResponseEntity updateInstance(@PathVariable Long id, @RequestBody @Valid InstanceUpdateRequest instanceUpdateRequest) { + public ResponseEntity updateInstance(@PathVariable Long id, + @RequestBody @Valid InstanceUpdateRequest instanceUpdateRequest) { instanceService.updateInstance(id, instanceUpdateRequest); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) diff --git a/src/main/java/com/genius/gitget/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java similarity index 90% rename from src/main/java/com/genius/gitget/instance/domain/Instance.java rename to src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 964486c9..b75721a8 100644 --- a/src/main/java/com/genius/gitget/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -1,9 +1,9 @@ -package com.genius.gitget.instance.domain; +package com.genius.gitget.challenge.instance.domain; -import com.genius.gitget.file.domain.Files; -import com.genius.gitget.hits.domain.Hits; -import com.genius.gitget.participantinfo.domain.ParticipantInfo; -import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.challenge.hits.domain.Hits; +import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.admin.topic.domain.Topic; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -25,8 +25,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.*; -import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; @Entity @@ -104,7 +102,7 @@ public void setTopic(Topic topic) { topic.getInstanceList().add(this); } } - + public void setFiles(Files files) { this.files = files; } diff --git a/src/main/java/com/genius/gitget/instance/domain/Progress.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java similarity index 62% rename from src/main/java/com/genius/gitget/instance/domain/Progress.java rename to src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java index d2f38aba..bb0a8014 100644 --- a/src/main/java/com/genius/gitget/instance/domain/Progress.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java @@ -1,4 +1,4 @@ -package com.genius.gitget.instance.domain; +package com.genius.gitget.challenge.instance.domain; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceCreateRequest.java similarity index 86% rename from src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/InstanceCreateRequest.java index 5a7e980c..4bdd435a 100644 --- a/src/main/java/com/genius/gitget/instance/dto/InstanceCreateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceCreateRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.instance.dto; +package com.genius.gitget.challenge.instance.dto; import java.time.LocalDateTime; diff --git a/src/main/java/com/genius/gitget/instance/dto/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceDetailResponse.java similarity index 87% rename from src/main/java/com/genius/gitget/instance/dto/InstanceDetailResponse.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/InstanceDetailResponse.java index b38ac6ae..f0eef986 100644 --- a/src/main/java/com/genius/gitget/instance/dto/InstanceDetailResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceDetailResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.instance.dto; +package com.genius.gitget.challenge.instance.dto; import java.time.LocalDateTime; diff --git a/src/main/java/com/genius/gitget/instance/dto/InstancePagingResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/InstancePagingResponse.java similarity index 82% rename from src/main/java/com/genius/gitget/instance/dto/InstancePagingResponse.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/InstancePagingResponse.java index 36c21ac8..7fde0599 100644 --- a/src/main/java/com/genius/gitget/instance/dto/InstancePagingResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/InstancePagingResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.instance.dto; +package com.genius.gitget.challenge.instance.dto; import java.time.LocalDateTime; diff --git a/src/main/java/com/genius/gitget/instance/dto/InstanceUpdateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceUpdateRequest.java similarity index 66% rename from src/main/java/com/genius/gitget/instance/dto/InstanceUpdateRequest.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/InstanceUpdateRequest.java index 1f80083c..1b483c8c 100644 --- a/src/main/java/com/genius/gitget/instance/dto/InstanceUpdateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceUpdateRequest.java @@ -1,12 +1,12 @@ -package com.genius.gitget.instance.dto; +package com.genius.gitget.challenge.instance.dto; import java.time.LocalDateTime; -public record InstanceUpdateRequest ( +public record InstanceUpdateRequest( Long topicId, String description, int pointPerPerson, LocalDateTime startedAt, LocalDateTime completedAt -){ +) { } diff --git a/src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java similarity index 73% rename from src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java rename to src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java index db8a0051..2917b745 100644 --- a/src/main/java/com/genius/gitget/instance/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java @@ -1,7 +1,6 @@ -package com.genius.gitget.instance.repository; +package com.genius.gitget.challenge.instance.repository; -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.challenge.instance.domain.Instance; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/genius/gitget/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java similarity index 71% rename from src/main/java/com/genius/gitget/instance/service/InstanceService.java rename to src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index 9767032b..148690c5 100644 --- a/src/main/java/com/genius/gitget/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -1,23 +1,23 @@ -package com.genius.gitget.instance.service; - -import com.genius.gitget.instance.domain.Progress; -import com.genius.gitget.instance.dto.InstanceCreateRequest; -import com.genius.gitget.instance.dto.InstanceDetailResponse; -import com.genius.gitget.instance.dto.InstancePagingResponse; -import com.genius.gitget.instance.dto.InstanceUpdateRequest; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.util.exception.BusinessException; -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.instance.repository.InstanceRepository; -import com.genius.gitget.topic.repository.TopicRepository; +package com.genius.gitget.challenge.instance.service; + +import com.genius.gitget.challenge.instance.dto.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.InstancePagingResponse; +import com.genius.gitget.challenge.instance.dto.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.admin.topic.repository.TopicRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.genius.gitget.util.exception.ErrorCode.INSTANCE_NOT_FOUND; -import static com.genius.gitget.util.exception.ErrorCode.TOPIC_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; @Service @RequiredArgsConstructor @@ -49,13 +49,15 @@ public void createInstance(InstanceCreateRequest instanceCreateRequest) { public Page getAllInstances(Pageable pageable) { Page instances = instanceRepository.findAllById(pageable); - return instances.map(instance -> new InstancePagingResponse(instance.getTopic().getId(), instance.getId(), instance.getTitle(), instance.getStartedDate(), instance.getCompletedDate())); + return instances.map(instance -> new InstancePagingResponse(instance.getTopic().getId(), instance.getId(), + instance.getTitle(), instance.getStartedDate(), instance.getCompletedDate())); } // 인스턴스 단건 조회 public InstanceDetailResponse getInstanceById(Long id) { - Instance instanceDetails = instanceRepository.findById(id).orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); + Instance instanceDetails = instanceRepository.findById(id) + .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); return new InstanceDetailResponse( instanceDetails.getTopic().getId(), instanceDetails.getId(), diff --git a/src/main/java/com/genius/gitget/participantinfo/domain/JoinResult.java b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinResult.java similarity index 59% rename from src/main/java/com/genius/gitget/participantinfo/domain/JoinResult.java rename to src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinResult.java index c42015dd..febec45d 100644 --- a/src/main/java/com/genius/gitget/participantinfo/domain/JoinResult.java +++ b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinResult.java @@ -1,4 +1,4 @@ -package com.genius.gitget.participantinfo.domain; +package com.genius.gitget.challenge.participantinfo.domain; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinStatus.java b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinStatus.java new file mode 100644 index 00000000..6f5240c9 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinStatus.java @@ -0,0 +1,5 @@ +package com.genius.gitget.challenge.participantinfo.domain; + +public enum JoinStatus { + NO, YES +} diff --git a/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java similarity index 84% rename from src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java rename to src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java index cf81bc44..5b569717 100644 --- a/src/main/java/com/genius/gitget/participantinfo/domain/ParticipantInfo.java +++ b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java @@ -1,7 +1,7 @@ -package com.genius.gitget.participantinfo.domain; +package com.genius.gitget.challenge.participantinfo.domain; -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.user.domain.User; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.user.domain.User; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; @@ -53,14 +53,14 @@ public void setUserAndInstance(User user, Instance instance) { } private void addParticipantInfoForUser(User user) { - if(!(user.getParticipantInfoList().contains(this))) { + if (!(user.getParticipantInfoList().contains(this))) { user.getParticipantInfoList().add(this); } this.user = user; } private void addParticipantInfoForInstance(Instance instance) { - if(!(instance.getParticipantInfoList().contains(this))) { + if (!(instance.getParticipantInfoList().contains(this))) { instance.getParticipantInfoList().add(this); } this.instance = instance; diff --git a/src/main/java/com/genius/gitget/participantinfo/repository/ParticipantInfoRepository.java b/src/main/java/com/genius/gitget/challenge/participantinfo/repository/ParticipantInfoRepository.java similarity index 52% rename from src/main/java/com/genius/gitget/participantinfo/repository/ParticipantInfoRepository.java rename to src/main/java/com/genius/gitget/challenge/participantinfo/repository/ParticipantInfoRepository.java index ae539f12..d1c316ed 100644 --- a/src/main/java/com/genius/gitget/participantinfo/repository/ParticipantInfoRepository.java +++ b/src/main/java/com/genius/gitget/challenge/participantinfo/repository/ParticipantInfoRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.participantinfo.repository; +package com.genius.gitget.challenge.participantinfo.repository; -import com.genius.gitget.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; import org.springframework.data.jpa.repository.JpaRepository; public interface ParticipantInfoRepository extends JpaRepository { diff --git a/src/main/java/com/genius/gitget/user/controller/UserController.java b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java similarity index 73% rename from src/main/java/com/genius/gitget/user/controller/UserController.java rename to src/main/java/com/genius/gitget/challenge/user/controller/UserController.java index 89fa0d43..683f8236 100644 --- a/src/main/java/com/genius/gitget/user/controller/UserController.java +++ b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java @@ -1,13 +1,13 @@ -package com.genius.gitget.user.controller; +package com.genius.gitget.challenge.user.controller; -import static com.genius.gitget.util.exception.SuccessCode.CREATED; -import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; +import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.security.dto.TokenDTO; -import com.genius.gitget.user.dto.SignupRequest; -import com.genius.gitget.user.service.UserService; -import com.genius.gitget.util.response.dto.CommonResponse; -import com.genius.gitget.util.response.dto.SingleResponse; +import com.genius.gitget.global.security.dto.TokenDTO; +import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/genius/gitget/user/domain/Role.java b/src/main/java/com/genius/gitget/challenge/user/domain/Role.java similarity index 87% rename from src/main/java/com/genius/gitget/user/domain/Role.java rename to src/main/java/com/genius/gitget/challenge/user/domain/Role.java index 7afe444b..938f643c 100644 --- a/src/main/java/com/genius/gitget/user/domain/Role.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/Role.java @@ -1,4 +1,4 @@ -package com.genius.gitget.user.domain; +package com.genius.gitget.challenge.user.domain; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java similarity index 86% rename from src/main/java/com/genius/gitget/user/domain/User.java rename to src/main/java/com/genius/gitget/challenge/user/domain/User.java index b881b01f..d257237d 100644 --- a/src/main/java/com/genius/gitget/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -1,10 +1,10 @@ -package com.genius.gitget.user.domain; +package com.genius.gitget.challenge.user.domain; -import com.genius.gitget.common.domain.BaseTimeEntity; -import com.genius.gitget.file.domain.Files; -import com.genius.gitget.hits.domain.Hits; -import com.genius.gitget.participantinfo.domain.ParticipantInfo; -import com.genius.gitget.security.constants.ProviderInfo; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.challenge.hits.domain.Hits; +import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/com/genius/gitget/user/dto/SignupRequest.java b/src/main/java/com/genius/gitget/challenge/user/dto/SignupRequest.java similarity index 81% rename from src/main/java/com/genius/gitget/user/dto/SignupRequest.java rename to src/main/java/com/genius/gitget/challenge/user/dto/SignupRequest.java index 79d3b963..4043e806 100644 --- a/src/main/java/com/genius/gitget/user/dto/SignupRequest.java +++ b/src/main/java/com/genius/gitget/challenge/user/dto/SignupRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.user.dto; +package com.genius.gitget.challenge.user.dto; import java.util.List; import lombok.Builder; diff --git a/src/main/java/com/genius/gitget/user/repository/UserRepository.java b/src/main/java/com/genius/gitget/challenge/user/repository/UserRepository.java similarity index 79% rename from src/main/java/com/genius/gitget/user/repository/UserRepository.java rename to src/main/java/com/genius/gitget/challenge/user/repository/UserRepository.java index 11128387..18d1613c 100644 --- a/src/main/java/com/genius/gitget/user/repository/UserRepository.java +++ b/src/main/java/com/genius/gitget/challenge/user/repository/UserRepository.java @@ -1,7 +1,7 @@ -package com.genius.gitget.user.repository; +package com.genius.gitget.challenge.user.repository; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.user.domain.User; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.challenge.user.domain.User; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/genius/gitget/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java similarity index 73% rename from src/main/java/com/genius/gitget/user/service/UserService.java rename to src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 22a1c0b6..4f49b1ef 100644 --- a/src/main/java/com/genius/gitget/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -1,13 +1,13 @@ -package com.genius.gitget.user.service; +package com.genius.gitget.challenge.user.service; -import static com.genius.gitget.util.exception.ErrorCode.DUPLICATED_NICKNAME; -import static com.genius.gitget.util.exception.ErrorCode.MEMBER_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.dto.SignupRequest; -import com.genius.gitget.user.repository.UserRepository; -import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.util.exception.BusinessException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/genius/gitget/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java similarity index 84% rename from src/main/java/com/genius/gitget/file/controller/FilesController.java rename to src/main/java/com/genius/gitget/global/file/controller/FilesController.java index 9fd07b7d..ffdbc247 100644 --- a/src/main/java/com/genius/gitget/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -1,10 +1,10 @@ -package com.genius.gitget.file.controller; +package com.genius.gitget.global.file.controller; -import static com.genius.gitget.util.exception.SuccessCode.CREATED; +import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; -import com.genius.gitget.file.dto.FileResponse; -import com.genius.gitget.file.service.FilesService; -import com.genius.gitget.util.response.dto.SingleResponse; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.util.response.dto.SingleResponse; import java.io.IOException; import java.net.MalformedURLException; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/file/domain/FileType.java b/src/main/java/com/genius/gitget/global/file/domain/FileType.java similarity index 73% rename from src/main/java/com/genius/gitget/file/domain/FileType.java rename to src/main/java/com/genius/gitget/global/file/domain/FileType.java index 62e226a9..9cfcb394 100644 --- a/src/main/java/com/genius/gitget/file/domain/FileType.java +++ b/src/main/java/com/genius/gitget/global/file/domain/FileType.java @@ -1,8 +1,8 @@ -package com.genius.gitget.file.domain; +package com.genius.gitget.global.file.domain; -import static com.genius.gitget.util.exception.ErrorCode.NOT_SUPPORTED_IMAGE_TYPE; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_IMAGE_TYPE; -import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.BusinessException; import java.util.Arrays; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/file/domain/Files.java b/src/main/java/com/genius/gitget/global/file/domain/Files.java similarity index 91% rename from src/main/java/com/genius/gitget/file/domain/Files.java rename to src/main/java/com/genius/gitget/global/file/domain/Files.java index d0c8493b..75d2a1e6 100644 --- a/src/main/java/com/genius/gitget/file/domain/Files.java +++ b/src/main/java/com/genius/gitget/global/file/domain/Files.java @@ -1,6 +1,6 @@ -package com.genius.gitget.file.domain; +package com.genius.gitget.global.file.domain; -import com.genius.gitget.common.domain.BaseTimeEntity; +import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/com/genius/gitget/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java similarity index 51% rename from src/main/java/com/genius/gitget/file/dto/FileResponse.java rename to src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index ee802236..f3cea961 100644 --- a/src/main/java/com/genius/gitget/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.file.dto; +package com.genius.gitget.global.file.dto; public record FileResponse(Long fileId) { } diff --git a/src/main/java/com/genius/gitget/file/dto/UploadDTO.java b/src/main/java/com/genius/gitget/global/file/dto/UploadDTO.java similarity index 69% rename from src/main/java/com/genius/gitget/file/dto/UploadDTO.java rename to src/main/java/com/genius/gitget/global/file/dto/UploadDTO.java index a83dbc97..e2890f6b 100644 --- a/src/main/java/com/genius/gitget/file/dto/UploadDTO.java +++ b/src/main/java/com/genius/gitget/global/file/dto/UploadDTO.java @@ -1,6 +1,6 @@ -package com.genius.gitget.file.dto; +package com.genius.gitget.global.file.dto; -import com.genius.gitget.file.domain.FileType; +import com.genius.gitget.global.file.domain.FileType; import lombok.Builder; @Builder diff --git a/src/main/java/com/genius/gitget/file/repository/FilesRepository.java b/src/main/java/com/genius/gitget/global/file/repository/FilesRepository.java similarity index 57% rename from src/main/java/com/genius/gitget/file/repository/FilesRepository.java rename to src/main/java/com/genius/gitget/global/file/repository/FilesRepository.java index 1b5a38a6..bfc9a9b5 100644 --- a/src/main/java/com/genius/gitget/file/repository/FilesRepository.java +++ b/src/main/java/com/genius/gitget/global/file/repository/FilesRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.file.repository; +package com.genius.gitget.global.file.repository; -import com.genius.gitget.file.domain.Files; +import com.genius.gitget.global.file.domain.Files; import org.springframework.data.jpa.repository.JpaRepository; public interface FilesRepository extends JpaRepository { diff --git a/src/main/java/com/genius/gitget/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java similarity index 83% rename from src/main/java/com/genius/gitget/file/service/FileUtil.java rename to src/main/java/com/genius/gitget/global/file/service/FileUtil.java index 06a6c381..d4c7c3fe 100644 --- a/src/main/java/com/genius/gitget/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -1,11 +1,11 @@ -package com.genius.gitget.file.service; +package com.genius.gitget.global.file.service; -import static com.genius.gitget.util.exception.ErrorCode.IMAGE_NOT_EXIST; -import static com.genius.gitget.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; +import static com.genius.gitget.global.util.exception.ErrorCode.IMAGE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; -import com.genius.gitget.file.domain.FileType; -import com.genius.gitget.file.dto.UploadDTO; -import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.dto.UploadDTO; +import com.genius.gitget.global.util.exception.BusinessException; import java.util.List; import java.util.Objects; import java.util.UUID; diff --git a/src/main/java/com/genius/gitget/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java similarity index 82% rename from src/main/java/com/genius/gitget/file/service/FilesService.java rename to src/main/java/com/genius/gitget/global/file/service/FilesService.java index 87d3e6a9..6dbf331c 100644 --- a/src/main/java/com/genius/gitget/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -1,11 +1,11 @@ -package com.genius.gitget.file.service; - -import com.genius.gitget.file.domain.Files; -import com.genius.gitget.file.dto.FileResponse; -import com.genius.gitget.file.dto.UploadDTO; -import com.genius.gitget.file.repository.FilesRepository; -import com.genius.gitget.util.exception.BusinessException; -import com.genius.gitget.util.exception.ErrorCode; +package com.genius.gitget.global.file.service; + +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.dto.UploadDTO; +import com.genius.gitget.global.file.repository.FilesRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; diff --git a/src/main/java/com/genius/gitget/security/config/CustomCorsConfigurationSource.java b/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java similarity index 95% rename from src/main/java/com/genius/gitget/security/config/CustomCorsConfigurationSource.java rename to src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java index 1ce8cadf..07056658 100644 --- a/src/main/java/com/genius/gitget/security/config/CustomCorsConfigurationSource.java +++ b/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.config; +package com.genius.gitget.global.security.config; import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; diff --git a/src/main/java/com/genius/gitget/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java similarity index 86% rename from src/main/java/com/genius/gitget/security/config/SecurityConfig.java rename to src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java index f30ef09f..e2273e69 100644 --- a/src/main/java/com/genius/gitget/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java @@ -1,12 +1,12 @@ -package com.genius.gitget.security.config; +package com.genius.gitget.global.security.config; -import com.genius.gitget.security.filter.JwtAuthenticationFilter; -import com.genius.gitget.security.handler.OAuth2FailureHandler; -import com.genius.gitget.security.handler.OAuth2SuccessHandler; -import com.genius.gitget.security.service.CustomOAuth2UserService; -import com.genius.gitget.security.service.JwtService; -import com.genius.gitget.user.service.UserService; +import com.genius.gitget.global.security.filter.JwtAuthenticationFilter; +import com.genius.gitget.global.security.handler.OAuth2FailureHandler; +import com.genius.gitget.global.security.handler.OAuth2SuccessHandler; +import com.genius.gitget.global.security.service.CustomOAuth2UserService; +import com.genius.gitget.global.security.service.JwtService; +import com.genius.gitget.challenge.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/genius/gitget/security/constants/JwtRule.java b/src/main/java/com/genius/gitget/global/security/constants/JwtRule.java similarity index 84% rename from src/main/java/com/genius/gitget/security/constants/JwtRule.java rename to src/main/java/com/genius/gitget/global/security/constants/JwtRule.java index 32e8c286..8892419d 100644 --- a/src/main/java/com/genius/gitget/security/constants/JwtRule.java +++ b/src/main/java/com/genius/gitget/global/security/constants/JwtRule.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.constants; +package com.genius.gitget.global.security.constants; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/security/constants/ProviderInfo.java b/src/main/java/com/genius/gitget/global/security/constants/ProviderInfo.java similarity index 93% rename from src/main/java/com/genius/gitget/security/constants/ProviderInfo.java rename to src/main/java/com/genius/gitget/global/security/constants/ProviderInfo.java index 2acdfaba..50a1d60e 100644 --- a/src/main/java/com/genius/gitget/security/constants/ProviderInfo.java +++ b/src/main/java/com/genius/gitget/global/security/constants/ProviderInfo.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.constants; +package com.genius.gitget.global.security.constants; import java.util.Arrays; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/security/constants/ProviderType.java b/src/main/java/com/genius/gitget/global/security/constants/ProviderType.java similarity index 88% rename from src/main/java/com/genius/gitget/security/constants/ProviderType.java rename to src/main/java/com/genius/gitget/global/security/constants/ProviderType.java index e35e9c5b..81de6425 100644 --- a/src/main/java/com/genius/gitget/security/constants/ProviderType.java +++ b/src/main/java/com/genius/gitget/global/security/constants/ProviderType.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.constants; +package com.genius.gitget.global.security.constants; import java.util.Arrays; diff --git a/src/main/java/com/genius/gitget/security/constants/TokenStatus.java b/src/main/java/com/genius/gitget/global/security/constants/TokenStatus.java similarity index 76% rename from src/main/java/com/genius/gitget/security/constants/TokenStatus.java rename to src/main/java/com/genius/gitget/global/security/constants/TokenStatus.java index b32273c5..7da9d3dd 100644 --- a/src/main/java/com/genius/gitget/security/constants/TokenStatus.java +++ b/src/main/java/com/genius/gitget/global/security/constants/TokenStatus.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.constants; +package com.genius.gitget.global.security.constants; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java similarity index 80% rename from src/main/java/com/genius/gitget/security/controller/AuthController.java rename to src/main/java/com/genius/gitget/global/security/controller/AuthController.java index e833d81b..76aa1fdd 100644 --- a/src/main/java/com/genius/gitget/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -1,13 +1,13 @@ -package com.genius.gitget.security.controller; +package com.genius.gitget.global.security.controller; -import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.security.domain.UserPrincipal; -import com.genius.gitget.security.dto.TokenDTO; -import com.genius.gitget.security.service.JwtService; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.service.UserService; -import com.genius.gitget.util.response.dto.CommonResponse; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.security.dto.TokenDTO; +import com.genius.gitget.global.security.service.JwtService; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.response.dto.CommonResponse; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/genius/gitget/security/domain/CustomCsrfToken.java b/src/main/java/com/genius/gitget/global/security/domain/CustomCsrfToken.java similarity index 87% rename from src/main/java/com/genius/gitget/security/domain/CustomCsrfToken.java rename to src/main/java/com/genius/gitget/global/security/domain/CustomCsrfToken.java index 71b44b51..0d4870bd 100644 --- a/src/main/java/com/genius/gitget/security/domain/CustomCsrfToken.java +++ b/src/main/java/com/genius/gitget/global/security/domain/CustomCsrfToken.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.domain; +package com.genius.gitget.global.security.domain; import org.springframework.security.web.csrf.CsrfToken; diff --git a/src/main/java/com/genius/gitget/security/domain/Token.java b/src/main/java/com/genius/gitget/global/security/domain/Token.java similarity index 90% rename from src/main/java/com/genius/gitget/security/domain/Token.java rename to src/main/java/com/genius/gitget/global/security/domain/Token.java index f08c45be..8d9584fe 100644 --- a/src/main/java/com/genius/gitget/security/domain/Token.java +++ b/src/main/java/com/genius/gitget/global/security/domain/Token.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.domain; +package com.genius.gitget.global.security.domain; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/security/domain/UserPrincipal.java b/src/main/java/com/genius/gitget/global/security/domain/UserPrincipal.java similarity index 94% rename from src/main/java/com/genius/gitget/security/domain/UserPrincipal.java rename to src/main/java/com/genius/gitget/global/security/domain/UserPrincipal.java index 801c285f..6ec0f43c 100644 --- a/src/main/java/com/genius/gitget/security/domain/UserPrincipal.java +++ b/src/main/java/com/genius/gitget/global/security/domain/UserPrincipal.java @@ -1,6 +1,6 @@ -package com.genius.gitget.security.domain; +package com.genius.gitget.global.security.domain; -import com.genius.gitget.user.domain.User; +import com.genius.gitget.challenge.user.domain.User; import java.util.Collection; import java.util.Collections; import java.util.Map; diff --git a/src/main/java/com/genius/gitget/security/dto/TokenDTO.java b/src/main/java/com/genius/gitget/global/security/dto/TokenDTO.java similarity index 50% rename from src/main/java/com/genius/gitget/security/dto/TokenDTO.java rename to src/main/java/com/genius/gitget/global/security/dto/TokenDTO.java index 00b1d1a3..1c601fdc 100644 --- a/src/main/java/com/genius/gitget/security/dto/TokenDTO.java +++ b/src/main/java/com/genius/gitget/global/security/dto/TokenDTO.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.dto; +package com.genius.gitget.global.security.dto; public record TokenDTO(String identifier) { } diff --git a/src/main/java/com/genius/gitget/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java similarity index 84% rename from src/main/java/com/genius/gitget/security/filter/JwtAuthenticationFilter.java rename to src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java index 5f43c83c..e9054f65 100644 --- a/src/main/java/com/genius/gitget/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java @@ -1,12 +1,11 @@ -package com.genius.gitget.security.filter; +package com.genius.gitget.global.security.filter; -import static com.genius.gitget.security.config.SecurityConfig.PERMITTED_URI; -import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.global.security.config.SecurityConfig.PERMITTED_URI; -import com.genius.gitget.security.service.JwtService; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.service.UserService; +import com.genius.gitget.global.security.constants.JwtRule; +import com.genius.gitget.global.security.service.JwtService; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -33,14 +32,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - String accessToken = jwtService.resolveTokenFromCookie(request, ACCESS_PREFIX); + String accessToken = jwtService.resolveTokenFromCookie(request, JwtRule.ACCESS_PREFIX); if (jwtService.validateAccessToken(accessToken)) { setAuthenticationToContext(accessToken); filterChain.doFilter(request, response); return; } - String refreshToken = jwtService.resolveTokenFromCookie(request, REFRESH_PREFIX); + String refreshToken = jwtService.resolveTokenFromCookie(request, JwtRule.REFRESH_PREFIX); User user = findUserByRefreshToken(refreshToken); if (jwtService.validateRefreshToken(refreshToken, user.getIdentifier())) { diff --git a/src/main/java/com/genius/gitget/security/handler/OAuth2FailureHandler.java b/src/main/java/com/genius/gitget/global/security/handler/OAuth2FailureHandler.java similarity index 95% rename from src/main/java/com/genius/gitget/security/handler/OAuth2FailureHandler.java rename to src/main/java/com/genius/gitget/global/security/handler/OAuth2FailureHandler.java index e7ba9e60..12e942ce 100644 --- a/src/main/java/com/genius/gitget/security/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/genius/gitget/global/security/handler/OAuth2FailureHandler.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.handler; +package com.genius.gitget.global.security.handler; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/genius/gitget/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java similarity index 84% rename from src/main/java/com/genius/gitget/security/handler/OAuth2SuccessHandler.java rename to src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java index 3b02e341..3521a9c7 100644 --- a/src/main/java/com/genius/gitget/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java @@ -1,10 +1,10 @@ -package com.genius.gitget.security.handler; +package com.genius.gitget.global.security.handler; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.repository.UserRepository; -import com.genius.gitget.util.exception.BusinessException; -import com.genius.gitget.util.exception.ErrorCode; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/com/genius/gitget/security/info/OAuth2UserInfo.java b/src/main/java/com/genius/gitget/global/security/info/OAuth2UserInfo.java similarity index 85% rename from src/main/java/com/genius/gitget/security/info/OAuth2UserInfo.java rename to src/main/java/com/genius/gitget/global/security/info/OAuth2UserInfo.java index 8ac649bb..73fec3f2 100644 --- a/src/main/java/com/genius/gitget/security/info/OAuth2UserInfo.java +++ b/src/main/java/com/genius/gitget/global/security/info/OAuth2UserInfo.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.info; +package com.genius.gitget.global.security.info; import java.util.Map; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/genius/gitget/security/info/OAuth2UserInfoFactory.java b/src/main/java/com/genius/gitget/global/security/info/OAuth2UserInfoFactory.java similarity index 66% rename from src/main/java/com/genius/gitget/security/info/OAuth2UserInfoFactory.java rename to src/main/java/com/genius/gitget/global/security/info/OAuth2UserInfoFactory.java index 0ecd0456..fcf3b056 100644 --- a/src/main/java/com/genius/gitget/security/info/OAuth2UserInfoFactory.java +++ b/src/main/java/com/genius/gitget/global/security/info/OAuth2UserInfoFactory.java @@ -1,10 +1,10 @@ -package com.genius.gitget.security.info; +package com.genius.gitget.global.security.info; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.security.info.impl.GithubOAuth2UserInfo; -import com.genius.gitget.security.info.impl.GoogleOAuth2UserInfo; -import com.genius.gitget.security.info.impl.KakaoOAuth2UserInfo; -import com.genius.gitget.security.info.impl.NaverOAuth2UserInfo; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.info.impl.GithubOAuth2UserInfo; +import com.genius.gitget.global.security.info.impl.GoogleOAuth2UserInfo; +import com.genius.gitget.global.security.info.impl.KakaoOAuth2UserInfo; +import com.genius.gitget.global.security.info.impl.NaverOAuth2UserInfo; import java.util.Map; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; diff --git a/src/main/java/com/genius/gitget/global/security/info/impl/GithubOAuth2UserInfo.java b/src/main/java/com/genius/gitget/global/security/info/impl/GithubOAuth2UserInfo.java new file mode 100644 index 00000000..0cafb08b --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/info/impl/GithubOAuth2UserInfo.java @@ -0,0 +1,22 @@ +package com.genius.gitget.global.security.info.impl; + +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.info.OAuth2UserInfo; +import java.util.Map; + +public class GithubOAuth2UserInfo extends OAuth2UserInfo { + + public GithubOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getProviderCode() { + return (String) attributes.get(ProviderInfo.GITHUB.getProviderCode()); + } + + @Override + public String getUserIdentifier() { + return (String) attributes.get(ProviderInfo.GITHUB.getIdentifier()); + } +} diff --git a/src/main/java/com/genius/gitget/global/security/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/gitget/global/security/info/impl/GoogleOAuth2UserInfo.java new file mode 100644 index 00000000..a830c6fc --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/info/impl/GoogleOAuth2UserInfo.java @@ -0,0 +1,23 @@ +package com.genius.gitget.global.security.info.impl; + + +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.info.OAuth2UserInfo; +import java.util.Map; + +public class GoogleOAuth2UserInfo extends OAuth2UserInfo { + + public GoogleOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getProviderCode() { + return (String) attributes.get(ProviderInfo.GOOGLE.getProviderCode()); + } + + @Override + public String getUserIdentifier() { + return (String) attributes.get(ProviderInfo.GOOGLE.getIdentifier()); + } +} diff --git a/src/main/java/com/genius/gitget/global/security/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/gitget/global/security/info/impl/KakaoOAuth2UserInfo.java new file mode 100644 index 00000000..c25f155c --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/info/impl/KakaoOAuth2UserInfo.java @@ -0,0 +1,24 @@ +package com.genius.gitget.global.security.info.impl; + +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.info.OAuth2UserInfo; +import java.util.Map; + +public class KakaoOAuth2UserInfo extends OAuth2UserInfo { + private String providerId; + + public KakaoOAuth2UserInfo(Map attributes) { + super((Map) attributes.get(ProviderInfo.KAKAO.getAttributeKey())); + this.providerId = String.valueOf(attributes.get(ProviderInfo.KAKAO.getIdentifier())); + } + + @Override + public String getProviderCode() { + return providerId; + } + + @Override + public String getUserIdentifier() { + return (String) attributes.get(ProviderInfo.KAKAO.getProviderCode()); + } +} diff --git a/src/main/java/com/genius/gitget/global/security/info/impl/NaverOAuth2UserInfo.java b/src/main/java/com/genius/gitget/global/security/info/impl/NaverOAuth2UserInfo.java new file mode 100644 index 00000000..8e70f062 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/info/impl/NaverOAuth2UserInfo.java @@ -0,0 +1,23 @@ +package com.genius.gitget.global.security.info.impl; + + +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.info.OAuth2UserInfo; +import java.util.Map; + +public class NaverOAuth2UserInfo extends OAuth2UserInfo { + + public NaverOAuth2UserInfo(Map attributes) { + super((Map) attributes.get(ProviderInfo.NAVER.getAttributeKey())); + } + + @Override + public String getProviderCode() { + return (String) attributes.get(ProviderInfo.NAVER.getProviderCode()); + } + + @Override + public String getUserIdentifier() { + return (String) attributes.get(ProviderInfo.NAVER.getIdentifier()); + } +} diff --git a/src/main/java/com/genius/gitget/security/repository/TokenRepository.java b/src/main/java/com/genius/gitget/global/security/repository/TokenRepository.java similarity index 63% rename from src/main/java/com/genius/gitget/security/repository/TokenRepository.java rename to src/main/java/com/genius/gitget/global/security/repository/TokenRepository.java index 67e9b07d..d540597b 100644 --- a/src/main/java/com/genius/gitget/security/repository/TokenRepository.java +++ b/src/main/java/com/genius/gitget/global/security/repository/TokenRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.security.repository; +package com.genius.gitget.global.security.repository; -import com.genius.gitget.security.domain.Token; +import com.genius.gitget.global.security.domain.Token; import org.springframework.data.mongodb.repository.MongoRepository; public interface TokenRepository extends MongoRepository { diff --git a/src/main/java/com/genius/gitget/security/service/CustomOAuth2UserService.java b/src/main/java/com/genius/gitget/global/security/service/CustomOAuth2UserService.java similarity index 83% rename from src/main/java/com/genius/gitget/security/service/CustomOAuth2UserService.java rename to src/main/java/com/genius/gitget/global/security/service/CustomOAuth2UserService.java index 9ba22809..4a4ec055 100644 --- a/src/main/java/com/genius/gitget/security/service/CustomOAuth2UserService.java +++ b/src/main/java/com/genius/gitget/global/security/service/CustomOAuth2UserService.java @@ -1,12 +1,12 @@ -package com.genius.gitget.security.service; +package com.genius.gitget.global.security.service; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.security.domain.UserPrincipal; -import com.genius.gitget.security.info.OAuth2UserInfo; -import com.genius.gitget.security.info.OAuth2UserInfoFactory; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.security.info.OAuth2UserInfo; +import com.genius.gitget.global.security.info.OAuth2UserInfoFactory; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; import jakarta.transaction.Transactional; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/com/genius/gitget/security/service/CustomUserDetailsService.java b/src/main/java/com/genius/gitget/global/security/service/CustomUserDetailsService.java similarity index 66% rename from src/main/java/com/genius/gitget/security/service/CustomUserDetailsService.java rename to src/main/java/com/genius/gitget/global/security/service/CustomUserDetailsService.java index 7ed9388a..3d69a7bc 100644 --- a/src/main/java/com/genius/gitget/security/service/CustomUserDetailsService.java +++ b/src/main/java/com/genius/gitget/global/security/service/CustomUserDetailsService.java @@ -1,11 +1,11 @@ -package com.genius.gitget.security.service; +package com.genius.gitget.global.security.service; -import static com.genius.gitget.util.exception.ErrorCode.MEMBER_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; -import com.genius.gitget.security.domain.UserPrincipal; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.repository.UserRepository; -import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.util.exception.BusinessException; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; diff --git a/src/main/java/com/genius/gitget/security/service/JwtGenerator.java b/src/main/java/com/genius/gitget/global/security/service/JwtGenerator.java similarity index 94% rename from src/main/java/com/genius/gitget/security/service/JwtGenerator.java rename to src/main/java/com/genius/gitget/global/security/service/JwtGenerator.java index a141f292..4c2fed46 100644 --- a/src/main/java/com/genius/gitget/security/service/JwtGenerator.java +++ b/src/main/java/com/genius/gitget/global/security/service/JwtGenerator.java @@ -1,6 +1,6 @@ -package com.genius.gitget.security.service; +package com.genius.gitget.global.security.service; -import com.genius.gitget.user.domain.User; +import com.genius.gitget.challenge.user.domain.User; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.security.Key; diff --git a/src/main/java/com/genius/gitget/security/service/JwtService.java b/src/main/java/com/genius/gitget/global/security/service/JwtService.java similarity index 85% rename from src/main/java/com/genius/gitget/security/service/JwtService.java rename to src/main/java/com/genius/gitget/global/security/service/JwtService.java index 795372a7..36af4d15 100644 --- a/src/main/java/com/genius/gitget/security/service/JwtService.java +++ b/src/main/java/com/genius/gitget/global/security/service/JwtService.java @@ -1,19 +1,19 @@ -package com.genius.gitget.security.service; - -import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.gitget.security.constants.JwtRule.JWT_ISSUE_HEADER; -import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; -import static com.genius.gitget.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; -import static com.genius.gitget.util.exception.ErrorCode.TOKEN_NOT_FOUND; - -import com.genius.gitget.security.constants.JwtRule; -import com.genius.gitget.security.constants.TokenStatus; -import com.genius.gitget.security.domain.Token; -import com.genius.gitget.security.repository.TokenRepository; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.util.exception.BusinessException; -import com.genius.gitget.util.exception.ErrorCode; +package com.genius.gitget.global.security.service; + +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.gitget.global.security.constants.JwtRule.JWT_ISSUE_HEADER; +import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; +import static com.genius.gitget.global.util.exception.ErrorCode.TOKEN_NOT_FOUND; + +import com.genius.gitget.global.security.constants.JwtRule; +import com.genius.gitget.global.security.constants.TokenStatus; +import com.genius.gitget.global.security.domain.Token; +import com.genius.gitget.global.security.repository.TokenRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import io.jsonwebtoken.Jwts; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/genius/gitget/security/service/JwtUtil.java b/src/main/java/com/genius/gitget/global/security/service/JwtUtil.java similarity index 83% rename from src/main/java/com/genius/gitget/security/service/JwtUtil.java rename to src/main/java/com/genius/gitget/global/security/service/JwtUtil.java index 0ab4cbcf..b11d7801 100644 --- a/src/main/java/com/genius/gitget/security/service/JwtUtil.java +++ b/src/main/java/com/genius/gitget/global/security/service/JwtUtil.java @@ -1,11 +1,11 @@ -package com.genius.gitget.security.service; +package com.genius.gitget.global.security.service; -import static com.genius.gitget.util.exception.ErrorCode.INVALID_EXPIRED_JWT; -import static com.genius.gitget.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_EXPIRED_JWT; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_JWT; -import com.genius.gitget.security.constants.JwtRule; -import com.genius.gitget.security.constants.TokenStatus; -import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.global.security.constants.JwtRule; +import com.genius.gitget.global.security.constants.TokenStatus; +import com.genius.gitget.global.util.exception.BusinessException; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; diff --git a/src/main/java/com/genius/gitget/security/service/TokenService.java b/src/main/java/com/genius/gitget/global/security/service/TokenService.java similarity index 71% rename from src/main/java/com/genius/gitget/security/service/TokenService.java rename to src/main/java/com/genius/gitget/global/security/service/TokenService.java index bb5f4035..ca596fbf 100644 --- a/src/main/java/com/genius/gitget/security/service/TokenService.java +++ b/src/main/java/com/genius/gitget/global/security/service/TokenService.java @@ -1,9 +1,9 @@ -package com.genius.gitget.security.service; +package com.genius.gitget.global.security.service; -import com.genius.gitget.security.domain.Token; -import com.genius.gitget.security.repository.TokenRepository; -import com.genius.gitget.util.exception.BusinessException; -import com.genius.gitget.util.exception.ErrorCode; +import com.genius.gitget.global.security.domain.Token; +import com.genius.gitget.global.security.repository.TokenRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/genius/gitget/util/config/AppConfig.java b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java similarity index 68% rename from src/main/java/com/genius/gitget/util/config/AppConfig.java rename to src/main/java/com/genius/gitget/global/util/config/AppConfig.java index d7d79a8e..6caed22f 100644 --- a/src/main/java/com/genius/gitget/util/config/AppConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java @@ -1,7 +1,7 @@ -package com.genius.gitget.util.config; +package com.genius.gitget.global.util.config; -import com.genius.gitget.util.formatter.LocalDateFormatter; -import com.genius.gitget.util.formatter.LocalDateTimeFormatter; +import com.genius.gitget.global.util.formatter.LocalDateFormatter; +import com.genius.gitget.global.util.formatter.LocalDateTimeFormatter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/genius/gitget/util/config/SwaggerConfig.java b/src/main/java/com/genius/gitget/global/util/config/SwaggerConfig.java similarity index 92% rename from src/main/java/com/genius/gitget/util/config/SwaggerConfig.java rename to src/main/java/com/genius/gitget/global/util/config/SwaggerConfig.java index a968aa00..5e25d5b7 100644 --- a/src/main/java/com/genius/gitget/util/config/SwaggerConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/SwaggerConfig.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.config; +package com.genius.gitget.global.util.config; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; diff --git a/src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java b/src/main/java/com/genius/gitget/global/util/domain/BaseTimeEntity.java similarity index 94% rename from src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java rename to src/main/java/com/genius/gitget/global/util/domain/BaseTimeEntity.java index c87a3fa8..dbca256d 100644 --- a/src/main/java/com/genius/gitget/common/domain/BaseTimeEntity.java +++ b/src/main/java/com/genius/gitget/global/util/domain/BaseTimeEntity.java @@ -1,10 +1,9 @@ -package com.genius.gitget.common.domain; +package com.genius.gitget.global.util.domain; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; - import lombok.Getter; import lombok.ToString; import org.springframework.data.annotation.CreatedDate; diff --git a/src/main/java/com/genius/gitget/util/exception/BusinessException.java b/src/main/java/com/genius/gitget/global/util/exception/BusinessException.java similarity index 92% rename from src/main/java/com/genius/gitget/util/exception/BusinessException.java rename to src/main/java/com/genius/gitget/global/util/exception/BusinessException.java index 468cc619..85099a8b 100644 --- a/src/main/java/com/genius/gitget/util/exception/BusinessException.java +++ b/src/main/java/com/genius/gitget/global/util/exception/BusinessException.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.exception; +package com.genius.gitget.global.util.exception; import lombok.Getter; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/genius/gitget/util/exception/BusinessExceptionHandler.java b/src/main/java/com/genius/gitget/global/util/exception/BusinessExceptionHandler.java similarity index 85% rename from src/main/java/com/genius/gitget/util/exception/BusinessExceptionHandler.java rename to src/main/java/com/genius/gitget/global/util/exception/BusinessExceptionHandler.java index 7956bfd6..c9151504 100644 --- a/src/main/java/com/genius/gitget/util/exception/BusinessExceptionHandler.java +++ b/src/main/java/com/genius/gitget/global/util/exception/BusinessExceptionHandler.java @@ -1,6 +1,6 @@ -package com.genius.gitget.util.exception; +package com.genius.gitget.global.util.exception; -import com.genius.gitget.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/genius/gitget/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java similarity index 96% rename from src/main/java/com/genius/gitget/util/exception/ErrorCode.java rename to src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 9b4b45d3..f3d91004 100644 --- a/src/main/java/com/genius/gitget/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.exception; +package com.genius.gitget.global.util.exception; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/util/exception/GlobalExceptionHandler.java b/src/main/java/com/genius/gitget/global/util/exception/GlobalExceptionHandler.java similarity index 87% rename from src/main/java/com/genius/gitget/util/exception/GlobalExceptionHandler.java rename to src/main/java/com/genius/gitget/global/util/exception/GlobalExceptionHandler.java index bbc94f45..189caa53 100644 --- a/src/main/java/com/genius/gitget/util/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/genius/gitget/global/util/exception/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ -package com.genius.gitget.util.exception; +package com.genius.gitget.global.util.exception; -import com.genius.gitget.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.CommonResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/genius/gitget/util/exception/SuccessCode.java b/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java similarity index 90% rename from src/main/java/com/genius/gitget/util/exception/SuccessCode.java rename to src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java index 48a7d4e9..edee8294 100644 --- a/src/main/java/com/genius/gitget/util/exception/SuccessCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.exception; +package com.genius.gitget.global.util.exception; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/util/formatter/CommonPattern.java b/src/main/java/com/genius/gitget/global/util/formatter/CommonPattern.java similarity index 90% rename from src/main/java/com/genius/gitget/util/formatter/CommonPattern.java rename to src/main/java/com/genius/gitget/global/util/formatter/CommonPattern.java index c5cc4fa1..c7964586 100644 --- a/src/main/java/com/genius/gitget/util/formatter/CommonPattern.java +++ b/src/main/java/com/genius/gitget/global/util/formatter/CommonPattern.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.formatter; +package com.genius.gitget.global.util.formatter; public final class CommonPattern { public static final String MANAGER_ID_PATTERN = "^(?=.*[a-z])(?=.*\\d)[a-z\\d]{8,16}$"; diff --git a/src/main/java/com/genius/gitget/util/formatter/LocalDateFormatter.java b/src/main/java/com/genius/gitget/global/util/formatter/LocalDateFormatter.java similarity index 92% rename from src/main/java/com/genius/gitget/util/formatter/LocalDateFormatter.java rename to src/main/java/com/genius/gitget/global/util/formatter/LocalDateFormatter.java index 700ddd56..3f1e376c 100644 --- a/src/main/java/com/genius/gitget/util/formatter/LocalDateFormatter.java +++ b/src/main/java/com/genius/gitget/global/util/formatter/LocalDateFormatter.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.formatter; +package com.genius.gitget.global.util.formatter; import java.time.LocalDate; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/com/genius/gitget/util/formatter/LocalDateTimeFormatter.java b/src/main/java/com/genius/gitget/global/util/formatter/LocalDateTimeFormatter.java similarity index 93% rename from src/main/java/com/genius/gitget/util/formatter/LocalDateTimeFormatter.java rename to src/main/java/com/genius/gitget/global/util/formatter/LocalDateTimeFormatter.java index 1a5c52b5..959b9507 100644 --- a/src/main/java/com/genius/gitget/util/formatter/LocalDateTimeFormatter.java +++ b/src/main/java/com/genius/gitget/global/util/formatter/LocalDateTimeFormatter.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.formatter; +package com.genius.gitget.global.util.formatter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; diff --git a/src/main/java/com/genius/gitget/util/response/dto/CommonResponse.java b/src/main/java/com/genius/gitget/global/util/response/dto/CommonResponse.java similarity index 90% rename from src/main/java/com/genius/gitget/util/response/dto/CommonResponse.java rename to src/main/java/com/genius/gitget/global/util/response/dto/CommonResponse.java index 2c4d5510..5091e150 100644 --- a/src/main/java/com/genius/gitget/util/response/dto/CommonResponse.java +++ b/src/main/java/com/genius/gitget/global/util/response/dto/CommonResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.response.dto; +package com.genius.gitget.global.util.response.dto; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/util/response/dto/ListResponse.java b/src/main/java/com/genius/gitget/global/util/response/dto/ListResponse.java similarity index 87% rename from src/main/java/com/genius/gitget/util/response/dto/ListResponse.java rename to src/main/java/com/genius/gitget/global/util/response/dto/ListResponse.java index 3ab6a67e..bf86871d 100644 --- a/src/main/java/com/genius/gitget/util/response/dto/ListResponse.java +++ b/src/main/java/com/genius/gitget/global/util/response/dto/ListResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.response.dto; +package com.genius.gitget.global.util.response.dto; import java.util.List; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java b/src/main/java/com/genius/gitget/global/util/response/dto/PagingResponse.java similarity index 90% rename from src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java rename to src/main/java/com/genius/gitget/global/util/response/dto/PagingResponse.java index 49e7754a..bc955e84 100644 --- a/src/main/java/com/genius/gitget/util/response/dto/PagingResponse.java +++ b/src/main/java/com/genius/gitget/global/util/response/dto/PagingResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.response.dto; +package com.genius.gitget.global.util.response.dto; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/util/response/dto/SingleResponse.java b/src/main/java/com/genius/gitget/global/util/response/dto/SingleResponse.java similarity index 89% rename from src/main/java/com/genius/gitget/util/response/dto/SingleResponse.java rename to src/main/java/com/genius/gitget/global/util/response/dto/SingleResponse.java index 69c6e899..af45565f 100644 --- a/src/main/java/com/genius/gitget/util/response/dto/SingleResponse.java +++ b/src/main/java/com/genius/gitget/global/util/response/dto/SingleResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.response.dto; +package com.genius.gitget.global.util.response.dto; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/util/response/dto/SlicingResponse.java b/src/main/java/com/genius/gitget/global/util/response/dto/SlicingResponse.java similarity index 80% rename from src/main/java/com/genius/gitget/util/response/dto/SlicingResponse.java rename to src/main/java/com/genius/gitget/global/util/response/dto/SlicingResponse.java index 4b3fd984..145a6006 100644 --- a/src/main/java/com/genius/gitget/util/response/dto/SlicingResponse.java +++ b/src/main/java/com/genius/gitget/global/util/response/dto/SlicingResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util.response.dto; +package com.genius.gitget.global.util.response.dto; import org.springframework.data.domain.Slice; diff --git a/src/main/java/com/genius/gitget/participantinfo/domain/JoinStatus.java b/src/main/java/com/genius/gitget/participantinfo/domain/JoinStatus.java deleted file mode 100644 index 44c3f952..00000000 --- a/src/main/java/com/genius/gitget/participantinfo/domain/JoinStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.genius.gitget.participantinfo.domain; - -public enum JoinStatus { - NO, YES -} diff --git a/src/main/java/com/genius/gitget/security/info/impl/GithubOAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/impl/GithubOAuth2UserInfo.java deleted file mode 100644 index 39ee1ef3..00000000 --- a/src/main/java/com/genius/gitget/security/info/impl/GithubOAuth2UserInfo.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.genius.gitget.security.info.impl; - -import static com.genius.gitget.security.constants.ProviderInfo.GITHUB; - -import com.genius.gitget.security.info.OAuth2UserInfo; -import java.util.Map; - -public class GithubOAuth2UserInfo extends OAuth2UserInfo { - - public GithubOAuth2UserInfo(Map attributes) { - super(attributes); - } - - @Override - public String getProviderCode() { - return (String) attributes.get(GITHUB.getProviderCode()); - } - - @Override - public String getUserIdentifier() { - return (String) attributes.get(GITHUB.getIdentifier()); - } -} diff --git a/src/main/java/com/genius/gitget/security/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/impl/GoogleOAuth2UserInfo.java deleted file mode 100644 index 7a03b8af..00000000 --- a/src/main/java/com/genius/gitget/security/info/impl/GoogleOAuth2UserInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.genius.gitget.security.info.impl; - - -import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; - -import com.genius.gitget.security.info.OAuth2UserInfo; -import java.util.Map; - -public class GoogleOAuth2UserInfo extends OAuth2UserInfo { - - public GoogleOAuth2UserInfo(Map attributes) { - super(attributes); - } - - @Override - public String getProviderCode() { - return (String) attributes.get(GOOGLE.getProviderCode()); - } - - @Override - public String getUserIdentifier() { - return (String) attributes.get(GOOGLE.getIdentifier()); - } -} diff --git a/src/main/java/com/genius/gitget/security/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/impl/KakaoOAuth2UserInfo.java deleted file mode 100644 index 54b301dd..00000000 --- a/src/main/java/com/genius/gitget/security/info/impl/KakaoOAuth2UserInfo.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.genius.gitget.security.info.impl; - -import static com.genius.gitget.security.constants.ProviderInfo.KAKAO; - -import com.genius.gitget.security.info.OAuth2UserInfo; -import java.util.Map; - -public class KakaoOAuth2UserInfo extends OAuth2UserInfo { - private String providerId; - - public KakaoOAuth2UserInfo(Map attributes) { - super((Map) attributes.get(KAKAO.getAttributeKey())); - this.providerId = String.valueOf(attributes.get(KAKAO.getIdentifier())); - } - - @Override - public String getProviderCode() { - return providerId; - } - - @Override - public String getUserIdentifier() { - return (String) attributes.get(KAKAO.getProviderCode()); - } -} diff --git a/src/main/java/com/genius/gitget/security/info/impl/NaverOAuth2UserInfo.java b/src/main/java/com/genius/gitget/security/info/impl/NaverOAuth2UserInfo.java deleted file mode 100644 index 0b443194..00000000 --- a/src/main/java/com/genius/gitget/security/info/impl/NaverOAuth2UserInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.genius.gitget.security.info.impl; - - -import static com.genius.gitget.security.constants.ProviderInfo.NAVER; - -import com.genius.gitget.security.info.OAuth2UserInfo; -import java.util.Map; - -public class NaverOAuth2UserInfo extends OAuth2UserInfo { - - public NaverOAuth2UserInfo(Map attributes) { - super((Map) attributes.get(NAVER.getAttributeKey())); - } - - @Override - public String getProviderCode() { - return (String) attributes.get(NAVER.getProviderCode()); - } - - @Override - public String getUserIdentifier() { - return (String) attributes.get(NAVER.getIdentifier()); - } -} diff --git a/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java b/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java index 9d974223..6f6297a0 100644 --- a/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java +++ b/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java @@ -2,8 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.file.domain.FileType; -import com.genius.gitget.file.domain.Files; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.repository.FilesRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java index 1d9e7d6b..d8807076 100644 --- a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java @@ -1,12 +1,13 @@ package com.genius.gitget.file.service; -import static com.genius.gitget.util.exception.ErrorCode.IMAGE_NOT_EXIST; -import static com.genius.gitget.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; +import static com.genius.gitget.global.util.exception.ErrorCode.IMAGE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.genius.gitget.file.dto.UploadDTO; -import com.genius.gitget.util.exception.BusinessException; +import com.genius.gitget.global.file.dto.UploadDTO; +import com.genius.gitget.global.file.service.FileUtil; +import com.genius.gitget.global.util.exception.BusinessException; import java.io.File; import java.io.IOException; import java.io.InputStream; diff --git a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java index 4d653549..c972efd2 100644 --- a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java +++ b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java @@ -1,5 +1,6 @@ package com.genius.gitget.file.service; +import com.genius.gitget.global.file.service.FilesService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; diff --git a/src/test/java/com/genius/gitget/hits/HitsTest.java b/src/test/java/com/genius/gitget/hits/HitsTest.java index 6a785efb..6bf5ca5e 100644 --- a/src/test/java/com/genius/gitget/hits/HitsTest.java +++ b/src/test/java/com/genius/gitget/hits/HitsTest.java @@ -1,19 +1,19 @@ package com.genius.gitget.hits; -import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; -import static com.genius.gitget.user.domain.Role.ADMIN; -import static com.genius.gitget.user.domain.Role.USER; +import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.challenge.user.domain.Role.ADMIN; +import static com.genius.gitget.challenge.user.domain.Role.USER; -import com.genius.gitget.hits.domain.Hits; -import com.genius.gitget.hits.repository.HitsRepository; -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.instance.domain.Progress; -import com.genius.gitget.instance.repository.InstanceRepository; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.challenge.hits.domain.Hits; +import com.genius.gitget.challenge.hits.repository.HitsRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; import java.time.LocalDateTime; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java b/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java index 8ae2a8d9..01cf05b2 100644 --- a/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java +++ b/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java @@ -4,7 +4,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.user.domain.Role; +import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; import jakarta.servlet.http.Cookie; diff --git a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java index 5fb38a88..0a1e0bdf 100644 --- a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java @@ -1,23 +1,24 @@ package com.genius.gitget.security.service; -import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; -import static com.genius.gitget.util.exception.ErrorCode.INVALID_JWT; -import static com.genius.gitget.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; -import static com.genius.gitget.util.exception.ErrorCode.TOKEN_NOT_FOUND; +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; +import static com.genius.gitget.global.util.exception.ErrorCode.TOKEN_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.genius.gitget.security.constants.JwtRule; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.security.repository.TokenRepository; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.JwtRule; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.repository.TokenRepository; +import com.genius.gitget.global.security.service.JwtService; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; -import com.genius.gitget.util.exception.BusinessException; -import com.genius.gitget.util.exception.ErrorCode; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; @@ -224,7 +225,7 @@ public void should_throwException_when_refreshTokenInvalid() { .isInstanceOf(BusinessException.class) .hasMessageContaining(INVALID_JWT.getMessage()); } - + private User getSavedUser() { return userRepository.save(User.builder() diff --git a/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java b/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java index ce5040bf..922e702c 100644 --- a/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java +++ b/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java @@ -2,7 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.security.constants.JwtRule; +import com.genius.gitget.global.security.constants.JwtRule; +import com.genius.gitget.global.security.service.JwtUtil; import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/gitget/security/service/TokenServiceTest.java b/src/test/java/com/genius/gitget/security/service/TokenServiceTest.java index d9030d55..c4a828a1 100644 --- a/src/test/java/com/genius/gitget/security/service/TokenServiceTest.java +++ b/src/test/java/com/genius/gitget/security/service/TokenServiceTest.java @@ -2,8 +2,9 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.security.domain.Token; -import com.genius.gitget.security.repository.TokenRepository; +import com.genius.gitget.global.security.domain.Token; +import com.genius.gitget.global.security.repository.TokenRepository; +import com.genius.gitget.global.security.service.TokenService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java index d74e4e52..ed73ff20 100644 --- a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java @@ -1,27 +1,27 @@ package com.genius.gitget.topic; -import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; -import static com.genius.gitget.user.domain.Role.ADMIN; -import static com.genius.gitget.user.domain.Role.USER; +import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.challenge.user.domain.Role.ADMIN; +import static com.genius.gitget.challenge.user.domain.Role.USER; import static org.hamcrest.Matchers.hasSize; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.hits.repository.HitsRepository; -import com.genius.gitget.instance.domain.Instance; -import com.genius.gitget.instance.domain.Progress; -import com.genius.gitget.instance.repository.InstanceRepository; -import com.genius.gitget.participantinfo.domain.JoinResult; -import com.genius.gitget.participantinfo.domain.JoinStatus; -import com.genius.gitget.participantinfo.domain.ParticipantInfo; -import com.genius.gitget.participantinfo.repository.ParticipantInfoRepository; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; -import com.genius.gitget.topic.service.TopicService; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.challenge.hits.repository.HitsRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participantinfo.domain.JoinResult; +import com.genius.gitget.challenge.participantinfo.domain.JoinStatus; +import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.challenge.participantinfo.repository.ParticipantInfoRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.admin.topic.service.TopicService; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; import java.nio.charset.StandardCharsets; diff --git a/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java b/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java index 92d839a0..f00323c1 100644 --- a/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java +++ b/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java @@ -1,17 +1,17 @@ package com.genius.gitget.user.controller; -import static com.genius.gitget.util.exception.ErrorCode.DUPLICATED_NICKNAME; -import static com.genius.gitget.util.exception.SuccessCode.SUCCESS; +import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/gitget/user/domain/UserTest.java b/src/test/java/com/genius/gitget/user/domain/UserTest.java index cbd91687..ab1afb18 100644 --- a/src/test/java/com/genius/gitget/user/domain/UserTest.java +++ b/src/test/java/com/genius/gitget/user/domain/UserTest.java @@ -1,16 +1,17 @@ package com.genius.gitget.user.domain; -import com.genius.gitget.user.repository.UserRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; import java.util.List; -import static com.genius.gitget.security.constants.ProviderInfo.GOOGLE; -import static com.genius.gitget.security.constants.ProviderInfo.NAVER; -import static com.genius.gitget.user.domain.Role.ADMIN; -import static com.genius.gitget.user.domain.Role.USER; +import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.global.security.constants.ProviderInfo.NAVER; +import static com.genius.gitget.challenge.user.domain.Role.ADMIN; +import static com.genius.gitget.challenge.user.domain.Role.USER; import static org.assertj.core.api.Assertions.*; import static org.xmlunit.util.Linqy.count; diff --git a/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java index b92eaf9f..7a8f51d4 100644 --- a/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java @@ -1,11 +1,12 @@ package com.genius.gitget.user.repository; -import static com.genius.gitget.security.constants.ProviderInfo.GITHUB; +import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/gitget/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/user/service/UserServiceTest.java index 08e03da2..7a2b40bd 100644 --- a/src/test/java/com/genius/gitget/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/user/service/UserServiceTest.java @@ -3,13 +3,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.dto.SignupRequest; -import com.genius.gitget.user.repository.UserRepository; -import com.genius.gitget.util.exception.BusinessException; -import com.genius.gitget.util.exception.ErrorCode; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/gitget/util/TokenTestUtil.java b/src/test/java/com/genius/gitget/util/TokenTestUtil.java index a449ac14..16d987ae 100644 --- a/src/test/java/com/genius/gitget/util/TokenTestUtil.java +++ b/src/test/java/com/genius/gitget/util/TokenTestUtil.java @@ -1,11 +1,11 @@ package com.genius.gitget.util; -import static com.genius.gitget.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.gitget.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; -import com.genius.gitget.security.domain.UserPrincipal; -import com.genius.gitget.security.service.JwtService; -import com.genius.gitget.user.domain.User; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.security.service.JwtService; +import com.genius.gitget.challenge.user.domain.User; import jakarta.servlet.http.Cookie; import lombok.RequiredArgsConstructor; import org.springframework.mock.web.MockHttpServletResponse; diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUser.java b/src/test/java/com/genius/gitget/util/WithMockCustomUser.java index d528a25f..8ab82b70 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUser.java +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUser.java @@ -1,7 +1,7 @@ package com.genius.gitget.util; -import com.genius.gitget.security.constants.ProviderInfo; -import com.genius.gitget.user.domain.Role; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.challenge.user.domain.Role; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.springframework.security.test.context.support.WithSecurityContext; diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java index e9156eb9..1fd18856 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java @@ -1,11 +1,11 @@ package com.genius.gitget.util; -import com.genius.gitget.security.service.CustomUserDetailsService; -import com.genius.gitget.user.domain.Role; -import com.genius.gitget.user.domain.User; -import com.genius.gitget.user.dto.SignupRequest; -import com.genius.gitget.user.repository.UserRepository; -import com.genius.gitget.user.service.UserService; +import com.genius.gitget.global.security.service.CustomUserDetailsService; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.challenge.user.service.UserService; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; From 27bd71465d8e6c43a4e64dcfb571ffd6a885f8a2 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:27:38 +0900 Subject: [PATCH 086/234] =?UTF-8?q?[REFACTOR]=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80/=ED=8C=8C=EC=9D=BC=20=EC=9A=94=EC=B2=AD=EA=B3=BC=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=8B=9C=20base64=20=EC=9D=B8=EC=BD=94?= =?UTF-8?q?=EB=94=A9=ED=95=98=EC=97=AC=20=EC=A0=84=EB=8B=AC=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20(#49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 파일과 관련된 응답 객체 전달 시, base64로 인코딩하여 전달 - 파일/이미지 저장 요청 & 파일/이미지 정보 요청에 대한 응답 시, base64로 인코딩하여 전달하도록 리팩토링 * refactor: base64로 인코딩한 값을 전달하도록 변경 --- .../file/controller/FilesController.java | 27 +++++++++---------- .../gitget/global/file/dto/FileRequest.java | 9 +++++++ .../gitget/global/file/dto/FileResponse.java | 8 +++++- .../gitget/global/file/service/FileUtil.java | 12 +++++++++ .../global/file/service/FilesService.java | 9 ++++++- 5 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/file/dto/FileRequest.java diff --git a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java index ffdbc247..49e74aab 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -1,24 +1,22 @@ package com.genius.gitget.global.file.controller; import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.global.file.dto.FileRequest; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.io.IOException; -import java.net.MalformedURLException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.Resource; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; @Slf4j @RestController @@ -29,22 +27,23 @@ public class FilesController { @PostMapping public ResponseEntity> uploadImage( - @RequestPart(value = "image") MultipartFile image, - @RequestPart(value = "type") String type) throws IOException { + @ModelAttribute FileRequest fileRequest) throws IOException { - FileResponse fileResponse = filesService.uploadFile(image, type); + FileResponse fileResponse = filesService.uploadFile(fileRequest.file(), fileRequest.type()); return ResponseEntity.ok().body( new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), fileResponse) ); } - @GetMapping(value = {"/{fileId}"}, - produces = MediaType.IMAGE_JPEG_VALUE) - public ResponseEntity downloadImage(@PathVariable(name = "fileId") Long fileId) - throws MalformedURLException { + @GetMapping(value = {"/{fileId}"}) + public ResponseEntity> getImage(@PathVariable(name = "fileId") Long fileId) + throws IOException { - Resource urlResource = filesService.getFile(fileId); - return ResponseEntity.ok(urlResource); + FileResponse encodedFile = filesService.getEncodedFile(fileId); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), encodedFile) + ); } } diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileRequest.java b/src/main/java/com/genius/gitget/global/file/dto/FileRequest.java new file mode 100644 index 00000000..80654c22 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/dto/FileRequest.java @@ -0,0 +1,9 @@ +package com.genius.gitget.global.file.dto; + +import org.springframework.web.multipart.MultipartFile; + +public record FileRequest( + MultipartFile file, + String type +) { +} diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index f3cea961..3b8522f9 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -1,4 +1,10 @@ package com.genius.gitget.global.file.dto; -public record FileResponse(Long fileId) { +public record FileResponse( + Long fileId, + String encodedFile) { + + public FileResponse(Long fileId) { + this(fileId, null); + } } diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index d4c7c3fe..ff9ed75f 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -4,12 +4,17 @@ import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.util.exception.BusinessException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.List; import java.util.Objects; import java.util.UUID; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; @@ -22,6 +27,13 @@ public FileUtil(@Value("${file.upload.path}") String uploadPath) { this.uploadPath = uploadPath; } + public String encodedImage(Files files) throws IOException { + UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); + + byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); + return new String(encode, StandardCharsets.UTF_8); + } + public UploadDTO getUploadInfo(MultipartFile file, String typeStr) { String originalFilename = file.getOriginalFilename(); String savedFilename = getSavedFilename(originalFilename); diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index 6dbf331c..d2244a59 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -41,7 +41,7 @@ public FileResponse uploadFile(MultipartFile receivedFile, String typeStr) throw .build(); Files savedFile = filesRepository.save(file); - return new FileResponse(savedFile.getId()); + return new FileResponse(savedFile.getId(), fileUtil.encodedImage(file)); } private void saveFile(MultipartFile receivedFile, String fileURI) throws IOException { @@ -53,6 +53,13 @@ private void saveFile(MultipartFile receivedFile, String fileURI) throws IOExcep receivedFile.transferTo(targetFile); } + public FileResponse getEncodedFile(Long fileId) throws IOException { + Files files = filesRepository.findById(fileId) + .orElseThrow(() -> new BusinessException(ErrorCode.IMAGE_NOT_EXIST)); + + return new FileResponse(fileId, fileUtil.encodedImage(files)); + } + public UrlResource getFile(Long fileId) throws MalformedURLException { Files files = filesRepository.findById(fileId) .orElseThrow(() -> new BusinessException(ErrorCode.IMAGE_NOT_EXIST)); From 89c31c56149fec830bd6b1bccdb7eb9b3873f684 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Tue, 30 Jan 2024 22:32:12 +0900 Subject: [PATCH 087/234] =?UTF-8?q?[FEAT]=20=ED=99=88=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20-=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20(#51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: testBaseUtil 개발 중 패키지 구조 변경으로 인한 커밋 * feat: Home 검색 기능 개발 * refactor: search dto 네임 변경 * test: 단위 테스트 수행 - controller test 중 mongodb 이슈 발생 -> 해결요망 - 검색 기능 postman test 완료 * test: 검색 기능 단위 테스트 * feat: 검색 조건에 따른 키워드 검색 기능 개발 - stringToEnum converter 재정의 - 스프링 빈으로 등록하여 생성한 컨버터 사용 적용 - 검색 조건에 따른 각 계층별 코드 작성 * refactor: 불필요한 코드 제거 * refactor: 코드 리펙토링 --- build.gradle | 5 +- .../gitget/admin/topic/domain/Topic.java | 9 +- .../admin/topic/service/TopicService.java | 8 +- .../challenge/instance/config/WebConfig.java | 15 ++ .../instance/controller/HomeController.java | 33 ++++ .../controller/InstanceController.java | 12 +- .../challenge/instance/domain/Instance.java | 2 +- .../challenge/instance/domain/Progress.java | 13 +- .../instance/domain/StringToEnum.java | 12 ++ .../dto/{ => crud}/InstanceCreateRequest.java | 2 +- .../{ => crud}/InstanceDetailResponse.java | 2 +- .../{ => crud}/InstancePagingResponse.java | 2 +- .../dto/{ => crud}/InstanceUpdateRequest.java | 2 +- .../dto/search/InstanceSearchRequest.java | 9 + .../dto/search/InstanceSearchResponse.java | 15 ++ .../repository/InstanceRepository.java | 2 + .../instance/repository/SearchRepository.java | 17 ++ .../service/InstanceSearchService.java | 43 ++++ .../instance/service/InstanceService.java | 23 ++- .../domain/ParticipantInfo.java | 4 +- .../file/controller/FilesController.java | 2 +- .../security/repository/TokenRepository.java | 2 + .../gitget/file/service/FilesServiceTest.java | 16 +- .../java/com/genius/gitget/hits/HitsTest.java | 1 - .../instance/InstanceControllerTest.java | 8 + .../instance/InstanceRepositoryTest.java | 129 ++++++++++++ .../InstanceSearchRepositoryTest.java | 78 ++++++++ .../instance/InstanceSearchServiceTest.java | 89 +++++++++ .../gitget/instance/InstanceServiceTest.java | 109 +++++++++++ .../gitget/topic/TopicControllerTest.java | 185 ++++-------------- .../gitget/topic/TopicRepositoryTest.java | 97 +++++++++ .../genius/gitget/topic/TopicServiceTest.java | 101 ++++++++++ 32 files changed, 857 insertions(+), 190 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/instance/config/WebConfig.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/controller/HomeController.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/domain/StringToEnum.java rename src/main/java/com/genius/gitget/challenge/instance/dto/{ => crud}/InstanceCreateRequest.java (85%) rename src/main/java/com/genius/gitget/challenge/instance/dto/{ => crud}/InstanceDetailResponse.java (85%) rename src/main/java/com/genius/gitget/challenge/instance/dto/{ => crud}/InstancePagingResponse.java (81%) rename src/main/java/com/genius/gitget/challenge/instance/dto/{ => crud}/InstanceUpdateRequest.java (80%) create mode 100644 src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchRequest.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java create mode 100644 src/test/java/com/genius/gitget/instance/InstanceControllerTest.java create mode 100644 src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/instance/InstanceSearchRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/instance/InstanceSearchServiceTest.java create mode 100644 src/test/java/com/genius/gitget/instance/InstanceServiceTest.java create mode 100644 src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/topic/TopicServiceTest.java diff --git a/build.gradle b/build.gradle index 6b48a617..010a2c66 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-websocket' // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' @@ -35,6 +34,7 @@ dependencies { // query log 띄우기 시작 implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' // query log 띄우기 끝 + //JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' @@ -43,6 +43,9 @@ dependencies { // MongoDB implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + // H2 + runtimeOnly 'com.h2database:h2:1.4.200'; + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' diff --git a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java index d37f61a4..339e2516 100644 --- a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java @@ -59,20 +59,13 @@ public void updateExistInstance(String description) { this.description = description; } - public void createInstance(String title, String description, String tags, int pointPerPerson) { + public void updateNotExistInstance(String title, String description, String tags, int pointPerPerson) { this.title = title; this.description = description; this.tags = tags; this.pointPerPerson = pointPerPerson; } - //== 연관관계 편의 메서드 ==// - public void setInstance(Instance instance) { - instanceList.add(instance); - if (instance.getTopic() != this) { - instance.setTopic(this); - } - } public void setFiles(Files files) { this.files = files; diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index 9330921b..fcc5df85 100644 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -35,7 +35,7 @@ public TopicDetailResponse getTopicById(Long id) { // 토픽 생성 요청 @Transactional - public void createTopic(TopicCreateRequest topicCreateRequest) { + public Long createTopic(TopicCreateRequest topicCreateRequest) { Topic topic = Topic.builder() .title(topicCreateRequest.title()) .description(topicCreateRequest.description()) @@ -44,8 +44,10 @@ public void createTopic(TopicCreateRequest topicCreateRequest) { // 이미지 // 유의사항 .build(); - topicRepository.save(topic); + Topic savedTopic = topicRepository.save(topic); + // 생성된 토픽을 ID로 조회 가능하도록 수정 (01/29) + return savedTopic.getId(); } @Transactional @@ -57,7 +59,7 @@ public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest) { if (hasInstance) { topic.updateExistInstance(topicUpdateRequest.description()); } else { - topic.createInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), + topic.updateNotExistInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), topicUpdateRequest.tags(), topicUpdateRequest.pointPerPerson()); } topicRepository.save(topic); diff --git a/src/main/java/com/genius/gitget/challenge/instance/config/WebConfig.java b/src/main/java/com/genius/gitget/challenge/instance/config/WebConfig.java new file mode 100644 index 00000000..3ae154ec --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/config/WebConfig.java @@ -0,0 +1,15 @@ +package com.genius.gitget.challenge.instance.config; + +import com.genius.gitget.challenge.instance.domain.StringToEnum; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new StringToEnum()); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/HomeController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/HomeController.java new file mode 100644 index 00000000..e7251925 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/HomeController.java @@ -0,0 +1,33 @@ +package com.genius.gitget.challenge.instance.controller; + +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.service.InstanceSearchService; +import com.genius.gitget.global.util.exception.SuccessCode; +import com.genius.gitget.global.util.response.dto.PagingResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class HomeController { + private final InstanceSearchService instanceSearchService; + + @PostMapping("/challenges/search") + public ResponseEntity> searchInstances(@RequestBody InstanceSearchRequest instanceSearchRequest, Pageable pageable) { + + Page searchResults + = instanceSearchService.searchInstances(instanceSearchRequest.keyword(), instanceSearchRequest.progress(), pageable); + + return ResponseEntity.ok().body( + new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), searchResults) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index 790be4b8..ea18130a 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -1,9 +1,9 @@ package com.genius.gitget.challenge.instance.controller; -import com.genius.gitget.challenge.instance.dto.InstanceCreateRequest; -import com.genius.gitget.challenge.instance.dto.InstanceDetailResponse; -import com.genius.gitget.challenge.instance.dto.InstancePagingResponse; -import com.genius.gitget.challenge.instance.dto.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.service.InstanceService; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; @@ -60,7 +60,7 @@ public ResponseEntity updateInstance(@PathVariable Long id, @RequestBody @Valid InstanceUpdateRequest instanceUpdateRequest) { instanceService.updateInstance(id, instanceUpdateRequest); return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) + new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) ); } @@ -69,7 +69,7 @@ public ResponseEntity updateInstance(@PathVariable Long id, public ResponseEntity deleteInstance(@PathVariable Long id) { instanceService.deleteInstance(id); return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) + new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) ); } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index b75721a8..37a1e8d4 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -62,7 +62,6 @@ public class Instance { @NotNull @Enumerated(EnumType.STRING) - // @Column(columnDefinition = "varchar(255) default 'PRE_ACTIVITY'") private Progress progress; @Column(name = "started_at") @@ -106,4 +105,5 @@ public void setTopic(Topic topic) { public void setFiles(Files files) { this.files = files; } + } diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java index bb0a8014..3cb055bf 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java @@ -1,8 +1,15 @@ package com.genius.gitget.challenge.instance.domain; -import lombok.Getter; +import com.fasterxml.jackson.annotation.JsonCreator; -@Getter public enum Progress { - PRE_ACTIVITY, ACTIVITY, DONE + ALL, + PREACTIVITY, + ACTIVITY, + DONE; + +// @JsonCreator +// public static Progress from(String s) { +// return Progress.valueOf(s.toUpperCase()); +// } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/StringToEnum.java b/src/main/java/com/genius/gitget/challenge/instance/domain/StringToEnum.java new file mode 100644 index 00000000..a2f88fa4 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/StringToEnum.java @@ -0,0 +1,12 @@ +package com.genius.gitget.challenge.instance.domain; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@Component +public class StringToEnum implements Converter { + @Override + public Progress convert(String source) { + return Progress.valueOf(source.toUpperCase()); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceCreateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java similarity index 85% rename from src/main/java/com/genius/gitget/challenge/instance/dto/InstanceCreateRequest.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java index 4bdd435a..4e544bdb 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceCreateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.instance.dto; +package com.genius.gitget.challenge.instance.dto.crud; import java.time.LocalDateTime; diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java similarity index 85% rename from src/main/java/com/genius/gitget/challenge/instance/dto/InstanceDetailResponse.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java index f0eef986..3c6a5484 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceDetailResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.instance.dto; +package com.genius.gitget.challenge.instance.dto.crud; import java.time.LocalDateTime; diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/InstancePagingResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java similarity index 81% rename from src/main/java/com/genius/gitget/challenge/instance/dto/InstancePagingResponse.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java index 7fde0599..7506929d 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/InstancePagingResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.instance.dto; +package com.genius.gitget.challenge.instance.dto.crud; import java.time.LocalDateTime; diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceUpdateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java similarity index 80% rename from src/main/java/com/genius/gitget/challenge/instance/dto/InstanceUpdateRequest.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java index 1b483c8c..85ca5407 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/InstanceUpdateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.instance.dto; +package com.genius.gitget.challenge.instance.dto.crud; import java.time.LocalDateTime; diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchRequest.java new file mode 100644 index 00000000..6e9b6ad0 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchRequest.java @@ -0,0 +1,9 @@ +package com.genius.gitget.challenge.instance.dto.search; + +import lombok.Builder; + +@Builder +public record InstanceSearchRequest( + String keyword, + String progress) { +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java new file mode 100644 index 00000000..c031efad --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -0,0 +1,15 @@ +package com.genius.gitget.challenge.instance.dto.search; + +import com.genius.gitget.challenge.instance.domain.Instance; + +public record InstanceSearchResponse( + Long topicId, + Long instanceId, + String keyword, + int pointPerPerson, + int participantCount +) { + public InstanceSearchResponse(Instance instance) { + this(instance.getTopic().getId(), instance.getId(), instance.getTitle(), instance.getPointPerPerson(),instance.getJoinPeopleCount()); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java index 2917b745..28ea1b34 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java @@ -10,4 +10,6 @@ public interface InstanceRepository extends JpaRepository { @Query("select i from Instance i ORDER BY i.id DESC ") Page findAllById(Pageable pageable); + + } diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepository.java new file mode 100644 index 00000000..e36664ab --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepository.java @@ -0,0 +1,17 @@ +package com.genius.gitget.challenge.instance.repository; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface SearchRepository extends JpaRepository { + + // 검색 키워드인 제목을 포함하는 경우 + Page findByTitleContainingOrderByStartedDateDesc(String title, Pageable pageable); + + // 모집 진행 상황의 조건과 일치하는 검색 키워드인 제목을 포함하는 경우 + Page findByProgressAndTitleContainingOrderByStartedDateDesc(Progress progress, String title, Pageable pageable); +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java new file mode 100644 index 00000000..e29e7422 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java @@ -0,0 +1,43 @@ +package com.genius.gitget.challenge.instance.service; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.domain.StringToEnum; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; +import com.genius.gitget.challenge.instance.repository.SearchRepository; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class InstanceSearchService { + + private final SearchRepository searchRepository; + private final StringToEnum stringToEnum; + + public Page searchInstances(String keyword, String progress, Pageable pageable) { + Page findByTitleContaining; + + if (stringToEnum.convert(progress) == Progress.ALL) { + findByTitleContaining = searchRepository.findByTitleContainingOrderByStartedDateDesc(keyword, pageable); + } else { + Progress convertProgress = stringToEnum.convert(progress); // Progress convertProgress = Progress.from(progress); + findByTitleContaining = searchRepository.findByProgressAndTitleContainingOrderByStartedDateDesc(convertProgress, keyword, pageable); + + } + return findByTitleContaining.map(this::convertToInstanceSearchResponse); + } + + private InstanceSearchResponse convertToInstanceSearchResponse(Instance instance) { + return new InstanceSearchResponse(instance); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index 148690c5..57bf843b 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -1,9 +1,9 @@ package com.genius.gitget.challenge.instance.service; -import com.genius.gitget.challenge.instance.dto.InstanceCreateRequest; -import com.genius.gitget.challenge.instance.dto.InstanceDetailResponse; -import com.genius.gitget.challenge.instance.dto.InstancePagingResponse; -import com.genius.gitget.challenge.instance.dto.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.global.util.exception.BusinessException; @@ -29,21 +29,24 @@ public class InstanceService { // 인스턴스 생성 @Transactional - public void createInstance(InstanceCreateRequest instanceCreateRequest) { + public Long createInstance(InstanceCreateRequest instanceCreateRequest) { Topic topic = topicRepository.findById(instanceCreateRequest.topicId()) .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); Instance instance = Instance.builder() + .title(instanceCreateRequest.title()) .description(instanceCreateRequest.description()) .pointPerPerson(instanceCreateRequest.pointPerPerson()) .startedDate(instanceCreateRequest.startedAt()) .completedDate(instanceCreateRequest.completedAt()) - .progress(Progress.PRE_ACTIVITY) + .progress(Progress.PREACTIVITY) .build(); instance.setTopic(topic); - instanceRepository.save(instance); + Instance savedInstance = instanceRepository.save(instance); + + return savedInstance.getId(); } @@ -79,13 +82,15 @@ public void deleteInstance(Long id) { // 인스턴스 수정 @Transactional - public void updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest) { + public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest) { Instance existingInstance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.pointPerPerson(), instanceUpdateRequest.startedAt(), instanceUpdateRequest.completedAt()); - instanceRepository.save(existingInstance); + Instance savedInstance = instanceRepository.save(existingInstance); + + return savedInstance.getId(); } } diff --git a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java index 5b569717..93e042ca 100644 --- a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java +++ b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java @@ -53,16 +53,16 @@ public void setUserAndInstance(User user, Instance instance) { } private void addParticipantInfoForUser(User user) { + this.user = user; if (!(user.getParticipantInfoList().contains(this))) { user.getParticipantInfoList().add(this); } - this.user = user; } private void addParticipantInfoForInstance(Instance instance) { + this.instance = instance; if (!(instance.getParticipantInfoList().contains(this))) { instance.getParticipantInfoList().add(this); } - this.instance = instance; } } diff --git a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java index 49e74aab..65f2cb3c 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -46,4 +46,4 @@ public ResponseEntity> getImage(@PathVariable(name new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), encodedFile) ); } -} +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/security/repository/TokenRepository.java b/src/main/java/com/genius/gitget/global/security/repository/TokenRepository.java index d540597b..b0064c77 100644 --- a/src/main/java/com/genius/gitget/global/security/repository/TokenRepository.java +++ b/src/main/java/com/genius/gitget/global/security/repository/TokenRepository.java @@ -2,6 +2,8 @@ import com.genius.gitget.global.security.domain.Token; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; +import org.springframework.stereotype.Repository; public interface TokenRepository extends MongoRepository { Token findByIdentifier(String identifier); diff --git a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java index c972efd2..9570c02e 100644 --- a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java +++ b/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java @@ -8,8 +8,18 @@ @SpringBootTest @Transactional class FilesServiceTest { - @Autowired - private FilesService filesService; - +// @Autowired +// private FilesService filesService; +// +// @Test +// @DisplayName("") +// public void (){ +// //given +// +// //when +// +// //then +// +// } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/hits/HitsTest.java b/src/test/java/com/genius/gitget/hits/HitsTest.java index 6bf5ca5e..cf8e373e 100644 --- a/src/test/java/com/genius/gitget/hits/HitsTest.java +++ b/src/test/java/com/genius/gitget/hits/HitsTest.java @@ -78,7 +78,6 @@ public void setup() { userRepository.save(user2); topicRepository.save(topic1); - topic1.setInstance(instance1); instance1.setTopic(topic1); instanceRepository.save(instance1); } diff --git a/src/test/java/com/genius/gitget/instance/InstanceControllerTest.java b/src/test/java/com/genius/gitget/instance/InstanceControllerTest.java new file mode 100644 index 00000000..8c02bc07 --- /dev/null +++ b/src/test/java/com/genius/gitget/instance/InstanceControllerTest.java @@ -0,0 +1,8 @@ +package com.genius.gitget.instance; + + +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; + + +public class InstanceControllerTest { +} diff --git a/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java new file mode 100644 index 00000000..c0f0e5fb --- /dev/null +++ b/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java @@ -0,0 +1,129 @@ +package com.genius.gitget.instance; + +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@SpringBootTest +@Transactional +@Rollback +@Slf4j +public class InstanceRepositoryTest { + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + + @Test + public void 인스턴스_생성() throws Exception { + //given + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + Instance savedInstance = instanceRepository.save(instance); + + //then + Assertions.assertThat(savedInstance.getId()).isEqualTo(1L); + } + + @Test + public void 인스턴스_수정() throws Exception { + //given + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + Instance savedInstance = instanceRepository.save(instance); + savedInstance.updateInstance("수정되었습니다.", 10000, LocalDateTime.now(), LocalDateTime.now().plusDays(5)); + + //then + Assertions.assertThat(instance.getDescription()).isEqualTo(savedInstance.getDescription()); + } + + @Test + public void 인스턴스_조회() throws Exception { + //given + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + Instance savedInstance = instanceRepository.save(instance); + + //then + Assertions.assertThat(savedInstance.getTitle()).isEqualTo("1일 1알고리즘"); + } + + @Test + public void 인스턴스_리스트_조회() throws Exception { + //given + Instance instance1 = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + Instance instance2 = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + instanceRepository.save(instance1); + instanceRepository.save(instance2); + + Page instances = instanceRepository.findAllById(PageRequest.of(0, 5, Sort.Direction.DESC, "id")); + + for (Instance instance : instances) { + if (instance != null) { + System.out.println("instance = " + instance.getId() + " " + instance.getTitle()); + } + } + + //then + Assertions.assertThat(instances.getTotalElements()).isEqualTo(2); + } +} diff --git a/src/test/java/com/genius/gitget/instance/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/instance/InstanceSearchRepositoryTest.java new file mode 100644 index 00000000..d1747c5e --- /dev/null +++ b/src/test/java/com/genius/gitget/instance/InstanceSearchRepositoryTest.java @@ -0,0 +1,78 @@ +package com.genius.gitget.instance; + +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.instance.repository.SearchRepository; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@SpringBootTest +@Transactional +@Rollback +@Slf4j +public class InstanceSearchRepositoryTest { + @Autowired + SearchRepository searchRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + + @Test + public void 인스턴스_검색() throws Exception { + //given + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + Instance instance1 = Instance.builder() + .title("1일 2알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.DONE) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + Instance instance2 = Instance.builder() + .title("1일 3알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + instanceRepository.save(instance); + instanceRepository.save(instance1); + instanceRepository.save(instance2); + + + //then + Page order = searchRepository.findByProgressAndTitleContainingOrderByStartedDateDesc( Progress.PREACTIVITY, "고리" , PageRequest.of(0, 3)); + for (Instance item : order) { + System.out.println("item = " + item); + } + + Assertions.assertThat(order.getTotalElements()).isEqualTo(2); + } + +} diff --git a/src/test/java/com/genius/gitget/instance/InstanceSearchServiceTest.java b/src/test/java/com/genius/gitget/instance/InstanceSearchServiceTest.java new file mode 100644 index 00000000..d34241c9 --- /dev/null +++ b/src/test/java/com/genius/gitget/instance/InstanceSearchServiceTest.java @@ -0,0 +1,89 @@ +package com.genius.gitget.instance; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.instance.repository.SearchRepository; +import com.genius.gitget.challenge.instance.service.InstanceSearchService; +import com.genius.gitget.challenge.instance.service.InstanceService; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@SpringBootTest +@Transactional +@Rollback +@Slf4j +public class InstanceSearchServiceTest { + @Autowired + SearchRepository searchRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + InstanceSearchService instanceSearchService; + @Autowired + InstanceService instanceService; + + + @Test + public void 인스턴스_검색() throws Exception { + //given + Topic topic = Topic.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .build(); + + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + Topic savedTopic = topicRepository.save(topic); + + instanceService.createInstance(new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); + + instanceService.createInstance(new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); + + instanceService.createInstance(new InstanceCreateRequest(savedTopic.getId(), "테스트", instance.getTags(), instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); + + + //when + InstanceSearchRequest instanceSearchRequest = new InstanceSearchRequest("고리", "preactivity"); + + + //then + Page orderList = instanceSearchService.searchInstances("고리", "preactivity", PageRequest.of(0, 3)); + + for (InstanceSearchResponse instanceSearchResponse : orderList) { + System.out.println("instanceSearchResponse = " + instanceSearchResponse.keyword()); + } + + Assertions.assertThat(orderList.getTotalElements()).isEqualTo(2); + + } +} diff --git a/src/test/java/com/genius/gitget/instance/InstanceServiceTest.java b/src/test/java/com/genius/gitget/instance/InstanceServiceTest.java new file mode 100644 index 00000000..f732fccd --- /dev/null +++ b/src/test/java/com/genius/gitget/instance/InstanceServiceTest.java @@ -0,0 +1,109 @@ +package com.genius.gitget.instance; + + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.instance.service.InstanceService; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Optional; + +@SpringBootTest +@Transactional +@Rollback +public class InstanceServiceTest { + @Autowired + InstanceService instanceService; + + @Autowired + InstanceRepository instanceRepository; + + @Autowired + TopicRepository topicRepository; + + private Instance instance; + private Topic topic; + + @BeforeEach + public void setup() { + instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + topic = Topic.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .build(); + } + + @Test + public void 인스턴스_생성() throws Exception { + //given + Topic savedTopic = topicRepository.save(topic); + InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); + + //when + Long savedInstanceId = instanceService.createInstance(instanceCreateRequest); + + //then + Optional byId = instanceRepository.findById(savedInstanceId); + Assertions.assertThat(byId.get().getId()).isEqualTo(savedInstanceId); + } + + @Test + public void 인스턴스_수정() throws Exception { + //given + Topic savedTopic = topicRepository.save(topic); + + InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); + Long savedInstanceId = instanceService.createInstance(instanceCreateRequest); + + InstanceUpdateRequest instanceUpdateRequest = new InstanceUpdateRequest(savedTopic.getId(), "이것은 수정본이지롱", + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); + + //when + Long updatedInstanceId = instanceService.updateInstance(savedInstanceId, instanceUpdateRequest); + + //then + Optional byId = instanceRepository.findById(updatedInstanceId); + Assertions.assertThat(byId.get().getDescription()).isEqualTo("이것은 수정본이지롱"); + } + + @Test + public void 인스턴스_단건_조회() throws Exception { + //given + Topic savedTopic = topicRepository.save(topic); + + InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); + Long savedInstanceId = instanceService.createInstance(instanceCreateRequest); + + //when + InstanceDetailResponse instanceById = instanceService.getInstanceById(savedInstanceId); + + //then + Assertions.assertThat(instanceById.title()).isEqualTo(instanceCreateRequest.title()); + } +} diff --git a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java index ed73ff20..d07a7b34 100644 --- a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java @@ -1,177 +1,66 @@ package com.genius.gitget.topic; -import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; -import static com.genius.gitget.challenge.user.domain.Role.ADMIN; -import static com.genius.gitget.challenge.user.domain.Role.USER; -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.genius.gitget.challenge.hits.repository.HitsRepository; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.participantinfo.domain.JoinResult; -import com.genius.gitget.challenge.participantinfo.domain.JoinStatus; -import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; -import com.genius.gitget.challenge.participantinfo.repository.ParticipantInfoRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.admin.topic.service.TopicService; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; +import jakarta.servlet.http.Cookie; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; + import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.http.MediaType; +import org.springframework.test.annotation.Rollback; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; -@SpringBootTest -@Transactional -@AutoConfigureMockMvc -public class TopicControllerTest { +import java.nio.charset.StandardCharsets; - protected MediaType contentType = - new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), - StandardCharsets.UTF_8); - @Autowired - UserRepository userRepository; - @Autowired - InstanceRepository instanceRepository; - @Autowired - HitsRepository hitsRepository; - @Autowired - TopicRepository topicRepository; +import static com.genius.gitget.challenge.user.domain.Role.USER; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest({ TopicControllerTest.class }) +@WithMockCustomUser(role = Role.USER) +@MockBean(JpaMetamodelMappingContext.class) +public class TopicControllerTest { @Autowired - ParticipantInfoRepository participantInfoRepository; + MockMvc mockMvc; @Autowired private TokenTestUtil tokenTestUtil; - @Autowired - private MockMvc mockMvc; + WebApplicationContext context; @MockBean - private TopicService topicService; - private User user1, user2; - private Instance instance1; - private Topic topic1, topic2; - private ParticipantInfo participantInfo1; - private ParticipantInfo participantInfo2; + TopicService topicService; + @BeforeEach public void setup() { - user1 = User.builder().identifier("neo5188@gmail.com") - .providerInfo(ProviderInfo.NAVER) - .nickname("kimdozzi") - .information("백엔드") - .interest("운동") - .role(ADMIN) - .build(); - - user2 = User.builder().identifier("ssang23@naver.com") - .providerInfo(GOOGLE) - .nickname("SEONG") - .information("프론트엔드") - .interest("영화") - .role(USER) - .build(); - - instance1 = Instance.builder() - .title("1일 1커밋") - .description("챌린지 세부사항입니다.") - .pointPerPerson(10) - .tags("BE, CS") - .progress(Progress.ACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - topic1 = Topic.builder() - .title("1일 1커밋") - .description("간단한 설명란") - .pointPerPerson(300) - .tags("BE, CS") - .build(); - - topic2 = Topic.builder() - .title("1일 2커밋") - .description("간단한 설명란") - .pointPerPerson(300) - .tags("BE, CS") - .build(); - - participantInfo1 = ParticipantInfo.builder() - .joinResult(JoinResult.PROCESSING) - .joinStatus(JoinStatus.YES) - .build(); - - participantInfo2 = ParticipantInfo.builder() - .joinResult(JoinResult.SUCCESS) - .joinStatus(JoinStatus.YES) + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) .build(); - - userRepository.save(user1); - userRepository.save(user2); - - topicRepository.save(topic1); - topicRepository.save(topic2); - - topic1.setInstance(instance1); - instance1.setTopic(topic1); - instanceRepository.save(instance1); - - participantInfo1.setUserAndInstance(user1, instance1); - participantInfoRepository.save(participantInfo1); - participantInfo2.setUserAndInstance(user2, instance1); - participantInfoRepository.save(participantInfo2); - } - @Test - @WithMockCustomUser - public void 토픽_조회() throws Exception { - Pageable pageable = PageRequest.of(0, 5, Sort.Direction.DESC, "id"); - List topics = Arrays.asList(topic1, topic2); - PageImpl topicPage = new PageImpl<>(topics, pageable, topics.size()); - -// when(topicService.getAllTopics(pageable)).thenReturn(topicPage); - - for (Topic topic : topicPage) { - System.out.println("topic.getInstanceList() = " + topic.getInstanceList()); - System.out.println("topic.getTitle() = " + topic.getTitle()); - } +// @Test +// public void 토픽_생성() throws Exception { +// //given +// +// //when +// mockMvc.perform(get("/api/admin/topic") +// .cookie(tokenTestUtil.createAccessCookie())) +// .andExpect(status().isOk()); +// } - System.out.println("topics.size() = " + topics.size()); - - // When & Then - mockMvc.perform(get("/api/admin/topic?page=0&size=5") - .cookie(tokenTestUtil.createAccessCookie()) - .contentType(contentType)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.content", hasSize(2))) - .andExpect(jsonPath("$.content[0].title").value("1일 1커밋")) - .andExpect(jsonPath("$.content[0].tags").value("BE, CS")) - .andExpect(jsonPath("$.content[0].description").value("간단한 설명란")) - .andExpect(jsonPath("$.content[0].point_per_person").value(300)) - .andExpect(jsonPath("$.content[1].title").value("1일 2커밋")) - .andExpect(jsonPath("$.content[1].tags").value("BE, CS")) - .andExpect(jsonPath("$.content[1].description").value("간단한 설명란")) - .andExpect(jsonPath("$.content[1].point_per_person").value(300)); - } } diff --git a/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java b/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java new file mode 100644 index 00000000..44076f40 --- /dev/null +++ b/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java @@ -0,0 +1,97 @@ +package com.genius.gitget.topic; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import jakarta.transaction.Transactional; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.Rollback; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +@Transactional +@Rollback +public class TopicRepositoryTest { + @Autowired + private TopicRepository topicRepository; + public Topic topic, topicA; + + @BeforeEach + public void setup() { + topic = Topic.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .build(); + + topicA = Topic.builder() + .title("1일 2알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(300) + .build(); + } + + @Test + public void 토픽_저장() { + Topic savedTopic = topicRepository.save(topic); + assertEquals(topic.getId(), savedTopic.getId()); + assertEquals(topic.getTitle(), savedTopic.getTitle()); + assertEquals(topic.getDescription(), savedTopic.getDescription()); + } + + @Test + public void 토픽_수정() { + Topic savedTopic = topicRepository.save(topic); + if (!topic.getInstanceList().isEmpty()) { + savedTopic.updateExistInstance("(수정) 하루에 두 문제씩 문제를 해결합니다."); + } else { + savedTopic.updateNotExistInstance("1일 1커밋", "하루에 1커밋 하기", "CS", 300); + } + assertEquals(topic.getId(), savedTopic.getId()); + assertEquals(topic.getTitle(), savedTopic.getTitle()); + assertEquals(topic.getDescription(), savedTopic.getDescription()); + + } + + @Test + public void 토픽_삭제() { + Topic savedTopic = topicRepository.save(topic); + topicRepository.delete(savedTopic); + Optional byId = topicRepository.findById(1L); + Assertions.assertThat(byId).isNotPresent(); + } + + + @Test + public void 토픽_리스트_조회() { + for (int i=1; i<=10; i++) { + topicRepository.save( + Topic.builder().title("user"+i+"L").build() + ); + } + Page allById = topicRepository.findAllById(PageRequest.of(0, 5)); + Assertions.assertThat(allById.getSize()).isEqualTo(5); + + for (Topic topic1 : allById) { + System.out.println("topic1.getTitle() = " + topic1.getTitle()); + } + } + + @Test + public void 토픽_단건_조회() { + Topic savedTopic = topicRepository.save(topic); + Optional byId = topicRepository.findById(savedTopic.getId()); + + Assertions.assertThat(byId.get().getTitle()).isEqualTo("1일 1알고리즘"); + } +} diff --git a/src/test/java/com/genius/gitget/topic/TopicServiceTest.java b/src/test/java/com/genius/gitget/topic/TopicServiceTest.java new file mode 100644 index 00000000..71111953 --- /dev/null +++ b/src/test/java/com/genius/gitget/topic/TopicServiceTest.java @@ -0,0 +1,101 @@ +package com.genius.gitget.topic; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.dto.TopicCreateRequest; +import com.genius.gitget.admin.topic.dto.TopicDetailResponse; +import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.admin.topic.service.TopicService; +import com.genius.gitget.global.util.exception.BusinessException; +import jakarta.transaction.Transactional; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; + +import java.util.Optional; + +@SpringBootTest +@Transactional +@Rollback +public class TopicServiceTest { + @Autowired + TopicService topicService; + @Autowired + TopicRepository topicRepository; + + + public Topic topic, topicA; + + @BeforeEach + public void setup() { + topic = Topic.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .build(); + + topicA = Topic.builder() + .title("1일 2알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(300) + .build(); + } + + @Test + public void 토픽_생성() throws Exception { + //given + TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), topic.getTags(), topic.getPointPerPerson()); + Long savedTopicId = topicService.createTopic(topicCreateRequest); + + //when + TopicDetailResponse topicById = topicService.getTopicById(savedTopicId); + + //then + Assertions.assertThat(topicById.title()).isEqualTo(topicCreateRequest.title()); + } + + @Test + public void 토픽_수정() throws Exception { + //given + TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), topic.getTags(), topic.getPointPerPerson()); + Long savedTopicId = topicService.createTopic(topicCreateRequest); + + //when + TopicUpdateRequest topicUpdateRequest = new TopicUpdateRequest("1일 5커밋", topic.getDescription(), topic.getTags(), topic.getPointPerPerson()); + topicService.updateTopic(savedTopicId, topicUpdateRequest); + + //then + Optional findTopic = topicRepository.findById(savedTopicId); + Topic findUpdatedTopic = findTopic.get(); + Assertions.assertThat(findUpdatedTopic.getTitle()).isEqualTo("1일 5커밋"); + } + + @Test + public void 토픽_삭제() throws Exception { + //given + TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), topic.getTags(), topic.getPointPerPerson()); + Long savedTopicId = topicService.createTopic(topicCreateRequest); + + //when + topicService.deleteTopic(savedTopicId); + + //then + org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, () -> { + topicService.getTopicById(1L); + }); +// +// Assertions.assertThatThrownBy(()-> topicService.getTopicById(1L)) +// .isInstanceOf(BusinessException.class); +// +// try { +// topicService.getTopicById(savedTopicId); +// } catch (BusinessException e) { +// org.junit.jupiter.api.Assertions.assertEquals("해당 토픽을 찾을 수 없습니다.", e.getMessage()); +// } + } +} From 64b18a0b23796ab4448844f03f4e9877074002b4 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:01:18 +0900 Subject: [PATCH 088/234] =?UTF-8?q?[FEAT]=20=ED=99=88=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20-=20=EC=B6=94=EC=B2=9C/=EC=9D=B8=EA=B8=B0/=EC=8B=A0=EA=B7=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Squashed commit of the following: commit 77f54caa2f77b9ce040738b2a2f642b03a84d1d8 Author: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri Jan 26 16:33:27 2024 +0900 chore: 패키지 구조 변경 - admin 패키지: 관리자 관련 기능과 밀접한 기능들 - challenge 패키지: 사용자 관련 기능과 밀접한 기능들 - global 패키지: file, security, util 같은 서비스 전체에 영향을 줄 수 있는 기능들 commit 8929e06e2cbb61717b00f76ae08af526d09a2fee Author: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri Jan 26 16:21:42 2024 +0900 [TEST] JWT, 회원가입 로직 테스트 코드 추가 (#47) * Squashed commit of the following: commit 025fd0c5faf4a2d6ab159f4b0a95c8c7cde751a3 Author: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu Jan 25 23:15:25 2024 +0900 !HOTFIX: conflict resolve 해결 commit d2f1db695856489b13de47accb700c9432051ff3 Author: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu Jan 25 23:14:14 2024 +0900 [FIX] JWT 재발급 관련 버그 픽스 (#45) * fix: access-token 재발급 안되는 버그 수정 * fix: 예외 처리 로직 추가 및 무한 리다이렉션 버그 픽스 - Cookie로부터 토큰을 얻을 때, cookie가 비어있을 때 예외 처리 로직 추가 - JWT 토큰 요청 시, 사용자의 권한이 NOT_REGISTERED(가입 이전)이라면 JWT 토큰 발급 거부 로직 추가 - refresh-token이 비어있는 경우 예외 처리 * chore: test 코드 정리 commit 42f6e36ae95189025342ee0730485d7c6e37cd20 Author: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu Jan 25 21:43:28 2024 +0900 !HOTFIX: 회원가입 기능 핫픽스 - 회원가입 완료 후 반환하는 Response 객체의 구조 변경 commit f9a0e3320212a76c1fc33f291c4146b93d81963a Author: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu Jan 25 18:09:04 2024 +0900 Update issue templates * test: JwtService에 대한 테스트 코드 추가 * test: JWT 테스트 코드 추가 * test: 회원가입 관련 테스트 코드 추가 * feat: InstanceRepository에 추천 인스턴스들을 받는 메서드 추가 - Instance 엔티티의 '참여자 수' 필드 추가 및 업데이트 메서드 추가 - InstanceRepository에 조건에 맞는 추천 인스턴스들을 받아오는 메서드 추가 - 추천 인스턴스를 받아오는 Repository 테스트 코드 추가 * chore: User의 관심사 필드의 이름 변경 User의 관심사 필드의 이름을 interest에서 tags로 변경하여, 다른 엔티티들과 통일 * refactor: 추천 인스턴스의 대상을 진행 중인 인스턴스로 변경 * feat: 추천 인스턴스를 반환하는 서비스 로직 구현 및 테스트 코드 추가 - 추천 인스턴스 페이징용 DTO인 RecommendPagingResponse 작성 - 추천 인스턴스 결과를 페이징 형식으로 반환 * feat: 홈 화면의 추천 챌린지 추천 컨트롤러 개발 - 컨트롤러 개발 및 테스트 코드 작성 * refactor: 추천 챌린지의 대상을 '시작 전'으로 변경 * chore: DTO 이름 및 테스트 코드 수정 * feat: 신규 & 인기 인스턴스 레포지토리 구현 및 테스트 코드 추가 * refactor: 파일 시스템 예외 상황 처리 - 인스턴스의 경우 파일이 존재하지 않을 수 있으므로, Optional을 반환하도록 변경 - FileResponse에서 파일이 존재하지 않는 경우에 호출할 팩토리 메서드 추가 * feat: 홈 화면의 인기/신규 기능 개발 * refactor: 파일 시스템 구조 변경 - FilesController: 파일 업로드 시, DTO와 이미지를 같이 받을 수 있는 예시로 변경 - FileUtil: 메서드들을 모두 static으로 변경 - FilesService: UPLOAD_PATH 관리 위치 변경 - FileResponse: 팩토리 메서드를 통해 생성할 수 있도록 추가 * refactor: 불필요한 메서드에 대해 리팩토링 진행 * feat: 파일(이미지) 갱신 기능 구현 - FileId(PK)와 변경하고싶은 파일(MultipartFile)을 받아 기존에 존재했던 파일(이미지)는 삭제하고, 전달받은 파일로 갱신하는 기능 구현 - 파일 갱신 테스트용 API 구현 * feat: 파일(이미지) 삭제 로직 추가 - 삭제하고자하는 Files 엔티티의 PK를 전달했을 때, 삭제하는 기능 구현 - 저장소에 저장되어 있던 파일(이미지) 삭제 - Files 엔티티 삭제 --- .../home/controller/HomeController.java | 63 +++++++++ .../home/dto/HomeInstanceResponse.java | 32 +++++ .../challenge/home/service/HomeService.java | 52 ++++++++ .../challenge/instance/domain/Instance.java | 22 ++- .../repository/InstanceRepository.java | 8 ++ .../gitget/challenge/user/domain/User.java | 10 +- .../file/controller/FilesController.java | 37 ++++- .../gitget/global/file/domain/Files.java | 8 ++ .../gitget/global/file/dto/FileResponse.java | 12 +- .../gitget/global/file/dto/UpdateDTO.java | 11 ++ .../gitget/global/file/service/FileUtil.java | 39 +++--- .../global/file/service/FilesService.java | 72 +++++++--- .../global/util/exception/ErrorCode.java | 5 +- .../util/response/dto/SlicingResponse.java | 10 ++ .../home/controller/HomeControllerTest.java | 95 +++++++++++++ .../home/service/HomeServiceTest.java | 87 ++++++++++++ .../repository/InstanceRepositoryTest.java | 126 ++++++++++++++++++ .../gitget/file/service/FileUtilTest.java | 41 ++++-- .../gitget/global/file/domain/FilesTest.java | 41 ++++++ .../java/com/genius/gitget/hits/HitsTest.java | 12 +- .../security/service/JwtServiceTest.java | 12 +- .../gitget/topic/TopicControllerTest.java | 70 ++++++++++ .../user/controller/UserControllerTest.java | 4 +- .../genius/gitget/user/domain/UserTest.java | 22 +-- .../gitget/user/service/UserServiceTest.java | 14 +- 25 files changed, 810 insertions(+), 95 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java create mode 100644 src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/home/service/HomeService.java create mode 100644 src/main/java/com/genius/gitget/global/file/dto/UpdateDTO.java create mode 100644 src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/global/file/domain/FilesTest.java diff --git a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java b/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java new file mode 100644 index 00000000..ba8e3d38 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java @@ -0,0 +1,63 @@ +package com.genius.gitget.challenge.home.controller; + +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.challenge.home.dto.HomeInstanceResponse; +import com.genius.gitget.challenge.home.service.HomeService; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.response.dto.SlicingResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/challenges") +public class HomeController { + private final HomeService homeService; + + @GetMapping("/recommend") + public ResponseEntity> getRecommendInstances( + Pageable pageable, + @AuthenticationPrincipal UserPrincipal userPrincipal) { + + PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), + Sort.by(Direction.DESC, "participantCnt")); + + Slice recommendations = homeService.getRecommendations( + userPrincipal.getUser(), pageRequest); + return ResponseEntity.ok().body( + new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) + ); + } + + @GetMapping("/popular") + public ResponseEntity> getPopularInstances(Pageable pageable) { + PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), + Sort.by(Direction.DESC, "participantCnt")); + + Slice recommendations = homeService.getInstancesByCondition(pageRequest); + return ResponseEntity.ok().body( + new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) + ); + } + + @GetMapping("/latest") + public ResponseEntity> getLatestInstances(Pageable pageable) { + PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), + Sort.by(Direction.DESC, "startedDate")); + + Slice recommendations = homeService.getInstancesByCondition(pageRequest); + return ResponseEntity.ok().body( + new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java b/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java new file mode 100644 index 00000000..41ea4e45 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java @@ -0,0 +1,32 @@ +package com.genius.gitget.challenge.home.dto; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import java.io.IOException; +import java.util.Optional; +import lombok.Builder; + +@Builder +public record HomeInstanceResponse( + String title, + int participantCnt, + int pointPerPerson, + FileResponse fileResponse +) { + public static HomeInstanceResponse createByEntity(Instance instance, Optional files) throws IOException { + return HomeInstanceResponse.builder() + .title(instance.getTitle()) + .participantCnt(instance.getParticipantCnt()) + .pointPerPerson(instance.getPointPerPerson()) + .fileResponse(convertToFileResponse(files)) + .build(); + } + + private static FileResponse convertToFileResponse(Optional files) throws IOException { + if (files.isEmpty()) { + return FileResponse.createNotExistFile(); + } + return FileResponse.createExistFile(files.get()); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java b/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java new file mode 100644 index 00000000..d2112606 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java @@ -0,0 +1,52 @@ +package com.genius.gitget.challenge.home.service; + +import static com.genius.gitget.challenge.instance.domain.Progress.PRE_ACTIVITY; + +import com.genius.gitget.challenge.home.dto.HomeInstanceResponse; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.util.exception.BusinessException; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class HomeService { + private final InstanceRepository instanceRepository; + private final FilesService filesService; + + + public Slice getRecommendations(User user, Pageable pageable) { + List userTags = Arrays.stream(user.getTags().split(",")).toList(); + + Slice recommendations = instanceRepository.findRecommendations(userTags, PRE_ACTIVITY, + pageable); + + return recommendations.map(this::mapToHomeInstanceResponse); + } + + public Slice getInstancesByCondition(Pageable pageable) { + + Slice instances = instanceRepository.findInstanceByCondition(PRE_ACTIVITY, pageable); + return instances.map(this::mapToHomeInstanceResponse); + } + + private HomeInstanceResponse mapToHomeInstanceResponse(Instance instance) { + try { + return HomeInstanceResponse.createByEntity(instance, instance.getFiles()); + } catch (IOException e) { + throw new BusinessException(e); + } + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 37a1e8d4..033e26fd 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -1,9 +1,10 @@ package com.genius.gitget.challenge.instance.domain; -import com.genius.gitget.global.file.domain.Files; + +import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.challenge.hits.domain.Hits; import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; -import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.global.file.domain.Files; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -21,6 +22,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -60,6 +62,8 @@ public class Instance { private int pointPerPerson; + private int participantCnt; + @NotNull @Enumerated(EnumType.STRING) private Progress progress; @@ -82,6 +86,8 @@ public Instance(String title, String description, String tags, int pointPerPerso this.completedDate = completedDate; } + //== 비지니스 로직 ==// + public void updateInstance(String description, int pointPerPerson, LocalDateTime startedDate, LocalDateTime completedDate) { this.description = description; @@ -90,8 +96,16 @@ public void updateInstance(String description, int pointPerPerson, LocalDateTime this.completedDate = completedDate; } - public int getJoinPeopleCount() { - return participantInfoList.size(); + public void updateParticipantCnt(int amount) { + this.participantCnt += amount; + } + + public Optional getFiles() { + return Optional.ofNullable(this.files); + } + + public void setFiles(Files files) { + this.files = files; } //== 연관관계 편의 메서드 ==// diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java index 28ea1b34..0e07e447 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java @@ -1,15 +1,23 @@ package com.genius.gitget.challenge.instance.repository; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface InstanceRepository extends JpaRepository { @Query("select i from Instance i ORDER BY i.id DESC ") Page findAllById(Pageable pageable); + @Query("select i from Instance i where i.progress = :progress and i.tags in :userTags") + Slice findRecommendations(@Param("userTags") List userTags, Progress progress, Pageable pageable); + @Query("select i from Instance i where i.progress = :progress") + Slice findInstanceByCondition(@Param("progress") Progress progress, Pageable pageable); } diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index d257237d..54417261 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -1,8 +1,8 @@ package com.genius.gitget.challenge.user.domain; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.challenge.hits.domain.Hits; import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.Column; @@ -59,26 +59,26 @@ public class User extends BaseTimeEntity { @Column(unique = true, length = 20) private String nickname; - private String interest; + private String tags; @Column(length = 100) private String information; @Builder public User(ProviderInfo providerInfo, String identifier, Role role, String nickname, String information, - String interest) { + String tags) { this.providerInfo = providerInfo; this.identifier = identifier; this.role = role; this.nickname = nickname; - this.interest = interest; + this.tags = tags; this.information = information; } public void updateUser(String nickname, String information, String interest) { this.nickname = nickname; this.information = information; - this.interest = interest; + this.tags = interest; } public void updateRole(Role role) { diff --git a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java index 65f2cb3c..35e7adbb 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -3,20 +3,24 @@ import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.global.file.dto.FileRequest; +import com.genius.gitget.challenge.instance.dto.InstanceCreateRequest; +import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @Slf4j @RestController @@ -27,9 +31,12 @@ public class FilesController { @PostMapping public ResponseEntity> uploadImage( - @ModelAttribute FileRequest fileRequest) throws IOException { + @RequestPart(value = "data") InstanceCreateRequest instanceCreateRequest, + @RequestPart(value = "files") MultipartFile multipartFile, + @RequestPart(value = "type") String type) throws IOException { - FileResponse fileResponse = filesService.uploadFile(fileRequest.file(), fileRequest.type()); + Files files = filesService.uploadFile(multipartFile, type); + FileResponse fileResponse = FileResponse.createExistFile(files); return ResponseEntity.ok().body( new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), fileResponse) @@ -46,4 +53,26 @@ public ResponseEntity> getImage(@PathVariable(name new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), encodedFile) ); } + + @PostMapping("/{fileId}") + public ResponseEntity> updateImage( + @RequestPart(value = "files") MultipartFile multipartFile, + @PathVariable Long fileId + ) throws IOException { + Files files = filesService.updateFile(fileId, multipartFile); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), + FileResponse.createExistFile(files)) + ); + } + + @DeleteMapping("/{fileId}") + public ResponseEntity deleteImage(@PathVariable Long fileId) throws IOException { + filesService.deleteFile(fileId); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/file/domain/Files.java b/src/main/java/com/genius/gitget/global/file/domain/Files.java index 75d2a1e6..a9ed5a3d 100644 --- a/src/main/java/com/genius/gitget/global/file/domain/Files.java +++ b/src/main/java/com/genius/gitget/global/file/domain/Files.java @@ -1,5 +1,6 @@ package com.genius.gitget.global.file.domain; +import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -40,4 +41,11 @@ public Files(FileType fileType, String originalFilename, String savedFilename, S this.savedFilename = savedFilename; this.fileURI = fileURI; } + + //== 비지니스 로직 ==// + public void updateFiles(UpdateDTO updateDTO) { + this.originalFilename = updateDTO.originalFilename(); + this.savedFilename = updateDTO.savedFilename(); + this.fileURI = updateDTO.fileURI(); + } } diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index 3b8522f9..258c027b 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -1,10 +1,18 @@ package com.genius.gitget.global.file.dto; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.service.FileUtil; +import java.io.IOException; + public record FileResponse( Long fileId, String encodedFile) { - public FileResponse(Long fileId) { - this(fileId, null); + public static FileResponse createExistFile(Files files) throws IOException { + return new FileResponse(files.getId(), FileUtil.encodedImage(files)); + } + + public static FileResponse createNotExistFile() { + return new FileResponse(0L, "none"); } } diff --git a/src/main/java/com/genius/gitget/global/file/dto/UpdateDTO.java b/src/main/java/com/genius/gitget/global/file/dto/UpdateDTO.java new file mode 100644 index 00000000..99ad7c40 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/dto/UpdateDTO.java @@ -0,0 +1,11 @@ +package com.genius.gitget.global.file.dto; + +import lombok.Builder; + +@Builder +public record UpdateDTO( + String originalFilename, + String savedFilename, + String fileURI +) { +} diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index ff9ed75f..26cbe005 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -1,10 +1,11 @@ package com.genius.gitget.global.file.service; -import static com.genius.gitget.global.util.exception.ErrorCode.IMAGE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.util.exception.BusinessException; import java.io.IOException; @@ -13,28 +14,20 @@ import java.util.List; import java.util.Objects; import java.util.UUID; -import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.UrlResource; -import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; -@Component public class FileUtil { - private final List validExtensions = List.of("jpg", "jpeg", "png", "gif"); - private final String uploadPath; + private static final List validExtensions = List.of("jpg", "jpeg", "png", "gif"); - public FileUtil(@Value("${file.upload.path}") String uploadPath) { - this.uploadPath = uploadPath; - } - - public String encodedImage(Files files) throws IOException { + public static String encodedImage(Files files) throws IOException { UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); return new String(encode, StandardCharsets.UTF_8); } - public UploadDTO getUploadInfo(MultipartFile file, String typeStr) { + public static UploadDTO getUploadInfo(MultipartFile file, String typeStr, final String UPLOAD_PATH) { String originalFilename = file.getOriginalFilename(); String savedFilename = getSavedFilename(originalFilename); FileType fileType = FileType.fineType(typeStr); @@ -43,15 +36,26 @@ public UploadDTO getUploadInfo(MultipartFile file, String typeStr) { .fileType(fileType) .originalFilename(originalFilename) .savedFilename(savedFilename) - .fileURI(uploadPath + fileType.getPath() + savedFilename) + .fileURI(UPLOAD_PATH + fileType.getPath() + savedFilename) .build(); } - public void validateFile(MultipartFile file) { + public static UpdateDTO getUpdateInfo(MultipartFile file, FileType fileType, final String UPLOAD_PATH) { + String originalFilename = file.getOriginalFilename(); + String savedFilename = getSavedFilename(originalFilename); + + return UpdateDTO.builder() + .originalFilename(originalFilename) + .savedFilename(savedFilename) + .fileURI(UPLOAD_PATH + fileType.getPath() + savedFilename) + .build(); + } + + public static void validateFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (originalFilename == null || Objects.equals(originalFilename, "")) { - throw new BusinessException(IMAGE_NOT_EXIST); + throw new BusinessException(FILE_NOT_EXIST); } String extension = extractExtension(originalFilename); @@ -61,15 +65,14 @@ public void validateFile(MultipartFile file) { } } - - public String getSavedFilename(String originalFilename) { + public static String getSavedFilename(String originalFilename) { String uuid = UUID.randomUUID().toString(); String extension = extractExtension(originalFilename); return uuid + "." + extension; } - private String extractExtension(String filename) { + private static String extractExtension(String filename) { int index = filename.lastIndexOf("."); return filename.substring(index + 1).toLowerCase(); } diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index d2244a59..bceed0aa 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -1,16 +1,20 @@ package com.genius.gitget.global.file.service; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_DELETED; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; + import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.file.repository.FilesRepository; import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; -import lombok.RequiredArgsConstructor; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,17 +23,20 @@ @Slf4j @Service @Transactional(readOnly = true) -@RequiredArgsConstructor public class FilesService { - private final FileUtil fileUtil; + private final String UPLOAD_PATH; private final FilesRepository filesRepository; + public FilesService(@Value("${file.upload.path}") String UPLOAD_PATH, FilesRepository filesRepository) { + this.UPLOAD_PATH = UPLOAD_PATH; + this.filesRepository = filesRepository; + } @Transactional - public FileResponse uploadFile(MultipartFile receivedFile, String typeStr) throws IOException { - fileUtil.validateFile(receivedFile); + public Files uploadFile(MultipartFile receivedFile, String typeStr) throws IOException { + FileUtil.validateFile(receivedFile); - UploadDTO uploadDTO = fileUtil.getUploadInfo(receivedFile, typeStr); + UploadDTO uploadDTO = FileUtil.getUploadInfo(receivedFile, typeStr, UPLOAD_PATH); saveFile(receivedFile, uploadDTO.fileURI()); @@ -40,29 +47,64 @@ public FileResponse uploadFile(MultipartFile receivedFile, String typeStr) throw .fileURI(uploadDTO.fileURI()) .build(); - Files savedFile = filesRepository.save(file); - return new FileResponse(savedFile.getId(), fileUtil.encodedImage(file)); + return filesRepository.save(file); } - private void saveFile(MultipartFile receivedFile, String fileURI) throws IOException { + private void saveFile(MultipartFile file, String fileURI) throws IOException { File targetFile = new File(fileURI); if (!targetFile.exists()) { targetFile.mkdirs(); } - receivedFile.transferTo(targetFile); + file.transferTo(targetFile); } - public FileResponse getEncodedFile(Long fileId) throws IOException { + @Transactional + public Files updateFile(Long fileId, MultipartFile file) throws IOException { + Files files = filesRepository.findById(fileId) + .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); + + deleteFilesInStorage(files); + + UpdateDTO updateDTO = FileUtil.getUpdateInfo(file, files.getFileType(), UPLOAD_PATH); + saveFile(file, updateDTO.fileURI()); + files.updateFiles(updateDTO); + return files; + } + + /** + * NOTE: 삭제하고자하는 Files 엔티티와 연관관계에 있는 엔티티에서 연관관계를 끊어줘야 합니다. + * + * @param fileId 삭제하고자하는 Files 엔티티의 PK + */ + @Transactional + public void deleteFile(Long fileId) throws IOException { Files files = filesRepository.findById(fileId) - .orElseThrow(() -> new BusinessException(ErrorCode.IMAGE_NOT_EXIST)); + .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); + + deleteFilesInStorage(files); + } + + private void deleteFilesInStorage(Files files) { + String fileURI = files.getFileURI(); + File targetFile = new File(fileURI); + if (!targetFile.delete()) { + throw new BusinessException(FILE_NOT_DELETED); + } + } + + public FileResponse getEncodedFile(Long fileId) throws IOException { + Optional optionalFiles = filesRepository.findById(fileId); + if (optionalFiles.isEmpty()) { + return FileResponse.createNotExistFile(); + } - return new FileResponse(fileId, fileUtil.encodedImage(files)); + return FileResponse.createExistFile(optionalFiles.get()); } public UrlResource getFile(Long fileId) throws MalformedURLException { Files files = filesRepository.findById(fileId) - .orElseThrow(() -> new BusinessException(ErrorCode.IMAGE_NOT_EXIST)); + .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); return new UrlResource("file:" + files.getFileURI()); } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index f3d91004..77d3683c 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -24,9 +24,10 @@ public enum ErrorCode { TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."), - IMAGE_NOT_EXIST(HttpStatus.BAD_REQUEST, "Image가 존재하지 않습니다."), + FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "해당 파일(이미지)이 존재하지 않습니다."), NOT_SUPPORTED_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 확장자입니다."), - NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 타입입니다."); + NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 타입입니다."), + FILE_NOT_DELETED(HttpStatus.BAD_REQUEST, "파일(이미지)이 정상적으로 삭제되지 않았습니다."); private final HttpStatus status; diff --git a/src/main/java/com/genius/gitget/global/util/response/dto/SlicingResponse.java b/src/main/java/com/genius/gitget/global/util/response/dto/SlicingResponse.java index 145a6006..50955b6a 100644 --- a/src/main/java/com/genius/gitget/global/util/response/dto/SlicingResponse.java +++ b/src/main/java/com/genius/gitget/global/util/response/dto/SlicingResponse.java @@ -1,10 +1,20 @@ package com.genius.gitget.global.util.response.dto; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; +import org.springframework.http.HttpStatus; +@Getter +@RequiredArgsConstructor public class SlicingResponse extends CommonResponse { private Slice data; + public SlicingResponse(HttpStatus status, String message, Slice data) { + super(status, message); + this.data = data; + } + public SlicingResponse(Slice data) { this.data = data; } diff --git a/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java b/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java new file mode 100644 index 00000000..58106072 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java @@ -0,0 +1,95 @@ +package com.genius.gitget.challenge.home.controller; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; +import java.time.LocalDateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest +@Transactional +class HomeControllerTest { + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + @Autowired + TokenTestUtil tokenTestUtil; + + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + + @Test + @DisplayName("특정 사용자의 관심사 태그를 확인하여, 태그와 일치하는 인스턴스들을 참여 인원 순으로 반환한다.") + @WithMockCustomUser + public void should_returnInstances_when_passUserTags() throws Exception { + //given + getSavedInstance("title1", "BE", 20); + getSavedInstance("title2", "BE", 34); + getSavedInstance("title3", "FE", 10); + getSavedInstance("title4", "AI", 2); + + //when & then + mockMvc.perform(get("/api/challenges/recommend") + .cookie(tokenTestUtil.createAccessCookie())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.numberOfElements").value(3)); + } + + + private Instance getSavedInstance(String title, String tags, int participantCnt) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PRE_ACTIVITY) + .pointPerPerson(100) + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCnt(participantCnt); + instance.setTopic(getSavedTopic()); + return instance; + } + + private Topic getSavedTopic() { + return topicRepository.save( + Topic.builder() + .title("title") + .description("description") + .tags("BE") + .pointPerPerson(100) + .build() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java b/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java new file mode 100644 index 00000000..d1e7921c --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java @@ -0,0 +1,87 @@ +package com.genius.gitget.challenge.home.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.home.dto.HomeInstanceResponse; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.user.domain.User; +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class HomeServiceTest { + @Autowired + HomeService homeService; + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + + @Test + @DisplayName("사용자가 설정한 태그에 맞는 추천 인스턴스들을 페이징 형태로 받아올 수 있다.") + public void should_getSuggestions_when_passUserTags() { + //given + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCnt")); + getSavedInstance("title1", "BE", 20); + getSavedInstance("title2", "BE", 10); + getSavedInstance("title3", "FE", 10); + getSavedInstance("title4", "FE", 12); + + User user = User.builder().tags("BE").build(); + + //when + Slice recommendations = homeService.getRecommendations(user, pageRequest); + + //then + assertThat(recommendations.getContent().size()).isEqualTo(2); + assertThat(recommendations.getContent().get(0).title()).isEqualTo("title1"); + assertThat(recommendations.getContent().get(0).participantCnt()).isEqualTo(20); + assertThat(recommendations.getContent().get(0).pointPerPerson()).isEqualTo(100); + + assertThat(recommendations.getContent().get(1).title()).isEqualTo("title2"); + assertThat(recommendations.getContent().get(1).participantCnt()).isEqualTo(10); + assertThat(recommendations.getContent().get(1).pointPerPerson()).isEqualTo(100); + } + + private Instance getSavedInstance(String title, String tags, int participantCnt) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PRE_ACTIVITY) + .pointPerPerson(100) + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCnt(participantCnt); + instance.setTopic(getSavedTopic()); + return instance; + } + + private Topic getSavedTopic() { + return topicRepository.save( + Topic.builder() + .title("title") + .description("description") + .tags("BE") + .pointPerPerson(100) + .build() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java new file mode 100644 index 00000000..80a2445d --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -0,0 +1,126 @@ +package com.genius.gitget.challenge.instance.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import java.time.LocalDateTime; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class InstanceRepositoryTest { + @Autowired + InstanceRepository instanceRepository; + + @Test + @DisplayName("인스턴스들 중, 사용자의 tag가 포함되어 있는 인스턴스들을 반환받을 수 있다.") + public void should_returnInstances_containsUserTags() { + //given + List userTags = List.of("BE", "FE", "AI"); + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCnt")); + + //when + getSavedInstance("title1", "BE", 10); + getSavedInstance("title2", "FE", 3); + getSavedInstance("title3", "FE", 20); + Slice suggestions = instanceRepository.findRecommendations(userTags, Progress.PRE_ACTIVITY, + pageRequest); + + //then + assertThat(suggestions.getContent().size()).isEqualTo(3); + assertThat(suggestions.getContent().get(0).getTitle()).isEqualTo("title3"); + assertThat(suggestions.getContent().get(0).getTags()).isEqualTo("FE"); + assertThat(suggestions.getContent().get(0).getParticipantCnt()).isEqualTo(20); + + assertThat(suggestions.getContent().get(1).getTitle()).isEqualTo("title1"); + assertThat(suggestions.getContent().get(1).getTags()).isEqualTo("BE"); + assertThat(suggestions.getContent().get(1).getParticipantCnt()).isEqualTo(10); + + assertThat(suggestions.getContent().get(2).getTitle()).isEqualTo("title2"); + assertThat(suggestions.getContent().get(2).getTags()).isEqualTo("FE"); + assertThat(suggestions.getContent().get(2).getParticipantCnt()).isEqualTo(3); + } + + @Test + @DisplayName("인스턴스들 중, 시작 일자가 늦은 순서대로 인스턴스들을 정렬하여 반환받을 수 있다.") + public void should_returnInstances_orderByStartedDate() { + //given + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "startedDate")); + + //when + getSavedInstance("title1", "BE", 10); + getSavedInstance("title2", "BE", 3); + getSavedInstance("title3", "BE", 20); + Slice instances = instanceRepository.findInstanceByCondition(Progress.PRE_ACTIVITY, pageRequest); + + //then + assertThat(instances.getContent().size()).isEqualTo(3); + assertThat(instances.getContent().get(0).getTitle()).isEqualTo("title3"); + assertThat(instances.getContent().get(0).getTags()).isEqualTo("BE"); + assertThat(instances.getContent().get(0).getParticipantCnt()).isEqualTo(20); + + assertThat(instances.getContent().get(1).getTitle()).isEqualTo("title2"); + assertThat(instances.getContent().get(1).getTags()).isEqualTo("BE"); + assertThat(instances.getContent().get(1).getParticipantCnt()).isEqualTo(3); + + assertThat(instances.getContent().get(2).getTitle()).isEqualTo("title1"); + assertThat(instances.getContent().get(2).getTags()).isEqualTo("BE"); + assertThat(instances.getContent().get(2).getParticipantCnt()).isEqualTo(10); + } + + @Test + @DisplayName("인스턴스들 중, 참여 인원 수가 많은 순서대로 인스턴스들을 정렬하여 반환받을 수 있다.") + public void should_returnInstances_orderByParticipantCnt() { + //given + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCnt")); + + //when + getSavedInstance("title1", "BE", 10); + getSavedInstance("title2", "BE", 3); + getSavedInstance("title3", "BE", 20); + Slice instances = instanceRepository.findInstanceByCondition(Progress.PRE_ACTIVITY, pageRequest); + + //then + assertThat(instances.getContent().size()).isEqualTo(3); + assertThat(instances.getContent().get(0).getTitle()).isEqualTo("title3"); + assertThat(instances.getContent().get(0).getTags()).isEqualTo("BE"); + assertThat(instances.getContent().get(0).getParticipantCnt()).isEqualTo(20); + + assertThat(instances.getContent().get(1).getTitle()).isEqualTo("title1"); + assertThat(instances.getContent().get(1).getTags()).isEqualTo("BE"); + assertThat(instances.getContent().get(1).getParticipantCnt()).isEqualTo(10); + + assertThat(instances.getContent().get(2).getTitle()).isEqualTo("title2"); + assertThat(instances.getContent().get(2).getTags()).isEqualTo("BE"); + assertThat(instances.getContent().get(2).getParticipantCnt()).isEqualTo(3); + } + + private Instance getSavedInstance(String title, String tags, int participantCnt) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PRE_ACTIVITY) + .pointPerPerson(100) + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCnt(participantCnt); + return instance; + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java index d8807076..faa1363c 100644 --- a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/file/service/FileUtilTest.java @@ -1,10 +1,12 @@ package com.genius.gitget.file.service; -import static com.genius.gitget.global.util.exception.ErrorCode.IMAGE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.file.service.FileUtil; import com.genius.gitget.global.util.exception.BusinessException; @@ -13,7 +15,6 @@ import java.io.InputStream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @@ -24,11 +25,8 @@ @Transactional @ActiveProfiles({"file"}) class FileUtilTest { - @Autowired - private FileUtil fileUtil; - @Value("${file.upload.path}") - private String uploadPath; + private String UPLOAD_PATH; @Test @DisplayName("file을 전달받았을 때, originFilename가 null일 때 예외를 발생해야 한다.") @@ -37,9 +35,9 @@ public void should_throwException_when_originFilenameIsNull() { MultipartFile multipartFile = getTestMultiPartFile(null); //when&then - assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) + assertThatThrownBy(() -> FileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(IMAGE_NOT_EXIST.getMessage()); + .hasMessageContaining(FILE_NOT_EXIST.getMessage()); } @Test @@ -49,9 +47,9 @@ public void should_throwException_when_originFilenameIsBlank() { MultipartFile multipartFile = getTestMultiPartFile(""); //when&then - assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) + assertThatThrownBy(() -> FileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(IMAGE_NOT_EXIST.getMessage()); + .hasMessageContaining(FILE_NOT_EXIST.getMessage()); } @Test @@ -61,7 +59,7 @@ public void should_throwException_when_notSupportedExtension() { MultipartFile multipartFile = getTestMultiPartFile("sky.pdf"); //when&then - assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) + assertThatThrownBy(() -> FileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) .hasMessageContaining(NOT_SUPPORTED_EXTENSION.getMessage()); } @@ -73,10 +71,27 @@ public void should_returnTargetFileInstance_when_passValidFile() { MultipartFile multipartFile = getTestMultiPartFile("sky.png"); //when - UploadDTO uploadDTO = fileUtil.getUploadInfo(multipartFile, "profile"); + UploadDTO uploadDTO = FileUtil.getUploadInfo(multipartFile, "profile", UPLOAD_PATH); + + //then + assertThat(uploadDTO.fileURI()).contains(UPLOAD_PATH); + } + + @Test + @DisplayName("갱신 대상인 File을 전달했을 때, 갱신해야 할 정보들을 담은 UpdateDTO를 반환받는다.") + public void should_returnUpdateDTO_when_passUpdateTargetFile() { + //given + String originalFilename = "sky.png"; + MultipartFile multipartFile = getTestMultiPartFile(originalFilename); + FileType fileType = FileType.PROFILE; + + //when + UpdateDTO updateDTO = FileUtil.getUpdateInfo(multipartFile, fileType, UPLOAD_PATH); //then - assertThat(uploadDTO.fileURI()).contains(uploadPath); + assertThat(updateDTO.originalFilename()).isEqualTo(originalFilename); + assertThat(updateDTO.fileURI()).contains(UPLOAD_PATH); + assertThat(updateDTO.fileURI()).contains(updateDTO.savedFilename()); } diff --git a/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java b/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java new file mode 100644 index 00000000..9b41b3c7 --- /dev/null +++ b/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java @@ -0,0 +1,41 @@ +package com.genius.gitget.global.file.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.global.file.dto.UpdateDTO; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class FilesTest { + + @Test + @DisplayName("파일이 수정되어야 할 때, UpdateDTO를 전달하여 정보를 수정할 수 있다.") + public void should_updateFiles_when_passUpdateDTO() { + //given + Files files = Files.builder() + .fileType(FileType.INSTANCE) + .originalFilename("originalFilename") + .savedFilename("savedFilename") + .fileURI("fileURI") + .build(); + + UpdateDTO updateDTO = UpdateDTO.builder() + .savedFilename("new savedFilename") + .originalFilename("new originalFilename") + .fileURI("new fileURI") + .build(); + + //when + files.updateFiles(updateDTO); + + //then + assertThat(files.getOriginalFilename()).isEqualTo(updateDTO.originalFilename()); + assertThat(files.getSavedFilename()).isEqualTo(updateDTO.savedFilename()); + assertThat(files.getFileURI()).isEqualTo(updateDTO.fileURI()); + + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/hits/HitsTest.java b/src/test/java/com/genius/gitget/hits/HitsTest.java index cf8e373e..4f18f449 100644 --- a/src/test/java/com/genius/gitget/hits/HitsTest.java +++ b/src/test/java/com/genius/gitget/hits/HitsTest.java @@ -1,19 +1,19 @@ package com.genius.gitget.hits; -import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; import static com.genius.gitget.challenge.user.domain.Role.ADMIN; import static com.genius.gitget.challenge.user.domain.Role.USER; +import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.hits.domain.Hits; import com.genius.gitget.challenge.hits.repository.HitsRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; import java.time.LocalDateTime; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -45,7 +45,7 @@ public void setup() { .providerInfo(ProviderInfo.NAVER) .nickname("kimdozzi") .information("백엔드") - .interest("운동") + .tags("운동") .role(ADMIN) .build(); @@ -53,7 +53,7 @@ public void setup() { .providerInfo(GOOGLE) .nickname("SEONG") .information("프론트엔드") - .interest("영화") + .tags("영화") .role(USER) .build(); diff --git a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java index 0a1e0bdf..a550c043 100644 --- a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java @@ -8,17 +8,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.JwtRule; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.security.repository.TokenRepository; import com.genius.gitget.global.security.service.JwtService; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; @@ -233,7 +233,7 @@ private User getSavedUser() { .nickname("nickname") .identifier("identifier") .role(Role.USER) - .interest("interest1,interest2") + .tags("interest1,interest2") .information("information") .build()); } diff --git a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java index d07a7b34..062119f1 100644 --- a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java @@ -1,6 +1,27 @@ package com.genius.gitget.topic; +import static com.genius.gitget.challenge.user.domain.Role.ADMIN; +import static com.genius.gitget.challenge.user.domain.Role.USER; +import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.admin.topic.service.TopicService; +import com.genius.gitget.challenge.hits.repository.HitsRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participantinfo.domain.JoinResult; +import com.genius.gitget.challenge.participantinfo.domain.JoinStatus; +import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.challenge.participantinfo.repository.ParticipantInfoRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; @@ -47,6 +68,55 @@ public class TopicControllerTest { @BeforeEach public void setup() { + user1 = User.builder().identifier("neo5188@gmail.com") + .providerInfo(ProviderInfo.NAVER) + .nickname("kimdozzi") + .information("백엔드") + .tags("운동") + .role(ADMIN) + .build(); + + user2 = User.builder().identifier("ssang23@naver.com") + .providerInfo(GOOGLE) + .nickname("SEONG") + .information("프론트엔드") + .tags("영화") + .role(USER) + .build(); + + instance1 = Instance.builder() + .title("1일 1커밋") + .description("챌린지 세부사항입니다.") + .pointPerPerson(10) + .tags("BE, CS") + .progress(Progress.ACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + topic1 = Topic.builder() + .title("1일 1커밋") + .description("간단한 설명란") + .pointPerPerson(300) + .tags("BE, CS") + .build(); + + topic2 = Topic.builder() + .title("1일 2커밋") + .description("간단한 설명란") + .pointPerPerson(300) + .tags("BE, CS") + .build(); + + participantInfo1 = ParticipantInfo.builder() + .joinResult(JoinResult.PROCESSING) + .joinStatus(JoinStatus.YES) + .build(); + + participantInfo2 = ParticipantInfo.builder() + .joinResult(JoinResult.SUCCESS) + .joinStatus(JoinStatus.YES) + mockMvc = MockMvcBuilders .webAppContextSetup(context) .apply(springSecurity()) diff --git a/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java b/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java index f00323c1..6eccd164 100644 --- a/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java +++ b/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java @@ -8,10 +8,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -95,7 +95,7 @@ private User getSavedUser() { .identifier("identifier") .role(Role.USER) .information("information") - .interest("interest1,interest2") + .tags("interest1,interest2") .nickname("nickname") .providerInfo(ProviderInfo.GITHUB) .build()); diff --git a/src/test/java/com/genius/gitget/user/domain/UserTest.java b/src/test/java/com/genius/gitget/user/domain/UserTest.java index ab1afb18..c8611cd9 100644 --- a/src/test/java/com/genius/gitget/user/domain/UserTest.java +++ b/src/test/java/com/genius/gitget/user/domain/UserTest.java @@ -1,19 +1,19 @@ package com.genius.gitget.user.domain; +import static com.genius.gitget.challenge.user.domain.Role.ADMIN; +import static com.genius.gitget.challenge.user.domain.Role.USER; +import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; +import static com.genius.gitget.global.security.constants.ProviderInfo.NAVER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.xmlunit.util.Linqy.count; + import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; +import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - -import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; -import static com.genius.gitget.global.security.constants.ProviderInfo.NAVER; -import static com.genius.gitget.challenge.user.domain.Role.ADMIN; -import static com.genius.gitget.challenge.user.domain.Role.USER; -import static org.assertj.core.api.Assertions.*; -import static org.xmlunit.util.Linqy.count; @SpringBootTest @Transactional @@ -28,7 +28,7 @@ public class UserTest { .providerInfo(NAVER) .nickname("kimdozzi") .information("백엔드") - .interest("운동") + .tags("운동") .role(ADMIN) .build(); @@ -84,7 +84,7 @@ private User userA() { .providerInfo(NAVER) .nickname("kimdozzi") .information("백엔드") - .interest("운동") + .tags("운동") .role(ADMIN) .build(); } @@ -94,7 +94,7 @@ private User userB() { .providerInfo(GOOGLE) .nickname("SEONG") .information("프론트엔드") - .interest("영화") + .tags("영화") .role(USER) .build(); } diff --git a/src/test/java/com/genius/gitget/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/user/service/UserServiceTest.java index 7a2b40bd..022d7ea1 100644 --- a/src/test/java/com/genius/gitget/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/user/service/UserServiceTest.java @@ -3,12 +3,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.genius.gitget.challenge.user.service.UserService; -import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import java.util.List; @@ -52,7 +52,7 @@ public void should_matchValues_when_signupUser() { assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); - assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); + assertThat(user.getTags()).isEqualTo(foundUser.getTags()); } @Test @@ -71,7 +71,7 @@ public void should_returnUser_when_passPK() { assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); assertThat(user.getRole()).isEqualTo(foundUser.getRole()); assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); - assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); + assertThat(user.getTags()).isEqualTo(foundUser.getTags()); } @Test @@ -90,7 +90,7 @@ public void should_returnUser_when_passIdentifier() { assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); assertThat(user.getRole()).isEqualTo(foundUser.getRole()); assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); - assertThat(user.getInterest()).isEqualTo(foundUser.getInterest()); + assertThat(user.getTags()).isEqualTo(foundUser.getTags()); } @Test @@ -104,7 +104,7 @@ public void should_throwException_when_nicknameIsDuplicated() { .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.DUPLICATED_NICKNAME.getMessage()); } - + private void saveUnsignedUser() { userRepository.save(User.builder() @@ -119,7 +119,7 @@ private User getSavedUser() { .identifier("identifier") .role(Role.USER) .information("information") - .interest("interest1,interest2") + .tags("interest1,interest2") .nickname("nickname") .providerInfo(ProviderInfo.GITHUB) .build()); From a957fb5c92de8d0db9d1f62946beff33bb30398a Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Wed, 31 Jan 2024 18:39:16 +0900 Subject: [PATCH 089/234] =?UTF-8?q?!HOTFIX:=20=EC=BB=B4=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/controller/HomeController.java | 24 +++++++++++++- .../challenge/home/service/HomeService.java | 6 ++-- .../instance/controller/HomeController.java | 33 ------------------- .../challenge/instance/domain/Instance.java | 4 --- .../dto/search/InstanceSearchResponse.java | 6 +++- .../file/controller/FilesController.java | 2 +- 6 files changed, 32 insertions(+), 43 deletions(-) delete mode 100644 src/main/java/com/genius/gitget/challenge/instance/controller/HomeController.java diff --git a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java b/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java index ba8e3d38..044733c6 100644 --- a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java +++ b/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java @@ -4,9 +4,15 @@ import com.genius.gitget.challenge.home.dto.HomeInstanceResponse; import com.genius.gitget.challenge.home.service.HomeService; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.service.InstanceSearchService; import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.exception.SuccessCode; +import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SlicingResponse; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -15,6 +21,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; +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; @@ -23,6 +31,20 @@ @RequestMapping("/api/challenges") public class HomeController { private final HomeService homeService; + private final InstanceSearchService instanceSearchService; + + @PostMapping("/search") + public ResponseEntity> searchInstances( + @RequestBody InstanceSearchRequest instanceSearchRequest, Pageable pageable) { + + Page searchResults + = instanceSearchService.searchInstances(instanceSearchRequest.keyword(), + instanceSearchRequest.progress(), pageable); + + return ResponseEntity.ok().body( + new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), searchResults) + ); + } @GetMapping("/recommend") public ResponseEntity> getRecommendInstances( @@ -31,7 +53,7 @@ public ResponseEntity> getRecommendInstanc PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Direction.DESC, "participantCnt")); - + Slice recommendations = homeService.getRecommendations( userPrincipal.getUser(), pageRequest); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java b/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java index d2112606..10c5224a 100644 --- a/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java +++ b/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java @@ -1,6 +1,6 @@ package com.genius.gitget.challenge.home.service; -import static com.genius.gitget.challenge.instance.domain.Progress.PRE_ACTIVITY; +import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; import com.genius.gitget.challenge.home.dto.HomeInstanceResponse; import com.genius.gitget.challenge.instance.domain.Instance; @@ -30,7 +30,7 @@ public class HomeService { public Slice getRecommendations(User user, Pageable pageable) { List userTags = Arrays.stream(user.getTags().split(",")).toList(); - Slice recommendations = instanceRepository.findRecommendations(userTags, PRE_ACTIVITY, + Slice recommendations = instanceRepository.findRecommendations(userTags, PREACTIVITY, pageable); return recommendations.map(this::mapToHomeInstanceResponse); @@ -38,7 +38,7 @@ public Slice getRecommendations(User user, Pageable pageab public Slice getInstancesByCondition(Pageable pageable) { - Slice instances = instanceRepository.findInstanceByCondition(PRE_ACTIVITY, pageable); + Slice instances = instanceRepository.findInstanceByCondition(PREACTIVITY, pageable); return instances.map(this::mapToHomeInstanceResponse); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/HomeController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/HomeController.java deleted file mode 100644 index e7251925..00000000 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/HomeController.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.genius.gitget.challenge.instance.controller; - -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; -import com.genius.gitget.challenge.instance.service.InstanceSearchService; -import com.genius.gitget.global.util.exception.SuccessCode; -import com.genius.gitget.global.util.response.dto.PagingResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@Slf4j -@RestController -@RequiredArgsConstructor -@RequestMapping("/api") -public class HomeController { - private final InstanceSearchService instanceSearchService; - - @PostMapping("/challenges/search") - public ResponseEntity> searchInstances(@RequestBody InstanceSearchRequest instanceSearchRequest, Pageable pageable) { - - Page searchResults - = instanceSearchService.searchInstances(instanceSearchRequest.keyword(), instanceSearchRequest.progress(), pageable); - - return ResponseEntity.ok().body( - new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), searchResults) - ); - } -} diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 033e26fd..f19b6d52 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -116,8 +116,4 @@ public void setTopic(Topic topic) { } } - public void setFiles(Files files) { - this.files = files; - } - } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index c031efad..3663c351 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -10,6 +10,10 @@ public record InstanceSearchResponse( int participantCount ) { public InstanceSearchResponse(Instance instance) { - this(instance.getTopic().getId(), instance.getId(), instance.getTitle(), instance.getPointPerPerson(),instance.getJoinPeopleCount()); + this(instance.getTopic().getId(), + instance.getId(), + instance.getTitle(), + instance.getPointPerPerson(), + instance.getParticipantCnt()); } } diff --git a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java index 35e7adbb..8048d856 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -3,7 +3,7 @@ import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.challenge.instance.dto.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; From ad3238240a9898c28f6c444f54e95f2633d6a134 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Wed, 31 Jan 2024 19:07:15 +0900 Subject: [PATCH 090/234] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genius/gitget/challenge/instance/domain/Instance.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index b75721a8..d84d3027 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -62,7 +62,6 @@ public class Instance { @NotNull @Enumerated(EnumType.STRING) - // @Column(columnDefinition = "varchar(255) default 'PRE_ACTIVITY'") private Progress progress; @Column(name = "started_at") From e93afa4a88f4147b727879359c47f32cdadfe570 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Wed, 31 Jan 2024 19:08:36 +0900 Subject: [PATCH 091/234] =?UTF-8?q?refactor:=20instanceService=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=EB=90=9C=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gitget/challenge/instance/service/InstanceService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index 57bf843b..caa42897 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -35,6 +35,7 @@ public Long createInstance(InstanceCreateRequest instanceCreateRequest) { Instance instance = Instance.builder() .title(instanceCreateRequest.title()) + .tags(instanceCreateRequest.tags()) .description(instanceCreateRequest.description()) .pointPerPerson(instanceCreateRequest.pointPerPerson()) .startedDate(instanceCreateRequest.startedAt()) From 7ac23d50981c81aa4f79827002b13f73f1e1a51c Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Wed, 31 Jan 2024 19:11:55 +0900 Subject: [PATCH 092/234] =?UTF-8?q?test:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20test=20class=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gitget/topic/TopicControllerTest.java | 136 ------------------ 1 file changed, 136 deletions(-) delete mode 100644 src/test/java/com/genius/gitget/topic/TopicControllerTest.java diff --git a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/TopicControllerTest.java deleted file mode 100644 index 062119f1..00000000 --- a/src/test/java/com/genius/gitget/topic/TopicControllerTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.genius.gitget.topic; - -import static com.genius.gitget.challenge.user.domain.Role.ADMIN; -import static com.genius.gitget.challenge.user.domain.Role.USER; -import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.admin.topic.service.TopicService; -import com.genius.gitget.challenge.hits.repository.HitsRepository; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.participantinfo.domain.JoinResult; -import com.genius.gitget.challenge.participantinfo.domain.JoinStatus; -import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; -import com.genius.gitget.challenge.participantinfo.repository.ParticipantInfoRepository; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; -import jakarta.servlet.http.Cookie; -import jakarta.transaction.Transactional; -import lombok.extern.slf4j.Slf4j; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.Rollback; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import java.nio.charset.StandardCharsets; - -import static com.genius.gitget.challenge.user.domain.Role.USER; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest({ TopicControllerTest.class }) -@WithMockCustomUser(role = Role.USER) -@MockBean(JpaMetamodelMappingContext.class) -public class TopicControllerTest { - @Autowired - MockMvc mockMvc; - @Autowired - private TokenTestUtil tokenTestUtil; - @Autowired - WebApplicationContext context; - - @MockBean - TopicService topicService; - - - @BeforeEach - public void setup() { - user1 = User.builder().identifier("neo5188@gmail.com") - .providerInfo(ProviderInfo.NAVER) - .nickname("kimdozzi") - .information("백엔드") - .tags("운동") - .role(ADMIN) - .build(); - - user2 = User.builder().identifier("ssang23@naver.com") - .providerInfo(GOOGLE) - .nickname("SEONG") - .information("프론트엔드") - .tags("영화") - .role(USER) - .build(); - - instance1 = Instance.builder() - .title("1일 1커밋") - .description("챌린지 세부사항입니다.") - .pointPerPerson(10) - .tags("BE, CS") - .progress(Progress.ACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - topic1 = Topic.builder() - .title("1일 1커밋") - .description("간단한 설명란") - .pointPerPerson(300) - .tags("BE, CS") - .build(); - - topic2 = Topic.builder() - .title("1일 2커밋") - .description("간단한 설명란") - .pointPerPerson(300) - .tags("BE, CS") - .build(); - - participantInfo1 = ParticipantInfo.builder() - .joinResult(JoinResult.PROCESSING) - .joinStatus(JoinStatus.YES) - .build(); - - participantInfo2 = ParticipantInfo.builder() - .joinResult(JoinResult.SUCCESS) - .joinStatus(JoinStatus.YES) - - mockMvc = MockMvcBuilders - .webAppContextSetup(context) - .apply(springSecurity()) - .build(); - } - -// @Test -// public void 토픽_생성() throws Exception { -// //given -// -// //when -// mockMvc.perform(get("/api/admin/topic") -// .cookie(tokenTestUtil.createAccessCookie())) -// .andExpect(status().isOk()); -// } - -} From 5d0aef2b68a3ae2399eb505da6c1dfdc48b8c90b Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:50:31 +0900 Subject: [PATCH 093/234] =?UTF-8?q?!HOTFIX:=20logout=20API=20endpoint=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/logout 에서 /api/auth/logout으로 변경 --- .../gitget/global/security/controller/AuthController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 76aa1fdd..8ad57060 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -2,11 +2,11 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.security.dto.TokenDTO; import com.genius.gitget.global.security.service.JwtService; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.util.response.dto.CommonResponse; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -41,7 +41,7 @@ public ResponseEntity generateToken(HttpServletResponse response ); } - @PostMapping("/logout") + @PostMapping("/auth/logout") public ResponseEntity logout(HttpServletResponse response) { UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() .getPrincipal(); From 2cf298f23f6f5b53c9caa29f1be938646d957a79 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Thu, 1 Feb 2024 23:04:12 +0900 Subject: [PATCH 094/234] =?UTF-8?q?hotfix:=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genius/gitget/challenge/instance/domain/Instance.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index f19b6d52..c1db260e 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -115,5 +115,4 @@ public void setTopic(Topic topic) { topic.getInstanceList().add(this); } } - } From 872120be8d6d0bd96ba1d21251426cc5123dc6f6 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Thu, 1 Feb 2024 23:13:49 +0900 Subject: [PATCH 095/234] =?UTF-8?q?hotfix:=20Instance=20entity=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/dto/HomeInstanceResponse.java | 2 +- .../challenge/instance/domain/Instance.java | 6 +++--- .../dto/search/InstanceSearchResponse.java | 2 +- .../home/controller/HomeControllerTest.java | 2 +- .../home/service/HomeServiceTest.java | 2 +- .../repository/InstanceRepositoryTest.java | 20 +++++++++---------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java b/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java index 41ea4e45..01064e80 100644 --- a/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java @@ -17,7 +17,7 @@ public record HomeInstanceResponse( public static HomeInstanceResponse createByEntity(Instance instance, Optional files) throws IOException { return HomeInstanceResponse.builder() .title(instance.getTitle()) - .participantCnt(instance.getParticipantCnt()) + .participantCnt(instance.getParticipantCount()) .pointPerPerson(instance.getPointPerPerson()) .fileResponse(convertToFileResponse(files)) .build(); diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index c1db260e..f869feff 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -62,7 +62,7 @@ public class Instance { private int pointPerPerson; - private int participantCnt; + private int participantCount; @NotNull @Enumerated(EnumType.STRING) @@ -96,8 +96,8 @@ public void updateInstance(String description, int pointPerPerson, LocalDateTime this.completedDate = completedDate; } - public void updateParticipantCnt(int amount) { - this.participantCnt += amount; + public void updateParticipantCount(int amount) { + this.participantCount += amount; } public Optional getFiles() { diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index 3663c351..ef0a633c 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -14,6 +14,6 @@ public InstanceSearchResponse(Instance instance) { instance.getId(), instance.getTitle(), instance.getPointPerPerson(), - instance.getParticipantCnt()); + instance.getParticipantCount()); } } diff --git a/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java b/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java index 58106072..211bf9d4 100644 --- a/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java @@ -77,7 +77,7 @@ private Instance getSavedInstance(String title, String tags, int participantCnt) .completedDate(now.plusDays(1)) .build() ); - instance.updateParticipantCnt(participantCnt); + instance.updateParticipantCount(participantCnt); instance.setTopic(getSavedTopic()); return instance; } diff --git a/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java b/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java index d1e7921c..d137f2a0 100644 --- a/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java @@ -69,7 +69,7 @@ private Instance getSavedInstance(String title, String tags, int participantCnt) .completedDate(now.plusDays(1)) .build() ); - instance.updateParticipantCnt(participantCnt); + instance.updateParticipantCount(participantCnt); instance.setTopic(getSavedTopic()); return instance; } diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java index 80a2445d..78e76345 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -42,15 +42,15 @@ public void should_returnInstances_containsUserTags() { assertThat(suggestions.getContent().size()).isEqualTo(3); assertThat(suggestions.getContent().get(0).getTitle()).isEqualTo("title3"); assertThat(suggestions.getContent().get(0).getTags()).isEqualTo("FE"); - assertThat(suggestions.getContent().get(0).getParticipantCnt()).isEqualTo(20); + assertThat(suggestions.getContent().get(0).getParticipantCount()).isEqualTo(20); assertThat(suggestions.getContent().get(1).getTitle()).isEqualTo("title1"); assertThat(suggestions.getContent().get(1).getTags()).isEqualTo("BE"); - assertThat(suggestions.getContent().get(1).getParticipantCnt()).isEqualTo(10); + assertThat(suggestions.getContent().get(1).getParticipantCount()).isEqualTo(10); assertThat(suggestions.getContent().get(2).getTitle()).isEqualTo("title2"); assertThat(suggestions.getContent().get(2).getTags()).isEqualTo("FE"); - assertThat(suggestions.getContent().get(2).getParticipantCnt()).isEqualTo(3); + assertThat(suggestions.getContent().get(2).getParticipantCount()).isEqualTo(3); } @Test @@ -69,15 +69,15 @@ public void should_returnInstances_orderByStartedDate() { assertThat(instances.getContent().size()).isEqualTo(3); assertThat(instances.getContent().get(0).getTitle()).isEqualTo("title3"); assertThat(instances.getContent().get(0).getTags()).isEqualTo("BE"); - assertThat(instances.getContent().get(0).getParticipantCnt()).isEqualTo(20); + assertThat(instances.getContent().get(0).getParticipantCount()).isEqualTo(20); assertThat(instances.getContent().get(1).getTitle()).isEqualTo("title2"); assertThat(instances.getContent().get(1).getTags()).isEqualTo("BE"); - assertThat(instances.getContent().get(1).getParticipantCnt()).isEqualTo(3); + assertThat(instances.getContent().get(1).getParticipantCount()).isEqualTo(3); assertThat(instances.getContent().get(2).getTitle()).isEqualTo("title1"); assertThat(instances.getContent().get(2).getTags()).isEqualTo("BE"); - assertThat(instances.getContent().get(2).getParticipantCnt()).isEqualTo(10); + assertThat(instances.getContent().get(2).getParticipantCount()).isEqualTo(10); } @Test @@ -96,15 +96,15 @@ public void should_returnInstances_orderByParticipantCnt() { assertThat(instances.getContent().size()).isEqualTo(3); assertThat(instances.getContent().get(0).getTitle()).isEqualTo("title3"); assertThat(instances.getContent().get(0).getTags()).isEqualTo("BE"); - assertThat(instances.getContent().get(0).getParticipantCnt()).isEqualTo(20); + assertThat(instances.getContent().get(0).getParticipantCount()).isEqualTo(20); assertThat(instances.getContent().get(1).getTitle()).isEqualTo("title1"); assertThat(instances.getContent().get(1).getTags()).isEqualTo("BE"); - assertThat(instances.getContent().get(1).getParticipantCnt()).isEqualTo(10); + assertThat(instances.getContent().get(1).getParticipantCount()).isEqualTo(10); assertThat(instances.getContent().get(2).getTitle()).isEqualTo("title2"); assertThat(instances.getContent().get(2).getTags()).isEqualTo("BE"); - assertThat(instances.getContent().get(2).getParticipantCnt()).isEqualTo(3); + assertThat(instances.getContent().get(2).getParticipantCount()).isEqualTo(3); } private Instance getSavedInstance(String title, String tags, int participantCnt) { @@ -120,7 +120,7 @@ private Instance getSavedInstance(String title, String tags, int participantCnt) .completedDate(now.plusDays(1)) .build() ); - instance.updateParticipantCnt(participantCnt); + instance.updateParticipantCount(participantCnt); return instance; } } \ No newline at end of file From 4b96a350677d1dfdbf8ef6803bb75016e5050241 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:54:10 +0900 Subject: [PATCH 096/234] =?UTF-8?q?[REFACTOR]=20FileType=20Enum=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=B4=EA=B0=95=20(#59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 프로덕션 코드 변경으로 인한 테스트 코드 수정 * refactor: FileType 정적 팩토리 메서드 예외 추가 처리 - 소문자 외에 대문자로 왔을 때에도 처리가 가능하도록 수정 - 메서드명 오타 수정 * fix: Files 삭제 시, DB의 값을 삭제되지 않는 버그 픽스 * chore: 프로덕션 패키지 구조에 맞게 테스트 코드 패키지 구조 변경 --- .../home/controller/HomeController.java | 4 +- .../gitget/global/file/domain/FileType.java | 5 +- .../gitget/global/file/service/FileUtil.java | 2 +- .../global/file/service/FilesService.java | 1 + .../repository}/TopicRepositoryTest.java | 16 +-- .../topic/service}/TopicServiceTest.java | 22 +-- .../gitget/{ => challenge}/hits/HitsTest.java | 2 +- .../home/controller/HomeControllerTest.java | 2 +- .../home/service/HomeServiceTest.java | 4 +- .../controller/InstanceControllerTest.java | 5 + .../repository/InstanceRepositoryTest.java | 115 +++++++++++++++- .../InstanceSearchRepositoryTest.java | 11 +- .../service}/InstanceSearchServiceTest.java | 29 ++-- .../service}/InstanceServiceTest.java | 17 +-- .../user/controller/UserControllerTest.java | 2 +- .../{ => challenge}/user/domain/UserTest.java | 3 +- .../user/repository/UserRepositoryTest.java | 5 +- .../user/service/UserServiceTest.java | 5 +- .../file/repository/FilesRepositoryTest.java | 3 +- .../file/service/FileUtilTest.java | 3 +- .../file/service/FilesServiceTest.java | 4 +- .../controller/AuthControllerTest.java | 2 +- .../security/service/JwtServiceTest.java | 3 +- .../security/service/JwtUtilTest.java | 3 +- .../security/service/TokenServiceTest.java | 3 +- .../instance/InstanceControllerTest.java | 8 -- .../instance/InstanceRepositoryTest.java | 129 ------------------ 27 files changed, 184 insertions(+), 224 deletions(-) rename src/test/java/com/genius/gitget/{topic => admin/topic/repository}/TopicRepositoryTest.java (94%) rename src/test/java/com/genius/gitget/{topic => admin/topic/service}/TopicServiceTest.java (87%) rename src/test/java/com/genius/gitget/{ => challenge}/hits/HitsTest.java (98%) create mode 100644 src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java rename src/test/java/com/genius/gitget/{instance => challenge/instance/repository}/InstanceSearchRepositoryTest.java (90%) rename src/test/java/com/genius/gitget/{instance => challenge/instance/service}/InstanceSearchServiceTest.java (73%) rename src/test/java/com/genius/gitget/{instance => challenge/instance/service}/InstanceServiceTest.java (90%) rename src/test/java/com/genius/gitget/{ => challenge}/user/controller/UserControllerTest.java (98%) rename src/test/java/com/genius/gitget/{ => challenge}/user/domain/UserTest.java (97%) rename src/test/java/com/genius/gitget/{ => challenge}/user/repository/UserRepositoryTest.java (96%) rename src/test/java/com/genius/gitget/{ => challenge}/user/service/UserServiceTest.java (97%) rename src/test/java/com/genius/gitget/{ => global}/file/repository/FilesRepositoryTest.java (92%) rename src/test/java/com/genius/gitget/{ => global}/file/service/FileUtilTest.java (98%) rename src/test/java/com/genius/gitget/{ => global}/file/service/FilesServiceTest.java (69%) rename src/test/java/com/genius/gitget/{ => global}/security/controller/AuthControllerTest.java (96%) rename src/test/java/com/genius/gitget/{ => global}/security/service/JwtServiceTest.java (98%) rename src/test/java/com/genius/gitget/{ => global}/security/service/JwtUtilTest.java (89%) rename src/test/java/com/genius/gitget/{ => global}/security/service/TokenServiceTest.java (96%) delete mode 100644 src/test/java/com/genius/gitget/instance/InstanceControllerTest.java delete mode 100644 src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java diff --git a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java b/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java index 044733c6..24c183db 100644 --- a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java +++ b/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java @@ -52,7 +52,7 @@ public ResponseEntity> getRecommendInstanc @AuthenticationPrincipal UserPrincipal userPrincipal) { PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), - Sort.by(Direction.DESC, "participantCnt")); + Sort.by(Direction.DESC, "participantCount")); Slice recommendations = homeService.getRecommendations( userPrincipal.getUser(), pageRequest); @@ -64,7 +64,7 @@ public ResponseEntity> getRecommendInstanc @GetMapping("/popular") public ResponseEntity> getPopularInstances(Pageable pageable) { PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), - Sort.by(Direction.DESC, "participantCnt")); + Sort.by(Direction.DESC, "participantCount")); Slice recommendations = homeService.getInstancesByCondition(pageRequest); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/global/file/domain/FileType.java b/src/main/java/com/genius/gitget/global/file/domain/FileType.java index 9cfcb394..d61d2fab 100644 --- a/src/main/java/com/genius/gitget/global/file/domain/FileType.java +++ b/src/main/java/com/genius/gitget/global/file/domain/FileType.java @@ -17,9 +17,10 @@ public enum FileType { private final String path; - public static FileType fineType(String targetType) { + public static FileType findType(String targetType) { + String lowerTargetType = targetType.toLowerCase(); return Arrays.stream(FileType.values()) - .filter(type -> type.path.contains(targetType)) + .filter(type -> type.path.contains(lowerTargetType)) .findFirst() .orElseThrow(() -> new BusinessException(NOT_SUPPORTED_IMAGE_TYPE)); } diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index 26cbe005..c6d13c38 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -30,7 +30,7 @@ public static String encodedImage(Files files) throws IOException { public static UploadDTO getUploadInfo(MultipartFile file, String typeStr, final String UPLOAD_PATH) { String originalFilename = file.getOriginalFilename(); String savedFilename = getSavedFilename(originalFilename); - FileType fileType = FileType.fineType(typeStr); + FileType fileType = FileType.findType(typeStr); return UploadDTO.builder() .fileType(fileType) diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index bceed0aa..1351aa5d 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -83,6 +83,7 @@ public void deleteFile(Long fileId) throws IOException { .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); deleteFilesInStorage(files); + filesRepository.delete(files); } private void deleteFilesInStorage(Files files) { diff --git a/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java b/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java similarity index 94% rename from src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java rename to src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java index 44076f40..d4de84aa 100644 --- a/src/test/java/com/genius/gitget/topic/TopicRepositoryTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java @@ -1,8 +1,10 @@ -package com.genius.gitget.topic; +package com.genius.gitget.admin.topic.repository; + +import static org.junit.jupiter.api.Assertions.assertEquals; import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; import jakarta.transaction.Transactional; +import java.util.Optional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -12,17 +14,13 @@ import org.springframework.data.domain.PageRequest; import org.springframework.test.annotation.Rollback; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; - @SpringBootTest @Transactional @Rollback public class TopicRepositoryTest { + public Topic topic, topicA; @Autowired private TopicRepository topicRepository; - public Topic topic, topicA; @BeforeEach public void setup() { @@ -74,9 +72,9 @@ public void setup() { @Test public void 토픽_리스트_조회() { - for (int i=1; i<=10; i++) { + for (int i = 1; i <= 10; i++) { topicRepository.save( - Topic.builder().title("user"+i+"L").build() + Topic.builder().title("user" + i + "L").build() ); } Page allById = topicRepository.findAllById(PageRequest.of(0, 5)); diff --git a/src/test/java/com/genius/gitget/topic/TopicServiceTest.java b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java similarity index 87% rename from src/test/java/com/genius/gitget/topic/TopicServiceTest.java rename to src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java index 71111953..a7507202 100644 --- a/src/test/java/com/genius/gitget/topic/TopicServiceTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java @@ -1,13 +1,13 @@ -package com.genius.gitget.topic; +package com.genius.gitget.admin.topic.service; import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.dto.TopicCreateRequest; import com.genius.gitget.admin.topic.dto.TopicDetailResponse; import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.admin.topic.service.TopicService; import com.genius.gitget.global.util.exception.BusinessException; import jakarta.transaction.Transactional; +import java.util.Optional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,20 +15,16 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; -import java.util.Optional; - @SpringBootTest @Transactional @Rollback public class TopicServiceTest { + public Topic topic, topicA; @Autowired TopicService topicService; @Autowired TopicRepository topicRepository; - - public Topic topic, topicA; - @BeforeEach public void setup() { topic = Topic.builder() @@ -49,7 +45,8 @@ public void setup() { @Test public void 토픽_생성() throws Exception { //given - TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), topic.getTags(), topic.getPointPerPerson()); + TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), + topic.getTags(), topic.getPointPerPerson()); Long savedTopicId = topicService.createTopic(topicCreateRequest); //when @@ -62,11 +59,13 @@ public void setup() { @Test public void 토픽_수정() throws Exception { //given - TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), topic.getTags(), topic.getPointPerPerson()); + TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), + topic.getTags(), topic.getPointPerPerson()); Long savedTopicId = topicService.createTopic(topicCreateRequest); //when - TopicUpdateRequest topicUpdateRequest = new TopicUpdateRequest("1일 5커밋", topic.getDescription(), topic.getTags(), topic.getPointPerPerson()); + TopicUpdateRequest topicUpdateRequest = new TopicUpdateRequest("1일 5커밋", topic.getDescription(), + topic.getTags(), topic.getPointPerPerson()); topicService.updateTopic(savedTopicId, topicUpdateRequest); //then @@ -78,7 +77,8 @@ public void setup() { @Test public void 토픽_삭제() throws Exception { //given - TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), topic.getTags(), topic.getPointPerPerson()); + TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), + topic.getTags(), topic.getPointPerPerson()); Long savedTopicId = topicService.createTopic(topicCreateRequest); //when diff --git a/src/test/java/com/genius/gitget/hits/HitsTest.java b/src/test/java/com/genius/gitget/challenge/hits/HitsTest.java similarity index 98% rename from src/test/java/com/genius/gitget/hits/HitsTest.java rename to src/test/java/com/genius/gitget/challenge/hits/HitsTest.java index 4f18f449..205f757c 100644 --- a/src/test/java/com/genius/gitget/hits/HitsTest.java +++ b/src/test/java/com/genius/gitget/challenge/hits/HitsTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.hits; +package com.genius.gitget.challenge.hits; import static com.genius.gitget.challenge.user.domain.Role.ADMIN; import static com.genius.gitget.challenge.user.domain.Role.USER; diff --git a/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java b/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java index 211bf9d4..2d8a018d 100644 --- a/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java @@ -71,7 +71,7 @@ private Instance getSavedInstance(String title, String tags, int participantCnt) .tags(tags) .title(title) .description("description") - .progress(Progress.PRE_ACTIVITY) + .progress(Progress.PREACTIVITY) .pointPerPerson(100) .startedDate(now) .completedDate(now.plusDays(1)) diff --git a/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java b/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java index d137f2a0..1ae9d96f 100644 --- a/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java @@ -34,7 +34,7 @@ class HomeServiceTest { @DisplayName("사용자가 설정한 태그에 맞는 추천 인스턴스들을 페이징 형태로 받아올 수 있다.") public void should_getSuggestions_when_passUserTags() { //given - PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCnt")); + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); getSavedInstance("title1", "BE", 20); getSavedInstance("title2", "BE", 10); getSavedInstance("title3", "FE", 10); @@ -63,7 +63,7 @@ private Instance getSavedInstance(String title, String tags, int participantCnt) .tags(tags) .title(title) .description("description") - .progress(Progress.PRE_ACTIVITY) + .progress(Progress.PREACTIVITY) .pointPerPerson(100) .startedDate(now) .completedDate(now.plusDays(1)) diff --git a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java new file mode 100644 index 00000000..bebb1f6d --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java @@ -0,0 +1,5 @@ +package com.genius.gitget.challenge.instance.controller; + + +public class InstanceControllerTest { +} diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java index 78e76345..945c18d4 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -7,10 +7,12 @@ import java.time.LocalDateTime; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; @@ -24,18 +26,119 @@ class InstanceRepositoryTest { @Autowired InstanceRepository instanceRepository; + @Test + public void 인스턴스_생성() throws Exception { + //given + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + Instance savedInstance = instanceRepository.save(instance); + + //then + Assertions.assertThat(savedInstance.getTitle()).isEqualTo("1일 1알고리즘"); + } + + @Test + public void 인스턴스_수정() throws Exception { + //given + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + Instance savedInstance = instanceRepository.save(instance); + savedInstance.updateInstance("수정되었습니다.", 10000, LocalDateTime.now(), LocalDateTime.now().plusDays(5)); + + //then + Assertions.assertThat(instance.getDescription()).isEqualTo(savedInstance.getDescription()); + } + + @Test + public void 인스턴스_조회() throws Exception { + //given + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + Instance savedInstance = instanceRepository.save(instance); + + //then + Assertions.assertThat(savedInstance.getTitle()).isEqualTo("1일 1알고리즘"); + } + + @Test + public void 인스턴스_리스트_조회() throws Exception { + //given + Instance instance1 = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + Instance instance2 = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + //when + instanceRepository.save(instance1); + instanceRepository.save(instance2); + + Page instances = instanceRepository.findAllById(PageRequest.of(0, 5, Sort.Direction.DESC, "id")); + + for (Instance instance : instances) { + if (instance != null) { + System.out.println("instance = " + instance.getId() + " " + instance.getTitle()); + } + } + + //then + Assertions.assertThat(instances.getTotalElements()).isEqualTo(2); + } + + @Test @DisplayName("인스턴스들 중, 사용자의 tag가 포함되어 있는 인스턴스들을 반환받을 수 있다.") public void should_returnInstances_containsUserTags() { //given List userTags = List.of("BE", "FE", "AI"); - PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCnt")); + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); //when getSavedInstance("title1", "BE", 10); getSavedInstance("title2", "FE", 3); getSavedInstance("title3", "FE", 20); - Slice suggestions = instanceRepository.findRecommendations(userTags, Progress.PRE_ACTIVITY, + Slice suggestions = instanceRepository.findRecommendations(userTags, Progress.PREACTIVITY, pageRequest); //then @@ -63,7 +166,7 @@ public void should_returnInstances_orderByStartedDate() { getSavedInstance("title1", "BE", 10); getSavedInstance("title2", "BE", 3); getSavedInstance("title3", "BE", 20); - Slice instances = instanceRepository.findInstanceByCondition(Progress.PRE_ACTIVITY, pageRequest); + Slice instances = instanceRepository.findInstanceByCondition(Progress.PREACTIVITY, pageRequest); //then assertThat(instances.getContent().size()).isEqualTo(3); @@ -84,13 +187,13 @@ public void should_returnInstances_orderByStartedDate() { @DisplayName("인스턴스들 중, 참여 인원 수가 많은 순서대로 인스턴스들을 정렬하여 반환받을 수 있다.") public void should_returnInstances_orderByParticipantCnt() { //given - PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCnt")); + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); //when getSavedInstance("title1", "BE", 10); getSavedInstance("title2", "BE", 3); getSavedInstance("title3", "BE", 20); - Slice instances = instanceRepository.findInstanceByCondition(Progress.PRE_ACTIVITY, pageRequest); + Slice instances = instanceRepository.findInstanceByCondition(Progress.PREACTIVITY, pageRequest); //then assertThat(instances.getContent().size()).isEqualTo(3); @@ -114,7 +217,7 @@ private Instance getSavedInstance(String title, String tags, int participantCnt) .tags(tags) .title(title) .description("description") - .progress(Progress.PRE_ACTIVITY) + .progress(Progress.PREACTIVITY) .pointPerPerson(100) .startedDate(now) .completedDate(now.plusDays(1)) diff --git a/src/test/java/com/genius/gitget/instance/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java similarity index 90% rename from src/test/java/com/genius/gitget/instance/InstanceSearchRepositoryTest.java rename to src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index d1747c5e..390c7971 100644 --- a/src/test/java/com/genius/gitget/instance/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -1,10 +1,9 @@ -package com.genius.gitget.instance; +package com.genius.gitget.challenge.instance.repository; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.instance.repository.SearchRepository; +import java.time.LocalDateTime; import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -15,8 +14,6 @@ import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - @SpringBootTest @Transactional @Rollback @@ -65,9 +62,9 @@ public class InstanceSearchRepositoryTest { instanceRepository.save(instance1); instanceRepository.save(instance2); - //then - Page order = searchRepository.findByProgressAndTitleContainingOrderByStartedDateDesc( Progress.PREACTIVITY, "고리" , PageRequest.of(0, 3)); + Page order = searchRepository.findByProgressAndTitleContainingOrderByStartedDateDesc( + Progress.PREACTIVITY, "고리", PageRequest.of(0, 3)); for (Instance item : order) { System.out.println("item = " + item); } diff --git a/src/test/java/com/genius/gitget/instance/InstanceSearchServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java similarity index 73% rename from src/test/java/com/genius/gitget/instance/InstanceSearchServiceTest.java rename to src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java index d34241c9..f8135169 100644 --- a/src/test/java/com/genius/gitget/instance/InstanceSearchServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.instance; +package com.genius.gitget.challenge.instance.service; import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; @@ -9,8 +9,7 @@ import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.instance.repository.SearchRepository; -import com.genius.gitget.challenge.instance.service.InstanceSearchService; -import com.genius.gitget.challenge.instance.service.InstanceService; +import java.time.LocalDateTime; import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -21,8 +20,6 @@ import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - @SpringBootTest @Transactional @Rollback @@ -62,22 +59,26 @@ public class InstanceSearchServiceTest { Topic savedTopic = topicRepository.save(topic); - instanceService.createInstance(new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); + instanceService.createInstance( + new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), + instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); - instanceService.createInstance(new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); - - instanceService.createInstance(new InstanceCreateRequest(savedTopic.getId(), "테스트", instance.getTags(), instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); + instanceService.createInstance( + new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), + instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); + instanceService.createInstance( + new InstanceCreateRequest(savedTopic.getId(), "테스트", instance.getTags(), instance.getDescription(), + instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); //when InstanceSearchRequest instanceSearchRequest = new InstanceSearchRequest("고리", "preactivity"); - //then - Page orderList = instanceSearchService.searchInstances("고리", "preactivity", PageRequest.of(0, 3)); + Page orderList = instanceSearchService.searchInstances("고리", "preactivity", + PageRequest.of(0, 3)); for (InstanceSearchResponse instanceSearchResponse : orderList) { System.out.println("instanceSearchResponse = " + instanceSearchResponse.keyword()); diff --git a/src/test/java/com/genius/gitget/instance/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java similarity index 90% rename from src/test/java/com/genius/gitget/instance/InstanceServiceTest.java rename to src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java index f732fccd..b456ddb9 100644 --- a/src/test/java/com/genius/gitget/instance/InstanceServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.instance; +package com.genius.gitget.challenge.instance.service; import com.genius.gitget.admin.topic.domain.Topic; @@ -9,7 +9,8 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.instance.service.InstanceService; +import java.time.LocalDateTime; +import java.util.Optional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,9 +19,6 @@ import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Optional; - @SpringBootTest @Transactional @Rollback @@ -60,7 +58,8 @@ public void setup() { public void 인스턴스_생성() throws Exception { //given Topic savedTopic = topicRepository.save(topic); - InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), + InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), + instance.getTags(), instance.getDescription(), instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); //when @@ -76,7 +75,8 @@ public void setup() { //given Topic savedTopic = topicRepository.save(topic); - InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), + InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), + instance.getTags(), instance.getDescription(), instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); Long savedInstanceId = instanceService.createInstance(instanceCreateRequest); @@ -96,7 +96,8 @@ public void setup() { //given Topic savedTopic = topicRepository.save(topic); - InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), instance.getDescription(), + InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), + instance.getTags(), instance.getDescription(), instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); Long savedInstanceId = instanceService.createInstance(instanceCreateRequest); diff --git a/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java b/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java similarity index 98% rename from src/test/java/com/genius/gitget/user/controller/UserControllerTest.java rename to src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java index 6eccd164..8091c136 100644 --- a/src/test/java/com/genius/gitget/user/controller/UserControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.user.controller; +package com.genius.gitget.challenge.user.controller; import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; diff --git a/src/test/java/com/genius/gitget/user/domain/UserTest.java b/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java similarity index 97% rename from src/test/java/com/genius/gitget/user/domain/UserTest.java rename to src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java index c8611cd9..be99cf9b 100644 --- a/src/test/java/com/genius/gitget/user/domain/UserTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.user.domain; +package com.genius.gitget.challenge.user.domain; import static com.genius.gitget.challenge.user.domain.Role.ADMIN; import static com.genius.gitget.challenge.user.domain.Role.USER; @@ -7,7 +7,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.xmlunit.util.Linqy.count; -import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/user/repository/UserRepositoryTest.java similarity index 96% rename from src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java rename to src/test/java/com/genius/gitget/challenge/user/repository/UserRepositoryTest.java index 7a8f51d4..30a1a8ff 100644 --- a/src/test/java/com/genius/gitget/user/repository/UserRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/repository/UserRepositoryTest.java @@ -1,12 +1,11 @@ -package com.genius.gitget.user.repository; +package com.genius.gitget.challenge.user.repository; import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.security.constants.ProviderInfo; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/gitget/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java similarity index 97% rename from src/test/java/com/genius/gitget/user/service/UserServiceTest.java rename to src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index 022d7ea1..2e6dd8f1 100644 --- a/src/test/java/com/genius/gitget/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.user.service; +package com.genius.gitget.challenge.user.service; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -7,7 +7,6 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; @@ -104,7 +103,7 @@ public void should_throwException_when_nicknameIsDuplicated() { .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.DUPLICATED_NICKNAME.getMessage()); } - + private void saveUnsignedUser() { userRepository.save(User.builder() diff --git a/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java b/src/test/java/com/genius/gitget/global/file/repository/FilesRepositoryTest.java similarity index 92% rename from src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java rename to src/test/java/com/genius/gitget/global/file/repository/FilesRepositoryTest.java index 6f6297a0..3a3e581f 100644 --- a/src/test/java/com/genius/gitget/file/repository/FilesRepositoryTest.java +++ b/src/test/java/com/genius/gitget/global/file/repository/FilesRepositoryTest.java @@ -1,10 +1,9 @@ -package com.genius.gitget.file.repository; +package com.genius.gitget.global.file.repository; import static org.assertj.core.api.Assertions.assertThat; import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.repository.FilesRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java similarity index 98% rename from src/test/java/com/genius/gitget/file/service/FileUtilTest.java rename to src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java index faa1363c..dcdc0f81 100644 --- a/src/test/java/com/genius/gitget/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.file.service; +package com.genius.gitget.global.file.service; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; @@ -8,7 +8,6 @@ import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; -import com.genius.gitget.global.file.service.FileUtil; import com.genius.gitget.global.util.exception.BusinessException; import java.io.File; import java.io.IOException; diff --git a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java b/src/test/java/com/genius/gitget/global/file/service/FilesServiceTest.java similarity index 69% rename from src/test/java/com/genius/gitget/file/service/FilesServiceTest.java rename to src/test/java/com/genius/gitget/global/file/service/FilesServiceTest.java index 9570c02e..4bf2b1af 100644 --- a/src/test/java/com/genius/gitget/file/service/FilesServiceTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FilesServiceTest.java @@ -1,7 +1,5 @@ -package com.genius.gitget.file.service; +package com.genius.gitget.global.file.service; -import com.genius.gitget.global.file.service.FilesService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; diff --git a/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java b/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java similarity index 96% rename from src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java rename to src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java index 01cf05b2..e78c5815 100644 --- a/src/test/java/com/genius/gitget/security/controller/AuthControllerTest.java +++ b/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.controller; +package com.genius.gitget.global.security.controller; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; diff --git a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java similarity index 98% rename from src/test/java/com/genius/gitget/security/service/JwtServiceTest.java rename to src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java index a550c043..bae38396 100644 --- a/src/test/java/com/genius/gitget/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.security.service; +package com.genius.gitget.global.security.service; import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; @@ -14,7 +14,6 @@ import com.genius.gitget.global.security.constants.JwtRule; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.security.repository.TokenRepository; -import com.genius.gitget.global.security.service.JwtService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.util.TokenTestUtil; diff --git a/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java b/src/test/java/com/genius/gitget/global/security/service/JwtUtilTest.java similarity index 89% rename from src/test/java/com/genius/gitget/security/service/JwtUtilTest.java rename to src/test/java/com/genius/gitget/global/security/service/JwtUtilTest.java index 922e702c..43b8c2f0 100644 --- a/src/test/java/com/genius/gitget/security/service/JwtUtilTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/JwtUtilTest.java @@ -1,9 +1,8 @@ -package com.genius.gitget.security.service; +package com.genius.gitget.global.security.service; import static org.assertj.core.api.Assertions.assertThat; import com.genius.gitget.global.security.constants.JwtRule; -import com.genius.gitget.global.security.service.JwtUtil; import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/gitget/security/service/TokenServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java similarity index 96% rename from src/test/java/com/genius/gitget/security/service/TokenServiceTest.java rename to src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java index c4a828a1..2ec125c8 100644 --- a/src/test/java/com/genius/gitget/security/service/TokenServiceTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java @@ -1,10 +1,9 @@ -package com.genius.gitget.security.service; +package com.genius.gitget.global.security.service; import static org.assertj.core.api.Assertions.assertThat; import com.genius.gitget.global.security.domain.Token; import com.genius.gitget.global.security.repository.TokenRepository; -import com.genius.gitget.global.security.service.TokenService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/gitget/instance/InstanceControllerTest.java b/src/test/java/com/genius/gitget/instance/InstanceControllerTest.java deleted file mode 100644 index 8c02bc07..00000000 --- a/src/test/java/com/genius/gitget/instance/InstanceControllerTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.genius.gitget.instance; - - -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; - - -public class InstanceControllerTest { -} diff --git a/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java deleted file mode 100644 index c0f0e5fb..00000000 --- a/src/test/java/com/genius/gitget/instance/InstanceRepositoryTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.genius.gitget.instance; - -import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import lombok.extern.slf4j.Slf4j; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; - -@SpringBootTest -@Transactional -@Rollback -@Slf4j -public class InstanceRepositoryTest { - @Autowired - TopicRepository topicRepository; - @Autowired - InstanceRepository instanceRepository; - - @Test - public void 인스턴스_생성() throws Exception { - //given - Instance instance = Instance.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - //when - Instance savedInstance = instanceRepository.save(instance); - - //then - Assertions.assertThat(savedInstance.getId()).isEqualTo(1L); - } - - @Test - public void 인스턴스_수정() throws Exception { - //given - Instance instance = Instance.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - //when - Instance savedInstance = instanceRepository.save(instance); - savedInstance.updateInstance("수정되었습니다.", 10000, LocalDateTime.now(), LocalDateTime.now().plusDays(5)); - - //then - Assertions.assertThat(instance.getDescription()).isEqualTo(savedInstance.getDescription()); - } - - @Test - public void 인스턴스_조회() throws Exception { - //given - Instance instance = Instance.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - //when - Instance savedInstance = instanceRepository.save(instance); - - //then - Assertions.assertThat(savedInstance.getTitle()).isEqualTo("1일 1알고리즘"); - } - - @Test - public void 인스턴스_리스트_조회() throws Exception { - //given - Instance instance1 = Instance.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - Instance instance2 = Instance.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - //when - instanceRepository.save(instance1); - instanceRepository.save(instance2); - - Page instances = instanceRepository.findAllById(PageRequest.of(0, 5, Sort.Direction.DESC, "id")); - - for (Instance instance : instances) { - if (instance != null) { - System.out.println("instance = " + instance.getId() + " " + instance.getTitle()); - } - } - - //then - Assertions.assertThat(instances.getTotalElements()).isEqualTo(2); - } -} From fa331c20c7f43a9d29a1ecc992bb4ebeac838bc2 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Fri, 2 Feb 2024 23:28:34 +0900 Subject: [PATCH 097/234] =?UTF-8?q?[FEAT]=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=8C=8C=EC=9D=BC=20api=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#61)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: enum converter global 패키지로 이동 * feat: 어드민 페이지 topic - file 적용 * feat: 어드민 페이지 instance - file 적용 * feat: admin file api 적용 완료 --- .../topic/controller/TopicController.java | 21 ++++-- .../gitget/admin/topic/domain/Topic.java | 15 ++++- .../admin/topic/dto/TopicCreateRequest.java | 5 +- .../admin/topic/dto/TopicDetailResponse.java | 38 ++++++++--- .../admin/topic/dto/TopicPagingResponse.java | 28 ++++++-- .../admin/topic/dto/TopicUpdateRequest.java | 5 +- .../admin/topic/service/TopicService.java | 52 +++++++++++---- .../{instance => }/config/WebConfig.java | 4 +- .../challenge/home/service/HomeService.java | 2 - .../controller/InstanceController.java | 27 +++++--- .../challenge/instance/domain/Instance.java | 12 +++- .../dto/crud/InstanceCreateRequest.java | 3 +- .../dto/crud/InstanceDetailResponse.java | 42 ++++++++---- .../dto/crud/InstancePagingResponse.java | 35 +++++++--- .../dto/crud/InstanceUpdateRequest.java | 1 + .../dto/search/InstanceSearchResponse.java | 2 +- .../service/InstanceSearchService.java | 4 -- .../instance/service/InstanceService.java | 64 +++++++++++++------ .../{domain => service}/StringToEnum.java | 5 +- .../gitget/global/file/dto/FileRequest.java | 9 --- .../topic/repository/TopicRepositoryTest.java | 2 +- 21 files changed, 261 insertions(+), 115 deletions(-) rename src/main/java/com/genius/gitget/challenge/{instance => }/config/WebConfig.java (76%) rename src/main/java/com/genius/gitget/challenge/instance/{domain => service}/StringToEnum.java (66%) delete mode 100644 src/main/java/com/genius/gitget/global/file/dto/FileRequest.java diff --git a/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java b/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java index e33ec350..f86a99f9 100644 --- a/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java +++ b/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java @@ -5,6 +5,9 @@ import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; import com.genius.gitget.admin.topic.dto.TopicDetailResponse; import com.genius.gitget.admin.topic.service.TopicService; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; @@ -17,6 +20,9 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; @RestController @RequiredArgsConstructor @@ -38,7 +44,7 @@ public ResponseEntity> getAllTopics( // 토픽 상세 정보 요청 @GetMapping("/{id}") - public ResponseEntity> getTopicById(@PathVariable Long id) { + public ResponseEntity> getTopicById(@PathVariable Long id) throws IOException { TopicDetailResponse topicDetail = topicService.getTopicById(id); return ResponseEntity.ok().body( new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), topicDetail) @@ -47,8 +53,9 @@ public ResponseEntity> getTopicById(@PathVar // 토픽 생성 요청 @PostMapping - public ResponseEntity createTopic(@RequestBody @Valid TopicCreateRequest topicCreateRequest) { - topicService.createTopic(topicCreateRequest); + public ResponseEntity createTopic(@RequestPart(value = "data") TopicCreateRequest topicCreateRequest, + @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type") String type) throws IOException { + topicService.createTopic(topicCreateRequest, multipartFile, type); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.CREATED.getStatus(), SuccessCode.CREATED.getMessage()) ); @@ -56,9 +63,9 @@ public ResponseEntity createTopic(@RequestBody @Valid TopicCreat // 토픽 수정 요청 @PatchMapping("/{id}") - public ResponseEntity updateTopic(@PathVariable Long id, - @RequestBody @Valid TopicUpdateRequest topicUpdateRequest) { - topicService.updateTopic(id, topicUpdateRequest); + public ResponseEntity updateTopic(@PathVariable Long id, @RequestPart(value = "data") TopicUpdateRequest topicUpdateRequest, + @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type") String type) throws IOException { + topicService.updateTopic(id, topicUpdateRequest, multipartFile, type); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) ); @@ -66,7 +73,7 @@ public ResponseEntity updateTopic(@PathVariable Long id, // 토픽 삭제 요청 @DeleteMapping("/{id}") - public ResponseEntity deleteTopic(@PathVariable Long id) { + public ResponseEntity deleteTopic(@PathVariable Long id) throws IOException { topicService.deleteTopic(id); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java index 339e2516..7461d146 100644 --- a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java @@ -14,6 +14,8 @@ import jakarta.persistence.Table; import java.util.ArrayList; import java.util.List; +import java.util.Optional; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -43,14 +45,17 @@ public class Topic { private String tags; + private String notice; + private int pointPerPerson; @Builder - public Topic(String title, String description, String tags, int pointPerPerson) { + public Topic(String title, String description, String tags, String notice, int pointPerPerson) { this.title = title; this.description = description; this.tags = tags; + this.notice = notice; this.pointPerPerson = pointPerPerson; } @@ -59,15 +64,19 @@ public void updateExistInstance(String description) { this.description = description; } - public void updateNotExistInstance(String title, String description, String tags, int pointPerPerson) { + public void updateNotExistInstance(String title, String description, String tags, String notice, int pointPerPerson) { this.title = title; this.description = description; this.tags = tags; + this.notice = notice; this.pointPerPerson = pointPerPerson; } - public void setFiles(Files files) { this.files = files; } + + public Optional getFiles() { + return Optional.ofNullable(this.files); + } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java index 183bf254..890e20f8 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java @@ -4,8 +4,7 @@ public record TopicCreateRequest( String title, String tags, String description, - int pointPerPerson - // 이미지 - // 유의사항 + int pointPerPerson, + String notice ) { } diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java index 768764ca..aa330ec0 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java @@ -1,12 +1,32 @@ package com.genius.gitget.admin.topic.dto; -public record TopicDetailResponse( - Long topicId, - String title, - String tags, - String description, - // 이미지 - // 유의사항 - int pointPerPerson -) { +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import lombok.Builder; + +import java.io.IOException; +import java.util.Optional; + +@Builder +public record TopicDetailResponse(Long topicId, String title, String tags, + String description, String notice, int pointPerPerson, FileResponse fileResponse) { + public static TopicDetailResponse createByEntity(Topic topic, Optional files) throws IOException { + return TopicDetailResponse.builder() + .topicId(topic.getId()) + .title(topic.getTitle()) + .tags(topic.getTags()) + .description(topic.getDescription()) + .notice(topic.getNotice()) + .pointPerPerson(topic.getPointPerPerson()) + .fileResponse(convertToFileResponse(files)) + .build(); + } + + private static FileResponse convertToFileResponse(Optional files) throws IOException { + if (files.isEmpty()) { + return FileResponse.createNotExistFile(); + } + return FileResponse.createExistFile(files.get()); + } } diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java index 02aa8a65..64caab46 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java @@ -1,8 +1,28 @@ package com.genius.gitget.admin.topic.dto; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import lombok.Builder; -public record TopicPagingResponse( - Long topicId, - String title -) { +import java.io.IOException; +import java.util.Optional; + +@Builder +public record TopicPagingResponse(Long topicId, String title, FileResponse fileResponse) { + + public static TopicPagingResponse createByEntity(Topic topic, Optional files) throws IOException { + return TopicPagingResponse.builder() + .topicId(topic.getId()) + .title(topic.getTitle()) + .fileResponse(convertToFileResponse(files)) + .build(); + } + + private static FileResponse convertToFileResponse(Optional files) throws IOException { + if (files.isEmpty()) { + return FileResponse.createNotExistFile(); + } + return FileResponse.createExistFile(files.get()); + } } diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java index 356a69bf..cf93d0ee 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java @@ -4,8 +4,7 @@ public record TopicUpdateRequest( String title, String tags, String description, - int pointPerPerson - // 이미지 - // 유의사항 + int pointPerPerson, + String notice ) { } diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index fcc5df85..07c8a482 100644 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -6,6 +6,10 @@ import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.dto.TopicDetailResponse; import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.repository.FilesRepository; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import lombok.RequiredArgsConstructor; @@ -13,62 +17,88 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Optional; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class TopicService { private final TopicRepository topicRepository; + private final FilesService filesService; // 토픽 리스트 요청 public Page getAllTopics(Pageable pageable) { Page topics = topicRepository.findAllById(pageable); - return topics.map(topic -> new TopicPagingResponse(topic.getId(), topic.getTitle())); + return topics.map(this::mapToTopicPagingResponse); } // 토픽 상세정보 요청 - public TopicDetailResponse getTopicById(Long id) { + public TopicDetailResponse getTopicById(Long id) throws IOException { Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - return new TopicDetailResponse(topic.getId(), topic.getTitle(), topic.getTags(), topic.getDescription(), - topic.getPointPerPerson()); + return TopicDetailResponse.createByEntity(topic,topic.getFiles()); } // 토픽 생성 요청 @Transactional - public Long createTopic(TopicCreateRequest topicCreateRequest) { + public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile multipartFile, String type) throws IOException { + Files uploadedFile = filesService.uploadFile(multipartFile, type); + Topic topic = Topic.builder() .title(topicCreateRequest.title()) .description(topicCreateRequest.description()) .tags(topicCreateRequest.tags()) .pointPerPerson(topicCreateRequest.pointPerPerson()) - // 이미지 - // 유의사항 + .notice(topicCreateRequest.notice()) .build(); + + topic.setFiles(uploadedFile); + Topic savedTopic = topicRepository.save(topic); - // 생성된 토픽을 ID로 조회 가능하도록 수정 (01/29) return savedTopic.getId(); } @Transactional - public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest) { + public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest, MultipartFile multipartFile, String type) throws IOException { Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + Optional findTopicFile = topic.getFiles(); + Long findTopicFileId = findTopicFile.get().getId(); + + filesService.updateFile(findTopicFileId, multipartFile); + // 서버에서 한번 더 검사 boolean hasInstance = !topic.getInstanceList().isEmpty(); if (hasInstance) { topic.updateExistInstance(topicUpdateRequest.description()); } else { topic.updateNotExistInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), - topicUpdateRequest.tags(), topicUpdateRequest.pointPerPerson()); + topicUpdateRequest.tags(), topicUpdateRequest.notice(), topicUpdateRequest.pointPerPerson()); } topicRepository.save(topic); } // 토픽 삭제 요청 @Transactional - public void deleteTopic(Long id) { + public void deleteTopic(Long id) throws IOException { Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + + Optional findTopicFile = topic.getFiles(); + Long findTopicFileId = findTopicFile.get().getId(); + + filesService.deleteFile(findTopicFileId); + topic.setFiles(null); topicRepository.delete(topic); } + + private TopicPagingResponse mapToTopicPagingResponse(Topic topic) { + try { + return TopicPagingResponse.createByEntity(topic, topic.getFiles()); + } catch (IOException e) { + throw new BusinessException(e); + } + } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/config/WebConfig.java b/src/main/java/com/genius/gitget/challenge/config/WebConfig.java similarity index 76% rename from src/main/java/com/genius/gitget/challenge/instance/config/WebConfig.java rename to src/main/java/com/genius/gitget/challenge/config/WebConfig.java index 3ae154ec..153a8eb4 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/config/WebConfig.java +++ b/src/main/java/com/genius/gitget/challenge/config/WebConfig.java @@ -1,6 +1,6 @@ -package com.genius.gitget.challenge.instance.config; +package com.genius.gitget.challenge.config; -import com.genius.gitget.challenge.instance.domain.StringToEnum; +import com.genius.gitget.challenge.instance.service.StringToEnum; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; diff --git a/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java b/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java index 10c5224a..69e6078f 100644 --- a/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java +++ b/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java @@ -24,8 +24,6 @@ @RequiredArgsConstructor public class HomeService { private final InstanceRepository instanceRepository; - private final FilesService filesService; - public Slice getRecommendations(User user, Pageable pageable) { List userTags = Arrays.stream(user.getTags().split(",")).toList(); diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index ea18130a..dc5c27c7 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -1,10 +1,13 @@ package com.genius.gitget.challenge.instance.controller; +import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; @@ -17,6 +20,10 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Optional; @RestController @RequestMapping("/api/admin/instance") @@ -26,8 +33,8 @@ public class InstanceController { // 인스턴스 리스트 조회 @GetMapping - public ResponseEntity> getAllInstances( - @PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id") Pageable pageable) { + public ResponseEntity> getAllInstances ( + @PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id") Pageable pageable) throws IOException{ Page instances = instanceService.getAllInstances(pageable); return ResponseEntity.ok().body( @@ -37,7 +44,7 @@ public ResponseEntity> getAllInstances( // 인스턴스 단건 조회 @GetMapping("/{id}") - public ResponseEntity> getInstanceById(@PathVariable Long id) { + public ResponseEntity> getInstanceById(@PathVariable Long id) throws IOException{ InstanceDetailResponse instanceDetails = instanceService.getInstanceById(id); return ResponseEntity.ok().body( new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), instanceDetails) @@ -47,8 +54,9 @@ public ResponseEntity> getInstanceById(@P // 인스턴스 생성 @PostMapping public ResponseEntity createInstance( - @RequestBody @Valid InstanceCreateRequest instanceCreateRequest) { - instanceService.createInstance(instanceCreateRequest); + @RequestPart(value = "data") InstanceCreateRequest instanceCreateRequest, + @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type") String type) throws IOException { + instanceService.createInstance(instanceCreateRequest, multipartFile, type); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) ); @@ -56,9 +64,10 @@ public ResponseEntity createInstance( // 인스턴스 수정 @PatchMapping("/{id}") - public ResponseEntity updateInstance(@PathVariable Long id, - @RequestBody @Valid InstanceUpdateRequest instanceUpdateRequest) { - instanceService.updateInstance(id, instanceUpdateRequest); + public ResponseEntity updateInstance(@PathVariable Long id, @RequestPart(value = "data") InstanceUpdateRequest instanceUpdateRequest, + @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type") String type) throws IOException{ + + instanceService.updateInstance(id, instanceUpdateRequest, multipartFile, type); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) ); @@ -66,7 +75,7 @@ public ResponseEntity updateInstance(@PathVariable Long id, // 인스턴스 삭제 @DeleteMapping("/{id}") - public ResponseEntity deleteInstance(@PathVariable Long id) { + public ResponseEntity deleteInstance(@PathVariable Long id) throws IOException{ instanceService.deleteInstance(id); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index f869feff..702875d8 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -64,6 +64,10 @@ public class Instance { private int participantCount; + private String notice; + + private String certificationMethod; + @NotNull @Enumerated(EnumType.STRING) private Progress progress; @@ -75,22 +79,24 @@ public class Instance { private LocalDateTime completedDate; @Builder - public Instance(String title, String description, String tags, int pointPerPerson, Progress progress, + public Instance(String title, String description, String tags, int pointPerPerson, Progress progress, String notice, String certificationMethod, LocalDateTime startedDate, LocalDateTime completedDate) { this.title = title; this.description = description; this.tags = tags; this.pointPerPerson = pointPerPerson; + this.notice = notice; + this.certificationMethod = certificationMethod; this.progress = progress; this.startedDate = startedDate; this.completedDate = completedDate; } //== 비지니스 로직 ==// - - public void updateInstance(String description, int pointPerPerson, LocalDateTime startedDate, + public void updateInstance(String description, String notice, int pointPerPerson, LocalDateTime startedDate, LocalDateTime completedDate) { this.description = description; + this.notice = notice; this.pointPerPerson = pointPerPerson; this.startedDate = startedDate; this.completedDate = completedDate; diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java index 4e544bdb..8faa54c7 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java @@ -7,8 +7,7 @@ public record InstanceCreateRequest( String title, String tags, String description, - // TODO 이미지 - // TODO 유의사항 + String notice, int pointPerPerson, LocalDateTime startedAt, LocalDateTime completedAt diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java index 3c6a5484..e5375dae 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java @@ -1,18 +1,36 @@ package com.genius.gitget.challenge.instance.dto.crud; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import lombok.Builder; + +import java.io.IOException; import java.time.LocalDateTime; +import java.util.Optional; -public record InstanceDetailResponse( - Long topicId, - Long instanceId, - String title, - String description, - int pointPerPerson, - String tags, - // 이미지 - // 유의사항 - LocalDateTime startedAt, - LocalDateTime completedAt -) { +@Builder +public record InstanceDetailResponse(Long topicId, Long instanceId, String title, String description, int pointPerPerson, + String tags, String notice, LocalDateTime startedAt, LocalDateTime completedAt, FileResponse fileResponse) { + public static InstanceDetailResponse createByEntity(Instance instance, Optional files) throws IOException { + return InstanceDetailResponse.builder() + .topicId(instance.getTopic().getId()) + .instanceId(instance.getId()) + .title(instance.getTitle()) + .description(instance.getDescription()) + .pointPerPerson(instance.getPointPerPerson()) + .tags(instance.getTags()) + .notice(instance.getNotice()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()) + .fileResponse(convertToFileResponse(files)) + .build(); + } + private static FileResponse convertToFileResponse(Optional files) throws IOException { + if (files.isEmpty()) { + return FileResponse.createNotExistFile(); + } + return FileResponse.createExistFile(files.get()); + } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java index 7506929d..c09c3503 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java @@ -1,13 +1,32 @@ package com.genius.gitget.challenge.instance.dto.crud; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import lombok.Builder; + +import java.io.IOException; import java.time.LocalDateTime; +import java.util.Optional; + +@Builder +public record InstancePagingResponse (Long topicId, Long instanceId, String title, + LocalDateTime startedAt, LocalDateTime completedAt, FileResponse fileResponse) { + public static InstancePagingResponse createByEntity(Instance instance, Optional files) throws IOException { + return InstancePagingResponse.builder() + .topicId(instance.getTopic().getId()) + .instanceId(instance.getId()) + .title(instance.getTitle()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()) + .fileResponse(convertToFileResponse(files)) + .build(); + } -public record InstancePagingResponse( - Long topicId, - Long instanceId, - String title, - // 이미지 - LocalDateTime startedAt, - LocalDateTime completedAt -) { + private static FileResponse convertToFileResponse(Optional files) throws IOException { + if (files.isEmpty()) { + return FileResponse.createNotExistFile(); + } + return FileResponse.createExistFile(files.get()); + } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java index 85ca5407..9ab449f9 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java @@ -5,6 +5,7 @@ public record InstanceUpdateRequest( Long topicId, String description, + String notice, int pointPerPerson, LocalDateTime startedAt, LocalDateTime completedAt diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index ef0a633c..022e6e74 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -16,4 +16,4 @@ public InstanceSearchResponse(Instance instance) { instance.getPointPerPerson(), instance.getParticipantCount()); } -} +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java index e29e7422..6f1ba23b 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java @@ -2,15 +2,11 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.domain.StringToEnum; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; import com.genius.gitget.challenge.instance.repository.SearchRepository; -import lombok.Builder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index caa42897..5ab6732e 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -6,6 +6,8 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.repository.InstanceRepository; @@ -15,6 +17,12 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Optional; import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; @@ -23,75 +31,89 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class InstanceService { - private final InstanceRepository instanceRepository; private final TopicRepository topicRepository; + private final FilesService filesService; // 인스턴스 생성 @Transactional - public Long createInstance(InstanceCreateRequest instanceCreateRequest) { + public Long createInstance(InstanceCreateRequest instanceCreateRequest, + MultipartFile multipartFile, String type) throws IOException { Topic topic = topicRepository.findById(instanceCreateRequest.topicId()) .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); + Files uploadedFile = filesService.uploadFile(multipartFile, type); + Instance instance = Instance.builder() .title(instanceCreateRequest.title()) .tags(instanceCreateRequest.tags()) .description(instanceCreateRequest.description()) .pointPerPerson(instanceCreateRequest.pointPerPerson()) + .notice(instanceCreateRequest.notice()) .startedDate(instanceCreateRequest.startedAt()) .completedDate(instanceCreateRequest.completedAt()) .progress(Progress.PREACTIVITY) .build(); instance.setTopic(topic); + instance.setFiles(uploadedFile); Instance savedInstance = instanceRepository.save(instance); return savedInstance.getId(); } - - public Page getAllInstances(Pageable pageable) { + // 인스턴스 리스트 조회 + public Page getAllInstances(Pageable pageable) throws IOException { Page instances = instanceRepository.findAllById(pageable); - return instances.map(instance -> new InstancePagingResponse(instance.getTopic().getId(), instance.getId(), - instance.getTitle(), instance.getStartedDate(), instance.getCompletedDate())); - + return instances.map(this::mapToInstancePagingResponse); } // 인스턴스 단건 조회 - public InstanceDetailResponse getInstanceById(Long id) { + public InstanceDetailResponse getInstanceById(Long id) throws IOException { Instance instanceDetails = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); - return new InstanceDetailResponse( - instanceDetails.getTopic().getId(), - instanceDetails.getId(), - instanceDetails.getTitle(), - instanceDetails.getDescription(), - instanceDetails.getPointPerPerson(), - instanceDetails.getTags(), - instanceDetails.getStartedDate(), - instanceDetails.getCompletedDate() - ); + return InstanceDetailResponse.createByEntity(instanceDetails, instanceDetails.getFiles()); } + // 인스턴스 삭제 @Transactional - public void deleteInstance(Long id) { + public void deleteInstance(Long id) throws IOException{ Instance instance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); + + Optional findInstanceFile = instance.getFiles(); + Long findInstanceFileId = findInstanceFile.get().getId(); + + filesService.deleteFile(findInstanceFileId); + instance.setFiles(null); instanceRepository.delete(instance); } // 인스턴스 수정 @Transactional - public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest) { + public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, + MultipartFile multipartFile, String type) throws IOException{ Instance existingInstance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); - existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.pointPerPerson(), + Optional findInstanceFile = existingInstance.getFiles(); + Long findInstanceFileId = findInstanceFile.get().getId(); + filesService.updateFile(findInstanceFileId, multipartFile); + + existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.notice(), instanceUpdateRequest.pointPerPerson(), instanceUpdateRequest.startedAt(), instanceUpdateRequest.completedAt()); Instance savedInstance = instanceRepository.save(existingInstance); return savedInstance.getId(); } + + private InstancePagingResponse mapToInstancePagingResponse(Instance instance) { + try { + return InstancePagingResponse.createByEntity(instance, instance.getFiles()); + } catch (IOException e) { + throw new BusinessException(e); + } + } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/StringToEnum.java b/src/main/java/com/genius/gitget/challenge/instance/service/StringToEnum.java similarity index 66% rename from src/main/java/com/genius/gitget/challenge/instance/domain/StringToEnum.java rename to src/main/java/com/genius/gitget/challenge/instance/service/StringToEnum.java index a2f88fa4..acf57075 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/StringToEnum.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/StringToEnum.java @@ -1,9 +1,12 @@ -package com.genius.gitget.challenge.instance.domain; +package com.genius.gitget.challenge.instance.service; +import com.genius.gitget.challenge.instance.domain.Progress; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; @Component +@Slf4j public class StringToEnum implements Converter { @Override public Progress convert(String source) { diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileRequest.java b/src/main/java/com/genius/gitget/global/file/dto/FileRequest.java deleted file mode 100644 index 80654c22..00000000 --- a/src/main/java/com/genius/gitget/global/file/dto/FileRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.genius.gitget.global.file.dto; - -import org.springframework.web.multipart.MultipartFile; - -public record FileRequest( - MultipartFile file, - String type -) { -} diff --git a/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java b/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java index d4de84aa..9e9c0870 100644 --- a/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java @@ -53,7 +53,7 @@ public void setup() { if (!topic.getInstanceList().isEmpty()) { savedTopic.updateExistInstance("(수정) 하루에 두 문제씩 문제를 해결합니다."); } else { - savedTopic.updateNotExistInstance("1일 1커밋", "하루에 1커밋 하기", "CS", 300); + savedTopic.updateNotExistInstance("1일 1커밋", "하루에 1커밋 하기", "CS", "유의사항",300); } assertEquals(topic.getId(), savedTopic.getId()); assertEquals(topic.getTitle(), savedTopic.getTitle()); From 63e2e807f9bf0cf0c4b4c03738ccae7e5c8ceebd Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 3 Feb 2024 10:27:23 +0900 Subject: [PATCH 098/234] =?UTF-8?q?[HOTFIX]=20JWT=20Filter=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=20=EC=8B=9C?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=ED=95=98=EC=A7=80=20=EB=AA=BB=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20(#60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: OAuth 로그인 성공 시 리다이렉트 주소 변경 - OAuth 로그인 성공 시, 토큰 발급 URL로 리다이렉트하도록 변경 * feat: JwtAuthenticationFilter 예외를 처리할 필터 추가 구현 --- .../security/config/SecurityConfig.java | 4 +- .../filter/ExceptionHandlerFilter.java | 38 +++++++++++++ .../handler/OAuth2SuccessHandler.java | 5 +- .../security/config/SecurityConfigTest.java | 57 +++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/security/filter/ExceptionHandlerFilter.java create mode 100644 src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java diff --git a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java index e2273e69..b6c55a2f 100644 --- a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java @@ -1,12 +1,13 @@ package com.genius.gitget.global.security.config; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.security.filter.ExceptionHandlerFilter; import com.genius.gitget.global.security.filter.JwtAuthenticationFilter; import com.genius.gitget.global.security.handler.OAuth2FailureHandler; import com.genius.gitget.global.security.handler.OAuth2SuccessHandler; import com.genius.gitget.global.security.service.CustomOAuth2UserService; import com.genius.gitget.global.security.service.JwtService; -import com.genius.gitget.challenge.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -56,6 +57,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // JWT 검증 필터 추가 .addFilterBefore(new JwtAuthenticationFilter(jwtService, userService), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class) // OAuth 로그인 설정 .oauth2Login(customConfigurer -> customConfigurer diff --git a/src/main/java/com/genius/gitget/global/security/filter/ExceptionHandlerFilter.java b/src/main/java/com/genius/gitget/global/security/filter/ExceptionHandlerFilter.java new file mode 100644 index 00000000..46226654 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/filter/ExceptionHandlerFilter.java @@ -0,0 +1,38 @@ +package com.genius.gitget.global.security.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.filter.OncePerRequestFilter; + +@Slf4j +public class ExceptionHandlerFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + try { + filterChain.doFilter(request, response); + } catch (BusinessException e) { + log.error("[ERROR]" + e.getMessage(), e); + setErrorResponse(response, e); + } + } + + private void setErrorResponse(HttpServletResponse response, BusinessException e) throws IOException { + CommonResponse commonResponse = new CommonResponse(e.getStatus(), e.getStatus().value(), e.getMessage()); + ObjectMapper objectMapper = new ObjectMapper(); + + response.setStatus(e.getStatus().value()); + response.setContentType("application/json; charset=UTF-8"); + + response.getWriter().write( + objectMapper.writeValueAsString(commonResponse) + ); + } +} diff --git a/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java index 3521a9c7..dd8fc17a 100644 --- a/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java @@ -20,7 +20,7 @@ @RequiredArgsConstructor public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final String SIGNUP_URL = "http://localhost:5173/login/signup"; - private final String MAIN_URL = "http://localhost:5173/main"; + private final String AUTH_URL = "http://localhost:5173/auth"; private final UserRepository userRepository; @Override @@ -44,7 +44,8 @@ private String getRedirectUrlByRole(Role role, String identifier) { .toUriString(); } - return UriComponentsBuilder.fromHttpUrl(MAIN_URL) + return UriComponentsBuilder.fromHttpUrl(AUTH_URL) + .queryParam("identifier", identifier) .build() .toUriString(); } diff --git a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java new file mode 100644 index 00000000..9a486c90 --- /dev/null +++ b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java @@ -0,0 +1,57 @@ +package com.genius.gitget.global.security.config; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.genius.gitget.util.WithMockCustomUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest +class SecurityConfigTest { + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + + } + + @Test + @DisplayName("swagger에 해당하는 URI에 대해서는 2xx 응답이 발생해야 한다.") + public void should_status2xx_when_swaggerUri() throws Exception { + //given + mockMvc.perform(get("/swagger-ui/index.html")) + .andExpect(status().is2xxSuccessful()); + } + + @Test + @DisplayName("permitAll에 해당하지 않는 URI에 대해서는 4xx 응답이 발생해야 한다.") + public void should_status2xx_when_uriIsPermitAll() throws Exception { + //given + + //when&then + mockMvc.perform(get("/api/test")) + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("USER 또는 ADMIN은 일반 API를 호출했을 때, 2xx 응답이 발생해야 한다.") + @WithMockCustomUser + public void should_status2xx_when_authorizedUser() throws Exception { + mockMvc.perform(get("/api/test")) + .andExpect(status().is2xxSuccessful()); + } +} \ No newline at end of file From 2fe76bd70d15585f99b4d43572df6c41107f543b Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Sat, 3 Feb 2024 15:22:41 +0900 Subject: [PATCH 099/234] =?UTF-8?q?feat:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A4=91=20=EC=9D=B4=EC=8A=88=20=EB=B0=9C=EA=B2=AC=20=EB=B0=8F?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0=20(#62)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토픽이 인스턴스를 가질 때 해당 토픽을 삭제하려 할 때의 예외처리 - ErrorCode 추가 --- .../admin/topic/service/TopicService.java | 12 +++- .../global/util/exception/ErrorCode.java | 1 + .../topic/controller/TopicControllerTest.java | 62 +++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index 07c8a482..dc98a28a 100644 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -6,6 +6,7 @@ import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.dto.TopicDetailResponse; import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.repository.FilesRepository; @@ -27,6 +28,7 @@ @Transactional(readOnly = true) public class TopicService { private final TopicRepository topicRepository; + private final InstanceRepository instanceRepository; private final FilesService filesService; // 토픽 리스트 요청 @@ -89,9 +91,13 @@ public void deleteTopic(Long id) throws IOException { Optional findTopicFile = topic.getFiles(); Long findTopicFileId = findTopicFile.get().getId(); - filesService.deleteFile(findTopicFileId); - topic.setFiles(null); - topicRepository.delete(topic); + try { + topicRepository.delete(topic); + filesService.deleteFile(findTopicFileId); + topic.setFiles(null); + } catch (Exception e) { + throw new BusinessException(ErrorCode.TOPIC_HAVE_INSTANCE); + } } private TopicPagingResponse mapToTopicPagingResponse(Topic topic) { diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 77d3683c..87b9e490 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -9,6 +9,7 @@ public enum ErrorCode { TOPIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 토픽을 찾을 수 없습니다."), + TOPIC_HAVE_INSTANCE(HttpStatus.BAD_REQUEST, "해당 토픽은 인스턴스를 가지고 있으므로 삭제할 수 없습니다."), INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java new file mode 100644 index 00000000..3837a90f --- /dev/null +++ b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java @@ -0,0 +1,62 @@ +package com.genius.gitget.admin.topic.controller; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.admin.topic.service.TopicService; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.time.LocalDateTime; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@Transactional +public class TopicControllerTest { + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + @Autowired + TokenTestUtil tokenTestUtil; + + @Autowired + TopicService topicService; + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceService instanceService; + @Autowired + InstanceRepository instanceRepository; + + + @BeforeEach + public void setup() { + //this.mockMvc = MockMvcBuilders.standaloneSetup(new TopicController()).build(); + } + + @Test + @WithMockCustomUser + public void 토픽_생성() throws Exception { + mockMvc = MockMvcBuilders.standaloneSetup(new TopicController(topicService)) + //.webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + + +} From 15c36249e54e62c35bc19715f6acf3c9d03a397e Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:23:09 +0900 Subject: [PATCH 100/234] =?UTF-8?q?test:=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20=EB=8F=8C?= =?UTF-8?q?=EC=95=84=EA=B0=80=EC=A7=80=20=EC=95=8A=EB=8D=98=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FileTestUtil 클래스 작성: MultiFile을 반환하는 정적 메서드 추가 - Topic, Instance 로직 변경으로 인해 돌아가지 않던 테스트 코드 추가 - DTO에 빌더패턴 적용 --- .../admin/topic/dto/TopicCreateRequest.java | 3 ++ .../admin/topic/dto/TopicUpdateRequest.java | 3 ++ .../dto/crud/InstanceCreateRequest.java | 2 + .../dto/crud/InstanceUpdateRequest.java | 2 + .../admin/topic/service/TopicServiceTest.java | 46 +++++++++++----- .../repository/InstanceRepositoryTest.java | 5 +- .../service/InstanceSearchServiceTest.java | 32 +++++++----- .../instance/service/InstanceServiceTest.java | 49 +++++++++++------ .../security/config/SecurityConfigTest.java | 9 ---- .../genius/gitget/util/file/FileTestUtil.java | 52 +++++++++++++++++++ 10 files changed, 151 insertions(+), 52 deletions(-) create mode 100644 src/test/java/com/genius/gitget/util/file/FileTestUtil.java diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java index 890e20f8..1ed238fd 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java @@ -1,5 +1,8 @@ package com.genius.gitget.admin.topic.dto; +import lombok.Builder; + +@Builder public record TopicCreateRequest( String title, String tags, diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java index cf93d0ee..6898bcd0 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java @@ -1,5 +1,8 @@ package com.genius.gitget.admin.topic.dto; +import lombok.Builder; + +@Builder public record TopicUpdateRequest( String title, String tags, diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java index 8faa54c7..e3d63d07 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java @@ -1,7 +1,9 @@ package com.genius.gitget.challenge.instance.dto.crud; import java.time.LocalDateTime; +import lombok.Builder; +@Builder public record InstanceCreateRequest( Long topicId, String title, diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java index 9ab449f9..bd0ba761 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java @@ -1,7 +1,9 @@ package com.genius.gitget.challenge.instance.dto.crud; import java.time.LocalDateTime; +import lombok.Builder; +@Builder public record InstanceUpdateRequest( Long topicId, String description, diff --git a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java index a7507202..77ba581e 100644 --- a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java @@ -6,6 +6,7 @@ import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.util.file.FileTestUtil; import jakarta.transaction.Transactional; import java.util.Optional; import org.assertj.core.api.Assertions; @@ -19,7 +20,8 @@ @Transactional @Rollback public class TopicServiceTest { - public Topic topic, topicA; + Topic topic, topicA; + String fileType; @Autowired TopicService topicService; @Autowired @@ -40,14 +42,17 @@ public void setup() { .tags("BE, FE, CS") .pointPerPerson(300) .build(); + + fileType = "topic"; } @Test public void 토픽_생성() throws Exception { //given - TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), - topic.getTags(), topic.getPointPerPerson()); - Long savedTopicId = topicService.createTopic(topicCreateRequest); + TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); + + Long savedTopicId = topicService.createTopic(topicCreateRequest, FileTestUtil.getMultipartFile("name"), + "topic"); //when TopicDetailResponse topicById = topicService.getTopicById(savedTopicId); @@ -59,14 +64,19 @@ public void setup() { @Test public void 토픽_수정() throws Exception { //given - TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), - topic.getTags(), topic.getPointPerPerson()); - Long savedTopicId = topicService.createTopic(topicCreateRequest); + TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); + Long savedTopicId = topicService.createTopic(topicCreateRequest, FileTestUtil.getMultipartFile("name"), + "topic"); //when - TopicUpdateRequest topicUpdateRequest = new TopicUpdateRequest("1일 5커밋", topic.getDescription(), - topic.getTags(), topic.getPointPerPerson()); - topicService.updateTopic(savedTopicId, topicUpdateRequest); + TopicUpdateRequest topicUpdateRequest = TopicUpdateRequest.builder() + .title("1일 5커밋") + .description(topic.getDescription()) + .tags(topic.getTags()) + .pointPerPerson(topic.getPointPerPerson()) + .notice(topic.getNotice()).build(); + + topicService.updateTopic(savedTopicId, topicUpdateRequest, FileTestUtil.getMultipartFile("name"), fileType); //then Optional findTopic = topicRepository.findById(savedTopicId); @@ -77,9 +87,9 @@ public void setup() { @Test public void 토픽_삭제() throws Exception { //given - TopicCreateRequest topicCreateRequest = new TopicCreateRequest(topic.getTitle(), topic.getDescription(), - topic.getTags(), topic.getPointPerPerson()); - Long savedTopicId = topicService.createTopic(topicCreateRequest); + TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); + Long savedTopicId = topicService.createTopic(topicCreateRequest, FileTestUtil.getMultipartFile("name"), + fileType); //when topicService.deleteTopic(savedTopicId); @@ -98,4 +108,14 @@ public void setup() { // org.junit.jupiter.api.Assertions.assertEquals("해당 토픽을 찾을 수 없습니다.", e.getMessage()); // } } + + private TopicCreateRequest getTopicCreateRequest() { + return TopicCreateRequest.builder() + .title(topic.getTitle()) + .description(topic.getDescription()) + .tags(topic.getTags()) + .pointPerPerson(topic.getPointPerPerson()) + .notice(topic.getNotice()) + .build(); + } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java index 945c18d4..563c1429 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -61,7 +61,8 @@ class InstanceRepositoryTest { //when Instance savedInstance = instanceRepository.save(instance); - savedInstance.updateInstance("수정되었습니다.", 10000, LocalDateTime.now(), LocalDateTime.now().plusDays(5)); + savedInstance.updateInstance("수정되었습니다.", "수정된 유의사항", 10000, LocalDateTime.now(), + LocalDateTime.now().plusDays(5)); //then Assertions.assertThat(instance.getDescription()).isEqualTo(savedInstance.getDescription()); @@ -125,7 +126,7 @@ class InstanceRepositoryTest { //then Assertions.assertThat(instances.getTotalElements()).isEqualTo(2); } - + @Test @DisplayName("인스턴스들 중, 사용자의 tag가 포함되어 있는 인스턴스들을 반환받을 수 있다.") diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java index f8135169..e922c024 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java @@ -9,6 +9,8 @@ import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.instance.repository.SearchRepository; +import com.genius.gitget.util.file.FileTestUtil; +import java.io.IOException; import java.time.LocalDateTime; import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; @@ -59,19 +61,9 @@ public class InstanceSearchServiceTest { Topic savedTopic = topicRepository.save(topic); - instanceService.createInstance( - new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), - instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); - - instanceService.createInstance( - new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), instance.getTags(), - instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); - - instanceService.createInstance( - new InstanceCreateRequest(savedTopic.getId(), "테스트", instance.getTags(), instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate())); + createInstance(savedTopic, instance, instance.getTitle()); + createInstance(savedTopic, instance, instance.getTitle()); + createInstance(savedTopic, instance, "title"); //when InstanceSearchRequest instanceSearchRequest = new InstanceSearchRequest("고리", "preactivity"); @@ -87,4 +79,18 @@ public class InstanceSearchServiceTest { Assertions.assertThat(orderList.getTotalElements()).isEqualTo(2); } + + private void createInstance(Topic savedTopic, Instance instance, String title) throws IOException { + instanceService.createInstance( + InstanceCreateRequest.builder() + .topicId(savedTopic.getId()) + .title(title) + .tags(instance.getTags()) + .description(instance.getDescription()) + .notice(instance.getNotice()) + .pointPerPerson(instance.getPointPerPerson()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()).build(), + FileTestUtil.getMultipartFile("name"), "instance"); + } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java index b456ddb9..4e0e8d5f 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java @@ -9,6 +9,7 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.util.file.FileTestUtil; import java.time.LocalDateTime; import java.util.Optional; import org.assertj.core.api.Assertions; @@ -34,6 +35,7 @@ public class InstanceServiceTest { private Instance instance; private Topic topic; + private String fileType; @BeforeEach public void setup() { @@ -52,18 +54,18 @@ public void setup() { .tags("BE, FE, CS") .pointPerPerson(100) .build(); + fileType = "instance"; } @Test public void 인스턴스_생성() throws Exception { //given Topic savedTopic = topicRepository.save(topic); - InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), - instance.getTags(), instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); + InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); //when - Long savedInstanceId = instanceService.createInstance(instanceCreateRequest); + Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, + FileTestUtil.getMultipartFile("name"), fileType); //then Optional byId = instanceRepository.findById(savedInstanceId); @@ -75,16 +77,21 @@ public void setup() { //given Topic savedTopic = topicRepository.save(topic); - InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), - instance.getTags(), instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); - Long savedInstanceId = instanceService.createInstance(instanceCreateRequest); + InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); + Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, + FileTestUtil.getMultipartFile("name"), fileType); - InstanceUpdateRequest instanceUpdateRequest = new InstanceUpdateRequest(savedTopic.getId(), "이것은 수정본이지롱", - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); + InstanceUpdateRequest instanceUpdateRequest = InstanceUpdateRequest.builder() + .topicId(savedTopic.getId()) + .description("이것은 수정본이지롱") + .pointPerPerson(instance.getPointPerPerson()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()) + .build(); //when - Long updatedInstanceId = instanceService.updateInstance(savedInstanceId, instanceUpdateRequest); + Long updatedInstanceId = instanceService.updateInstance(savedInstanceId, instanceUpdateRequest, + FileTestUtil.getMultipartFile("name"), fileType); //then Optional byId = instanceRepository.findById(updatedInstanceId); @@ -96,10 +103,9 @@ public void setup() { //given Topic savedTopic = topicRepository.save(topic); - InstanceCreateRequest instanceCreateRequest = new InstanceCreateRequest(savedTopic.getId(), instance.getTitle(), - instance.getTags(), instance.getDescription(), - instance.getPointPerPerson(), instance.getStartedDate(), instance.getCompletedDate()); - Long savedInstanceId = instanceService.createInstance(instanceCreateRequest); + InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); + Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, + FileTestUtil.getMultipartFile("name"), fileType); //when InstanceDetailResponse instanceById = instanceService.getInstanceById(savedInstanceId); @@ -107,4 +113,17 @@ public void setup() { //then Assertions.assertThat(instanceById.title()).isEqualTo(instanceCreateRequest.title()); } + + private InstanceCreateRequest getInstanceCreateRequest(Topic savedTopic, Instance instance) { + return InstanceCreateRequest.builder() + .topicId(savedTopic.getId()) + .title(instance.getTitle()) + .tags(instance.getTags()) + .description(instance.getDescription()) + .notice(instance.getNotice()) + .pointPerPerson(instance.getPointPerPerson()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()) + .build(); + } } diff --git a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java index 9a486c90..4b7ff16c 100644 --- a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java +++ b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java @@ -4,7 +4,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.util.WithMockCustomUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -46,12 +45,4 @@ public void should_status2xx_when_uriIsPermitAll() throws Exception { mockMvc.perform(get("/api/test")) .andExpect(status().is4xxClientError()); } - - @Test - @DisplayName("USER 또는 ADMIN은 일반 API를 호출했을 때, 2xx 응답이 발생해야 한다.") - @WithMockCustomUser - public void should_status2xx_when_authorizedUser() throws Exception { - mockMvc.perform(get("/api/test")) - .andExpect(status().is2xxSuccessful()); - } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/util/file/FileTestUtil.java b/src/test/java/com/genius/gitget/util/file/FileTestUtil.java new file mode 100644 index 00000000..7ebdfcd9 --- /dev/null +++ b/src/test/java/com/genius/gitget/util/file/FileTestUtil.java @@ -0,0 +1,52 @@ +package com.genius.gitget.util.file; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import org.springframework.web.multipart.MultipartFile; + +public class FileTestUtil { + public static MultipartFile getMultipartFile(String filename) { + return new MultipartFile() { + @Override + public String getName() { + return filename; + } + + @Override + public String getOriginalFilename() { + return filename + ".png"; + } + + @Override + public String getContentType() { + return "png"; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public long getSize() { + return 0; + } + + @Override + public byte[] getBytes() throws IOException { + return new byte[0]; + } + + @Override + public InputStream getInputStream() throws IOException { + return null; + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + + } + }; + } +} From e0791f510aaa7b0b2690259447be981a7e35e030 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:50:51 +0900 Subject: [PATCH 101/234] =?UTF-8?q?feat:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=98=81=EC=86=8D=EC=84=B1=20=EC=A0=84=EC=9D=B4=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genius/gitget/admin/topic/domain/Topic.java | 17 +++++++++-------- .../challenge/instance/domain/Instance.java | 6 ++++-- .../gitget/challenge/user/domain/User.java | 3 ++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java index 7461d146..e405b5be 100644 --- a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java @@ -1,7 +1,8 @@ package com.genius.gitget.admin.topic.domain; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.domain.Files; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -15,7 +16,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; - import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -32,7 +32,7 @@ public class Topic { @Column(name = "topic_id") private Long id; - @OneToOne(fetch = FetchType.LAZY) + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "files_id") private Files files; @@ -64,7 +64,8 @@ public void updateExistInstance(String description) { this.description = description; } - public void updateNotExistInstance(String title, String description, String tags, String notice, int pointPerPerson) { + public void updateNotExistInstance(String title, String description, String tags, String notice, + int pointPerPerson) { this.title = title; this.description = description; this.tags = tags; @@ -72,11 +73,11 @@ public void updateNotExistInstance(String title, String description, String tags this.pointPerPerson = pointPerPerson; } - public void setFiles(Files files) { - this.files = files; - } - public Optional getFiles() { return Optional.ofNullable(this.files); } + + public void setFiles(Files files) { + this.files = files; + } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 702875d8..fd60d4d4 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -5,6 +5,7 @@ import com.genius.gitget.challenge.hits.domain.Hits; import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; import com.genius.gitget.global.file.domain.Files; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -40,7 +41,7 @@ public class Instance { @Column(name = "instance_id") private Long id; - @OneToOne(fetch = FetchType.LAZY) + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "files_id") private Files files; @@ -79,7 +80,8 @@ public class Instance { private LocalDateTime completedDate; @Builder - public Instance(String title, String description, String tags, int pointPerPerson, Progress progress, String notice, String certificationMethod, + public Instance(String title, String description, String tags, int pointPerPerson, Progress progress, String notice, + String certificationMethod, LocalDateTime startedDate, LocalDateTime completedDate) { this.title = title; this.description = description; diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 54417261..3d34a6d4 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -5,6 +5,7 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.domain.BaseTimeEntity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -35,7 +36,7 @@ public class User extends BaseTimeEntity { @Column(name = "user_id") private Long id; - @OneToOne(fetch = FetchType.LAZY) + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "files_id") private Files files; From 6d4f561fdd7ffba2e10bac1e1d133eee40d583ce Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 9 Feb 2024 23:27:07 +0900 Subject: [PATCH 102/234] =?UTF-8?q?hotfix:=20=ED=99=88=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20API=EC=97=90=EC=84=9C=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4?= =?UTF-8?q?=20=EC=8B=9D=EB=B3=84=EC=9E=90=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=ED=94=BD=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genius/gitget/challenge/home/dto/HomeInstanceResponse.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java b/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java index 01064e80..c5e4a57c 100644 --- a/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java @@ -9,6 +9,7 @@ @Builder public record HomeInstanceResponse( + Long instanceId, String title, int participantCnt, int pointPerPerson, @@ -16,6 +17,7 @@ public record HomeInstanceResponse( ) { public static HomeInstanceResponse createByEntity(Instance instance, Optional files) throws IOException { return HomeInstanceResponse.builder() + .instanceId(instance.getId()) .title(instance.getTitle()) .participantCnt(instance.getParticipantCount()) .pointPerPerson(instance.getPointPerPerson()) From 89a82600dcb6ac0c764607dc22482dc5db985e5c Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Tue, 13 Feb 2024 22:44:16 +0900 Subject: [PATCH 103/234] =?UTF-8?q?[FEAT]=20=EB=8F=99=EC=A0=81=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=A0=81=EC=9A=A9=ED=95=9C=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20=20(#65)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: testBaseUtil 개발 중 패키지 구조 변경으로 인한 커밋 * feat: Home 검색 기능 개발 * refactor: search dto 네임 변경 * test: 단위 테스트 수행 - controller test 중 mongodb 이슈 발생 -> 해결요망 - 검색 기능 postman test 완료 * test: 검색 기능 단위 테스트 * feat: 검색 조건에 따른 키워드 검색 기능 개발 - stringToEnum converter 재정의 - 스프링 빈으로 등록하여 생성한 컨버터 사용 적용 - 검색 조건에 따른 각 계층별 코드 작성 * refactor: 불필요한 코드 제거 * refactor: 코드 리펙토링 * test: topicController mongodb issue test * chore: Querydsl dependency 추가 * test: querydsl test * test: querydsl 적용 테스트 * feat: querydsl dto 생성 - querydsl로 작성한 프로젝션을 받아오기 위한 dto * test: querydsl 별도의 dto를 사용한 instance와 files join table 테스트 * test: 프로젝션 결과반환 - 생성자 방식 사용 * test: 동적쿼리 - booleanBuilder 테스트 * test: 동적쿼리 - 다중 where - composition 가능한 장점이 있음 * test: 수정, 삭제 배치쿼리 - bulkUpdate - bulkAdd - bulkDelete * feat: 동적 쿼리 적용 및 querydsl 페이징 연동 * feat: 검색 조건과 챌린지 진행 현황에 따른 검색 기능 개발 - querydsl 적용 - 사용자 정의 리포지토리 적용 - BooleamBuilder 적용으로 성능 최적화 - instanceSearchService 코드 수정 및 개선 - progress entity ALL 제거 - 검색 기능 repository 테스트 코드 작성 * feat: 동적쿼리를 위한 instanceSearchService 로직 개발 - instanceSearchService 코드 개선 - ErrorCode 추가 - postmand api 테스트 완료 * refactor: 코드 리펙토링 --- build.gradle | 12 + .../admin/topic/service/TopicService.java | 3 + .../home/controller/HomeController.java | 10 +- .../challenge/instance/domain/Progress.java | 10 +- .../dto/search/InstanceSearchResponse.java | 51 +++- .../instance/repository/SearchRepository.java | 12 +- .../repository/SearchRepositoryCustom.java | 10 + .../repository/SearchRepositoryImpl.java | 59 +++++ .../service/InstanceSearchService.java | 27 ++- .../instance/service/InstanceService.java | 2 +- .../global/util/exception/ErrorCode.java | 1 + .../topic/controller/TopicControllerTest.java | 10 +- .../admin/topic/service/TopicServiceTest.java | 9 +- .../InstanceSearchRepositoryTest.java | 152 +++++++++--- .../service/InstanceSearchServiceTest.java | 2 +- .../gitget/querydsl/QuerydslBasicTest.java | 229 ++++++++++++++++++ .../genius/gitget/util/file/FileTestUtil.java | 3 + 17 files changed, 509 insertions(+), 93 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryCustom.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java create mode 100644 src/test/java/com/genius/gitget/querydsl/QuerydslBasicTest.java diff --git a/build.gradle b/build.gradle index 010a2c66..be752564 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,11 @@ dependencies { implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' // query log 띄우기 끝 + implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta" + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + //JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' @@ -59,3 +64,10 @@ dependencies { tasks.named('test') { useJUnitPlatform() } + +// Querydsl 설정부 +def generatedDir = 'src/main/generated' + +clean { + delete file (generatedDir) +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index dc98a28a..21cb6096 100644 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -14,6 +14,7 @@ import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -25,6 +26,7 @@ @Service @RequiredArgsConstructor +@Slf4j @Transactional(readOnly = true) public class TopicService { private final TopicRepository topicRepository; @@ -96,6 +98,7 @@ public void deleteTopic(Long id) throws IOException { filesService.deleteFile(findTopicFileId); topic.setFiles(null); } catch (Exception e) { + e.getStackTrace(); throw new BusinessException(ErrorCode.TOPIC_HAVE_INSTANCE); } } diff --git a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java b/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java index 24c183db..960d24d1 100644 --- a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java +++ b/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java @@ -7,6 +7,7 @@ import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.service.InstanceSearchService; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.PagingResponse; @@ -20,11 +21,10 @@ import org.springframework.data.domain.Sort.Direction; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -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 org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; @RestController @RequiredArgsConstructor diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java index 3cb055bf..6aa7f917 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java @@ -1,15 +1,7 @@ package com.genius.gitget.challenge.instance.domain; -import com.fasterxml.jackson.annotation.JsonCreator; - public enum Progress { - ALL, PREACTIVITY, ACTIVITY, - DONE; - -// @JsonCreator -// public static Progress from(String s) { -// return Progress.valueOf(s.toUpperCase()); -// } + DONE } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index 022e6e74..bdaadff9 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -1,19 +1,44 @@ package com.genius.gitget.challenge.instance.dto.search; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FileUtil; +import com.querydsl.core.annotations.QueryProjection; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; -public record InstanceSearchResponse( - Long topicId, - Long instanceId, - String keyword, - int pointPerPerson, - int participantCount -) { - public InstanceSearchResponse(Instance instance) { - this(instance.getTopic().getId(), - instance.getId(), - instance.getTitle(), - instance.getPointPerPerson(), - instance.getParticipantCount()); + +import java.io.IOException; +import java.util.Optional; + +@NoArgsConstructor +@Data +public class InstanceSearchResponse { + private Long topicId; + private Long instanceId; + private String keyword; + private int pointPerPerson; + private int participantCount; + private FileResponse fileResponse; + + @Builder + @QueryProjection + public InstanceSearchResponse(Long topicId, Long instanceId, String keyword, int pointPerPerson, int participantCount, Files files) throws IOException { + this.topicId = topicId; + this.instanceId = instanceId; + this.keyword = keyword; + this.pointPerPerson = pointPerPerson; + this.participantCount = participantCount; + this.fileResponse = convertToFileResponse(Optional.of(files)); + } + + + private static FileResponse convertToFileResponse(Optional files) throws IOException { + if (files.isEmpty()) { + return FileResponse.createNotExistFile(); + } + return FileResponse.createExistFile(files.get()); } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepository.java index e36664ab..6a684ae1 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepository.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepository.java @@ -1,17 +1,7 @@ package com.genius.gitget.challenge.instance.repository; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -public interface SearchRepository extends JpaRepository { - - // 검색 키워드인 제목을 포함하는 경우 - Page findByTitleContainingOrderByStartedDateDesc(String title, Pageable pageable); - - // 모집 진행 상황의 조건과 일치하는 검색 키워드인 제목을 포함하는 경우 - Page findByProgressAndTitleContainingOrderByStartedDateDesc(Progress progress, String title, Pageable pageable); +public interface SearchRepository extends JpaRepository, SearchRepositoryCustom { } diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryCustom.java b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryCustom.java new file mode 100644 index 00000000..206366c8 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.genius.gitget.challenge.instance.repository; + +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface SearchRepositoryCustom { + Page search(Progress progress, String title, Pageable pageable); +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java new file mode 100644 index 00000000..7eaa16f3 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java @@ -0,0 +1,59 @@ +package com.genius.gitget.challenge.instance.repository; + +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.dto.search.QInstanceSearchResponse; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; + +import static com.genius.gitget.challenge.instance.domain.QInstance.instance; +import static com.genius.gitget.global.file.domain.QFiles.files; + +import java.util.List; + +public class SearchRepositoryImpl implements SearchRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + public SearchRepositoryImpl(EntityManager em) { + this.queryFactory = new JPAQueryFactory(em); + } + + @Override + public Page search(Progress progressCond, String titleCond, Pageable pageable) { + BooleanBuilder builder = new BooleanBuilder(); + + if (progressCond != null) { + builder.and(instance.progress.eq(progressCond)); + } + if (titleCond != null) { + builder.and(instance.title.contains(titleCond)); + } + + List content = queryFactory + .select(new QInstanceSearchResponse( + instance.topic.id, instance.id, instance.title, instance.pointPerPerson, instance.participantCount, + instance.files)) + .from(instance) + .leftJoin(instance.files, files) + .on(instance.files.id.eq(files.id)) + .where(builder) + .orderBy(instance.startedDate.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(instance.count()) + .from(instance) + .leftJoin(instance.files, files) + .where(builder); + + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java index 6f1ba23b..b449b0ec 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java @@ -1,6 +1,5 @@ package com.genius.gitget.challenge.instance.service; -import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.repository.SearchRepository; @@ -20,20 +19,22 @@ public class InstanceSearchService { private final SearchRepository searchRepository; private final StringToEnum stringToEnum; - public Page searchInstances(String keyword, String progress, Pageable pageable) { - Page findByTitleContaining; + private final String[] progressData = {"PREACTIVITY", "ACTIVITY", "DONE"}; - if (stringToEnum.convert(progress) == Progress.ALL) { - findByTitleContaining = searchRepository.findByTitleContainingOrderByStartedDateDesc(keyword, pageable); - } else { - Progress convertProgress = stringToEnum.convert(progress); // Progress convertProgress = Progress.from(progress); - findByTitleContaining = searchRepository.findByProgressAndTitleContainingOrderByStartedDateDesc(convertProgress, keyword, pageable); + public Page searchInstances(String keyword, String progress, Pageable pageable){ + Progress convertProgress; + boolean flag = false; + for (String progressCond : progressData) { + if (progressCond.equals(progress)) { + flag = true; + } + } + if (flag) { + convertProgress = stringToEnum.convert(progress); + return searchRepository.search(convertProgress, keyword, pageable); + } else { + return searchRepository.search(null, keyword, pageable); } - return findByTitleContaining.map(this::convertToInstanceSearchResponse); - } - - private InstanceSearchResponse convertToInstanceSearchResponse(Instance instance) { - return new InstanceSearchResponse(instance); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index 5ab6732e..a55800bc 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -64,7 +64,7 @@ public Long createInstance(InstanceCreateRequest instanceCreateRequest, } // 인스턴스 리스트 조회 - public Page getAllInstances(Pageable pageable) throws IOException { + public Page getAllInstances(Pageable pageable) { Page instances = instanceRepository.findAllById(pageable); return instances.map(this::mapToInstancePagingResponse); } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 87b9e490..e70cea1d 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -22,6 +22,7 @@ public enum ErrorCode { INVALID_CLAIM_JWT(HttpStatus.BAD_REQUEST, "JWT의 Claim이 유효하지 않습니다."), UNSUPPORTED_JWT(HttpStatus.BAD_REQUEST, "지원하지 않는 JWT 형식입니다."), INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."), + INVALID_PROGRESS(HttpStatus.BAD_REQUEST, "존재하지 않는 정보입니다."), TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."), diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java index 3837a90f..e4a3b698 100644 --- a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java @@ -51,12 +51,10 @@ public void setup() { @Test @WithMockCustomUser public void 토픽_생성() throws Exception { - mockMvc = MockMvcBuilders.standaloneSetup(new TopicController(topicService)) - //.webAppContextSetup(context) - .apply(springSecurity()) - .build(); +// mockMvc = MockMvcBuilders.standaloneSetup(new TopicController(topicService)) +// //.webAppContextSetup(context) +// .apply(springSecurity()) +// .build(); } - - } diff --git a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java index 77ba581e..d173e073 100644 --- a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java @@ -92,12 +92,13 @@ public void setup() { fileType); //when - topicService.deleteTopic(savedTopicId); + Assertions.assertThatThrownBy(() -> topicService.deleteTopic(savedTopicId)) + .isInstanceOf(BusinessException.class); //then - org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, () -> { - topicService.getTopicById(1L); - }); +// org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, () -> { +// topicService.getTopicById(1L); +// }); // // Assertions.assertThatThrownBy(()-> topicService.getTopicById(1L)) // .isInstanceOf(BusinessException.class); diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index 390c7971..67f480e3 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -1,9 +1,20 @@ package com.genius.gitget.challenge.instance.repository; +import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; + +import java.io.IOException; import java.time.LocalDateTime; + +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.service.InstanceSearchService; +import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.util.file.FileTestUtil; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -14,62 +25,143 @@ import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; +import static com.genius.gitget.challenge.instance.domain.Progress.*; + @SpringBootTest @Transactional @Rollback @Slf4j public class InstanceSearchRepositoryTest { + @PersistenceContext + EntityManager em; @Autowired SearchRepository searchRepository; @Autowired TopicRepository topicRepository; @Autowired InstanceRepository instanceRepository; + @Autowired + InstanceSearchService instanceSearchService; + @Autowired + InstanceService instanceService; - @Test - public void 인스턴스_검색() throws Exception { - //given - Instance instance = Instance.builder() + @Autowired + FileTestUtil fileTestUtil; + + JPAQueryFactory queryFactory; + + // @BeforeEach + public void setup() throws IOException{ + + queryFactory = new JPAQueryFactory(em); + + Topic topic = Topic.builder() .title("1일 1알고리즘") .description("하루에 한 문제씩 문제를 해결합니다.") .tags("BE, FE, CS") .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - Instance instance1 = Instance.builder() - .title("1일 2알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .progress(Progress.DONE) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) .build(); - Instance instance2 = Instance.builder() - .title("1일 3알고리즘") + + Instance instance = Instance.builder() + .title("1일 1알고리즘") .description("하루에 한 문제씩 문제를 해결합니다.") .tags("BE, FE, CS") .pointPerPerson(100) - .progress(Progress.PREACTIVITY) + .notice("유의사항") + .progress(PREACTIVITY) .startedDate(LocalDateTime.now()) .completedDate(LocalDateTime.now().plusDays(3)) .build(); - //when - instanceRepository.save(instance); - instanceRepository.save(instance1); - instanceRepository.save(instance2); - //then - Page order = searchRepository.findByProgressAndTitleContainingOrderByStartedDateDesc( - Progress.PREACTIVITY, "고리", PageRequest.of(0, 3)); - for (Instance item : order) { - System.out.println("item = " + item); + Topic savedTopic = topicRepository.save(topic); + + createInstance(savedTopic, instance, instance.getTitle()); + createInstance(savedTopic, instance, instance.getTitle()); + + instanceService.getAllInstances(PageRequest.of(0, 5)); + } + + + @Test + public void 검색_조건_없이_테스트() throws Exception { + for (int i = 0; i<5; i++) { + PageRequest pageRequest = PageRequest.of(i, 2); + Page result = searchRepository.search(null, null, pageRequest); + for (InstanceSearchResponse instanceSearchResponse : result) { + System.out.println("instanceSearchResponse = " + instanceSearchResponse.getInstanceId()); + } + System.out.println("========== " + i+1 + " 번째 끝 ========="); } + } - Assertions.assertThat(order.getTotalElements()).isEqualTo(2); + @Test + public void 챌린지_제목으로_검색_테스트() throws Exception { + PageRequest pageRequest = PageRequest.of(0, 10); + Page result = searchRepository.search(null, "리", pageRequest); + int cnt = 0; + for (InstanceSearchResponse instanceSearchResponse : result) { + if (instanceSearchResponse != null) cnt++; + } + Assertions.assertThat(cnt).isEqualTo(2); } + @Test + public void 챌린지_현황으로_검색_테스트1() throws Exception { + PageRequest pageRequest = PageRequest.of(0, 10); + Page result = searchRepository.search(PREACTIVITY, null, pageRequest); + int cnt = 0; + for (InstanceSearchResponse instanceSearchResponse : result) { + if (instanceSearchResponse != null) cnt++; + } + Assertions.assertThat(cnt).isEqualTo(4); + } + + @Test + public void 챌린지_현황으로_검색_테스트2() throws Exception { + PageRequest pageRequest = PageRequest.of(0, 10); + Page result = searchRepository.search(DONE, null, pageRequest); + int cnt = 0; + for (InstanceSearchResponse instanceSearchResponse : result) { + if (instanceSearchResponse != null) cnt++; + } + Assertions.assertThat(cnt).isEqualTo(1); + } + + @Test + public void 챌린지_현황으로_검색_테스트3() throws Exception { + PageRequest pageRequest = PageRequest.of(0, 10); + Page result = searchRepository.search(ACTIVITY, null, pageRequest); + int cnt = 0; + for (InstanceSearchResponse instanceSearchResponse : result) { + if (instanceSearchResponse != null) cnt++; + } + Assertions.assertThat(cnt).isEqualTo(0); + } + + @Test + public void 챌린지_현황과_챌린지_제목으로_검색_테스트() throws Exception { + PageRequest pageRequest = PageRequest.of(0, 10); + Page result = searchRepository.search(PREACTIVITY, "1", pageRequest); + int cnt = 0; + for (InstanceSearchResponse instanceSearchResponse : result) { + if (instanceSearchResponse != null) cnt++; + } + Assertions.assertThat(cnt).isEqualTo(3); + } + + + private void createInstance(Topic savedTopic, Instance instance, String title) throws IOException { + instanceService.createInstance( + InstanceCreateRequest.builder() + .topicId(savedTopic.getId()) + .title(title) + .tags(instance.getTags()) + .description(instance.getDescription()) + .notice(instance.getNotice()) + .pointPerPerson(instance.getPointPerPerson()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()).build(), + FileTestUtil.getMultipartFile("name"), "instance"); + } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java index e922c024..4e07b142 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java @@ -73,7 +73,7 @@ public class InstanceSearchServiceTest { PageRequest.of(0, 3)); for (InstanceSearchResponse instanceSearchResponse : orderList) { - System.out.println("instanceSearchResponse = " + instanceSearchResponse.keyword()); + System.out.println("instanceSearchResponse = " + instanceSearchResponse.getKeyword()); } Assertions.assertThat(orderList.getTotalElements()).isEqualTo(2); diff --git a/src/test/java/com/genius/gitget/querydsl/QuerydslBasicTest.java b/src/test/java/com/genius/gitget/querydsl/QuerydslBasicTest.java new file mode 100644 index 00000000..6aea4a40 --- /dev/null +++ b/src/test/java/com/genius/gitget/querydsl/QuerydslBasicTest.java @@ -0,0 +1,229 @@ +package com.genius.gitget.querydsl; + +import com.genius.gitget.admin.topic.domain.QTopic; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.domain.QInstance; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.instance.repository.SearchRepository; +import com.genius.gitget.challenge.instance.service.InstanceSearchService; +import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.global.file.domain.QFiles; +import com.genius.gitget.util.file.FileTestUtil; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; + +import static com.genius.gitget.admin.topic.domain.QTopic.*; +import static com.genius.gitget.challenge.instance.domain.Progress.*; +import static com.genius.gitget.challenge.instance.domain.QInstance.*; +import static com.genius.gitget.global.file.domain.QFiles.*; + +@Transactional +@SpringBootTest +public class QuerydslBasicTest { + + @PersistenceContext + EntityManager em; + @Autowired + SearchRepository searchRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + InstanceSearchService instanceSearchService; + @Autowired + InstanceService instanceService; + + @Autowired + FileTestUtil fileTestUtil; + + JPAQueryFactory queryFactory; + + QTopic t; + QInstance i; + QFiles f; + + @BeforeEach + public void setup() throws IOException{ + + queryFactory = new JPAQueryFactory(em); + t = topic; + i = instance; + f = files; + + Topic topic = Topic.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .build(); + + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .notice("유의사항") + .progress(PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + + Topic savedTopic = topicRepository.save(topic); + + createInstance(savedTopic, instance, instance.getTitle()); + createInstance(savedTopic, instance, instance.getTitle()); + + Page allInstances = instanceService.getAllInstances(PageRequest.of(0, 5)); + for (InstancePagingResponse allInstance : allInstances) { + System.out.println("allInstance = " + allInstance); + } + } + + @Test + public void findDtoByQuerydsl() throws IOException { + List fetch = queryFactory.select(Projections.fields(QuerydslDTO.class, + i.topic.id.as("topicId"), i.id.as("instanceId"), i.title, i.pointPerPerson, i.participantCount, + f.fileURI, f.originalFilename, f.savedFilename)) + .from(i) + .leftJoin(f) + .on(i.files.id.eq(f.id)) + .where(i.progress.eq(Progress.valueOf("PREACTIVITY")), i.title.like("%아%")) + .orderBy(i.startedDate.desc()) + .fetch(); + + System.out.println("fetch = " + fetch.size()); + for (QuerydslDTO querydslDTO : fetch) { + System.out.println("querydslDTO.toString() = " + querydslDTO.toString()); + } + } + + + @Test + public void dynamicQuery_BooleanBuilder() { + String instanceParam = "1일 1알고리즘"; + Integer pointParam = 100; + + List result = searchInstance(instanceParam, pointParam); + Assertions.assertThat(result.size()).isEqualTo(2); + + } + + private List searchInstance(String instanceCond, Integer pointCond) { + + BooleanBuilder builder = new BooleanBuilder(); + if (instanceCond != null) { + builder.and(i.title.eq(instanceCond)); + } + if (pointCond != null) { + builder.and(i.pointPerPerson.eq(pointCond)); + } + + return queryFactory + .selectFrom(i) + .where(builder) + .fetch(); + } + + @Test + public void dynamicQuery_WhereParam() { + String instanceParam = "1일 1알고리즘"; + Integer pointParam = 100; + + List result = searchInstance2(instanceParam, pointParam); + Assertions.assertThat(result.size()).isEqualTo(2); + } + + private List searchInstance2(String instanceCond, Integer pointCond) { + return queryFactory + .selectFrom(i) + .where(instanceCondEq(instanceCond), pointCondEq(pointCond)) + .fetch(); + } + + private Predicate instanceCondEq(String instanceCond) { + if (instanceCond == null) { + return null; + } + return i.title.eq(instanceCond); + } + + private Predicate pointCondEq(Integer pointCond) { + if (pointCond == null) { + return null; + } + return i.pointPerPerson.eq(pointCond); + } + + @Test + public void bulkUpdate() { + long execute = queryFactory + .update(i) + .set(i.title, "1일 1커밋") + .where(i.progress.notIn(ACTIVITY, DONE)) + .execute(); + + em.flush(); + em.clear(); + + List list = queryFactory + .selectFrom(i) + .fetch(); + for (Instance instance : list) { + System.out.println("instance = " + instance.getTitle()); + } + } + + @Test + public void bulkAdd() { + queryFactory + .update(i) + .set(i.pointPerPerson, i.pointPerPerson.add(50)) + .execute(); + } + + @Test + public void bulkDelete() { + queryFactory + .delete(i) + .where(i.pointPerPerson.gt(100)) + .execute(); + } + + + private void createInstance(Topic savedTopic, Instance instance, String title) throws IOException { + instanceService.createInstance( + InstanceCreateRequest.builder() + .topicId(savedTopic.getId()) + .title(title) + .tags(instance.getTags()) + .description(instance.getDescription()) + .notice(instance.getNotice()) + .pointPerPerson(instance.getPointPerPerson()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()).build(), + FileTestUtil.getMultipartFile("name"), "instance"); + } +} diff --git a/src/test/java/com/genius/gitget/util/file/FileTestUtil.java b/src/test/java/com/genius/gitget/util/file/FileTestUtil.java index 7ebdfcd9..7126b8c8 100644 --- a/src/test/java/com/genius/gitget/util/file/FileTestUtil.java +++ b/src/test/java/com/genius/gitget/util/file/FileTestUtil.java @@ -3,8 +3,11 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; + +import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +@Component public class FileTestUtil { public static MultipartFile getMultipartFile(String filename) { return new MultipartFile() { From 40159008d3b81bfa8f9d855fd2caac87dcd79995 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:04:10 +0900 Subject: [PATCH 104/234] =?UTF-8?q?[BUG]=20Topic=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=8B=9C,=20=EC=82=AD=EC=A0=9C=EA=B0=80?= =?UTF-8?q?=20=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20(#67)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: topic 삭제 시 예외가 발생하던 버그 픽스 - topic 삭제 가능 조건에서 삭제 요청을 했을 때, 삭제가 되지 않고 예외가 발생하던 버그 픽스 - 영속성 전이(cascade) 추가로 인해 발생하던 문제임을 인지 후 수정 * refactor: File 로직 예외 추가 처리 - File 시스템과 관련된 로직에서 따로 처리하지 않았던 부분에 대해 try-catch문을 통해 명시적으로 처리 * feat: instance 생성 로직 보강 - Topic에 등록된 이미지를 사용하기 위해, instance 생성 시 이미지를 별도로 전달하지 않은 경우에 대해 처리 로직 추가 - FileUtil에 복사 관련 로직 추가 구현 --- .../admin/topic/service/TopicService.java | 37 ++++--------- .../controller/InstanceController.java | 33 ++++++------ .../dto/crud/InstanceDetailResponse.java | 14 ++--- .../instance/service/InstanceService.java | 38 +++++++------ .../gitget/global/file/dto/FileResponse.java | 3 +- .../gitget/global/file/service/FileUtil.java | 40 ++++++++++++-- .../global/file/service/FilesService.java | 54 ++++++++++++------- .../global/util/exception/ErrorCode.java | 3 ++ .../global/file/service/FileUtilTest.java | 22 ++++++++ 9 files changed, 152 insertions(+), 92 deletions(-) diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index 21cb6096..db6f8b1f 100644 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -1,18 +1,17 @@ package com.genius.gitget.admin.topic.service; +import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.dto.TopicCreateRequest; +import com.genius.gitget.admin.topic.dto.TopicDetailResponse; import com.genius.gitget.admin.topic.dto.TopicPagingResponse; import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.dto.TopicDetailResponse; import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.repository.FilesRepository; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import java.io.IOException; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -21,16 +20,12 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.util.Optional; - @Service @RequiredArgsConstructor @Slf4j @Transactional(readOnly = true) public class TopicService { private final TopicRepository topicRepository; - private final InstanceRepository instanceRepository; private final FilesService filesService; // 토픽 리스트 요청 @@ -42,12 +37,12 @@ public Page getAllTopics(Pageable pageable) { // 토픽 상세정보 요청 public TopicDetailResponse getTopicById(Long id) throws IOException { Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - return TopicDetailResponse.createByEntity(topic,topic.getFiles()); + return TopicDetailResponse.createByEntity(topic, topic.getFiles()); } // 토픽 생성 요청 @Transactional - public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile multipartFile, String type) throws IOException { + public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile multipartFile, String type) { Files uploadedFile = filesService.uploadFile(multipartFile, type); Topic topic = Topic.builder() @@ -66,7 +61,7 @@ public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile mul } @Transactional - public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest, MultipartFile multipartFile, String type) throws IOException { + public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest, MultipartFile multipartFile, String type) { Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); Optional findTopicFile = topic.getFiles(); @@ -87,20 +82,10 @@ public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest, Multipar // 토픽 삭제 요청 @Transactional - public void deleteTopic(Long id) throws IOException { - Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - - Optional findTopicFile = topic.getFiles(); - Long findTopicFileId = findTopicFile.get().getId(); - - try { - topicRepository.delete(topic); - filesService.deleteFile(findTopicFileId); - topic.setFiles(null); - } catch (Exception e) { - e.getStackTrace(); - throw new BusinessException(ErrorCode.TOPIC_HAVE_INSTANCE); - } + public void deleteTopic(Long id) { + Topic topic = topicRepository.findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + topicRepository.delete(topic); } private TopicPagingResponse mapToTopicPagingResponse(Topic topic) { diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index dc5c27c7..d787a3e1 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -1,30 +1,30 @@ package com.genius.gitget.challenge.instance.controller; -import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.instance.service.InstanceService; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.util.Optional; - @RestController @RequestMapping("/api/admin/instance") @RequiredArgsConstructor @@ -33,8 +33,8 @@ public class InstanceController { // 인스턴스 리스트 조회 @GetMapping - public ResponseEntity> getAllInstances ( - @PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id") Pageable pageable) throws IOException{ + public ResponseEntity> getAllInstances( + @PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id") Pageable pageable) { Page instances = instanceService.getAllInstances(pageable); return ResponseEntity.ok().body( @@ -44,7 +44,7 @@ public ResponseEntity> getAllInstances ( // 인스턴스 단건 조회 @GetMapping("/{id}") - public ResponseEntity> getInstanceById(@PathVariable Long id) throws IOException{ + public ResponseEntity> getInstanceById(@PathVariable Long id) { InstanceDetailResponse instanceDetails = instanceService.getInstanceById(id); return ResponseEntity.ok().body( new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), instanceDetails) @@ -55,7 +55,8 @@ public ResponseEntity> getInstanceById(@P @PostMapping public ResponseEntity createInstance( @RequestPart(value = "data") InstanceCreateRequest instanceCreateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type") String type) throws IOException { + @RequestPart(value = "files", required = false) MultipartFile multipartFile, + @RequestPart(value = "type") String type) { instanceService.createInstance(instanceCreateRequest, multipartFile, type); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) @@ -64,8 +65,10 @@ public ResponseEntity createInstance( // 인스턴스 수정 @PatchMapping("/{id}") - public ResponseEntity updateInstance(@PathVariable Long id, @RequestPart(value = "data") InstanceUpdateRequest instanceUpdateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type") String type) throws IOException{ + public ResponseEntity updateInstance(@PathVariable Long id, + @RequestPart(value = "data") InstanceUpdateRequest instanceUpdateRequest, + @RequestPart(value = "files", required = false) MultipartFile multipartFile, + @RequestPart(value = "type") String type) { instanceService.updateInstance(id, instanceUpdateRequest, multipartFile, type); return ResponseEntity.ok().body( @@ -75,7 +78,7 @@ public ResponseEntity updateInstance(@PathVariable Long id, @Req // 인스턴스 삭제 @DeleteMapping("/{id}") - public ResponseEntity deleteInstance(@PathVariable Long id) throws IOException{ + public ResponseEntity deleteInstance(@PathVariable Long id) { instanceService.deleteInstance(id); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java index e5375dae..83ebf5bf 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java @@ -3,16 +3,16 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import lombok.Builder; - -import java.io.IOException; import java.time.LocalDateTime; import java.util.Optional; +import lombok.Builder; @Builder -public record InstanceDetailResponse(Long topicId, Long instanceId, String title, String description, int pointPerPerson, - String tags, String notice, LocalDateTime startedAt, LocalDateTime completedAt, FileResponse fileResponse) { - public static InstanceDetailResponse createByEntity(Instance instance, Optional files) throws IOException { +public record InstanceDetailResponse(Long topicId, Long instanceId, String title, String description, + int pointPerPerson, + String tags, String notice, LocalDateTime startedAt, LocalDateTime completedAt, + FileResponse fileResponse) { + public static InstanceDetailResponse createByEntity(Instance instance, Optional files) { return InstanceDetailResponse.builder() .topicId(instance.getTopic().getId()) .instanceId(instance.getId()) @@ -27,7 +27,7 @@ public static InstanceDetailResponse createByEntity(Instance instance, Optional< .build(); } - private static FileResponse convertToFileResponse(Optional files) throws IOException { + private static FileResponse convertToFileResponse(Optional files) { if (files.isEmpty()) { return FileResponse.createNotExistFile(); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index a55800bc..f85724fa 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -1,32 +1,29 @@ package com.genius.gitget.challenge.instance.service; +import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import java.io.IOException; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.util.Optional; - -import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; -import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -38,11 +35,11 @@ public class InstanceService { // 인스턴스 생성 @Transactional public Long createInstance(InstanceCreateRequest instanceCreateRequest, - MultipartFile multipartFile, String type) throws IOException { + MultipartFile multipartFile, String type) { Topic topic = topicRepository.findById(instanceCreateRequest.topicId()) .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); - Files uploadedFile = filesService.uploadFile(multipartFile, type); + Files uploadedFile = filesService.uploadFile(topic.getFiles(), multipartFile, type); Instance instance = Instance.builder() .title(instanceCreateRequest.title()) @@ -70,7 +67,7 @@ public Page getAllInstances(Pageable pageable) { } // 인스턴스 단건 조회 - public InstanceDetailResponse getInstanceById(Long id) throws IOException { + public InstanceDetailResponse getInstanceById(Long id) { Instance instanceDetails = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); return InstanceDetailResponse.createByEntity(instanceDetails, instanceDetails.getFiles()); @@ -78,7 +75,7 @@ public InstanceDetailResponse getInstanceById(Long id) throws IOException { // 인스턴스 삭제 @Transactional - public void deleteInstance(Long id) throws IOException{ + public void deleteInstance(Long id) { Instance instance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); @@ -92,8 +89,8 @@ public void deleteInstance(Long id) throws IOException{ // 인스턴스 수정 @Transactional - public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, - MultipartFile multipartFile, String type) throws IOException{ + public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, + MultipartFile multipartFile, String type) { Instance existingInstance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); @@ -101,7 +98,8 @@ public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest Long findInstanceFileId = findInstanceFile.get().getId(); filesService.updateFile(findInstanceFileId, multipartFile); - existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.notice(), instanceUpdateRequest.pointPerPerson(), + existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.notice(), + instanceUpdateRequest.pointPerPerson(), instanceUpdateRequest.startedAt(), instanceUpdateRequest.completedAt()); Instance savedInstance = instanceRepository.save(existingInstance); diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index 258c027b..de57e20a 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -2,13 +2,12 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FileUtil; -import java.io.IOException; public record FileResponse( Long fileId, String encodedFile) { - public static FileResponse createExistFile(Files files) throws IOException { + public static FileResponse createExistFile(Files files) { return new FileResponse(files.getId(), FileUtil.encodedImage(files)); } diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index c6d13c38..89d9bc54 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -1,6 +1,8 @@ package com.genius.gitget.global.file.service; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_COPIED; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.IMAGE_NOT_ENCODED; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import com.genius.gitget.global.file.domain.FileType; @@ -8,8 +10,10 @@ import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.util.exception.BusinessException; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.StandardCopyOption; import java.util.Base64; import java.util.List; import java.util.Objects; @@ -20,11 +24,15 @@ public class FileUtil { private static final List validExtensions = List.of("jpg", "jpeg", "png", "gif"); - public static String encodedImage(Files files) throws IOException { - UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); + public static String encodedImage(Files files) { + try { + UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); - byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); - return new String(encode, StandardCharsets.UTF_8); + byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); + return new String(encode, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new BusinessException(IMAGE_NOT_ENCODED); + } } public static UploadDTO getUploadInfo(MultipartFile file, String typeStr, final String UPLOAD_PATH) { @@ -51,6 +59,30 @@ public static UpdateDTO getUpdateInfo(MultipartFile file, FileType fileType, fin .build(); } + public static UploadDTO getCopyInfo(Files files, FileType fileType, final String UPLOAD_PATH) { + String originalFilename = files.getOriginalFilename(); + String savedFilename = getSavedFilename(originalFilename); + + return UploadDTO.builder() + .fileType(fileType) + .originalFilename(originalFilename) + .savedFilename(savedFilename) + .fileURI(UPLOAD_PATH + fileType.getPath() + savedFilename) + .build(); + } + + public static void copyImage(String originFilePath, String copyFilePath) { + File originFile = new File(originFilePath); + File copyFile = new File(copyFilePath); + + try { + java.nio.file.Files.copy(originFile.toPath(), copyFile.toPath(), + StandardCopyOption.COPY_ATTRIBUTES); + } catch (IOException e) { + throw new BusinessException(FILE_NOT_COPIED); + } + } + public static void validateFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index 1351aa5d..b2eb0541 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -1,7 +1,9 @@ package com.genius.gitget.global.file.service; +import static com.genius.gitget.global.file.domain.FileType.INSTANCE; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_DELETED; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_SAVED; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; @@ -11,11 +13,9 @@ import com.genius.gitget.global.util.exception.BusinessException; import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -33,7 +33,28 @@ public FilesService(@Value("${file.upload.path}") String UPLOAD_PATH, FilesRepos } @Transactional - public Files uploadFile(MultipartFile receivedFile, String typeStr) throws IOException { + public Files uploadFile(Optional optionalFiles, MultipartFile receivedFile, String typeStr) { + if (receivedFile != null) { + return uploadFile(receivedFile, typeStr); + } + + Files files = optionalFiles.orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); + UploadDTO uploadDTO = FileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); + //REFACTOR: 정적 팩토리 메서드로 처리하면 깔끔할 듯! + Files copyFiles = Files.builder() + .originalFilename(uploadDTO.originalFilename()) + .savedFilename(uploadDTO.savedFilename()) + .fileType(uploadDTO.fileType()) + .fileURI(uploadDTO.fileURI()) + .build(); + + FileUtil.copyImage(files.getFileURI(), copyFiles.getFileURI()); + + return filesRepository.save(copyFiles); + } + + @Transactional + public Files uploadFile(MultipartFile receivedFile, String typeStr) { FileUtil.validateFile(receivedFile); UploadDTO uploadDTO = FileUtil.getUploadInfo(receivedFile, typeStr, UPLOAD_PATH); @@ -50,17 +71,21 @@ public Files uploadFile(MultipartFile receivedFile, String typeStr) throws IOExc return filesRepository.save(file); } - private void saveFile(MultipartFile file, String fileURI) throws IOException { - File targetFile = new File(fileURI); + private void saveFile(MultipartFile file, String fileURI) { + try { + File targetFile = new File(fileURI); - if (!targetFile.exists()) { - targetFile.mkdirs(); + if (!targetFile.exists()) { + targetFile.mkdirs(); + } + file.transferTo(targetFile); + } catch (IOException e) { + throw new BusinessException(FILE_NOT_SAVED); } - file.transferTo(targetFile); } @Transactional - public Files updateFile(Long fileId, MultipartFile file) throws IOException { + public Files updateFile(Long fileId, MultipartFile file) { Files files = filesRepository.findById(fileId) .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); @@ -78,7 +103,7 @@ public Files updateFile(Long fileId, MultipartFile file) throws IOException { * @param fileId 삭제하고자하는 Files 엔티티의 PK */ @Transactional - public void deleteFile(Long fileId) throws IOException { + public void deleteFile(Long fileId) { Files files = filesRepository.findById(fileId) .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); @@ -94,7 +119,7 @@ private void deleteFilesInStorage(Files files) { } } - public FileResponse getEncodedFile(Long fileId) throws IOException { + public FileResponse getEncodedFile(Long fileId) { Optional optionalFiles = filesRepository.findById(fileId); if (optionalFiles.isEmpty()) { return FileResponse.createNotExistFile(); @@ -102,11 +127,4 @@ public FileResponse getEncodedFile(Long fileId) throws IOException { return FileResponse.createExistFile(optionalFiles.get()); } - - public UrlResource getFile(Long fileId) throws MalformedURLException { - Files files = filesRepository.findById(fileId) - .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - - return new UrlResource("file:" + files.getFileURI()); - } } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index e70cea1d..71b95364 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -29,6 +29,9 @@ public enum ErrorCode { FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "해당 파일(이미지)이 존재하지 않습니다."), NOT_SUPPORTED_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 확장자입니다."), NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 타입입니다."), + IMAGE_NOT_ENCODED(HttpStatus.BAD_REQUEST, "이미지를 인코딩하는 과정에서 오류가 발생했습니다."), + FILE_NOT_SAVED(HttpStatus.BAD_REQUEST, "파일(이미지)가 정상적으로 저장되지 않았습니다."), + FILE_NOT_COPIED(HttpStatus.BAD_REQUEST, "파일(이미지)가 정상적으로 복사되지 않았습니다."), FILE_NOT_DELETED(HttpStatus.BAD_REQUEST, "파일(이미지)이 정상적으로 삭제되지 않았습니다."); diff --git a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java index dcdc0f81..03af2c55 100644 --- a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java @@ -1,11 +1,14 @@ package com.genius.gitget.global.file.service; +import static com.genius.gitget.global.file.domain.FileType.INSTANCE; +import static com.genius.gitget.global.file.domain.FileType.TOPIC; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.util.exception.BusinessException; @@ -93,6 +96,25 @@ public void should_returnUpdateDTO_when_passUpdateTargetFile() { assertThat(updateDTO.fileURI()).contains(updateDTO.savedFilename()); } + @Test + @DisplayName("기존의 파일을 복사하려고 할 때, 복사에 필요한 정보들을 추출하여 전달할 수 있다.") + public void should_passInformation_when_tryToCopy() { + //given + Files files = Files.builder() + .originalFilename("original file name.png") + .savedFilename("saved file name") + .fileURI("file URI") + .fileType(TOPIC) + .build(); + + //when + UploadDTO copyInfo = FileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); + + //then + assertThat(copyInfo.fileType()).isEqualTo(INSTANCE); + assertThat(copyInfo.fileURI()).contains(UPLOAD_PATH); + } + private MultipartFile getTestMultiPartFile(String originalFilename) { return new MultipartFile() { From 30449befceef678627c20cce1dfd96b30c829865 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:29:31 +0900 Subject: [PATCH 105/234] =?UTF-8?q?hotfix:=20instance=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EC=8B=9C,=20=EA=B2=BD=EB=A1=9C=EA=B0=80=20=EC=97=86?= =?UTF-8?q?=EC=96=B4=20=EC=98=88=EC=99=B8=EB=A5=BC=20=EB=8D=98=EC=A7=80?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=ED=94=BD=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/InstanceController.java | 4 +-- .../gitget/global/file/domain/Files.java | 2 -- .../gitget/global/file/dto/CopyDTO.java | 12 +++++++ .../gitget/global/file/service/FileUtil.java | 29 ++++++++++++++--- .../global/file/service/FilesService.java | 32 ++++++------------- .../global/file/service/FileUtilTest.java | 7 ++-- 6 files changed, 52 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/file/dto/CopyDTO.java diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index d787a3e1..1ae46613 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -56,10 +56,10 @@ public ResponseEntity> getInstanceById(@P public ResponseEntity createInstance( @RequestPart(value = "data") InstanceCreateRequest instanceCreateRequest, @RequestPart(value = "files", required = false) MultipartFile multipartFile, - @RequestPart(value = "type") String type) { + @RequestPart(value = "type", required = false) String type) { instanceService.createInstance(instanceCreateRequest, multipartFile, type); return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.CREATED.getMessage()) + new CommonResponse(SuccessCode.CREATED.getStatus(), SuccessCode.CREATED.getMessage()) ); } diff --git a/src/main/java/com/genius/gitget/global/file/domain/Files.java b/src/main/java/com/genius/gitget/global/file/domain/Files.java index a9ed5a3d..f9e65331 100644 --- a/src/main/java/com/genius/gitget/global/file/domain/Files.java +++ b/src/main/java/com/genius/gitget/global/file/domain/Files.java @@ -23,8 +23,6 @@ public class Files extends BaseTimeEntity { @Column(name = "files_id") private Long id; - //TODO: 추후 PET쪽과 연관관계 설정 필요 - @Enumerated(value = EnumType.STRING) private FileType fileType; diff --git a/src/main/java/com/genius/gitget/global/file/dto/CopyDTO.java b/src/main/java/com/genius/gitget/global/file/dto/CopyDTO.java new file mode 100644 index 00000000..b1e7e711 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/dto/CopyDTO.java @@ -0,0 +1,12 @@ +package com.genius.gitget.global.file.dto; + +import com.genius.gitget.global.file.domain.FileType; +import lombok.Builder; + +@Builder +public record CopyDTO(FileType fileType, + String originalFilename, + String savedFilename, + String fileURI, + String folderURI) { +} diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index 89d9bc54..6d16ad2d 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -2,11 +2,13 @@ import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_COPIED; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_SAVED; import static com.genius.gitget.global.util.exception.ErrorCode.IMAGE_NOT_ENCODED; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.CopyDTO; import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.util.exception.BusinessException; @@ -59,23 +61,35 @@ public static UpdateDTO getUpdateInfo(MultipartFile file, FileType fileType, fin .build(); } - public static UploadDTO getCopyInfo(Files files, FileType fileType, final String UPLOAD_PATH) { + public static void saveFile(MultipartFile file, String fileURI) { + try { + File targetFile = new File(fileURI); + createPath(fileURI); + file.transferTo(targetFile); + } catch (IOException e) { + throw new BusinessException(FILE_NOT_SAVED); + } + } + + public static CopyDTO getCopyInfo(Files files, FileType fileType, final String UPLOAD_PATH) { String originalFilename = files.getOriginalFilename(); String savedFilename = getSavedFilename(originalFilename); - return UploadDTO.builder() + return CopyDTO.builder() .fileType(fileType) .originalFilename(originalFilename) .savedFilename(savedFilename) .fileURI(UPLOAD_PATH + fileType.getPath() + savedFilename) + .folderURI(UPLOAD_PATH + fileType.getPath()) .build(); } - public static void copyImage(String originFilePath, String copyFilePath) { + public static void copyImage(String originFilePath, CopyDTO copyDTO) { File originFile = new File(originFilePath); - File copyFile = new File(copyFilePath); + File copyFile = new File(copyDTO.fileURI()); try { + createPath(copyDTO.folderURI()); java.nio.file.Files.copy(originFile.toPath(), copyFile.toPath(), StandardCopyOption.COPY_ATTRIBUTES); } catch (IOException e) { @@ -108,4 +122,11 @@ private static String extractExtension(String filename) { int index = filename.lastIndexOf("."); return filename.substring(index + 1).toLowerCase(); } + + private static void createPath(String uri) { + File file = new File(uri); + if (!file.exists()) { + file.mkdirs(); + } + } } diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index b2eb0541..ed6b714c 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -3,16 +3,15 @@ import static com.genius.gitget.global.file.domain.FileType.INSTANCE; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_DELETED; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; -import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_SAVED; import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.CopyDTO; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.file.repository.FilesRepository; import com.genius.gitget.global.util.exception.BusinessException; import java.io.File; -import java.io.IOException; import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -39,16 +38,16 @@ public Files uploadFile(Optional optionalFiles, MultipartFile receivedFil } Files files = optionalFiles.orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - UploadDTO uploadDTO = FileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); + CopyDTO copyDTO = FileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); //REFACTOR: 정적 팩토리 메서드로 처리하면 깔끔할 듯! Files copyFiles = Files.builder() - .originalFilename(uploadDTO.originalFilename()) - .savedFilename(uploadDTO.savedFilename()) - .fileType(uploadDTO.fileType()) - .fileURI(uploadDTO.fileURI()) + .originalFilename(copyDTO.originalFilename()) + .savedFilename(copyDTO.savedFilename()) + .fileType(copyDTO.fileType()) + .fileURI(copyDTO.fileURI()) .build(); - FileUtil.copyImage(files.getFileURI(), copyFiles.getFileURI()); + FileUtil.copyImage(files.getFileURI(), copyDTO); return filesRepository.save(copyFiles); } @@ -59,7 +58,7 @@ public Files uploadFile(MultipartFile receivedFile, String typeStr) { UploadDTO uploadDTO = FileUtil.getUploadInfo(receivedFile, typeStr, UPLOAD_PATH); - saveFile(receivedFile, uploadDTO.fileURI()); + FileUtil.saveFile(receivedFile, uploadDTO.fileURI()); Files file = Files.builder() .originalFilename(uploadDTO.originalFilename()) @@ -71,19 +70,6 @@ public Files uploadFile(MultipartFile receivedFile, String typeStr) { return filesRepository.save(file); } - private void saveFile(MultipartFile file, String fileURI) { - try { - File targetFile = new File(fileURI); - - if (!targetFile.exists()) { - targetFile.mkdirs(); - } - file.transferTo(targetFile); - } catch (IOException e) { - throw new BusinessException(FILE_NOT_SAVED); - } - } - @Transactional public Files updateFile(Long fileId, MultipartFile file) { Files files = filesRepository.findById(fileId) @@ -92,7 +78,7 @@ public Files updateFile(Long fileId, MultipartFile file) { deleteFilesInStorage(files); UpdateDTO updateDTO = FileUtil.getUpdateInfo(file, files.getFileType(), UPLOAD_PATH); - saveFile(file, updateDTO.fileURI()); + FileUtil.saveFile(file, updateDTO.fileURI()); files.updateFiles(updateDTO); return files; } diff --git a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java index 03af2c55..3b57b220 100644 --- a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java @@ -9,6 +9,7 @@ import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.CopyDTO; import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.util.exception.BusinessException; @@ -108,11 +109,11 @@ public void should_passInformation_when_tryToCopy() { .build(); //when - UploadDTO copyInfo = FileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); + CopyDTO copyDTO = FileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); //then - assertThat(copyInfo.fileType()).isEqualTo(INSTANCE); - assertThat(copyInfo.fileURI()).contains(UPLOAD_PATH); + assertThat(copyDTO.fileType()).isEqualTo(INSTANCE); + assertThat(copyDTO.fileURI()).contains(UPLOAD_PATH); } From 8b15712bfe48dca94d64d8020e1adf5f6d7bef63 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 15 Feb 2024 00:33:39 +0900 Subject: [PATCH 106/234] =?UTF-8?q?chore:=20gitignore=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 59217f0b..f03837f2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,5 @@ out/ .vscode/ ### MAC ### -*.DS_Store \ No newline at end of file +*.DS_Store +src/main/generated/** From c964d56d0e586ee958ae0492a58b954c6ea9eff3 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 15 Feb 2024 01:13:50 +0900 Subject: [PATCH 107/234] =?UTF-8?q?chore:=20Querydsl=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20build.gradle=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 - .../gitget/querydsl/QuerydslBasicTest.java | 229 ------------------ 2 files changed, 236 deletions(-) delete mode 100644 src/test/java/com/genius/gitget/querydsl/QuerydslBasicTest.java diff --git a/build.gradle b/build.gradle index be752564..5f2adee4 100644 --- a/build.gradle +++ b/build.gradle @@ -63,11 +63,4 @@ dependencies { tasks.named('test') { useJUnitPlatform() -} - -// Querydsl 설정부 -def generatedDir = 'src/main/generated' - -clean { - delete file (generatedDir) } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/querydsl/QuerydslBasicTest.java b/src/test/java/com/genius/gitget/querydsl/QuerydslBasicTest.java deleted file mode 100644 index 6aea4a40..00000000 --- a/src/test/java/com/genius/gitget/querydsl/QuerydslBasicTest.java +++ /dev/null @@ -1,229 +0,0 @@ -package com.genius.gitget.querydsl; - -import com.genius.gitget.admin.topic.domain.QTopic; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.domain.QInstance; -import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; -import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.instance.repository.SearchRepository; -import com.genius.gitget.challenge.instance.service.InstanceSearchService; -import com.genius.gitget.challenge.instance.service.InstanceService; -import com.genius.gitget.global.file.domain.QFiles; -import com.genius.gitget.util.file.FileTestUtil; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.Projections; -import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.transaction.Transactional; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.List; - -import static com.genius.gitget.admin.topic.domain.QTopic.*; -import static com.genius.gitget.challenge.instance.domain.Progress.*; -import static com.genius.gitget.challenge.instance.domain.QInstance.*; -import static com.genius.gitget.global.file.domain.QFiles.*; - -@Transactional -@SpringBootTest -public class QuerydslBasicTest { - - @PersistenceContext - EntityManager em; - @Autowired - SearchRepository searchRepository; - @Autowired - TopicRepository topicRepository; - @Autowired - InstanceRepository instanceRepository; - @Autowired - InstanceSearchService instanceSearchService; - @Autowired - InstanceService instanceService; - - @Autowired - FileTestUtil fileTestUtil; - - JPAQueryFactory queryFactory; - - QTopic t; - QInstance i; - QFiles f; - - @BeforeEach - public void setup() throws IOException{ - - queryFactory = new JPAQueryFactory(em); - t = topic; - i = instance; - f = files; - - Topic topic = Topic.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .build(); - - Instance instance = Instance.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .notice("유의사항") - .progress(PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - - Topic savedTopic = topicRepository.save(topic); - - createInstance(savedTopic, instance, instance.getTitle()); - createInstance(savedTopic, instance, instance.getTitle()); - - Page allInstances = instanceService.getAllInstances(PageRequest.of(0, 5)); - for (InstancePagingResponse allInstance : allInstances) { - System.out.println("allInstance = " + allInstance); - } - } - - @Test - public void findDtoByQuerydsl() throws IOException { - List fetch = queryFactory.select(Projections.fields(QuerydslDTO.class, - i.topic.id.as("topicId"), i.id.as("instanceId"), i.title, i.pointPerPerson, i.participantCount, - f.fileURI, f.originalFilename, f.savedFilename)) - .from(i) - .leftJoin(f) - .on(i.files.id.eq(f.id)) - .where(i.progress.eq(Progress.valueOf("PREACTIVITY")), i.title.like("%아%")) - .orderBy(i.startedDate.desc()) - .fetch(); - - System.out.println("fetch = " + fetch.size()); - for (QuerydslDTO querydslDTO : fetch) { - System.out.println("querydslDTO.toString() = " + querydslDTO.toString()); - } - } - - - @Test - public void dynamicQuery_BooleanBuilder() { - String instanceParam = "1일 1알고리즘"; - Integer pointParam = 100; - - List result = searchInstance(instanceParam, pointParam); - Assertions.assertThat(result.size()).isEqualTo(2); - - } - - private List searchInstance(String instanceCond, Integer pointCond) { - - BooleanBuilder builder = new BooleanBuilder(); - if (instanceCond != null) { - builder.and(i.title.eq(instanceCond)); - } - if (pointCond != null) { - builder.and(i.pointPerPerson.eq(pointCond)); - } - - return queryFactory - .selectFrom(i) - .where(builder) - .fetch(); - } - - @Test - public void dynamicQuery_WhereParam() { - String instanceParam = "1일 1알고리즘"; - Integer pointParam = 100; - - List result = searchInstance2(instanceParam, pointParam); - Assertions.assertThat(result.size()).isEqualTo(2); - } - - private List searchInstance2(String instanceCond, Integer pointCond) { - return queryFactory - .selectFrom(i) - .where(instanceCondEq(instanceCond), pointCondEq(pointCond)) - .fetch(); - } - - private Predicate instanceCondEq(String instanceCond) { - if (instanceCond == null) { - return null; - } - return i.title.eq(instanceCond); - } - - private Predicate pointCondEq(Integer pointCond) { - if (pointCond == null) { - return null; - } - return i.pointPerPerson.eq(pointCond); - } - - @Test - public void bulkUpdate() { - long execute = queryFactory - .update(i) - .set(i.title, "1일 1커밋") - .where(i.progress.notIn(ACTIVITY, DONE)) - .execute(); - - em.flush(); - em.clear(); - - List list = queryFactory - .selectFrom(i) - .fetch(); - for (Instance instance : list) { - System.out.println("instance = " + instance.getTitle()); - } - } - - @Test - public void bulkAdd() { - queryFactory - .update(i) - .set(i.pointPerPerson, i.pointPerPerson.add(50)) - .execute(); - } - - @Test - public void bulkDelete() { - queryFactory - .delete(i) - .where(i.pointPerPerson.gt(100)) - .execute(); - } - - - private void createInstance(Topic savedTopic, Instance instance, String title) throws IOException { - instanceService.createInstance( - InstanceCreateRequest.builder() - .topicId(savedTopic.getId()) - .title(title) - .tags(instance.getTags()) - .description(instance.getDescription()) - .notice(instance.getNotice()) - .pointPerPerson(instance.getPointPerPerson()) - .startedAt(instance.getStartedDate()) - .completedAt(instance.getCompletedDate()).build(), - FileTestUtil.getMultipartFile("name"), "instance"); - } -} From 50a499732c9cf560c7113d1d0e40d41eefe97dcb Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 15 Feb 2024 01:16:33 +0900 Subject: [PATCH 108/234] =?UTF-8?q?chore:=20package=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - home 패키지 내의 controller, service, dto를 instance 패키지로 이동 --- .../controller/InstanceHomeController.java} | 26 +++++++++---------- .../dto/home}/HomeInstanceResponse.java | 2 +- .../service/InstanceHomeService.java} | 7 +++-- ...t.java => InstanceHomeControllerTest.java} | 2 +- ...Test.java => InstanceHomeServiceTest.java} | 9 ++++--- 5 files changed, 23 insertions(+), 23 deletions(-) rename src/main/java/com/genius/gitget/challenge/{home/controller/HomeController.java => instance/controller/InstanceHomeController.java} (77%) rename src/main/java/com/genius/gitget/challenge/{home/dto => instance/dto/home}/HomeInstanceResponse.java (95%) rename src/main/java/com/genius/gitget/challenge/{home/service/HomeService.java => instance/service/InstanceHomeService.java} (89%) rename src/test/java/com/genius/gitget/challenge/home/controller/{HomeControllerTest.java => InstanceHomeControllerTest.java} (99%) rename src/test/java/com/genius/gitget/challenge/home/service/{HomeServiceTest.java => InstanceHomeServiceTest.java} (90%) diff --git a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java similarity index 77% rename from src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java rename to src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java index 960d24d1..b9cc1ba2 100644 --- a/src/main/java/com/genius/gitget/challenge/home/controller/HomeController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java @@ -1,13 +1,12 @@ -package com.genius.gitget.challenge.home.controller; +package com.genius.gitget.challenge.instance.controller; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.challenge.home.dto.HomeInstanceResponse; -import com.genius.gitget.challenge.home.service.HomeService; +import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.service.InstanceHomeService; import com.genius.gitget.challenge.instance.service.InstanceSearchService; -import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.PagingResponse; @@ -21,16 +20,17 @@ import org.springframework.data.domain.Sort.Direction; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; +import org.springframework.web.bind.annotation.GetMapping; +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; @RestController @RequiredArgsConstructor @RequestMapping("/api/challenges") -public class HomeController { - private final HomeService homeService; +public class InstanceHomeController { + private final InstanceHomeService instanceHomeService; private final InstanceSearchService instanceSearchService; @PostMapping("/search") @@ -54,7 +54,7 @@ public ResponseEntity> getRecommendInstanc PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Direction.DESC, "participantCount")); - Slice recommendations = homeService.getRecommendations( + Slice recommendations = instanceHomeService.getRecommendations( userPrincipal.getUser(), pageRequest); return ResponseEntity.ok().body( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) @@ -66,7 +66,7 @@ public ResponseEntity> getPopularInstances PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Direction.DESC, "participantCount")); - Slice recommendations = homeService.getInstancesByCondition(pageRequest); + Slice recommendations = instanceHomeService.getInstancesByCondition(pageRequest); return ResponseEntity.ok().body( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) ); @@ -77,7 +77,7 @@ public ResponseEntity> getLatestInstances( PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Direction.DESC, "startedDate")); - Slice recommendations = homeService.getInstancesByCondition(pageRequest); + Slice recommendations = instanceHomeService.getInstancesByCondition(pageRequest); return ResponseEntity.ok().body( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) ); diff --git a/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java similarity index 95% rename from src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java rename to src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java index c5e4a57c..5717806d 100644 --- a/src/main/java/com/genius/gitget/challenge/home/dto/HomeInstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.home.dto; +package com.genius.gitget.challenge.instance.dto.home; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.domain.Files; diff --git a/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java similarity index 89% rename from src/main/java/com/genius/gitget/challenge/home/service/HomeService.java rename to src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java index 69e6078f..7e942fb1 100644 --- a/src/main/java/com/genius/gitget/challenge/home/service/HomeService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java @@ -1,12 +1,11 @@ -package com.genius.gitget.challenge.home.service; +package com.genius.gitget.challenge.instance.service; import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; -import com.genius.gitget.challenge.home.dto.HomeInstanceResponse; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import java.io.IOException; import java.util.Arrays; @@ -22,7 +21,7 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class HomeService { +public class InstanceHomeService { private final InstanceRepository instanceRepository; public Slice getRecommendations(User user, Pageable pageable) { diff --git a/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java b/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java similarity index 99% rename from src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java rename to src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java index 2d8a018d..c7dfa4f0 100644 --- a/src/test/java/com/genius/gitget/challenge/home/controller/HomeControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java @@ -25,7 +25,7 @@ @SpringBootTest @Transactional -class HomeControllerTest { +class InstanceHomeControllerTest { MockMvc mockMvc; @Autowired WebApplicationContext context; diff --git a/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java b/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java similarity index 90% rename from src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java rename to src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java index 1ae9d96f..50687f61 100644 --- a/src/test/java/com/genius/gitget/challenge/home/service/HomeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java @@ -4,10 +4,11 @@ import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.challenge.home.dto.HomeInstanceResponse; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.instance.service.InstanceHomeService; import com.genius.gitget.challenge.user.domain.User; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; @@ -22,9 +23,9 @@ @SpringBootTest @Transactional -class HomeServiceTest { +class InstanceHomeServiceTest { @Autowired - HomeService homeService; + InstanceHomeService instanceHomeService; @Autowired TopicRepository topicRepository; @Autowired @@ -43,7 +44,7 @@ public void should_getSuggestions_when_passUserTags() { User user = User.builder().tags("BE").build(); //when - Slice recommendations = homeService.getRecommendations(user, pageRequest); + Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); //then assertThat(recommendations.getContent().size()).isEqualTo(2); From 3a03c50a8f773de6cdf7ca5d5aa1166384f21d84 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:19:01 +0900 Subject: [PATCH 109/234] =?UTF-8?q?fix:=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=88=98=EC=A0=95=20API=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=B4=EA=B0=95=20(#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인스턴스 수정 API 요청 시, MultipartFile이 없을 때 처리하는 로직 보강 --- .../instance/dto/crud/InstancePagingResponse.java | 9 ++++----- .../genius/gitget/global/file/service/FilesService.java | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java index c09c3503..e8dab50d 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java @@ -3,15 +3,14 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import lombok.Builder; - import java.io.IOException; import java.time.LocalDateTime; import java.util.Optional; +import lombok.Builder; @Builder -public record InstancePagingResponse (Long topicId, Long instanceId, String title, - LocalDateTime startedAt, LocalDateTime completedAt, FileResponse fileResponse) { +public record InstancePagingResponse(Long topicId, Long instanceId, String title, + LocalDateTime startedAt, LocalDateTime completedAt, FileResponse fileResponse) { public static InstancePagingResponse createByEntity(Instance instance, Optional files) throws IOException { return InstancePagingResponse.builder() .topicId(instance.getTopic().getId()) @@ -23,7 +22,7 @@ public static InstancePagingResponse createByEntity(Instance instance, Optional< .build(); } - private static FileResponse convertToFileResponse(Optional files) throws IOException { + private static FileResponse convertToFileResponse(Optional files) { if (files.isEmpty()) { return FileResponse.createNotExistFile(); } diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index ed6b714c..eb8d9289 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -75,6 +75,10 @@ public Files updateFile(Long fileId, MultipartFile file) { Files files = filesRepository.findById(fileId) .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); + if (file == null) { + return files; + } + deleteFilesInStorage(files); UpdateDTO updateDTO = FileUtil.getUpdateInfo(file, files.getFileType(), UPLOAD_PATH); From e609840cd640cdcefadc86f16bda1f005a9bbaf5 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:39:11 +0900 Subject: [PATCH 110/234] =?UTF-8?q?feat:=20Admin=20API=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=B4=20=EC=A0=91=EA=B7=BC=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20(#75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SecurityConfig 설정을 통해 접근 권한 설정 - 관련 테스트 코드 작성 --- .../security/config/SecurityConfig.java | 1 + .../security/config/SecurityConfigTest.java | 28 +++++++++++++++++++ ...hMockCustomUserSecurityContextFactory.java | 5 ++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java index b6c55a2f..5b34a047 100644 --- a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java @@ -46,6 +46,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .httpBasic(HttpBasicConfigurer::disable) .formLogin(FormLoginConfigurer::disable) .authorizeHttpRequests(request -> request + .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .requestMatchers(PERMITTED_URI).permitAll() .anyRequest().hasAnyRole(PERMITTED_ROLES)) diff --git a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java index 4b7ff16c..f36cf928 100644 --- a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java +++ b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java @@ -4,6 +4,9 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,13 +14,17 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; @SpringBootTest +@Transactional class SecurityConfigTest { MockMvc mockMvc; @Autowired WebApplicationContext context; + @Autowired + TokenTestUtil tokenTestUtil; @BeforeEach public void setup() { @@ -45,4 +52,25 @@ public void should_status2xx_when_uriIsPermitAll() throws Exception { mockMvc.perform(get("/api/test")) .andExpect(status().is4xxClientError()); } + + @Test + @DisplayName("Admin API에 대해서 Role이 Admin일 때에는 2xx 응답이 발생해야 한다.") + @WithMockCustomUser(role = Role.ADMIN) + public void should_status2xx_when_roleIsAdmin() throws Exception { + //given + + //when & then + mockMvc.perform(get("/api/admin/topic") + .cookie(tokenTestUtil.createAccessCookie())) + .andExpect(status().is2xxSuccessful()); + } + + @Test + @DisplayName("Admin API에 대해서 Role이 Admin이 아닐 때에는 4xx 응답이 발생해야 한다.") + @WithMockCustomUser(role = Role.USER) + public void should_status4xx_when_roleNotAdmin() throws Exception { + mockMvc.perform(get("/api/admin/topic") + .cookie(tokenTestUtil.createAccessCookie())) + .andExpect(status().is4xxClientError()); + } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java index 1fd18856..64265ab8 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java @@ -1,11 +1,11 @@ package com.genius.gitget.util; -import com.genius.gitget.global.security.service.CustomUserDetailsService; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.security.service.CustomUserDetailsService; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -38,8 +38,9 @@ public SecurityContext createSecurityContext(WithMockCustomUser customUser) { .information(customUser.information()) .build(); - userRepository.save(user); + User savedUser = userRepository.save(user); Long signupId = userService.signup(signupRequest); + savedUser.updateRole(customUser.role()); UserDetails principal = customUserDetailsService.loadUserByUsername(String.valueOf(signupId)); Authentication auth = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), From 8b59af092d73725dd154b381d6d187de26a832c9 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Mon, 26 Feb 2024 19:48:52 +0900 Subject: [PATCH 111/234] =?UTF-8?q?feat:=20=ED=8A=B9=EC=A0=95=20=ED=86=A0?= =?UTF-8?q?=ED=94=BD=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9D=B8=EC=8A=A4?= =?UTF-8?q?=ED=84=B4=EC=8A=A4=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../topic/controller/TopicController.java | 32 +++++++++++-------- .../admin/topic/service/TopicService.java | 17 +++++----- .../controller/InstanceController.java | 29 +++++++++++++---- .../repository/InstanceRepository.java | 3 ++ .../instance/service/InstanceService.java | 12 +++++++ 5 files changed, 66 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java b/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java index f86a99f9..e86f0bc5 100644 --- a/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java +++ b/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java @@ -1,29 +1,31 @@ package com.genius.gitget.admin.topic.controller; import com.genius.gitget.admin.topic.dto.TopicCreateRequest; +import com.genius.gitget.admin.topic.dto.TopicDetailResponse; import com.genius.gitget.admin.topic.dto.TopicPagingResponse; import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; -import com.genius.gitget.admin.topic.dto.TopicDetailResponse; import com.genius.gitget.admin.topic.service.TopicService; -import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; -import jakarta.validation.Valid; +import java.io.IOException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; - @RestController @RequiredArgsConstructor @RequestMapping("/api/admin/topic") @@ -53,8 +55,10 @@ public ResponseEntity> getTopicById(@PathVar // 토픽 생성 요청 @PostMapping - public ResponseEntity createTopic(@RequestPart(value = "data") TopicCreateRequest topicCreateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type") String type) throws IOException { + public ResponseEntity createTopic( + @RequestPart(value = "data") TopicCreateRequest topicCreateRequest, + @RequestPart(value = "files", required = false) MultipartFile multipartFile, + @RequestPart(value = "type") String type) { topicService.createTopic(topicCreateRequest, multipartFile, type); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.CREATED.getStatus(), SuccessCode.CREATED.getMessage()) @@ -63,8 +67,10 @@ public ResponseEntity createTopic(@RequestPart(value = "data") T // 토픽 수정 요청 @PatchMapping("/{id}") - public ResponseEntity updateTopic(@PathVariable Long id, @RequestPart(value = "data") TopicUpdateRequest topicUpdateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type") String type) throws IOException { + public ResponseEntity updateTopic(@PathVariable Long id, + @RequestPart(value = "data") TopicUpdateRequest topicUpdateRequest, + @RequestPart(value = "files", required = false) MultipartFile multipartFile, + @RequestPart(value = "type") String type) { topicService.updateTopic(id, topicUpdateRequest, multipartFile, type); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) @@ -73,7 +79,7 @@ public ResponseEntity updateTopic(@PathVariable Long id, @Reques // 토픽 삭제 요청 @DeleteMapping("/{id}") - public ResponseEntity deleteTopic(@PathVariable Long id) throws IOException { + public ResponseEntity deleteTopic(@PathVariable Long id) { topicService.deleteTopic(id); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index db6f8b1f..4e835b42 100644 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -34,6 +34,14 @@ public Page getAllTopics(Pageable pageable) { return topics.map(this::mapToTopicPagingResponse); } + private TopicPagingResponse mapToTopicPagingResponse(Topic topic) { + try { + return TopicPagingResponse.createByEntity(topic, topic.getFiles()); + } catch (IOException e) { + throw new BusinessException(e); + } + } + // 토픽 상세정보 요청 public TopicDetailResponse getTopicById(Long id) throws IOException { Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); @@ -60,6 +68,7 @@ public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile mul return savedTopic.getId(); } + // 토픽 업데이트 요청 @Transactional public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest, MultipartFile multipartFile, String type) { Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); @@ -87,12 +96,4 @@ public void deleteTopic(Long id) { .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); topicRepository.delete(topic); } - - private TopicPagingResponse mapToTopicPagingResponse(Topic topic) { - try { - return TopicPagingResponse.createByEntity(topic, topic.getFiles()); - } catch (IOException e) { - throw new BusinessException(e); - } - } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index 1ae46613..6b9e550d 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -11,6 +11,7 @@ import com.genius.gitget.global.util.response.dto.SingleResponse; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; @@ -26,13 +27,13 @@ import org.springframework.web.multipart.MultipartFile; @RestController -@RequestMapping("/api/admin/instance") +@RequestMapping("/api/admin") @RequiredArgsConstructor public class InstanceController { private final InstanceService instanceService; // 인스턴스 리스트 조회 - @GetMapping + @GetMapping("/instance") public ResponseEntity> getAllInstances( @PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id") Pageable pageable) { Page instances = instanceService.getAllInstances(pageable); @@ -42,8 +43,24 @@ public ResponseEntity> getAllInstances( ); } + // 특정 토픽에 대한 리스트 조회 + @GetMapping("topic/instances/{id}") + public ResponseEntity> getAllInstancesOfSpecificTopic( + @PageableDefault Pageable pageable, @PathVariable Long id) { + PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), + Sort.by("id")); + Page allInstancesOfSpecificTopic = instanceService.getAllInstancesOfSpecificTopic( + pageRequest, id); + + return ResponseEntity.ok().body( + new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + allInstancesOfSpecificTopic) + ); + } + + // 인스턴스 단건 조회 - @GetMapping("/{id}") + @GetMapping("/instance/{id}") public ResponseEntity> getInstanceById(@PathVariable Long id) { InstanceDetailResponse instanceDetails = instanceService.getInstanceById(id); return ResponseEntity.ok().body( @@ -52,7 +69,7 @@ public ResponseEntity> getInstanceById(@P } // 인스턴스 생성 - @PostMapping + @PostMapping("/instance") public ResponseEntity createInstance( @RequestPart(value = "data") InstanceCreateRequest instanceCreateRequest, @RequestPart(value = "files", required = false) MultipartFile multipartFile, @@ -64,7 +81,7 @@ public ResponseEntity createInstance( } // 인스턴스 수정 - @PatchMapping("/{id}") + @PatchMapping("/instance/{id}") public ResponseEntity updateInstance(@PathVariable Long id, @RequestPart(value = "data") InstanceUpdateRequest instanceUpdateRequest, @RequestPart(value = "files", required = false) MultipartFile multipartFile, @@ -77,7 +94,7 @@ public ResponseEntity updateInstance(@PathVariable Long id, } // 인스턴스 삭제 - @DeleteMapping("/{id}") + @DeleteMapping("/instance/{id}") public ResponseEntity deleteInstance(@PathVariable Long id) { instanceService.deleteInstance(id); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java index 0e07e447..e15e8244 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java @@ -15,6 +15,9 @@ public interface InstanceRepository extends JpaRepository { @Query("select i from Instance i ORDER BY i.id DESC ") Page findAllById(Pageable pageable); + @Query("select i from Instance i where i.topic.id = :topicId") + Page findInstancesByTopicId(Pageable pageable, Long topicId); + @Query("select i from Instance i where i.progress = :progress and i.tags in :userTags") Slice findRecommendations(@Param("userTags") List userTags, Progress progress, Pageable pageable); diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index f85724fa..e8f1dfb5 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -15,6 +15,7 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import java.io.IOException; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -66,6 +67,15 @@ public Page getAllInstances(Pageable pageable) { return instances.map(this::mapToInstancePagingResponse); } + + // 특정 토픽에 대한 리스트 조회 + public Page getAllInstancesOfSpecificTopic(Pageable pageable, Long id) { + Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + Page instancesByTopicId = instanceRepository.findInstancesByTopicId(pageable, topic.getId()); + return instancesByTopicId.map(this::mapToInstancePagingResponse); + } + + // 인스턴스 단건 조회 public InstanceDetailResponse getInstanceById(Long id) { Instance instanceDetails = instanceRepository.findById(id) @@ -73,6 +83,7 @@ public InstanceDetailResponse getInstanceById(Long id) { return InstanceDetailResponse.createByEntity(instanceDetails, instanceDetails.getFiles()); } + // 인스턴스 삭제 @Transactional public void deleteInstance(Long id) { @@ -87,6 +98,7 @@ public void deleteInstance(Long id) { instanceRepository.delete(instance); } + // 인스턴스 수정 @Transactional public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, From 16fd818893048e84dd29c7562fa9274eb92faf18 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 29 Feb 2024 19:12:30 +0900 Subject: [PATCH 112/234] =?UTF-8?q?[FEAT]=20Item=20=EA=B4=80=EB=A0=A8=20En?= =?UTF-8?q?tity=20=EB=B0=8F=20Repository=20=EC=9E=91=EC=84=B1=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: item과 관련된 엔티티 작성 - Item, UserItem, User 엔티티 작성 - Item entity에 필요한 Enum 클래스 작성 * feat: item 관련 repository 작성 * refactor: 필드 이름 변경 * refactor: 필드 이름 변경 --- .../gitget/challenge/item/domain/Item.java | 38 +++++++++++++++ .../challenge/item/domain/ItemCategory.java | 12 +++++ .../challenge/item/domain/UserItem.java | 47 +++++++++++++++++++ .../item/repository/ItemRepository.java | 7 +++ .../item/repository/UserItemRepository.java | 7 +++ .../gitget/challenge/user/domain/User.java | 4 ++ 6 files changed, 115 insertions(+) create mode 100644 src/main/java/com/genius/gitget/challenge/item/domain/Item.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java b/src/main/java/com/genius/gitget/challenge/item/domain/Item.java new file mode 100644 index 00000000..14747c51 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/domain/Item.java @@ -0,0 +1,38 @@ +package com.genius.gitget.challenge.item.domain; + +import com.genius.gitget.global.util.domain.BaseTimeEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Item extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "item_id") + private Long id; + + @OneToMany(mappedBy = "item") + private List userItemList = new ArrayList<>(); + + private String name; + + private int cost; + + @Enumerated(EnumType.STRING) + private ItemCategory itemCategory; + + private String details; +} diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java b/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java new file mode 100644 index 00000000..24cc1ac7 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java @@ -0,0 +1,12 @@ +package com.genius.gitget.challenge.item.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum ItemCategory { + PROFILE_FRAME, + CERTIFICATION_SKIPPER, + POINT_MULTIPLIER +} diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java b/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java new file mode 100644 index 00000000..37c4934a --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java @@ -0,0 +1,47 @@ +package com.genius.gitget.challenge.item.domain; + +import com.genius.gitget.challenge.user.domain.User; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserItem { + @Id + @GeneratedValue + Long userItemId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id") + private Item item; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + private int count; + + + //=== 연관관계 편의 메서드 ===// + public void setUser(User user) { + this.user = user; + if (!user.getUserItemList().contains(this)) { + user.getUserItemList().add(this); + } + } + + public void setItem(Item item) { + this.item = item; + if (!item.getUserItemList().contains(this)) { + item.getUserItemList().add(this); + } + } +} diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java b/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java new file mode 100644 index 00000000..549f6f32 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java @@ -0,0 +1,7 @@ +package com.genius.gitget.challenge.item.repository; + +import com.genius.gitget.challenge.item.domain.Item; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ItemRepository extends JpaRepository { +} diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java b/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java new file mode 100644 index 00000000..20d3f55c --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java @@ -0,0 +1,7 @@ +package com.genius.gitget.challenge.item.repository; + +import com.genius.gitget.challenge.item.domain.UserItem; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserItemRepository extends JpaRepository { +} diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 3d34a6d4..00ecd4c0 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -1,6 +1,7 @@ package com.genius.gitget.challenge.user.domain; import com.genius.gitget.challenge.hits.domain.Hits; +import com.genius.gitget.challenge.item.domain.UserItem; import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; @@ -46,6 +47,9 @@ public class User extends BaseTimeEntity { @OneToMany(mappedBy = "user") private List participantInfoList = new ArrayList<>(); + @OneToMany(mappedBy = "user") + private List userItemList = new ArrayList<>(); + @NotNull @Enumerated(EnumType.STRING) private ProviderInfo providerInfo; From 6285f66a382dd39eb2a4ad6a2c16b7cb5ef750d9 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Thu, 29 Feb 2024 19:13:32 +0900 Subject: [PATCH 113/234] =?UTF-8?q?[FEAT]=20certification=5Fmethod=20entit?= =?UTF-8?q?y=20=EC=B6=94=EA=B0=80=20=20(#86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 특정 토픽에 대한 인스턴스 리스트 조회 * feat: certificationMethod entity 추가 --- src/main/java/com/genius/gitget/admin/topic/domain/Topic.java | 3 ++- .../com/genius/gitget/challenge/instance/domain/Instance.java | 3 ++- .../challenge/instance/dto/crud/InstanceCreateRequest.java | 3 ++- .../challenge/instance/dto/crud/InstanceDetailResponse.java | 2 ++ .../challenge/instance/dto/crud/InstanceUpdateRequest.java | 3 ++- .../gitget/challenge/instance/service/InstanceService.java | 4 +++- .../java/com/genius/gitget/challenge/user/domain/User.java | 2 ++ 7 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java index e405b5be..bf33a0bc 100644 --- a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java @@ -2,6 +2,7 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -26,7 +27,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "topic") -public class Topic { +public class Topic extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "topic_id") diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index fd60d4d4..08d265c0 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -96,12 +96,13 @@ public Instance(String title, String description, String tags, int pointPerPerso //== 비지니스 로직 ==// public void updateInstance(String description, String notice, int pointPerPerson, LocalDateTime startedDate, - LocalDateTime completedDate) { + LocalDateTime completedDate, String certificationMethod) { this.description = description; this.notice = notice; this.pointPerPerson = pointPerPerson; this.startedDate = startedDate; this.completedDate = completedDate; + this.certificationMethod = certificationMethod; } public void updateParticipantCount(int amount) { diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java index e3d63d07..84a7d3aa 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java @@ -12,6 +12,7 @@ public record InstanceCreateRequest( String notice, int pointPerPerson, LocalDateTime startedAt, - LocalDateTime completedAt + LocalDateTime completedAt, + String certificationMethod ) { } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java index 83ebf5bf..4ba3e5ce 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java @@ -11,6 +11,7 @@ public record InstanceDetailResponse(Long topicId, Long instanceId, String title, String description, int pointPerPerson, String tags, String notice, LocalDateTime startedAt, LocalDateTime completedAt, + String certificationMethod, FileResponse fileResponse) { public static InstanceDetailResponse createByEntity(Instance instance, Optional files) { return InstanceDetailResponse.builder() @@ -23,6 +24,7 @@ public static InstanceDetailResponse createByEntity(Instance instance, Optional< .notice(instance.getNotice()) .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()) + .certificationMethod(instance.getCertificationMethod()) .fileResponse(convertToFileResponse(files)) .build(); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java index bd0ba761..9534007d 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateRequest.java @@ -10,6 +10,7 @@ public record InstanceUpdateRequest( String notice, int pointPerPerson, LocalDateTime startedAt, - LocalDateTime completedAt + LocalDateTime completedAt, + String certificationMethod ) { } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index e8f1dfb5..c75c5b04 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -50,6 +50,7 @@ public Long createInstance(InstanceCreateRequest instanceCreateRequest, .notice(instanceCreateRequest.notice()) .startedDate(instanceCreateRequest.startedAt()) .completedDate(instanceCreateRequest.completedAt()) + .certificationMethod(instanceCreateRequest.certificationMethod()) .progress(Progress.PREACTIVITY) .build(); @@ -112,7 +113,8 @@ public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.notice(), instanceUpdateRequest.pointPerPerson(), - instanceUpdateRequest.startedAt(), instanceUpdateRequest.completedAt()); + instanceUpdateRequest.startedAt(), instanceUpdateRequest.completedAt(), + instanceUpdateRequest.certificationMethod()); Instance savedInstance = instanceRepository.save(existingInstance); diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 00ecd4c0..c796ea0f 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -69,6 +69,8 @@ public class User extends BaseTimeEntity { @Column(length = 100) private String information; + private Long point; + @Builder public User(ProviderInfo providerInfo, String identifier, Role role, String nickname, String information, String tags) { From 995c4f56f8a547e99ab3f5526b965cdca310c3d0 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Sat, 2 Mar 2024 17:45:41 +0900 Subject: [PATCH 114/234] =?UTF-8?q?[FEAT]=20=EA=B2=B0=EC=A0=9C=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=EA=B0=9C=EB=B0=9C=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: iamport dependency 추가 * test: 사용자 access_token 정보 취득 테스트 * test: 결제 검증을 위한 테스트 환경 구축 * feat: payment domain 개발 * feat: 포트원 -> 토스페이먼츠로 변경 - DB 테이블 설계 - 도메인 개발 - 결제 생성 api 호출 및 success callback 확인 * feat: 결제 관련 ErrorCode 추가 * chore: json dependency 추가 * feat: payment controller 수정 * feat: payment dto 개발 * feat: payment domain 및 dto 수정 * feat: requestPaymentAccept Casting 문제 해결 * refactor: 코드 리펙토링 * refactor: 코드 리펙토링 * feat: user, payment 일대다 관계 설정 및 서비스 코드 추가 * feat: User entity 추가 - paymentService 리펙토링 - user entity point 추가 - test code 정리 * refactor: 코드 리팩토링 --------- Co-authored-by: HEY <50323157+SSung023@users.noreply.github.com> --- build.gradle | 9 + .../com/genius/gitget/GitgetApplication.java | 3 +- .../controller/InstanceHomeController.java | 2 +- .../dto/search/InstanceSearchResponse.java | 11 +- .../repository/SearchRepositoryImpl.java | 12 +- .../gitget/challenge/user/domain/User.java | 9 + .../global/util/domain/BaseTimeEntity.java | 1 + .../global/util/exception/ErrorCode.java | 5 + .../payment/config/TossPaymentConfig.java | 23 + .../payment/controller/PaymentController.java | 52 + .../genius/gitget/payment/domain/Payment.java | 79 + .../payment/dto/PaymentFailRequest.java | 9 + .../gitget/payment/dto/PaymentRequest.java | 20 + .../gitget/payment/dto/PaymentResponse.java | 22 + .../payment/dto/PaymentSuccessRequest.java | 10 + .../payment/dto/PaymentSuccessResponse.java | 29 + .../payment/repository/PaymentRepository.java | 9 + .../payment/service/PaymentService.java | 194 +++ .../topic/controller/TopicControllerTest.java | 10 - .../admin/topic/service/TopicServiceTest.java | 20 +- .../InstanceSearchRepositoryTest.java | 107 +- .../gitget/payment/PaymentRepositoryTest.java | 19 + tatus | 1426 +++++++++++++++++ 23 files changed, 1985 insertions(+), 96 deletions(-) create mode 100644 src/main/java/com/genius/gitget/payment/config/TossPaymentConfig.java create mode 100644 src/main/java/com/genius/gitget/payment/controller/PaymentController.java create mode 100644 src/main/java/com/genius/gitget/payment/domain/Payment.java create mode 100644 src/main/java/com/genius/gitget/payment/dto/PaymentFailRequest.java create mode 100644 src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java create mode 100644 src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java create mode 100644 src/main/java/com/genius/gitget/payment/dto/PaymentSuccessRequest.java create mode 100644 src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java create mode 100644 src/main/java/com/genius/gitget/payment/repository/PaymentRepository.java create mode 100644 src/main/java/com/genius/gitget/payment/service/PaymentService.java create mode 100644 src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java create mode 100644 tatus diff --git a/build.gradle b/build.gradle index 5f2adee4..841e5b1d 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + // json + implementation 'com.googlecode.json-simple:json-simple:1.1.1' + // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' @@ -51,6 +54,7 @@ dependencies { // H2 runtimeOnly 'com.h2database:h2:1.4.200'; + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' @@ -63,4 +67,9 @@ dependencies { tasks.named('test') { useJUnitPlatform() +} + +repositories { + mavenCentral() + maven { url 'https://jitpack.io' } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/GitgetApplication.java b/src/main/java/com/genius/gitget/GitgetApplication.java index 41dcb083..0cfe27cd 100644 --- a/src/main/java/com/genius/gitget/GitgetApplication.java +++ b/src/main/java/com/genius/gitget/GitgetApplication.java @@ -2,11 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; -@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) +@SpringBootApplication // (exclude = {SecurityAutoConfiguration.class}) @EnableJpaAuditing @EnableMongoRepositories public class GitgetApplication { diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java index b9cc1ba2..9d34fb92 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java @@ -82,4 +82,4 @@ public ResponseEntity> getLatestInstances( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) ); } -} +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index bdaadff9..a163dff7 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -1,18 +1,14 @@ package com.genius.gitget.challenge.instance.dto.search; -import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FileUtil; import com.querydsl.core.annotations.QueryProjection; +import java.io.IOException; +import java.util.Optional; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; - -import java.io.IOException; -import java.util.Optional; - @NoArgsConstructor @Data public class InstanceSearchResponse { @@ -25,7 +21,8 @@ public class InstanceSearchResponse { @Builder @QueryProjection - public InstanceSearchResponse(Long topicId, Long instanceId, String keyword, int pointPerPerson, int participantCount, Files files) throws IOException { + public InstanceSearchResponse(Long topicId, Long instanceId, String keyword, int pointPerPerson, + int participantCount, Files files) throws IOException { this.topicId = topicId; this.instanceId = instanceId; this.keyword = keyword; diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java index 7eaa16f3..0fcd8671 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java @@ -1,5 +1,8 @@ package com.genius.gitget.challenge.instance.repository; +import static com.genius.gitget.challenge.instance.domain.QInstance.instance; +import static com.genius.gitget.global.file.domain.QFiles.files; + import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.dto.search.QInstanceSearchResponse; @@ -7,15 +10,11 @@ import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; +import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.support.PageableExecutionUtils; -import static com.genius.gitget.challenge.instance.domain.QInstance.instance; -import static com.genius.gitget.global.file.domain.QFiles.files; - -import java.util.List; - public class SearchRepositoryImpl implements SearchRepositoryCustom { private final JPAQueryFactory queryFactory; @@ -37,7 +36,8 @@ public Page search(Progress progressCond, String titleCo List content = queryFactory .select(new QInstanceSearchResponse( - instance.topic.id, instance.id, instance.title, instance.pointPerPerson, instance.participantCount, + instance.topic.id, instance.id, instance.title, instance.pointPerPerson, + instance.participantCount, instance.files)) .from(instance) .leftJoin(instance.files, files) diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index c796ea0f..a9703efc 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -6,6 +6,7 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.domain.BaseTimeEntity; +import com.genius.gitget.payment.domain.Payment; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -47,6 +48,9 @@ public class User extends BaseTimeEntity { @OneToMany(mappedBy = "user") private List participantInfoList = new ArrayList<>(); + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List payment = new ArrayList<>(); + @OneToMany(mappedBy = "user") private List userItemList = new ArrayList<>(); @@ -96,4 +100,9 @@ public void updateRole(Role role) { public void setFiles(Files files) { this.files = files; } + + public void setPoint(Long point) { + this.point += point; + } + } diff --git a/src/main/java/com/genius/gitget/global/util/domain/BaseTimeEntity.java b/src/main/java/com/genius/gitget/global/util/domain/BaseTimeEntity.java index dbca256d..ad870e55 100644 --- a/src/main/java/com/genius/gitget/global/util/domain/BaseTimeEntity.java +++ b/src/main/java/com/genius/gitget/global/util/domain/BaseTimeEntity.java @@ -27,4 +27,5 @@ public class BaseTimeEntity { @Column(name = "deleted_at") private LocalDateTime deletedDate; + } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 71b95364..668a05f7 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -8,6 +8,11 @@ @RequiredArgsConstructor public enum ErrorCode { + FAILED_POINT_PAYMENT(HttpStatus.BAD_REQUEST, "최소 충전 금액은 100원 이상입니다."), + INVALID_PAYMENT_AMOUNT(HttpStatus.BAD_REQUEST, "최초 결제 요청 금액과 일치하지 않습니다."), + FAILED_FINAL_PAYMENT(HttpStatus.BAD_REQUEST, "최종 결제가 승인되지 않았습니다"), + INVALID_ORDERID(HttpStatus.NOT_FOUND, "해당 주문번호가 존재하지 않습니다."), + TOPIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 토픽을 찾을 수 없습니다."), TOPIC_HAVE_INSTANCE(HttpStatus.BAD_REQUEST, "해당 토픽은 인스턴스를 가지고 있으므로 삭제할 수 없습니다."), diff --git a/src/main/java/com/genius/gitget/payment/config/TossPaymentConfig.java b/src/main/java/com/genius/gitget/payment/config/TossPaymentConfig.java new file mode 100644 index 00000000..821c721d --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/config/TossPaymentConfig.java @@ -0,0 +1,23 @@ +package com.genius.gitget.payment.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Getter +public class TossPaymentConfig { + @Value("{payment.toss.test_client_api_key}") + private String testClientApiKey; + + @Value("{payment.toss.test_secrete_api_key}") + private String testSecretKey; + + @Value("{payment.toss.success_url}") + private String successUrl; + + @Value("{payment.toss.fail_url}") + private String failUrl; + + public static final String URL = "https://api.tosspayments.com/v1/payments/confirm"; +} diff --git a/src/main/java/com/genius/gitget/payment/controller/PaymentController.java b/src/main/java/com/genius/gitget/payment/controller/PaymentController.java new file mode 100644 index 00000000..1aae74e1 --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/controller/PaymentController.java @@ -0,0 +1,52 @@ +package com.genius.gitget.payment.controller; + +import com.genius.gitget.global.util.exception.SuccessCode; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import com.genius.gitget.payment.dto.PaymentFailRequest; +import com.genius.gitget.payment.dto.PaymentRequest; +import com.genius.gitget.payment.dto.PaymentResponse; +import com.genius.gitget.payment.dto.PaymentSuccessRequest; +import com.genius.gitget.payment.dto.PaymentSuccessResponse; +import com.genius.gitget.payment.service.PaymentService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/payment") +@Slf4j +public class PaymentController { + + private final PaymentService paymentService; + + @PostMapping("/toss") + public ResponseEntity> requestTossPayment( + @RequestBody PaymentRequest paymentRequest) { + PaymentResponse paymentResponse = paymentService.requestTossPayment(paymentRequest); + return ResponseEntity.ok().body( + new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), paymentResponse) + ); + } + + @PostMapping("/toss/success") + public ResponseEntity> tossPaymentSuccess( + @RequestBody PaymentSuccessRequest paymentSuccessRequest) throws Exception { + PaymentSuccessResponse successResponse = paymentService.tossPaymentSuccess(paymentSuccessRequest); + return ResponseEntity.ok().body( + new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), successResponse) + ); + } + + @PostMapping("/toss/fail") + public ResponseEntity tossPaymentFail(@RequestBody PaymentFailRequest paymentFailRequest) { + paymentService.tossPaymentFail(paymentFailRequest); + return ResponseEntity.ok().body(new CommonResponse( + SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); + } +} diff --git a/src/main/java/com/genius/gitget/payment/domain/Payment.java b/src/main/java/com/genius/gitget/payment/domain/Payment.java new file mode 100644 index 00000000..2247e011 --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/domain/Payment.java @@ -0,0 +1,79 @@ +package com.genius.gitget.payment.domain; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.domain.BaseTimeEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicInsert; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DynamicInsert +@Table(name = "payment") +public class Payment extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "payment_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + private String orderId; + + private String paymentKey; + + private Long amount; + + private Long pointAmount; + + private String orderName; + + private boolean isSuccess; + + private String failReason; + + @Column(name = "success_at", updatable = false) + private LocalDateTime successDate; + + @Builder + public Payment(String orderId, String paymentKey, Long amount, Long pointAmount, String orderName, + boolean isSuccess, String failReason, User user) { + this.orderId = orderId; + this.paymentKey = paymentKey; + this.amount = amount; + this.pointAmount = pointAmount; + this.orderName = orderName; + this.isSuccess = isSuccess; + this.failReason = failReason; + this.user = user; + } + + public void setPaymentSuccessStatus(String paymentKey, boolean isSuccess) { + this.paymentKey = paymentKey; + this.isSuccess = isSuccess; + } + + public void setPaymentFailStatus(String message, boolean isSuccess) { + this.failReason = message; + this.isSuccess = isSuccess; + } + + public void setUser(User user) { + this.user = user; + } +} diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentFailRequest.java b/src/main/java/com/genius/gitget/payment/dto/PaymentFailRequest.java new file mode 100644 index 00000000..6c80f787 --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/dto/PaymentFailRequest.java @@ -0,0 +1,9 @@ +package com.genius.gitget.payment.dto; + +import lombok.Data; + +@Data +public class PaymentFailRequest { + private String message; + private String orderId; +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java b/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java new file mode 100644 index 00000000..a7a468dc --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java @@ -0,0 +1,20 @@ +package com.genius.gitget.payment.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +public class PaymentRequest { + private Long amount; + private String orderName; + private Long pointAmount; + private String userEmail; + + @Builder + public PaymentRequest(Long amount, String orderName, Long pointAmount, String userEmail) { + this.amount = amount; + this.orderName = orderName; + this.pointAmount = pointAmount; + this.userEmail = userEmail; + } +} diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java b/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java new file mode 100644 index 00000000..c4f9d711 --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java @@ -0,0 +1,22 @@ +package com.genius.gitget.payment.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +public class PaymentResponse { + private Long amount; + private Long pointAmount; + private String orderName; + private String orderId; + private String userEmail; + + @Builder + public PaymentResponse(Long amount, Long pointAmount, String orderName, String orderId, String userEmail) { + this.amount = amount; + this.pointAmount = pointAmount; + this.orderName = orderName; + this.orderId = orderId; + this.userEmail = userEmail; + } +} diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessRequest.java b/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessRequest.java new file mode 100644 index 00000000..7723de21 --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessRequest.java @@ -0,0 +1,10 @@ +package com.genius.gitget.payment.dto; + +import lombok.Data; + +@Data +public class PaymentSuccessRequest { + private String orderId; + private String paymentKey; + private String amount; +} diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java b/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java new file mode 100644 index 00000000..de888ae0 --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java @@ -0,0 +1,29 @@ +package com.genius.gitget.payment.dto; + + +import lombok.Builder; +import lombok.Data; + +@Data +public class PaymentSuccessResponse { + + private String orderId; + private String paymentKey; + private Long amount; + private Long pointAmount; + private String orderName; + private String isSuccess; + private String failReason; + + @Builder + public PaymentSuccessResponse(String orderId, String paymentKey, Long amount, Long pointAmount, String orderName, + boolean isSuccess, String failReason) { + this.orderId = orderId; + this.paymentKey = paymentKey; + this.amount = amount; + this.pointAmount = pointAmount; + this.orderName = orderName; + this.isSuccess = String.valueOf(isSuccess); + this.failReason = failReason; + } +} diff --git a/src/main/java/com/genius/gitget/payment/repository/PaymentRepository.java b/src/main/java/com/genius/gitget/payment/repository/PaymentRepository.java new file mode 100644 index 00000000..79498d42 --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/repository/PaymentRepository.java @@ -0,0 +1,9 @@ +package com.genius.gitget.payment.repository; + +import com.genius.gitget.payment.domain.Payment; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PaymentRepository extends JpaRepository { + Optional findByOrderId(String orderId); +} diff --git a/src/main/java/com/genius/gitget/payment/service/PaymentService.java b/src/main/java/com/genius/gitget/payment/service/PaymentService.java new file mode 100644 index 00000000..10e14cb7 --- /dev/null +++ b/src/main/java/com/genius/gitget/payment/service/PaymentService.java @@ -0,0 +1,194 @@ +package com.genius.gitget.payment.service; + +import static java.lang.Long.valueOf; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.payment.config.TossPaymentConfig; +import com.genius.gitget.payment.domain.Payment; +import com.genius.gitget.payment.dto.PaymentFailRequest; +import com.genius.gitget.payment.dto.PaymentRequest; +import com.genius.gitget.payment.dto.PaymentResponse; +import com.genius.gitget.payment.dto.PaymentSuccessRequest; +import com.genius.gitget.payment.dto.PaymentSuccessResponse; +import com.genius.gitget.payment.repository.PaymentRepository; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class PaymentService { + + private final PaymentRepository paymentRepository; + private final UserRepository userRepository; + private final TossPaymentConfig tossPaymentConfig; + + private static Payment getPayment(PaymentRequest paymentRequest, User findByEmailUser) { + return Payment.builder() + .orderId(UUID.randomUUID().toString()) + .amount(paymentRequest.getAmount()) + .orderName(paymentRequest.getOrderName()) + .pointAmount(paymentRequest.getPointAmount()) + .user(findByEmailUser) + .isSuccess(false) + .failReason("") + .build(); + } + + private static PaymentSuccessResponse getPaymentSuccessResponse(Payment payment) { + return PaymentSuccessResponse.builder() + .paymentKey(payment.getPaymentKey()) + .amount(payment.getAmount()) + .orderName(payment.getOrderName()) + .pointAmount(payment.getPointAmount()) + .orderId(payment.getOrderId()) + .isSuccess(payment.isSuccess()) + .build(); + } + + public static PaymentResponse getPaymentResponse(Payment payment) { + return PaymentResponse.builder() + .amount(payment.getAmount()) + .pointAmount(payment.getPointAmount()) + .orderName(payment.getOrderName()) + .orderId(payment.getOrderId()) + .userEmail(payment.getUser().getIdentifier()) + .build(); + } + + @Transactional + public PaymentResponse requestTossPayment(PaymentRequest paymentRequest) { + + Payment paymentRequestToEntity = paymentRequestToEntity(paymentRequest); + + if (paymentRequestToEntity.getAmount() < 100L) { + throw new BusinessException(ErrorCode.FAILED_POINT_PAYMENT); + } + if (!(paymentRequest.getAmount() == 1000L || paymentRequest.getAmount() == 3000L + || paymentRequest.getAmount() == 5000L || paymentRequest.getAmount() == 7000L)) { + throw new BusinessException(ErrorCode.FAILED_POINT_PAYMENT); + } + + Payment savedPayment = paymentRepository.save(paymentRequestToEntity); + + return getPaymentResponse(savedPayment); + } + + private Payment paymentRequestToEntity(PaymentRequest paymentRequest) { + User findByEmailUser = userRepository.findByIdentifier(paymentRequest.getUserEmail()) + .orElseThrow(() -> new BusinessException( + ErrorCode.MEMBER_NOT_FOUND)); + + return getPayment(paymentRequest, findByEmailUser); + } + + @Transactional + public PaymentSuccessResponse tossPaymentSuccess(PaymentSuccessRequest paymentSuccessRequest) throws Exception { + Payment payment = verifyPayment(paymentSuccessRequest.getOrderId(), + valueOf(paymentSuccessRequest.getAmount())); + PaymentSuccessResponse result = requestPaymentAccept(paymentSuccessRequest); + payment.setPaymentSuccessStatus(paymentSuccessRequest.getPaymentKey(), true); + + User user = userRepository.findByIdentifier(payment.getUser().getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + user.setPoint(payment.getPointAmount()); + + return result; + } + + @Transactional + public PaymentSuccessResponse requestPaymentAccept(PaymentSuccessRequest paymentSuccessRequest) throws Exception { + String orderId; + String amount; + String paymentKey; + + paymentKey = paymentSuccessRequest.getPaymentKey(); + orderId = paymentSuccessRequest.getOrderId(); + amount = String.valueOf(paymentSuccessRequest.getAmount()); + + HashMap hashMap = new HashMap<>(); + hashMap.put("paymentKey", paymentKey); + hashMap.put("orderId", orderId); + hashMap.put("amount", String.valueOf(amount)); + + JSONObject obj = new JSONObject(hashMap); + + String widgetSecretKey = tossPaymentConfig.getTestSecretKey(); + Base64.Encoder encoder = Base64.getEncoder(); + byte[] encodedBytes = encoder.encode((widgetSecretKey + ":").getBytes(StandardCharsets.UTF_8)); + String authorizations = "Basic " + new String(encodedBytes); + + URL url = new URL(TossPaymentConfig.URL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestProperty("Authorization", authorizations); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + + OutputStream outputStream = connection.getOutputStream(); + outputStream.write(obj.toString().getBytes(StandardCharsets.UTF_8)); + + int code = connection.getResponseCode(); + boolean isSuccess = code == 200; + + InputStream responseStream = isSuccess ? connection.getInputStream() : connection.getErrorStream(); + + Reader reader = new InputStreamReader(responseStream, StandardCharsets.UTF_8); + + JSONParser parser = new JSONParser(); + JSONObject jsonObject = (JSONObject) parser.parse(reader); + responseStream.close(); + + Payment payment = paymentRepository.findByOrderId(orderId) + .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_ORDERID)); + + if (!((jsonObject.get("orderId") != null && jsonObject.get("orderId") == payment.getOrderId()) + && (jsonObject.get("paymentKey") != null && jsonObject.get("paymentKey") == payment.getPaymentKey()))) { + throw new BusinessException(ErrorCode.FAILED_FINAL_PAYMENT); + } + + return getPaymentSuccessResponse(payment); + } + + public Payment verifyPayment(String orderId, Long amount) { + Payment payment = paymentRepository.findByOrderId(orderId).orElseThrow(() -> new BusinessException( + ErrorCode.MEMBER_NOT_FOUND)); + + if (amount < 100L) { + throw new BusinessException(ErrorCode.FAILED_POINT_PAYMENT); + } + + if (payment.getAmount().equals(amount)) { + Long pointAmount = payment.getPointAmount(); + if (pointAmount == (amount / 10L) && (pointAmount * 10L) == amount) { + return payment; + } + } + throw new BusinessException(ErrorCode.INVALID_PAYMENT_AMOUNT); + } + + public void tossPaymentFail(PaymentFailRequest paymentFailRequest) { + Payment payment = paymentRepository.findByOrderId(paymentFailRequest.getOrderId()) + .orElseThrow(() -> new BusinessException( + ErrorCode.FAILED_FINAL_PAYMENT)); + payment.setPaymentFailStatus(paymentFailRequest.getMessage(), false); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java index e4a3b698..ff89781f 100644 --- a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java @@ -1,10 +1,7 @@ package com.genius.gitget.admin.topic.controller; -import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.admin.topic.service.TopicService; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.instance.service.InstanceService; import com.genius.gitget.util.TokenTestUtil; @@ -15,15 +12,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import java.time.LocalDateTime; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @SpringBootTest @Transactional public class TopicControllerTest { diff --git a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java index d173e073..6309f961 100644 --- a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java @@ -92,22 +92,14 @@ public void setup() { fileType); //when - Assertions.assertThatThrownBy(() -> topicService.deleteTopic(savedTopicId)) - .isInstanceOf(BusinessException.class); + topicService.deleteTopic(savedTopicId); //then -// org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, () -> { -// topicService.getTopicById(1L); -// }); -// -// Assertions.assertThatThrownBy(()-> topicService.getTopicById(1L)) -// .isInstanceOf(BusinessException.class); -// -// try { -// topicService.getTopicById(savedTopicId); -// } catch (BusinessException e) { -// org.junit.jupiter.api.Assertions.assertEquals("해당 토픽을 찾을 수 없습니다.", e.getMessage()); -// } + try { + topicService.getTopicById(savedTopicId); + } catch (BusinessException e) { + org.junit.jupiter.api.Assertions.assertEquals("해당 토픽을 찾을 수 없습니다.", e.getMessage()); + } } private TopicCreateRequest getTopicCreateRequest() { diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index 67f480e3..77085116 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -1,12 +1,10 @@ package com.genius.gitget.challenge.instance.repository; +import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; + import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; - -import java.io.IOException; -import java.time.LocalDateTime; - import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.service.InstanceSearchService; @@ -15,8 +13,11 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import java.io.IOException; +import java.time.LocalDateTime; import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -25,8 +26,6 @@ import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; -import static com.genius.gitget.challenge.instance.domain.Progress.*; - @SpringBootTest @Transactional @Rollback @@ -50,8 +49,8 @@ public class InstanceSearchRepositoryTest { JPAQueryFactory queryFactory; - // @BeforeEach - public void setup() throws IOException{ + @BeforeEach + public void setup() throws IOException { queryFactory = new JPAQueryFactory(em); @@ -62,101 +61,97 @@ public void setup() throws IOException{ .pointPerPerson(100) .build(); - Instance instance = Instance.builder() + Instance instanceA = Instance.builder() .title("1일 1알고리즘") .description("하루에 한 문제씩 문제를 해결합니다.") .tags("BE, FE, CS") .pointPerPerson(100) .notice("유의사항") - .progress(PREACTIVITY) .startedDate(LocalDateTime.now()) .completedDate(LocalDateTime.now().plusDays(3)) .build(); + Instance instanceB = Instance.builder() + .title("1일 3알고리즘") + .description("하루에 세 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(500) + .notice("유의사항") + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(7)) + .build(); + + Instance instanceC = Instance.builder() + .title("2일 3알고리즘") + .description("이것은 끝난 챌린지입니다.") + .tags("BE, FE, CS") + .pointPerPerson(500) + .notice("유의사항") + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(7)) + .build(); Topic savedTopic = topicRepository.save(topic); - createInstance(savedTopic, instance, instance.getTitle()); - createInstance(savedTopic, instance, instance.getTitle()); - - instanceService.getAllInstances(PageRequest.of(0, 5)); + createInstance(savedTopic, instanceA, instanceA.getTitle()); + createInstance(savedTopic, instanceA, instanceA.getTitle()); + createInstance(savedTopic, instanceA, instanceA.getTitle()); + createInstance(savedTopic, instanceB, instanceB.getTitle()); + createInstance(savedTopic, instanceB, instanceB.getTitle()); + createInstance(savedTopic, instanceB, "2일 3알고리즘"); + createInstance(savedTopic, instanceC, instanceC.getTitle()); } - @Test - public void 검색_조건_없이_테스트() throws Exception { - for (int i = 0; i<5; i++) { - PageRequest pageRequest = PageRequest.of(i, 2); - Page result = searchRepository.search(null, null, pageRequest); - for (InstanceSearchResponse instanceSearchResponse : result) { - System.out.println("instanceSearchResponse = " + instanceSearchResponse.getInstanceId()); - } - System.out.println("========== " + i+1 + " 번째 끝 ========="); - } - } - @Test public void 챌린지_제목으로_검색_테스트() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(null, "리", pageRequest); + Page result = searchRepository.search(null, "2", pageRequest); int cnt = 0; for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) cnt++; + if (instanceSearchResponse != null) { + cnt++; + } } Assertions.assertThat(cnt).isEqualTo(2); } - @Test - public void 챌린지_현황으로_검색_테스트1() throws Exception { - PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(PREACTIVITY, null, pageRequest); - int cnt = 0; - for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) cnt++; - } - Assertions.assertThat(cnt).isEqualTo(4); - } @Test - public void 챌린지_현황으로_검색_테스트2() throws Exception { + public void 챌린지_현황으로_검색_테스트() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(DONE, null, pageRequest); + Page result = searchRepository.search(PREACTIVITY, null, pageRequest); int cnt = 0; for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) cnt++; + if (instanceSearchResponse != null) { + cnt++; + } } - Assertions.assertThat(cnt).isEqualTo(1); + Assertions.assertThat(cnt).isEqualTo(7); } - @Test - public void 챌린지_현황으로_검색_테스트3() throws Exception { - PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(ACTIVITY, null, pageRequest); - int cnt = 0; - for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) cnt++; - } - Assertions.assertThat(cnt).isEqualTo(0); - } @Test public void 챌린지_현황과_챌린지_제목으로_검색_테스트() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(PREACTIVITY, "1", pageRequest); + Page result = searchRepository.search(PREACTIVITY, "3", pageRequest); int cnt = 0; for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) cnt++; + if (instanceSearchResponse != null) { + cnt++; + } } - Assertions.assertThat(cnt).isEqualTo(3); + Assertions.assertThat(cnt).isEqualTo(4); } - private void createInstance(Topic savedTopic, Instance instance, String title) throws IOException { + private void createInstance(Topic savedTopic, Instance instance, String title) { instanceService.createInstance( InstanceCreateRequest.builder() .topicId(savedTopic.getId()) .title(title) .tags(instance.getTags()) + .description(instance.getDescription()) .notice(instance.getNotice()) .pointPerPerson(instance.getPointPerPerson()) diff --git a/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java b/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java new file mode 100644 index 00000000..37f3687c --- /dev/null +++ b/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java @@ -0,0 +1,19 @@ +package com.genius.gitget.payment; + +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.payment.repository.PaymentRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Rollback +public class PaymentRepositoryTest { + @Autowired + UserRepository userRepository; + @Autowired + PaymentRepository paymentRepository; + +} diff --git a/tatus b/tatus new file mode 100644 index 00000000..dbf3b16e --- /dev/null +++ b/tatus @@ -0,0 +1,1426 @@ +commit 0bf88386d2c54e1514492811010360948868e0fd (HEAD -> feat/52-payment) +Merge: f7e3001 50a4997 +Author: kimdozzi +Date: Thu Feb 15 01:30:27 2024 +0900 + + chore: from main to feat/52-payment merge 완료 + +commit 50a499732c9cf560c7113d1d0e40d41eefe97dcb (origin/main, origin/HEAD, main) +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Feb 15 01:16:33 2024 +0900 + + chore: package 구조 변경 + + - home 패키지 내의 controller, service, dto를 instance 패키지로 이동 + +commit c964d56d0e586ee958ae0492a58b954c6ea9eff3 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Feb 15 01:13:50 2024 +0900 + + chore: Querydsl 관련 build.gradle 수정 + +commit 8b15712bfe48dca94d64d8020e1adf5f6d7bef63 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Feb 15 00:33:39 2024 +0900 + + chore: gitignore 추가 설정 + +commit 30449befceef678627c20cce1dfd96b30c829865 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Wed Feb 14 14:29:31 2024 +0900 + + hotfix: instance 생성 시, 경로가 없어 예외를 던지는 버그 픽스 + +commit 40159008d3b81bfa8f9d855fd2caac87dcd79995 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Wed Feb 14 10:04:10 2024 +0900 + + [BUG] Topic 삭제 요청 시, 삭제가 되지 않는 버그 (#67) + + * fix: topic 삭제 시 예외가 발생하던 버그 픽스 + + - topic 삭제 가능 조건에서 삭제 요청을 했을 때, 삭제가 되지 않고 예외가 발생하던 버그 픽스 + - 영속성 전이(cascade) 추가로 인해 발생하던 문제임을 인지 후 수정 + + * refactor: File 로직 예외 추가 처리 + + - File 시스템과 관련된 로직에서 따로 처리하지 않았던 부분에 대해 try-catch문을 통해 명시적으로 처리 + + * feat: instance 생성 로직 보강 + + - Topic에 등록된 이미지를 사용하기 위해, instance 생성 시 이미지를 별도로 전달하지 않은 경우에 대해 처리 로직 추가 + - FileUtil에 복사 관련 로직 추가 구현 + +commit 89a82600dcb6ac0c764607dc22482dc5db985e5c +Author: DoHyung Kim +Date: Tue Feb 13 22:44:16 2024 +0900 + + [FEAT] 동적쿼리를 적용한 검색 기능 개발 (#65) + + * chore: testBaseUtil 개발 중 패키지 구조 변경으로 인한 커밋 + + * feat: Home 검색 기능 개발 + + * refactor: search dto 네임 변경 + + * test: 단위 테스트 수행 + + - controller test 중 mongodb 이슈 발생 -> 해결요망 + - 검색 기능 postman test 완료 + + * test: 검색 기능 단위 테스트 + + * feat: 검색 조건에 따른 키워드 검색 기능 개발 + + - stringToEnum converter 재정의 + - 스프링 빈으로 등록하여 생성한 컨버터 사용 적용 + - 검색 조건에 따른 각 계층별 코드 작성 + + * refactor: 불필요한 코드 제거 + + * refactor: 코드 리펙토링 + + * test: topicController mongodb issue test + + * chore: Querydsl dependency 추가 + + * test: querydsl test + + * test: querydsl 적용 테스트 + + * feat: querydsl dto 생성 + + - querydsl로 작성한 프로젝션을 받아오기 위한 dto + + * test: querydsl 별도의 dto를 사용한 instance와 files join table 테스트 + + * test: 프로젝션 결과반환 - 생성자 방식 사용 + + * test: 동적쿼리 - booleanBuilder 테스트 + + * test: 동적쿼리 - 다중 where + + - composition 가능한 장점이 있음 + + * test: 수정, 삭제 배치쿼리 + + - bulkUpdate + - bulkAdd + - bulkDelete + + * feat: 동적 쿼리 적용 및 querydsl 페이징 연동 + + * feat: 검색 조건과 챌린지 진행 현황에 따른 검색 기능 개발 + + - querydsl 적용 + - 사용자 정의 리포지토리 적용 + - BooleamBuilder 적용으로 성능 최적화 + - instanceSearchService 코드 수정 및 개선 + - progress entity ALL 제거 + - 검색 기능 repository 테스트 코드 작성 + + * feat: 동적쿼리를 위한 instanceSearchService 로직 개발 + + - instanceSearchService 코드 개선 + - ErrorCode 추가 + - postmand api 테스트 완료 + + * refactor: 코드 리펙토링 + +commit f7e3001fae9604e4c5f3a5343d14472ff8b601a6 +Author: kimdozzi +Date: Mon Feb 12 19:12:06 2024 +0900 + + test: 결제 검증을 위한 테스트 환경 구축 + +commit e5418de60708f437ff1e01db7685f03aaed43261 +Author: kimdozzi +Date: Mon Feb 12 15:27:01 2024 +0900 + + test: 사용자 access_token 정보 취득 테스트 + +commit 6cd7dcf0329fa5f516fd5cf7892fbf4a104d665e +Author: kimdozzi +Date: Mon Feb 12 15:26:37 2024 +0900 + + chore: iamport dependency 추가 + +commit 6d4f561fdd7ffba2e10bac1e1d133eee40d583ce +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Feb 9 23:27:07 2024 +0900 + + hotfix: 홈화면 API에서 인스턴스 식별자를 반환하지 않는 버그 픽스 + +commit e0791f510aaa7b0b2690259447be981a7e35e030 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Wed Feb 7 01:50:51 2024 +0900 + + feat: 엔티티 영속성 전이 설정 (#64) + +commit 15c36249e54e62c35bc19715f6acf3c9d03a397e +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sat Feb 3 15:23:09 2024 +0900 + + test: 로직 변경으로 인해 돌아가지 않던 테스트 코드 수정 + + - FileTestUtil 클래스 작성: MultiFile을 반환하는 정적 메서드 추가 + - Topic, Instance 로직 변경으로 인해 돌아가지 않던 테스트 코드 추가 + - DTO에 빌더패턴 적용 + +commit 2fe76bd70d15585f99b4d43572df6c41107f543b +Author: DoHyung Kim +Date: Sat Feb 3 15:22:41 2024 +0900 + + feat: 테스트 중 이슈 발견 및 해결 (#62) + + - 토픽이 인스턴스를 가질 때 해당 토픽을 삭제하려 할 때의 예외처리 + - ErrorCode 추가 + +commit 63e2e807f9bf0cf0c4b4c03738ccae7e5c8ceebd +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Sat Feb 3 10:27:23 2024 +0900 + + [HOTFIX] JWT Filter에서 예외 발생 시 처리하지 못하는 로직 수정 (#60) + + * feat: OAuth 로그인 성공 시 리다이렉트 주소 변경 + + - OAuth 로그인 성공 시, 토큰 발급 URL로 리다이렉트하도록 변경 + + * feat: JwtAuthenticationFilter 예외를 처리할 필터 추가 구현 + +commit fa331c20c7f43a9d29a1ecc992bb4ebeac838bc2 +Author: DoHyung Kim +Date: Fri Feb 2 23:28:34 2024 +0900 + + [FEAT] 어드민 페이지 파일 api 적용 (#61) + + * chore: enum converter global 패키지로 이동 + + * feat: 어드민 페이지 topic - file 적용 + + * feat: 어드민 페이지 instance - file 적용 + + * feat: admin file api 적용 완료 + +commit 4b96a350677d1dfdbf8ef6803bb75016e5050241 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Fri Feb 2 14:54:10 2024 +0900 + + [REFACTOR] FileType Enum 예외처리 로직 보강 (#59) + + * refactor: 프로덕션 코드 변경으로 인한 테스트 코드 수정 + + * refactor: FileType 정적 팩토리 메서드 예외 추가 처리 + + - 소문자 외에 대문자로 왔을 때에도 처리가 가능하도록 수정 + - 메서드명 오타 수정 + + * fix: Files 삭제 시, DB의 값을 삭제되지 않는 버그 픽스 + + * chore: 프로덕션 패키지 구조에 맞게 테스트 코드 패키지 구조 변경 + +commit 872120be8d6d0bd96ba1d21251426cc5123dc6f6 (origin/refactor/42-constructor) +Author: DoHyung Kim +Date: Thu Feb 1 23:13:49 2024 +0900 + + hotfix: Instance entity 이름 변경 (#56) + +commit df2feaacf6c61dfd6129a9388b9efdee76da64d5 +Merge: 2cf298f 5d0aef2 +Author: kimdozzi +Date: Thu Feb 1 23:04:39 2024 +0900 + + Merge branch 'main' of https://github.com/TeamTheGenius/TeamTheGenius_Server + +commit 2cf298f23f6f5b53c9caa29f1be938646d957a79 +Author: kimdozzi +Date: Thu Feb 1 23:04:12 2024 +0900 + + hotfix: 충돌 코드 삭제 + +commit 5d0aef2b68a3ae2399eb505da6c1dfdc48b8c90b +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Feb 1 14:50:31 2024 +0900 + + !HOTFIX: logout API endpoint 변경 + + - /api/logout 에서 /api/auth/logout으로 변경 + +commit 7ac23d50981c81aa4f79827002b13f73f1e1a51c +Author: kimdozzi +Date: Wed Jan 31 19:11:55 2024 +0900 + + test: 불필요한 test class 제거 + +commit e93afa4a88f4147b727879359c47f32cdadfe570 +Author: kimdozzi +Date: Wed Jan 31 19:08:36 2024 +0900 + + refactor: instanceService 누락된 코드 추가 + +commit 4e84a310c1b34830d035cdb3b971ab5f60e69836 +Merge: ad32382 a957fb5 +Author: kimdozzi +Date: Wed Jan 31 19:07:23 2024 +0900 + + Merge branch 'main' of https://github.com/TeamTheGenius/TeamTheGenius_Server + +commit ad3238240a9898c28f6c444f54e95f2633d6a134 +Author: kimdozzi +Date: Wed Jan 31 19:07:15 2024 +0900 + + refactor: 불필요한 주석 제거 + +commit a957fb5c92de8d0db9d1f62946beff33bb30398a +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Wed Jan 31 18:39:16 2024 +0900 + + !HOTFIX: 컴파일 에러 해결 + +commit 64b18a0b23796ab4448844f03f4e9877074002b4 (origin/feat/52-payment) +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 30 23:01:18 2024 +0900 + + [FEAT] 홈 화면 - 추천/인기/신규 기능 개발 (#50) + + * Squashed commit of the following: + + commit 77f54caa2f77b9ce040738b2a2f642b03a84d1d8 + Author: SSung023 <50323157+SSung023@users.noreply.github.com> + Date: Fri Jan 26 16:33:27 2024 +0900 + + chore: 패키지 구조 변경 + + - admin 패키지: 관리자 관련 기능과 밀접한 기능들 + - challenge 패키지: 사용자 관련 기능과 밀접한 기능들 + - global 패키지: file, security, util 같은 서비스 전체에 영향을 줄 수 있는 기능들 + + commit 8929e06e2cbb61717b00f76ae08af526d09a2fee + Author: HEY <50323157+SSung023@users.noreply.github.com> + Date: Fri Jan 26 16:21:42 2024 +0900 + + [TEST] JWT, 회원가입 로직 테스트 코드 추가 (#47) + + * Squashed commit of the following: + + commit 025fd0c5faf4a2d6ab159f4b0a95c8c7cde751a3 + Author: SSung023 <50323157+SSung023@users.noreply.github.com> + Date: Thu Jan 25 23:15:25 2024 +0900 + + !HOTFIX: conflict resolve 해결 + + commit d2f1db695856489b13de47accb700c9432051ff3 + Author: HEY <50323157+SSung023@users.noreply.github.com> + Date: Thu Jan 25 23:14:14 2024 +0900 + + [FIX] JWT 재발급 관련 버그 픽스 (#45) + + * fix: access-token 재발급 안되는 버그 수정 + + * fix: 예외 처리 로직 추가 및 무한 리다이렉션 버그 픽스 + + - Cookie로부터 토큰을 얻을 때, cookie가 비어있을 때 예외 처리 로직 추가 + - JWT 토큰 요청 시, 사용자의 권한이 NOT_REGISTERED(가입 이전)이라면 JWT 토큰 발급 거부 로직 추가 + - refresh-token이 비어있는 경우 예외 처리 + + * chore: test 코드 정리 + + commit 42f6e36ae95189025342ee0730485d7c6e37cd20 + Author: SSung023 <50323157+SSung023@users.noreply.github.com> + Date: Thu Jan 25 21:43:28 2024 +0900 + + !HOTFIX: 회원가입 기능 핫픽스 + + - 회원가입 완료 후 반환하는 Response 객체의 구조 변경 + + commit f9a0e3320212a76c1fc33f291c4146b93d81963a + Author: HEY <50323157+SSung023@users.noreply.github.com> + Date: Thu Jan 25 18:09:04 2024 +0900 + + Update issue templates + + * test: JwtService에 대한 테스트 코드 추가 + + * test: JWT 테스트 코드 추가 + + * test: 회원가입 관련 테스트 코드 추가 + + * feat: InstanceRepository에 추천 인스턴스들을 받는 메서드 추가 + + - Instance 엔티티의 '참여자 수' 필드 추가 및 업데이트 메서드 추가 + - InstanceRepository에 조건에 맞는 추천 인스턴스들을 받아오는 메서드 추가 + - 추천 인스턴스를 받아오는 Repository 테스트 코드 추가 + + * chore: User의 관심사 필드의 이름 변경 + + User의 관심사 필드의 이름을 interest에서 tags로 변경하여, 다른 엔티티들과 통일 + + * refactor: 추천 인스턴스의 대상을 진행 중인 인스턴스로 변경 + + * feat: 추천 인스턴스를 반환하는 서비스 로직 구현 및 테스트 코드 추가 + + - 추천 인스턴스 페이징용 DTO인 RecommendPagingResponse 작성 + - 추천 인스턴스 결과를 페이징 형식으로 반환 + + * feat: 홈 화면의 추천 챌린지 추천 컨트롤러 개발 + + - 컨트롤러 개발 및 테스트 코드 작성 + + * refactor: 추천 챌린지의 대상을 '시작 전'으로 변경 + + * chore: DTO 이름 및 테스트 코드 수정 + + * feat: 신규 & 인기 인스턴스 레포지토리 구현 및 테스트 코드 추가 + + * refactor: 파일 시스템 예외 상황 처리 + + - 인스턴스의 경우 파일이 존재하지 않을 수 있으므로, Optional을 반환하도록 변경 + - FileResponse에서 파일이 존재하지 않는 경우에 호출할 팩토리 메서드 추가 + + * feat: 홈 화면의 인기/신규 기능 개발 + + * refactor: 파일 시스템 구조 변경 + + - FilesController: 파일 업로드 시, DTO와 이미지를 같이 받을 수 있는 예시로 변경 + - FileUtil: 메서드들을 모두 static으로 변경 + - FilesService: UPLOAD_PATH 관리 위치 변경 + - FileResponse: 팩토리 메서드를 통해 생성할 수 있도록 추가 + + * refactor: 불필요한 메서드에 대해 리팩토링 진행 + + * feat: 파일(이미지) 갱신 기능 구현 + + - FileId(PK)와 변경하고싶은 파일(MultipartFile)을 받아 기존에 존재했던 파일(이미지)는 삭제하고, 전달받은 파일로 갱신하는 기능 구현 + - 파일 갱신 테스트용 API 구현 + + * feat: 파일(이미지) 삭제 로직 추가 + + - 삭제하고자하는 Files 엔티티의 PK를 전달했을 때, 삭제하는 기능 구현 + - 저장소에 저장되어 있던 파일(이미지) 삭제 + - Files 엔티티 삭제 + +commit 89c31c56149fec830bd6b1bccdb7eb9b3873f684 +Author: DoHyung Kim +Date: Tue Jan 30 22:32:12 2024 +0900 + + [FEAT] 홈 화면 - 챌린지 검색 기능 개발 (#51) + + * chore: testBaseUtil 개발 중 패키지 구조 변경으로 인한 커밋 + + * feat: Home 검색 기능 개발 + + * refactor: search dto 네임 변경 + + * test: 단위 테스트 수행 + + - controller test 중 mongodb 이슈 발생 -> 해결요망 + - 검색 기능 postman test 완료 + + * test: 검색 기능 단위 테스트 + + * feat: 검색 조건에 따른 키워드 검색 기능 개발 + + - stringToEnum converter 재정의 + - 스프링 빈으로 등록하여 생성한 컨버터 사용 적용 + - 검색 조건에 따른 각 계층별 코드 작성 + + * refactor: 불필요한 코드 제거 + + * refactor: 코드 리펙토링 + +commit 27bd71465d8e6c43a4e64dcfb571ffd6a885f8a2 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Sat Jan 27 23:27:38 2024 +0900 + + [REFACTOR] 이미지/파일 요청과 응답 시 base64 인코딩하여 전달하도록 변경 (#49) + + * refactor: 파일과 관련된 응답 객체 전달 시, base64로 인코딩하여 전달 + + - 파일/이미지 저장 요청 & 파일/이미지 정보 요청에 대한 응답 시, base64로 인코딩하여 전달하도록 리팩토링 + + * refactor: base64로 인코딩한 값을 전달하도록 변경 + +commit 77f54caa2f77b9ce040738b2a2f642b03a84d1d8 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Jan 26 16:33:27 2024 +0900 + + chore: 패키지 구조 변경 + + - admin 패키지: 관리자 관련 기능과 밀접한 기능들 + - challenge 패키지: 사용자 관련 기능과 밀접한 기능들 + - global 패키지: file, security, util 같은 서비스 전체에 영향을 줄 수 있는 기능들 + +commit 8929e06e2cbb61717b00f76ae08af526d09a2fee +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Fri Jan 26 16:21:42 2024 +0900 + + [TEST] JWT, 회원가입 로직 테스트 코드 추가 (#47) + + * Squashed commit of the following: + + commit 025fd0c5faf4a2d6ab159f4b0a95c8c7cde751a3 + Author: SSung023 <50323157+SSung023@users.noreply.github.com> + Date: Thu Jan 25 23:15:25 2024 +0900 + + !HOTFIX: conflict resolve 해결 + + commit d2f1db695856489b13de47accb700c9432051ff3 + Author: HEY <50323157+SSung023@users.noreply.github.com> + Date: Thu Jan 25 23:14:14 2024 +0900 + + [FIX] JWT 재발급 관련 버그 픽스 (#45) + + * fix: access-token 재발급 안되는 버그 수정 + + * fix: 예외 처리 로직 추가 및 무한 리다이렉션 버그 픽스 + + - Cookie로부터 토큰을 얻을 때, cookie가 비어있을 때 예외 처리 로직 추가 + - JWT 토큰 요청 시, 사용자의 권한이 NOT_REGISTERED(가입 이전)이라면 JWT 토큰 발급 거부 로직 추가 + - refresh-token이 비어있는 경우 예외 처리 + + * chore: test 코드 정리 + + commit 42f6e36ae95189025342ee0730485d7c6e37cd20 + Author: SSung023 <50323157+SSung023@users.noreply.github.com> + Date: Thu Jan 25 21:43:28 2024 +0900 + + !HOTFIX: 회원가입 기능 핫픽스 + + - 회원가입 완료 후 반환하는 Response 객체의 구조 변경 + + commit f9a0e3320212a76c1fc33f291c4146b93d81963a + Author: HEY <50323157+SSung023@users.noreply.github.com> + Date: Thu Jan 25 18:09:04 2024 +0900 + + Update issue templates + + * test: JwtService에 대한 테스트 코드 추가 + + * test: JWT 테스트 코드 추가 + + * test: 회원가입 관련 테스트 코드 추가 + +commit 025fd0c5faf4a2d6ab159f4b0a95c8c7cde751a3 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Jan 25 23:15:25 2024 +0900 + + !HOTFIX: conflict resolve 해결 + +commit d2f1db695856489b13de47accb700c9432051ff3 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Thu Jan 25 23:14:14 2024 +0900 + + [FIX] JWT 재발급 관련 버그 픽스 (#45) + + * fix: access-token 재발급 안되는 버그 수정 + + * fix: 예외 처리 로직 추가 및 무한 리다이렉션 버그 픽스 + + - Cookie로부터 토큰을 얻을 때, cookie가 비어있을 때 예외 처리 로직 추가 + - JWT 토큰 요청 시, 사용자의 권한이 NOT_REGISTERED(가입 이전)이라면 JWT 토큰 발급 거부 로직 추가 + - refresh-token이 비어있는 경우 예외 처리 + + * chore: test 코드 정리 + +commit 42f6e36ae95189025342ee0730485d7c6e37cd20 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Jan 25 21:43:28 2024 +0900 + + !HOTFIX: 회원가입 기능 핫픽스 + + - 회원가입 완료 후 반환하는 Response 객체의 구조 변경 + +commit f9a0e3320212a76c1fc33f291c4146b93d81963a +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Thu Jan 25 18:09:04 2024 +0900 + + Update issue templates + +commit 8bbc369a49f124b039e6d8c8f483d82abc399053 +Merge: a120e65 caa3f58 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Wed Jan 24 21:05:09 2024 +0900 + + Merge pull request #39 from TeamTheGenius/feat/21-image-util + + [FEAT] 이미지/파일 업로드 유틸 클래스 개발 + +commit caa3f58041fa48216e8fa7fa98a57711a6fc00c0 +Merge: 32ca58a a120e65 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Wed Jan 24 21:04:10 2024 +0900 + + Merge branch 'main' into feat/21-image-util + +commit a120e651f47371ee7d809823a6e8fd3c1ecd01a6 +Author: DoHyung Kim +Date: Wed Jan 24 20:48:02 2024 +0900 + + 24 feat admin topic api (#41) + + * feat: Topic Controller 개발 + + * feat: Topic Domain 비즈니스 로직 개발 + + * feat: Topic Service 개발 + + * test: topic rest api 테스트 코드 작성 + + - jwt 관련 오류로 인해 테스트 불가 -> 해결 방안 모색 중 + + * feat: 전체 Topic 조회 페이징과 정렬 + + * feat: Instance API 개발 + + - DTO 활용 + - BusinessException 활용 + + * refactor: topic controller & service DTO 도입 + + * refactor: topic, instance paging 리팩토링 + + * refactor: 리펙토링 + + - participant_count entity 제거 -> participantInfo list size로 해결 가능 + - 테스트 코드 작성 + - 코드 리펙토링 등 + + * refactor: admin page refactoring + + - 연관관계 편의 메서드 수정 + - DTO 수정 + - API Response 재정의 + - util의 ErrorCode Enum 추가 + - PagingResponse 추가 + + * test: postman api test + + - test 수행 중 발견한 수정사항 해결 + + * test: test 완료 + +commit 32ca58aa9da64d644921bbdd2eb515214ab8cc7c +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Wed Jan 24 01:22:25 2024 +0900 + + feat: Files 엔티티 연관관계 설정 + +commit cf3b20ef842ac2d925e590fc1188639074e6f63f +Merge: a1014bc 061171e +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Wed Jan 24 00:39:39 2024 +0900 + + Merge branch 'main' into feat/21-image-util + +commit 061171e13bf48328f6f5bc94b3a9dc5899cab431 +Author: DoHyung Kim +Date: Wed Jan 24 00:37:56 2024 +0900 + + [FEAT] 어드민 페이지 개발 (#38) + + * [FEAT] Admin 페이지 Instance , Topic API 개발 (#33) + + * feat: Topic Controller 개발 + + * feat: Topic Domain 비즈니스 로직 개발 + + * feat: Topic Service 개발 + + * test: topic rest api 테스트 코드 작성 + + - jwt 관련 오류로 인해 테스트 불가 -> 해결 방안 모색 중 + + * feat: 전체 Topic 조회 페이징과 정렬 + + * feat: Instance API 개발 + + - DTO 활용 + - BusinessException 활용 + + * refactor: topic controller & service DTO 도입 + + * refactor: topic, instance paging 리팩토링 + + * refactor: 리펙토링 + + - participant_count entity 제거 -> participantInfo list size로 해결 가능 + - 테스트 코드 작성 + - 코드 리펙토링 등 + + * refactor: admin page refactoring + + - 연관관계 편의 메서드 수정 + - DTO 수정 + - API Response 재정의 + - util의 ErrorCode Enum 추가 + - PagingResponse 추가 + + * [REFACTOR] 어드민 페이지 리펙토링 (#36) + + * feat: Topic Controller 개발 + + * feat: Topic Domain 비즈니스 로직 개발 + + * feat: Topic Service 개발 + + * test: topic rest api 테스트 코드 작성 + + - jwt 관련 오류로 인해 테스트 불가 -> 해결 방안 모색 중 + + * feat: 전체 Topic 조회 페이징과 정렬 + + * feat: Instance API 개발 + + - DTO 활용 + - BusinessException 활용 + + * refactor: topic controller & service DTO 도입 + + * refactor: topic, instance paging 리팩토링 + + * refactor: 리펙토링 + + - participant_count entity 제거 -> participantInfo list size로 해결 가능 + - 테스트 코드 작성 + - 코드 리펙토링 등 + + * refactor: admin page refactoring + + - 연관관계 편의 메서드 수정 + - DTO 수정 + - API Response 재정의 + - util의 ErrorCode Enum 추가 + - PagingResponse 추가 + + * [FEAT] 어드민 페이지 postman API 테스트 (#37) + + * feat: Topic Controller 개발 + + * feat: Topic Domain 비즈니스 로직 개발 + + * feat: Topic Service 개발 + + * test: topic rest api 테스트 코드 작성 + + - jwt 관련 오류로 인해 테스트 불가 -> 해결 방안 모색 중 + + * feat: 전체 Topic 조회 페이징과 정렬 + + * feat: Instance API 개발 + + - DTO 활용 + - BusinessException 활용 + + * refactor: topic controller & service DTO 도입 + + * refactor: topic, instance paging 리팩토링 + + * refactor: 리펙토링 + + - participant_count entity 제거 -> participantInfo list size로 해결 가능 + - 테스트 코드 작성 + - 코드 리펙토링 등 + + * refactor: admin page refactoring + + - 연관관계 편의 메서드 수정 + - DTO 수정 + - API Response 재정의 + - util의 ErrorCode Enum 추가 + - PagingResponse 추가 + + * test: postman api test + + - test 수행 중 발견한 수정사항 해결 + + --------- + + Co-authored-by: HEY <50323157+SSung023@users.noreply.github.com> + +commit a1014bc447c985f10cf9014cf07623c1c4896d3c +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 23 21:30:13 2024 +0900 + + feat: 파일(이미지) 전송 기능 개발 + +commit 077a466c9fe52d1459f622eeb115d5460e8069d8 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 23 20:58:05 2024 +0900 + + feat: 이미지 업로드 API 추가 + +commit 8a235d40eb5364ad4992108f6d3225e9ce3b4394 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 23 19:35:26 2024 +0900 + + feat: 파일 업로드를 테스트하는 임시 API 추가 + +commit d04d40df208c56d70d7540b06a86060c1b7ae94c +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 23 19:34:52 2024 +0900 + + feat: 저장소, DB에 이미지를 저장하는 로직 개발 + +commit 4ca9006aeaf03af28867c5bb60db0b37e3abb368 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 23 18:33:56 2024 +0900 + + feat: 유효성 검사, 저장할 File 생성하는 FileUtil 클래스 구현 + + - 전달받은 파일의 유효성 검사, 저장할 File을 생성하는 로직을 FileUtil 클래스에 구현 + - FileUtil 관련 서비스 코드 작성 + - Files 클래스에 BaseTimeEntity 상속 + +commit c513a3ee39ac6b4aea10a3783e7fb4c813ad2694 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 23 16:06:12 2024 +0900 + + feat: 이미지/파일 레포지토리 생성 및 테스트 코드 추가 + +commit beccdca33dbbe36b9aa060817b72920f0b26b465 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 23 16:05:27 2024 +0900 + + feat: 이미지/파일을 저장할 엔티티 작성 + + - 이미지/파일을 저장할 엔티티 및 enum 클래스 작성 + +commit 6f17d9f078e024f323320cf65d19ebab1f9d73a9 +Merge: da4a86f 5b1c149 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 22 23:13:53 2024 +0900 + + Merge pull request #35 from TeamTheGenius/feat/34-auth-anotation + + [FEAT] Controller(API) 테스트 코드 작성에 필요한 유틸 개발 + +commit 5b1c149da4e82c4c81e873199a10611d23a13030 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 22 09:57:47 2024 +0900 + + feat: 테스트에 이용할 JWT 토큰 생성 유틸 클래스 구현 + +commit 70fda414ec1e8da9e68e703b28eb5c0d85d8fc5e +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 22 02:46:16 2024 +0900 + + feat: 커스텀 어노테이션 통해 인증 객체를 반환받는 기능 추가 + +commit da4a86f539863d3a99abffb721b36a2a185093eb +Author: kimdozzi +Date: Fri Jan 19 17:55:28 2024 +0900 + + refactor: 엔티티 리펙토링 + +commit da5bf8b7d93d19647c882a13c8ee2e2ec4c9deaa +Author: DoHyung Kim +Date: Fri Jan 19 15:01:39 2024 +0900 + + [REFACTOR] DB entity 리펙토링 (#32) + + * refactor: 소셜 로그인 facebook 관련 파일 제거 + + * feat: DB Entity 개발 + + - User Entity 수정 + - Hits, Topic, Instance, ParticipantInfo Entity 개발 + + * feat: challenge domain 및 repository 개발 + + - entity 수정 및 애노테이션 추가 + - DB Table 별 repository 추가 + - User entity : email -> identifier 로 변경 + + * refactor: 엔티티와 파일 리펙토링 + + - entity refactoring + - 프로젝트 구조 변경 + + * chore: P6Spy query logging 의존성 추가 + + * test: 기능별 도메인 생성 테스트 코드 추가 + + * test: User 회원 추가/조회/수정/삭제 테스트 코드 작성 완료 + + * feat: User와 Instance 다대다 연관관계 편의 메서드 작성 + + - 테스트 코드 작성 완료 + + * feat: User와 Instance 다대다 연관관계 편의 메서드 작성 - 2 + + - 관심목록, 인스턴스에 참여한 유저 정보 - 테스트 완료 + - 하나의 인스턴스가 가지는 Hits 테이블의 컬럼 갯수를 조회함으로서 Instance 테이블의 like_count 컬럼을 제거해도 됨. -> 리펙토링 예정 + + * refactor: 불필요한 entity 제거 + + * feat: 토픽과 인스턴스 연관관계 편의 메서드 작성 + + * test: 기존에 작성한 불필요한 코드 제거 및 테스트 + +commit 204b043da7ea421155219f71d07ce43b4a72a638 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 16 20:36:46 2024 +0900 + + chore: 프로젝트 이름을 GitGet로 변경 + +commit fd5ebec58e04486882552f8e79c37c635c9450af +Merge: c93b35e 8c0ff0f +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 16 20:20:28 2024 +0900 + + Merge pull request #29 from TeamTheGenius/feat/17-jwt + + [FEAT] 회원가입&JWT(로그인, 로그아웃) 기능 개발 + +commit 8c0ff0f3c4416ad7ec6e12d9a23c602a09319b6b +Merge: 0101d4a c93b35e +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 16 20:20:02 2024 +0900 + + Merge branch 'main' into feat/17-jwt + +commit 0101d4a87f3262feae0b0fa2518bd773702d8026 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 16 20:03:41 2024 +0900 + + test: JwtService 관련 테스트 코드 추가 + +commit c93b35e4a4c0156e05de48cb302d8d78072742ae +Author: DoHyung Kim +Date: Tue Jan 16 20:03:32 2024 +0900 + + [FEAT] DB Entity 개발 (#28) + + * refactor: 소셜 로그인 facebook 관련 파일 제거 + + * feat: DB Entity 개발 + + - User Entity 수정 + - Hits, Topic, Instance, ParticipantInfo Entity 개발 + + * feat: challenge domain 및 repository 개발 + + - entity 수정 및 애노테이션 추가 + - DB Table 별 repository 추가 + - User entity : email -> identifier 로 변경 + +commit ef0e5a065a9ad1e6ad782e3b6439d453cc3722c5 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 16 16:09:16 2024 +0900 + + feat: refresh token 탈취 감지 로직 구현 + + 1. 요청받은 Refresh-token과 DB에 저장된 토큰이 불일치 시 토큰 탈취됨을 감지하는 로직 구현 + 2. 토큰 탈취 감지 시, 강제 로그아웃 실행 + 3. 관련 테스트코드 추가 + +commit 061b7d472c85732abdb21e07446e989adbee7363 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 16 10:25:30 2024 +0900 + + Update pull_request_template.md + +commit 69f008dab7d216ee2ce8f9c6eedf4c676da242a2 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 15 14:30:10 2024 +0900 + + refactor: JWT 관련 코드 내 리팩토링 + + 1. 변수명 적절하게 수정 + 2. TokenStatus enum 도입 및 적용 + 3. 메서드 추출 + +commit 48eb1aca2ee19c2aec82bdf56560357bcad07dce +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 15 13:01:41 2024 +0900 + + feat: 회원가입 시 닉네임 중복 확인 API 구현 + +commit cfba09dbb126867bf49471dfcebda89d8d027dd0 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 15 02:31:15 2024 +0900 + + feat: logout API 구현 + +commit a3f87848bee19b15caf3dfe040c453137fcc6b66 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 15 01:46:08 2024 +0900 + + refactor: JwtUtil 클래스 분리를 통한 리팩터링 + +commit 64094f3848cc380f03370e0483d699b1dde88b55 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Jan 14 23:39:51 2024 +0900 + + feat: JWT 검증 필터 로직 및 재발급 로직 구현 + + - RTR 로직 구현 + - 전반적인 리팩토링 필요 + +commit f96fd8b8e7e05149611e5a32b7be9bfbaaada32a +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Jan 14 19:47:24 2024 +0900 + + refactor: JWT에 사용될 enum 클래스 선언 및 적용 + +commit 4ad0ea05a841b901864ef20ab1e03db9c9f20028 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Jan 14 16:13:45 2024 +0900 + + feat: refresh-token 저장하는 로직 구현 + + - MongoDB에 refresh token을 저장하는 로직 구현 + +commit b684f925c84e38bd50cc33bfd59d575badfc9704 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Jan 14 15:19:22 2024 +0900 + + feat: Token 정보를 담을 클래스/리포지토리 설정 + +commit bd259b4fd9e5d439c9e383575e549723bbaa4d42 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Jan 14 15:18:08 2024 +0900 + + feat: MongoDB 의존성 추가 + +commit 921e387c72c436980afe372ef4dbdc924aadf839 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Jan 14 14:37:28 2024 +0900 + + refactor: JWT 요청 시, 전달하는 사용자 정보 변경 + + - 사용자의 PK를 보내는 방법에서 Identifier 정보 전달로 변경 + +commit bb2852482ff076106c27187a3dd6d1a006e21df8 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Jan 14 14:30:32 2024 +0900 + + chore: mongoDB 의존성 추가 + +commit 9398af7f9304eff4587fa099d140cead98652580 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Jan 12 10:54:37 2024 +0900 + + fix: 회원가입 요청 DTO 버그 수정 + +commit ebec72d9cae6dc93c79bbf3afdfcff94c084fc3a +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Jan 12 09:56:35 2024 +0900 + + fix: OAuthRule enum 클래스 수정으로 인한 버그 수정 + +commit 1f6dddf88be7aab31b948da4da939b72f70e0cf9 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Jan 11 11:48:33 2024 +0900 + + refactor: OAuth2와 관련된 상수를 하나의 enum 클래스에 모음 + +commit f42e400b1e49713dc062f6df8682ffe188f36447 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Jan 11 01:25:55 2024 +0900 + + refactor: filterChain을 Security 6에 맞게 람다로 변경 + +commit 8be9964397d99e5054019c5068074248d11d83ed +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Jan 11 01:11:58 2024 +0900 + + refactor: CorsConfigurationSource 클래스 분리 + +commit 2b93f74ed713bb4142925d1da07b817fcb49186f +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Thu Jan 11 00:53:31 2024 +0900 + + feat: JWT 발급 API 요청 시, access & refresh token 발급 로직 개발 + + - JwtAuthorizationFilter 세부 구현 필요 + - JwtService 내의 매직 넘버 처리 필요 + +commit edb1c2f2048014ab5cda2bb9c690140bdafe0216 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 9 14:40:43 2024 +0900 + + feat: Github 소셜로그인 추가 및 구조 변경 + + - Github 소셜로그인 추가 + - Github 소셜로그인 추가에 따라, Spring security에서 Authentication을 찾는데에 사용하는 값 변경 + +commit 6de4dbfc9dbd764729afea474ddd659636e5d955 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Wed Jan 3 14:50:44 2024 +0900 + + feat: refresh token 생성 로직 구현 + +commit d71bfe4970ac3df816e4b4774edee3c87095f588 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Wed Jan 3 12:19:15 2024 +0900 + + chore: 코드 최적화 + +commit 9dfc46d82297b3bbfc9c57098b57b1f7d0d81d03 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Wed Jan 3 12:17:29 2024 +0900 + + feat: Security Context에 사용자의 정보를 담는 클래스 구현 + + - UserPrincipal가 DefaultOAuth2User 상속 -> OAuth2User 인터페이스 상속으로 변경 + - UserPrincipal 클래스에 UserDetails 인터페이스 상속 + - 인터페이스 상속으로 인한 메서드 구현 + - OAuth2User의 getName(): email 반환 + - UserDetails의 getUsername(): User PK를 String 형으로 반환 + +commit 7f1fdd0d98afbd9c87845532fc4e1e766af3d5d2 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 2 11:55:45 2024 +0900 + + chore: jwt 관련 의존성 추가 + +commit b7848d77953aa7c7549cf35c9a09e54a9955cb4e +Merge: f03d1f7 61d873c +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 2 10:16:42 2024 +0900 + + Merge pull request #16 from TeamTheGenius/feat/social-login + + Feat: 회원가입 API 개발 + +commit f03d1f7742cc4dd13a756903a1446188752b90b2 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 2 01:06:37 2024 +0900 + + Update issue templates + +commit 61d873c68b7441a487732328f2e34a7c778c9fd6 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Tue Jan 2 00:57:13 2024 +0900 + + refactor: 공통 응답 객체에 값 추가 + +commit 6cea0ac9e1170ca19ea6277ddc78a22f8cd392a6 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 1 14:15:19 2024 +0900 + + refactor: BusinessExceptionHandler의 반환타입 변경 + +commit 09330b408572662244ddfe08b75661e9d42cd470 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 1 13:45:06 2024 +0900 + + chore: final 상수 이름을 네이밍 컨벤션에 맞게 변경 + +commit 76b6c8f5aec2090e1b54b3512bfd1cfdf8c107ac +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 1 02:22:58 2024 +0900 + + feat: 회원가입 로직 개발 + + - 소셜로그인 성공 이후 회원가입 이전 사용자를 대상으로, 회원가입을 진행하는 로직 개발 + - FE로부터 배열 형태로 받은 관심사를 처리하는 Converter 클래스 구현 필요 + +commit 8f0137fd9f749d09eeba6e9cb632240a7267b250 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Mon Jan 1 01:51:23 2024 +0900 + + chore: build.gradle에서 lombok 의존성 추가 + + - test 코드에서도 사용할 수 있도록 의존성 추가 + +commit 748cda29f95d3557139fe694309856462f8d3e78 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 17:17:55 2023 +0900 + + chore: 패키지명 변경 + +commit d074cc947e66bcefe7b671b6ac7e9ea4ebe86d69 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 15:54:08 2023 +0900 + + chore: 어플리케이션 baseURL 수정 + +commit 17db90d6c66f971665979df363e13d4861ffb84c +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 14:56:02 2023 +0900 + + chore: swagger 관련 URI 수정 + +commit baf94401d73f809e5b1f7c6d352aabb8b7b49f79 +Merge: 33c4234 3cb3358 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 14:37:45 2023 +0900 + + Merge pull request #15 from TeamTheGenius/feat/7-add-utils + + Feat: Utils 파일 추가 + +commit 3cb335899010df60b07385d26d51c4a0b2b04b04 +Merge: af5971f 33c4234 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 14:37:23 2023 +0900 + + Merge branch 'feat/social-login' into feat/7-add-utils + +commit af5971f3cd09e23180c41eaeeaa9a8af3c48350e +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 14:09:03 2023 +0900 + + feat: swagger 설정 파일 추가 + +commit 33c42342a038ac0b613ccfa79a79770ee5522b6b +Merge: c24f5bd ea5d3b2 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 12:39:58 2023 +0900 + + Merge pull request #14 from TeamTheGenius/feat/social-login + + Feat: 소셜로그인에 필요한 DTO 개발, 클래스 커스터마이징 및 로직 구현 + +commit ea5d3b225e0ac9a5f0b18acdd5ad2b4ec40f50a1 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 12:37:03 2023 +0900 + + chore: 사용하지 않는 클래스 삭제 및 magic number 상수로 변환 + +commit a103100304b1ba94c7094e40e86d29c7f6bdb398 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 12:05:54 2023 +0900 + + fix: 인증 클래스 생성자에 사용자의 역할을 전달하도록 수정 + + - 기존에 무조건 ROLE_USER를 전달하던 로직에서, 생성자에 사용자의 역할을 전달하도록 수정 + +commit ec2a0acf3248181747e9b04dc952a1c043c3ce65 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 03:26:46 2023 +0900 + + refactor: 인증 객체 상속관계 변경 + + - 기존에 OAuth2User 인터페이스 상속에서 DefaultOAuth2User 상속으로 변경 + - getName() 메서드 오버라이드: 사용자의 이메일을 반환하도록 설정 + +commit 25ca958b019ba9c96fb8234b19dccf77f5f05aba +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 03:17:50 2023 +0900 + + refactor: 소셜 별 UserInfo 클래스 리팩토링 진행 + + - 매직 넘버(magic number)에 대해 상수로 변경 + - 인증 객체에 필요한 메서드 추가 + +commit 20580d9478afba19c63391a905f6dc334482099c +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 02:05:50 2023 +0900 + + feat: 소셜로그인 관련 Handler 로직 구현 + + - 소셜로그인을 통한 인증 성공 후, 실행되는 OAuth2SuccessHandler 클래스 로직 구현 + - 소셜로그인 도중 모종의 이유로 인해 실패했을 경우, 실행되는 OAuth2FailureHandler 클래스 로직 구현 + +commit 52dbed24f9c1c4f837ada471d423c1c23f0d82e1 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sun Dec 31 01:34:24 2023 +0900 + + feat: 소셜로그인 성공 시 실행되는 서비스 클래스 개발 + + - 서드파티로부터 access-token 받은 이후 실행되는 custom 서비스 클래스 로직 구현 + - 사용자 정보(email)을 받아온 후 -> DB에 저장 여부 확인 -> 없다면 DB에 저장 + +commit 431013464b7112a4ed472982f39c4f3c0d532def +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sat Dec 30 23:06:06 2023 +0900 + + refactor: 소셜로그인 관련 파일들 구조 변경 + +commit bd1572b8eabee22e5cff10bb31f5a0c76d21bb89 +Merge: 7e6f06a 3d17f1e +Author: kimdozzi +Date: Sat Dec 30 17:06:46 2023 +0900 + + Merge remote-tracking branch 'upstream/feat/social-login' into feat/social-login + +commit 7e6f06a8007ff56881ecea6ec58eeccdd8f17342 +Author: kimdozzi +Date: Sat Dec 30 16:58:37 2023 +0900 + + Feat: 폴더 구조 변경 + +commit 368c804680dac110069481cd4f7b56a17efcd857 +Author: kimdozzi +Date: Sat Dec 30 16:58:09 2023 +0900 + + Feat: 소셜로그인 테스트를 위한 의존성 추가 및 수정 + +commit db87b1a7287d7bf9535b80d59062214603e62285 +Author: kimdozzi +Date: Sat Dec 30 16:54:32 2023 +0900 + + Feat: 소셜별 UserInfo DTO 개발 + +commit 5005b951fa755cc17b3c4fe1165a861027d2cd6b +Author: kimdozzi +Date: Sat Dec 30 16:53:24 2023 +0900 + + Feat: 소셜로그인에 필요한 클래스 커스터마이징 및 개발 + +commit 6c828bbe32b96749e2da988304f5a6b6e97465d4 +Author: kimdozzi +Date: Sat Dec 30 16:51:44 2023 +0900 + + Feat: Config 수정 및 entity 수정 + +commit 264c5e2b37ecb781fdcd2714a6560f114ac7bfe2 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sat Dec 30 02:19:56 2023 +0900 + + feat: CORS 처리 필터 추가 + +commit 27f54dbc88c2b8b23c9326e9a381b8ec05c087b3 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sat Dec 30 02:16:50 2023 +0900 + + feat: Time formatting 파일 추가 + +commit 7534ca1d2e3f2b1309058832c58e9fd8bb14b542 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sat Dec 30 02:16:25 2023 +0900 + + feat: Response 형식에 따른 타입 통일 + + - 반환하는 값의 형식에 따른 Response DTO 타입 지정 + +commit c3fe05f2ffe9888fce0d9d9d93ecf1b8b9c56c55 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Sat Dec 30 02:14:48 2023 +0900 + + feat: Exception 클래스 및 ExceptionHandler 클래스 생성 + + - BusinessException: 비지니스 로직에서 발생한 예외를 처리할 때 해당 예외 클래스를 throw + - BusinessExceptionHandler : 사용자가 처리한 예외(BusinessException)을 어떻게 처리할 것인지 구현 + - GlobalExceptionHandler: 개발자가 처리하지 않은 예외가 던져졌을 때 처리되는 부분 -> 추가 처리 필요함을 날리는 곳 + - ErrorCode: 예외 메시지를 저장하는 Enum 클래스 + +commit 3d17f1e8b3c619c49612e6f14489ecb7dc200a35 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 23:59:03 2023 +0900 + + hotfix: security 패키지 위치 변경 + +commit 86c029e7edddf644018f6b7afc5ebcb7b568a688 +Merge: 31a7b79 c24f5bd +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 23:44:15 2023 +0900 + + Merge branch 'main' into feat/social-login + +commit 31a7b790c06827303da27af54df70cf141dee8be +Merge: 8ae3e86 8ec6058 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 13:35:45 2023 +0900 + + Merge pull request #12 from TeamTheGenius/setting + + Feat: Spring Security, OAuth 2.0에서 필요한 설정 진행 + +commit c24f5bddf9f746e98c2cb88c5468806dbcee984c +Merge: 8ae3e86 29e6938 +Author: HEY <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 13:35:35 2023 +0900 + + Merge pull request #13 from TeamTheGenius/common + + Feat: 소셜로그인에 필요한 User Entity 및 Repository 개발 + +commit 29e6938f3642fe13d9884d1b175bfc08023a870b +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 02:21:47 2023 +0900 + + feat: 사용자를 이메일, provider를 통해 찾는 기능 개발 + + - JpaRepository 인터페이스를 상속하여 구현 + - 이메일 / 이메일+provider 를 통해 DB에서 사용자를 찾는 기능 구현 + - 두 메서드에 대해 단위 테스트 코드 작성 + +commit 6bf4468a966d026e7b56c54a2fca0a142ea534eb +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 02:20:07 2023 +0900 + + feat: User Entity 추가 + + - JPA를 활용하여 User 엔티티 클래스 작성 + - BaseTimeEntity 상속 + - 사용자의 회원가입 여부/ 권한에 대한 정보를 담고 있는 Enum class 작성 + +commit 844cedddb9ea95f5cffc7073e62777cb2e9da227 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 02:10:30 2023 +0900 + + feat: 공통 필드를 가지고 있는 Entity 추가 + + - 생성시간, 수정시간, 삭제시간 필드를 가지고 있는 공통 Entity 추가 + - createdDate, modifiedDate 자동 기록을 위해 + @EnableJpaAuditing 어노테이션 추가 + +commit 8ec6058776a0ed0beaa87062b3d9471c1246b7f3 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 01:24:55 2023 +0900 + + feat: 서비스에 적용되는 Provider Enum 클래스 생성 + + - 정적 팩토리 메서드를 통해 provider에 맞는 AuthProvider를 반환 + +commit 9f887ff18e8e5642c268a60143700eeeb3ca6988 +Author: SSung023 <50323157+SSung023@users.noreply.github.com> +Date: Fri Dec 29 01:23:23 2023 +0900 + + feat: SecurityConfig에서 소셜로그인에 필요한 설정 추가 + + - OAuth2.0을 이용한 소셜로그인에 필요한 설정 추가 (filterChain을 통해 구현) + - 실제 객체가 생성되어있지 않아 작성할 수 없는 부분은 주석 처리 (추후 주석 해제 필요) + - application-oauth.yml 파일에 소셜로그인에 대한 설정 후, 테스트 시 정상작동 확인 + +commit 8ae3e86e7916db6951020743d1ed28c54430cbfa +Author: DoHyung Kim +Date: Mon Dec 25 21:23:18 2023 +0900 + + Update issue templates + +commit 4ca2ccf2d8bdb7d767987cbd53969fae228f3a40 +Author: DoHyung Kim +Date: Mon Dec 25 21:21:38 2023 +0900 + + Update issue templates + +commit ed9ef2799075a0ee06beeac6130e1cfd607899cd +Author: DoHyung Kim +Date: Mon Dec 25 21:15:11 2023 +0900 + + Create pull_request_template.md + +commit 03de9128b78939781f1ba83d4b31b33dcae96474 (origin/production, origin/pre-production) +Author: SSung023 +Date: Mon Dec 25 20:48:00 2023 +0900 + + init: 프로젝트 초기 세팅 + + - .yml 파일에 DB 추가 세팅 필요 + +commit e1c602ff455e6d6cf6eca31f69bf2b03311cc90a +Author: SSung023 +Date: Mon Dec 25 20:42:34 2023 +0900 + + init: 초기 세팅 revert + +commit f033f9991c5722d2ba7f2fd2c65704c243e3e87c +Author: SSung023 +Date: Mon Dec 25 20:41:23 2023 +0900 + + Revert "init: 프로젝트 초기 세팅" + + This reverts commit ac57c42d0c3241a46bf24c6757518d26b8f822e7. + +commit ac57c42d0c3241a46bf24c6757518d26b8f822e7 +Author: SSung023 +Date: Mon Dec 25 20:40:08 2023 +0900 + + init: 프로젝트 초기 세팅 + + - .yml 파일에 DB 추가 세팅 필요 From 4aad04542532ef8c246922b54bc7503f640cda5f Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:57:16 +0900 Subject: [PATCH 115/234] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=91=90=20=EB=B2=88=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=8B=9C,=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95=20(#93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미 한 차례 회원가입이 완료된 사용자에 대해, 회원가입 요청이 한 번 더 오면 예외가 발생하도록 변경 - 관련 테스트 코드 작성 --- .../challenge/user/service/UserService.java | 16 +++++++++---- .../global/util/exception/ErrorCode.java | 1 + .../repository/InstanceRepositoryTest.java | 2 +- .../user/service/UserServiceTest.java | 23 +++++++++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 4f49b1ef..9817171f 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.user.service; +import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REGISTERED; import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; @@ -33,17 +34,18 @@ public User findUserByIdentifier(String identifier) { @Transactional public Long signup(SignupRequest requestUser) { - User targetUser = findUserByIdentifier(requestUser.identifier()); + User user = findUserByIdentifier(requestUser.identifier()); + isAlreadyRegistered(user); //TODO: Converter 클래스 만들어서 적용하기 String interest = String.join(",", requestUser.interest()); - targetUser.updateUser(requestUser.nickname(), + user.updateUser(requestUser.nickname(), requestUser.information(), interest); - targetUser.updateRole(Role.USER); + user.updateRole(Role.USER); - return targetUser.getId(); + return user.getId(); } public void isNicknameDuplicate(String nickname) { @@ -51,4 +53,10 @@ public void isNicknameDuplicate(String nickname) { throw new BusinessException(DUPLICATED_NICKNAME); } } + + public void isAlreadyRegistered(User user) { + if (user.getRole() != Role.NOT_REGISTERED) { + throw new BusinessException(ALREADY_REGISTERED); + } + } } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 668a05f7..67451e6c 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -18,6 +18,7 @@ public enum ErrorCode { INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), + ALREADY_REGISTERED(HttpStatus.BAD_REQUEST, "이미 회원가입이 완료된 사용자입니다."), MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."), DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "이미 존재하는 닉네임입니다"), NOT_AUTHENTICATED_USER(HttpStatus.BAD_REQUEST, "인증 가능한 사용자가 아닙니다."), diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java index 563c1429..9668ef45 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -62,7 +62,7 @@ class InstanceRepositoryTest { //when Instance savedInstance = instanceRepository.save(instance); savedInstance.updateInstance("수정되었습니다.", "수정된 유의사항", 10000, LocalDateTime.now(), - LocalDateTime.now().plusDays(5)); + LocalDateTime.now().plusDays(5), "수정된 인증 방식"); //then Assertions.assertThat(instance.getDescription()).isEqualTo(savedInstance.getDescription()); diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index 2e6dd8f1..98230693 100644 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -54,6 +54,29 @@ public void should_matchValues_when_signupUser() { assertThat(user.getTags()).isEqualTo(foundUser.getTags()); } + @Test + @DisplayName("사용자가 한 차례 회원가입을 진행한 후, 한 번 더 회원가입을 요청하면 예외가 발생해야 한다.") + public void should_throwException_when_requestRegisterAgain() { + //given + String email = "test@naver.com"; + saveUnsignedUser(); + SignupRequest signupRequest = SignupRequest.builder() + .identifier(email) + .nickname("nickname") + .information("information") + .interest(List.of("관심사1", "관심사2")) + .build(); + + //when + User user = userService.findUserByIdentifier(email); + Long signupUserId = userService.signup(signupRequest); + + //then + assertThatThrownBy(() -> userService.signup(signupRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.ALREADY_REGISTERED.getMessage()); + } + @Test @DisplayName("저장되어 있는 사용자를 PK를 통해 찾을 수 있다.") public void should_returnUser_when_passPK() { From 30db9d504e1a46cdce1fce43e669394b9f44a1ab Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:57:29 +0900 Subject: [PATCH 116/234] =?UTF-8?q?[BUG/FIX]=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20API=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=B0=9C=EC=83=9D=20(#95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 로그아웃 API 버그 픽스 * refactor: 어노테이션을 통해 인증 객체 반환하도록 변경 - SecurityContext에서 인증 객체를 받아오던 방식에서 @AuthenticationPrincipal을 통해 받아오도록 변경 --- .../global/security/controller/AuthController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 8ad57060..a32f83ec 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -12,7 +12,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -41,10 +41,10 @@ public ResponseEntity generateToken(HttpServletResponse response ); } - @PostMapping("/auth/logout") - public ResponseEntity logout(HttpServletResponse response) { - UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() - .getPrincipal(); + @PostMapping("/logout") + public ResponseEntity logout( + @AuthenticationPrincipal UserPrincipal userPrincipal, + HttpServletResponse response) { jwtService.logout(userPrincipal.getUser(), response); return ResponseEntity.ok().body( From 296019eb04a56ff5e464f0b83c780e8f34009e15 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:18:28 +0900 Subject: [PATCH 117/234] =?UTF-8?q?chore:=20=ED=8C=8C=EC=9D=BC=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=A9?= =?UTF-8?q?=EB=B2=95=20=EC=9E=90=EB=A3=8C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/file/service/FileUtilTest.java | 20 ++++++++ .../gitget/util/file/FileTest_README.md | 51 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/test/java/com/genius/gitget/util/file/FileTest_README.md diff --git a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java index 3b57b220..99337796 100644 --- a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java @@ -13,21 +13,27 @@ import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.util.file.FileTestUtil; import java.io.File; import java.io.IOException; import java.io.InputStream; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +@Slf4j @SpringBootTest @Transactional @ActiveProfiles({"file"}) class FileUtilTest { + @Autowired + private FilesService filesService; @Value("${file.upload.path}") private String UPLOAD_PATH; @@ -116,6 +122,20 @@ public void should_passInformation_when_tryToCopy() { assertThat(copyDTO.fileURI()).contains(UPLOAD_PATH); } + @Test + @DisplayName("FileTestUtil을 통해 받은 MultipartFile을 통해 인코딩 파일을 받을 수 있다") + public void should_getEncodedFiles() { + //given + MultipartFile multipartFile = FileTestUtil.getMultipartFile("filename"); + Files files = filesService.uploadFile(multipartFile, "topic"); + + //when + String encoded = FileUtil.encodedImage(files); + + //then + log.info(encoded); + } + private MultipartFile getTestMultiPartFile(String originalFilename) { return new MultipartFile() { diff --git a/src/test/java/com/genius/gitget/util/file/FileTest_README.md b/src/test/java/com/genius/gitget/util/file/FileTest_README.md new file mode 100644 index 00000000..3aef2928 --- /dev/null +++ b/src/test/java/com/genius/gitget/util/file/FileTest_README.md @@ -0,0 +1,51 @@ +### File 테스트 방법 + +통합 테스트를 진행한다면 Service 단의 메서드들을 호출하여 진행 +Service 단의 매개변수에 `MultipartFile`이 전달된 후, 서비스 단에서 이를 활용하기 때문에 +MultipartFile만 잘 생성하면 됨. + +#### 1. FileTestUtil을 통해 MultipartFile 받아오기 + +`FileTestUtil.getMultipartFile("파일 이름")`을 통해 `MultiPart` 객체를 받을 수 있다. +매개 변수로는 `filename`을 전달받는데, "sky", "aws_image"와 같은 값을 전달하면, +내부적으로 "sky.png", "aws_image.png" 로 저장된다. + +`InstanceService`에 파일을 전달하여 인스턴스 생성 시의 코드 + +```java +Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, + FileTestUtil.getMultipartFile("name"), fileType); +``` + +
+ +#### 2. Files 객체를 단독으로 생성하고 싶을 때 - FilesService 이용 + +`MultipartFile`을 통해 토픽/인스턴스의 생성/수정 하는 방법이 아니라, `Files` 엔티티를 만들고 싶을 때에는 +`FilesService`의 코드를 사용해야 한다. + +1. `FileTestUtil.getMultipartFile("파일이름")`을 통해 MultipartFile을 반환받는다. +2. `public Files uploadFile(MultipartFile receivedFile, String typeStr)`의 매개변수로 전달하면, + FilesRepository를 통해 저장한 Files 엔티티를 반환받을 수 있다. +3. 이후 Topic, Instance, User의 `setFiles`를 통해 연관관계를 설정하면 된다. + +`FileUtilTest`에서 작성한 테스트 코드의 예시이다. +저장 이후 다시 encoding 해도 에러가 발생하지 않는다. + +```java + +@Test +@DisplayName("FileTestUtil을 통해 받은 MultipartFile을 통해 인코딩 파일을 받을 수 있다") +public void should_getEncodedFiles() { + //given + MultipartFile multipartFile = FileTestUtil.getMultipartFile("filename"); + Files files = filesService.uploadFile(multipartFile, "topic"); + + //when + String encoded = FileUtil.encodedImage(files); + + //then + log.info(encoded); +} + +``` \ No newline at end of file From 996ef8dfbb21f8ca2f4fffdfbdb9fd1fd102c1e7 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Sat, 9 Mar 2024 22:14:04 +0900 Subject: [PATCH 118/234] =?UTF-8?q?[FEAT]=20=EB=A7=88=EC=9D=B4=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B0=9C=EB=B0=9C=20=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: mypage 유저 정보 업데이트를 위한 메서드 수정 * feat: mypage - 유저정보 조회/수정, 관심사 수정, 챌린지 현황 controller 개발 * feat: mypage - 유저정보 조회/수정, 관심사 수정, 챌린지 현황 service 개발 * feat: mypage - 유저정보 조회/수정, 관심사 수정, 챌린지 현황 dto 개발 * feat: 유저 회원가입 탈퇴 개발 * feat: 좋아요 목록 개발 및 리펙토링 * feat: 좋아요 목록 기능 개발 - native query 반환 타입 이슈로 interface dto 생성 - 서비스 계층 분리 필요 - 현재 복잡한 구조인 것 같음 - controller, service 등 다른 계층 개발 완료 * refactor: 불필요한 dto 제거 및 리펙토링 * refactor: 코드 리펙토링 - 편한 테스트 코드 작성을 위한 data.sql 작성 중 - errorcode 추가 - 컨트롤러 리펙토링 - nullpointerException 예외 처리 * refactor: 사용자 정보 조회 시 file이 없을 때 예외 처리 * refactor: 사용자 정보 수정 시 image file이 null일 때의 예외 처리 * test: mypage service 계층 테스트 - 챌린지 현황 제외 - 브랜치 통합 후 진행할 것 * test: 테스트 코드 추가 - postman 테스트 예외 처리 완료 - 통합 테스트를 위한 사전 셋팅 중 * refactor: profile dto 전달 방식 변경 * refactor: 코드 리펙토링 * hotfix: 좋아요 목록 조회 시 발생하는 이슈 해결 - 좋아요 목록 조회 시 토픽의 이미지가 들어옴 - 좋아요 목록 삭제 시 인스턴스가 같이 삭제됨 - dto likesId 추가 전달 * hotfix: 관심사 조회 이슈 해결 - jpa에서 entity가 아닌 dto로 데이터를 받아오면서 생긴 이슈 - interface를 생성 후 dto를 받아오는 방법으로 해결 - 여기서 생긴 문제 -> 유닛 테스트 시 데이터베이스에서 데이터 조회가 안되는 문제가 생김. postman 테스트 시 정상 작동 - 문제를 해결하기 위해 interface 방식으로 dto를 받아오는 방법을 쓰지 않고, jpa에서 paging 형식으로 받아온 뒤, map()을 사용해 내부에서 image 파일을 저장해주는 방법으로 문제 해결 완료 * refactor: 사용자의 좋아요 목록 조회 로직 수정 - UserLikesResponse에 likesId 데이터 추가 * feat: 유저 포인트 조회 - 유저가 가지는 포인트 기본 값을 0L 로 지정 * hotfix: 좋아요 목록 삭제 이슈 - param으로 받은 id로 좋아요 목록 삭제 시 lazy error 발생 - 이전에 사용하던 방식으로 일단 해결 -> 나중에 해결해볼 것 (어떤 이유로 lazy loading이 제대로 작동하지 않는 것인지) - eager 또는 fetch join, n+1 문제 등 - 테스트 코드 작성 * feat: 관심사 조회 및 사용자 조회 api 개발 --------- Co-authored-by: SSung023 <50323157+SSung023@users.noreply.github.com> --- build.gradle | 2 +- .../genius/gitget/admin/signout/Signout.java | 43 ++++ .../admin/signout/SignoutRepository.java | 10 + .../gitget/admin/topic/domain/Topic.java | 1 - .../hits/repository/HitsRepository.java | 7 - .../challenge/instance/domain/Instance.java | 4 +- .../dto/search/InstanceSearchResponse.java | 3 +- .../likes/controller/LikesController.java | 70 ++++++ .../Hits.java => likes/domain/Likes.java} | 46 ++-- .../likes/dto/UserLikesAddRequest.java | 14 ++ .../likes/dto/UserLikesAddResponse.java | 14 ++ .../likes/dto/UserLikesResponse.java | 24 ++ .../likes/repository/LikesRepository.java | 7 + .../challenge/likes/service/LikesService.java | 105 +++++++++ .../user/controller/UserController.java | 2 +- .../gitget/challenge/user/domain/User.java | 22 +- .../gitget/global/file/dto/FileResponse.java | 8 + .../global/util/exception/ErrorCode.java | 2 + .../payment/controller/PaymentController.java | 5 +- .../gitget/payment/dto/PaymentRequest.java | 15 ++ .../gitget/payment/dto/PaymentResponse.java | 12 + .../payment/dto/PaymentSuccessResponse.java | 12 + .../payment/service/PaymentService.java | 64 +----- .../profile/controller/ProfileController.java | 127 +++++++++++ .../dto/UserChallengeResultResponse.java | 20 ++ .../dto/UserDetailsInformationResponse.java | 48 ++++ .../profile/dto/UserInformationRequest.java | 8 + .../profile/dto/UserInformationResponse.java | 37 +++ .../dto/UserInformationUpdateRequest.java | 16 ++ .../profile/dto/UserInterestResponse.java | 15 ++ .../gitget/profile/dto/UserPointResponse.java | 16 ++ .../profile/dto/UserSignoutRequest.java | 14 ++ .../profile/dto/UserTagsUpdateRequest.java | 15 ++ .../profile/service/ProfileService.java | 203 +++++++++++++++++ .../genius/gitget/GitgetApplicationTests.java | 9 +- .../HitsTest.java => likes/LikesTest.java} | 16 +- .../likes/service/LikesServiceTest.java | 208 +++++++++++++++++ .../challenge/user/domain/UserTest.java | 5 +- .../java/com/genius/gitget/mock/MockTest.java | 21 -- .../profile/service/ProfileServiceTest.java | 210 ++++++++++++++++++ src/test/resources/data.sql | 20 ++ 41 files changed, 1377 insertions(+), 123 deletions(-) create mode 100644 src/main/java/com/genius/gitget/admin/signout/Signout.java create mode 100644 src/main/java/com/genius/gitget/admin/signout/SignoutRepository.java delete mode 100644 src/main/java/com/genius/gitget/challenge/hits/repository/HitsRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java rename src/main/java/com/genius/gitget/challenge/{hits/domain/Hits.java => likes/domain/Likes.java} (51%) create mode 100644 src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddRequest.java create mode 100644 src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/likes/repository/LikesRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java create mode 100644 src/main/java/com/genius/gitget/profile/controller/ProfileController.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserChallengeResultResponse.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserDetailsInformationResponse.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserInformationRequest.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserInformationUpdateRequest.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserInterestResponse.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserPointResponse.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserTagsUpdateRequest.java create mode 100644 src/main/java/com/genius/gitget/profile/service/ProfileService.java rename src/test/java/com/genius/gitget/challenge/{hits/HitsTest.java => likes/LikesTest.java} (88%) create mode 100644 src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java delete mode 100644 src/test/java/com/genius/gitget/mock/MockTest.java create mode 100644 src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java create mode 100644 src/test/resources/data.sql diff --git a/build.gradle b/build.gradle index 841e5b1d..35a743ec 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' // H2 - runtimeOnly 'com.h2database:h2:1.4.200'; + runtimeOnly 'com.h2database:h2:2.2.222'; compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/com/genius/gitget/admin/signout/Signout.java b/src/main/java/com/genius/gitget/admin/signout/Signout.java new file mode 100644 index 00000000..5b807206 --- /dev/null +++ b/src/main/java/com/genius/gitget/admin/signout/Signout.java @@ -0,0 +1,43 @@ +package com.genius.gitget.admin.signout; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +@Table(name = "signout") +public class Signout { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "signout_id") + private Long id; + + private String identifier; + + private String reason; + + @CreatedDate + @Column(name = "signout_at", updatable = false) + private LocalDateTime signoutDate; + + @Builder + public Signout(String identifier, String reason, LocalDateTime signoutDate) { + this.identifier = identifier; + this.reason = reason; + this.signoutDate = signoutDate; + } +} diff --git a/src/main/java/com/genius/gitget/admin/signout/SignoutRepository.java b/src/main/java/com/genius/gitget/admin/signout/SignoutRepository.java new file mode 100644 index 00000000..2a064d8a --- /dev/null +++ b/src/main/java/com/genius/gitget/admin/signout/SignoutRepository.java @@ -0,0 +1,10 @@ +package com.genius.gitget.admin.signout; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface SignoutRepository extends JpaRepository { + + @Query("select s from Signout s where s.identifier = :identifier") + Signout findByIdentifier(String identifier); +} diff --git a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java index bf33a0bc..64ba5ca3 100644 --- a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java @@ -26,7 +26,6 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "topic") - public class Topic extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/genius/gitget/challenge/hits/repository/HitsRepository.java b/src/main/java/com/genius/gitget/challenge/hits/repository/HitsRepository.java deleted file mode 100644 index 31919b47..00000000 --- a/src/main/java/com/genius/gitget/challenge/hits/repository/HitsRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.genius.gitget.challenge.hits.repository; - -import com.genius.gitget.challenge.hits.domain.Hits; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface HitsRepository extends JpaRepository { -} diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 08d265c0..6d1dcf5a 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -2,7 +2,7 @@ import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.challenge.hits.domain.Hits; +import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; import com.genius.gitget.global.file.domain.Files; import jakarta.persistence.CascadeType; @@ -46,7 +46,7 @@ public class Instance { private Files files; @OneToMany(mappedBy = "instance") - private List hitsList = new ArrayList<>(); + private List likesList = new ArrayList<>(); @OneToMany(mappedBy = "instance") private List participantInfoList = new ArrayList<>(); diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index a163dff7..b9deaae3 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -28,10 +28,9 @@ public InstanceSearchResponse(Long topicId, Long instanceId, String keyword, int this.keyword = keyword; this.pointPerPerson = pointPerPerson; this.participantCount = participantCount; - this.fileResponse = convertToFileResponse(Optional.of(files)); + this.fileResponse = convertToFileResponse(Optional.ofNullable(files)); } - private static FileResponse convertToFileResponse(Optional files) throws IOException { if (files.isEmpty()) { return FileResponse.createNotExistFile(); diff --git a/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java b/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java new file mode 100644 index 00000000..652f7727 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java @@ -0,0 +1,70 @@ +package com.genius.gitget.challenge.likes.controller; + +import com.genius.gitget.challenge.likes.dto.UserLikesAddRequest; +import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; +import com.genius.gitget.challenge.likes.dto.UserLikesResponse; +import com.genius.gitget.challenge.likes.service.LikesService; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.exception.SuccessCode; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.PagingResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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; + +@RestController +@RequiredArgsConstructor +@Slf4j +@RequestMapping("/api/profile") +public class LikesController { + private final LikesService likesService; + + // 좋아요 목록 조회 + @GetMapping("/likes") + public ResponseEntity> getLikesListOfUser(Pageable pageable, + @AuthenticationPrincipal UserPrincipal userPrincipal) { + + PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); + Page likesResponses = likesService.getLikesList(userPrincipal.getUser(), pageRequest); + + return ResponseEntity.ok().body( + new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), likesResponses) + ); + } + + // 좋아요 목록 추가 + @PostMapping("/likes") + public ResponseEntity> addLikes( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody UserLikesAddRequest userLikesAddRequest) { + UserLikesAddResponse userLikesAddResponse = likesService.addLikes(userPrincipal.getUser(), + userLikesAddRequest.getIdentifier(), + userLikesAddRequest.getInstanceId()); + return ResponseEntity.ok().body( + new SingleResponse<>(SuccessCode.CREATED.getStatus(), SuccessCode.CREATED.getMessage(), + userLikesAddResponse) + ); + } + + // 좋아요 목록 삭제 + @DeleteMapping("/likes/{likesId}") + public ResponseEntity deleteLikes(@AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable(value = "likesId") Long likesId) { + likesService.deleteLikes(userPrincipal.getUser(), likesId); + return ResponseEntity.ok().body( + new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/hits/domain/Hits.java b/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java similarity index 51% rename from src/main/java/com/genius/gitget/challenge/hits/domain/Hits.java rename to src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java index 9b9fd9dc..4dd6cc05 100644 --- a/src/main/java/com/genius/gitget/challenge/hits/domain/Hits.java +++ b/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java @@ -1,25 +1,35 @@ -package com.genius.gitget.challenge.hits.domain; +package com.genius.gitget.challenge.likes.domain; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.user.domain.User; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "hits") +@Table(name = "likes") @EntityListeners(AuditingEntityListener.class) -public class Hits { +public class Likes { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "hits_id") + @Column(name = "likes_id") private Long id; @ManyToOne(fetch = FetchType.LAZY) @@ -28,12 +38,14 @@ public class Hits { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") + @OnDelete(action = OnDeleteAction.CASCADE) private User user; + @CreatedDate - @Column(name = "liked_at", unique = false) + @Column(name = "liked_at") private LocalDateTime likedAt; // 찜하기 누른 시각 - public Hits(User user, Instance instance) { + public Likes(User user, Instance instance) { this.instance = instance; this.user = user; setUserAndInstance(user, instance); @@ -41,20 +53,20 @@ public Hits(User user, Instance instance) { /*== 연관관계 편의 메서드 ==*/ public void setUserAndInstance(User user, Instance instance) { - addHitsForUser(user); - addHitsForInstance(instance); + addLikesForUser(user); + addLikesForInstance(instance); } - private void addHitsForUser(User user) { - if (!(user.getHitsList().contains(this))) { - user.getHitsList().add(this); + private void addLikesForUser(User user) { + if (!(user.getLikesList().contains(this))) { + user.getLikesList().add(this); } this.user = user; } - private void addHitsForInstance(Instance instance) { - if (!(instance.getHitsList().contains(this))) { - instance.getHitsList().add(this); + private void addLikesForInstance(Instance instance) { + if (!(instance.getLikesList().contains(this))) { + instance.getLikesList().add(this); } this.instance = instance; } diff --git a/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddRequest.java b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddRequest.java new file mode 100644 index 00000000..2b0f307e --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddRequest.java @@ -0,0 +1,14 @@ +package com.genius.gitget.challenge.likes.dto; + +import lombok.Data; + +@Data +public class UserLikesAddRequest { + private String identifier; + private Long instanceId; + + public UserLikesAddRequest(String identifier, Long instanceId) { + this.identifier = identifier; + this.instanceId = instanceId; + } +} diff --git a/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddResponse.java b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddResponse.java new file mode 100644 index 00000000..7012d030 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddResponse.java @@ -0,0 +1,14 @@ +package com.genius.gitget.challenge.likes.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +public class UserLikesAddResponse { + private Long likesId; + + @Builder + public UserLikesAddResponse(Long likesId) { + this.likesId = likesId; + } +} diff --git a/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesResponse.java b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesResponse.java new file mode 100644 index 00000000..41c708a2 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesResponse.java @@ -0,0 +1,24 @@ +package com.genius.gitget.challenge.likes.dto; + +import com.genius.gitget.global.file.dto.FileResponse; +import lombok.Builder; +import lombok.Data; + +@Data +public class UserLikesResponse { + private Long likesId; + private Long instanceId; + private String title; + private int pointPerPerson; + private FileResponse fileResponse; + + @Builder + public UserLikesResponse(Long likesId, Long instanceId, String title, int pointPerPerson, + FileResponse fileResponse) { + this.likesId = likesId; + this.instanceId = instanceId; + this.title = title; + this.pointPerPerson = pointPerPerson; + this.fileResponse = fileResponse; + } +} diff --git a/src/main/java/com/genius/gitget/challenge/likes/repository/LikesRepository.java b/src/main/java/com/genius/gitget/challenge/likes/repository/LikesRepository.java new file mode 100644 index 00000000..324ad4fc --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/likes/repository/LikesRepository.java @@ -0,0 +1,7 @@ +package com.genius.gitget.challenge.likes.repository; + +import com.genius.gitget.challenge.likes.domain.Likes; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LikesRepository extends JpaRepository { +} diff --git a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java new file mode 100644 index 00000000..20a396c0 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java @@ -0,0 +1,105 @@ +package com.genius.gitget.challenge.likes.service; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.domain.Likes; +import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; +import com.genius.gitget.challenge.likes.dto.UserLikesResponse; +import com.genius.gitget.challenge.likes.repository.LikesRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +@Service +public class LikesService { + private final UserRepository userRepository; + private final InstanceRepository instanceRepository; + private final LikesRepository likesRepository; + + public Page getLikesList(User user, Pageable pageable) { + User verifiedUser = verifyUser(user); + List likes = verifiedUser.getLikesList(); + List userLikesResponses = new ArrayList<>(); + + for (Likes like : likes) { + Instance instance = like.getInstance(); + UserLikesResponse userLikesResponse = UserLikesResponse.builder() + .likesId(like.getId()) + .instanceId(instance.getId()) + .title(instance.getTitle()) + .pointPerPerson(instance.getPointPerPerson()) + .fileResponse(FileResponse.create(instance.getFiles())) + .build(); + + userLikesResponses.add(userLikesResponse); + } + + return new PageImpl<>(userLikesResponses, pageable, userLikesResponses.size()); + } + + @Transactional + public UserLikesAddResponse addLikes(User user, String identifier, Long instanceId) { + User comparedUser = compareToUserIdentifier(user, identifier); + User findUser = verifyUser(comparedUser); + Instance findInstance = verifyInstance(instanceId); + + Likes likes = new Likes(findUser, findInstance); + Long id = likesRepository.save(likes).getId(); + return UserLikesAddResponse.builder() + .likesId(id).build(); + } + + @Transactional + public void deleteLikes(User user, Long likesId) { + Likes findLikes = likesRepository.findById(likesId) + .orElseThrow(() -> new BusinessException(ErrorCode.LIKES_NOT_FOUND)); + + likesRepository.deleteById(findLikes.getId()); + } + + @Transactional + public void deleteLikesLazy(User user, Long likesId) { + try { + likesRepository.deleteById(likesId); + } catch (Exception e) { + e.getStackTrace(); + } + } + + + private User verifyUser(User user) { + return userRepository.findByIdentifier(user.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + } + + private Instance verifyInstance(Long instanceId) { + return instanceRepository.findById(instanceId) + .orElseThrow(() -> new BusinessException(ErrorCode.INSTANCE_NOT_FOUND)); + } + + private User compareToUserIdentifier(User AuthenticatedUser, String identifier) { + if (!(AuthenticatedUser.getIdentifier().equals(identifier))) { + throw new BusinessException(ErrorCode.MEMBER_NOT_FOUND); + } + return AuthenticatedUser; + } + + private Likes getLikes(Long likesId) { + return likesRepository.findById(likesId) + .orElseThrow(() -> new BusinessException(ErrorCode.LIKES_NOT_FOUND)); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java index 683f8236..3dc0808b 100644 --- a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java +++ b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java @@ -3,9 +3,9 @@ import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.global.security.dto.TokenDTO; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.security.dto.TokenDTO; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index a9703efc..806a8708 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -1,7 +1,7 @@ package com.genius.gitget.challenge.user.domain; -import com.genius.gitget.challenge.hits.domain.Hits; import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; @@ -43,7 +43,7 @@ public class User extends BaseTimeEntity { private Files files; @OneToMany(mappedBy = "user") - private List hitsList = new ArrayList<>(); + private List likesList = new ArrayList<>(); @OneToMany(mappedBy = "user") private List participantInfoList = new ArrayList<>(); @@ -73,7 +73,7 @@ public class User extends BaseTimeEntity { @Column(length = 100) private String information; - private Long point; + private Long point = 0L; @Builder public User(ProviderInfo providerInfo, String identifier, Role role, String nickname, String information, @@ -86,10 +86,13 @@ public User(ProviderInfo providerInfo, String identifier, Role role, String nick this.information = information; } - public void updateUser(String nickname, String information, String interest) { + public void updateUserInformation(String nickname, String information) { this.nickname = nickname; this.information = information; - this.tags = interest; + } + + public void updateUserTags(String tags) { + this.tags = tags; } public void updateRole(Role role) { @@ -101,8 +104,17 @@ public void setFiles(Files files) { this.files = files; } + public void updateUser(String nickname, String information, String tags) { + this.nickname = nickname; + this.information = information; + this.tags = tags; + } + public void setPoint(Long point) { this.point += point; } + public void deleteLikesList() { + this.likesList.clear(); + } } diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index de57e20a..fb3874bd 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -2,11 +2,19 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FileUtil; +import java.util.Optional; public record FileResponse( Long fileId, String encodedFile) { + public static FileResponse create(Optional files) { + if (files.isEmpty()) { + return createNotExistFile(); + } + return createExistFile(files.get()); + } + public static FileResponse createExistFile(Files files) { return new FileResponse(files.getId(), FileUtil.encodedImage(files)); } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 67451e6c..a576ad33 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -8,6 +8,8 @@ @RequiredArgsConstructor public enum ErrorCode { + MEMBER_NOT_UPDATED(HttpStatus.BAD_REQUEST, "유저 정보가 업데이트되지 않았습니다."), + LIKES_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 좋아요 목록을 찾을 수 없습니다"), FAILED_POINT_PAYMENT(HttpStatus.BAD_REQUEST, "최소 충전 금액은 100원 이상입니다."), INVALID_PAYMENT_AMOUNT(HttpStatus.BAD_REQUEST, "최초 결제 요청 금액과 일치하지 않습니다."), FAILED_FINAL_PAYMENT(HttpStatus.BAD_REQUEST, "최종 결제가 승인되지 않았습니다"), diff --git a/src/main/java/com/genius/gitget/payment/controller/PaymentController.java b/src/main/java/com/genius/gitget/payment/controller/PaymentController.java index 1aae74e1..adf9ea88 100644 --- a/src/main/java/com/genius/gitget/payment/controller/PaymentController.java +++ b/src/main/java/com/genius/gitget/payment/controller/PaymentController.java @@ -1,5 +1,6 @@ package com.genius.gitget.payment.controller; +import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -12,6 +13,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,8 +29,9 @@ public class PaymentController { @PostMapping("/toss") public ResponseEntity> requestTossPayment( + @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody PaymentRequest paymentRequest) { - PaymentResponse paymentResponse = paymentService.requestTossPayment(paymentRequest); + PaymentResponse paymentResponse = paymentService.requestTossPayment(userPrincipal.getUser(), paymentRequest); return ResponseEntity.ok().body( new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), paymentResponse) ); diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java b/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java index a7a468dc..895807eb 100644 --- a/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java +++ b/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java @@ -1,5 +1,8 @@ package com.genius.gitget.payment.dto; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.payment.domain.Payment; +import java.util.UUID; import lombok.Builder; import lombok.Data; @@ -17,4 +20,16 @@ public PaymentRequest(Long amount, String orderName, Long pointAmount, String us this.pointAmount = pointAmount; this.userEmail = userEmail; } + + public Payment paymentRequestToEntity(User user, PaymentRequest paymentRequest) { + return Payment.builder() + .orderId(UUID.randomUUID().toString()) + .amount(paymentRequest.getAmount()) + .orderName(paymentRequest.getOrderName()) + .pointAmount(paymentRequest.getPointAmount()) + .user(user) + .isSuccess(false) + .failReason("") + .build(); + } } diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java b/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java index c4f9d711..8f6c4b27 100644 --- a/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java +++ b/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java @@ -1,5 +1,6 @@ package com.genius.gitget.payment.dto; +import com.genius.gitget.payment.domain.Payment; import lombok.Builder; import lombok.Data; @@ -19,4 +20,15 @@ public PaymentResponse(Long amount, Long pointAmount, String orderName, String o this.orderId = orderId; this.userEmail = userEmail; } + + public static PaymentResponse createByEntity(Payment payment) { + + return PaymentResponse.builder() + .amount(payment.getAmount()) + .pointAmount(payment.getPointAmount()) + .orderName(payment.getOrderName()) + .orderId(payment.getOrderId()) + .userEmail(payment.getUser().getIdentifier()) + .build(); + } } diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java b/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java index de888ae0..0204b5c9 100644 --- a/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java +++ b/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java @@ -1,6 +1,7 @@ package com.genius.gitget.payment.dto; +import com.genius.gitget.payment.domain.Payment; import lombok.Builder; import lombok.Data; @@ -26,4 +27,15 @@ public PaymentSuccessResponse(String orderId, String paymentKey, Long amount, Lo this.isSuccess = String.valueOf(isSuccess); this.failReason = failReason; } + + public static PaymentSuccessResponse createByEntity(Payment payment) { + return PaymentSuccessResponse.builder() + .paymentKey(payment.getPaymentKey()) + .amount(payment.getAmount()) + .orderName(payment.getOrderName()) + .pointAmount(payment.getPointAmount()) + .orderId(payment.getOrderId()) + .isSuccess(payment.isSuccess()) + .build(); + } } diff --git a/src/main/java/com/genius/gitget/payment/service/PaymentService.java b/src/main/java/com/genius/gitget/payment/service/PaymentService.java index 10e14cb7..b36d9352 100644 --- a/src/main/java/com/genius/gitget/payment/service/PaymentService.java +++ b/src/main/java/com/genius/gitget/payment/service/PaymentService.java @@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.HashMap; -import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.json.simple.JSONObject; @@ -41,63 +40,24 @@ public class PaymentService { private final UserRepository userRepository; private final TossPaymentConfig tossPaymentConfig; - private static Payment getPayment(PaymentRequest paymentRequest, User findByEmailUser) { - return Payment.builder() - .orderId(UUID.randomUUID().toString()) - .amount(paymentRequest.getAmount()) - .orderName(paymentRequest.getOrderName()) - .pointAmount(paymentRequest.getPointAmount()) - .user(findByEmailUser) - .isSuccess(false) - .failReason("") - .build(); - } - - private static PaymentSuccessResponse getPaymentSuccessResponse(Payment payment) { - return PaymentSuccessResponse.builder() - .paymentKey(payment.getPaymentKey()) - .amount(payment.getAmount()) - .orderName(payment.getOrderName()) - .pointAmount(payment.getPointAmount()) - .orderId(payment.getOrderId()) - .isSuccess(payment.isSuccess()) - .build(); - } - - public static PaymentResponse getPaymentResponse(Payment payment) { - return PaymentResponse.builder() - .amount(payment.getAmount()) - .pointAmount(payment.getPointAmount()) - .orderName(payment.getOrderName()) - .orderId(payment.getOrderId()) - .userEmail(payment.getUser().getIdentifier()) - .build(); - } - @Transactional - public PaymentResponse requestTossPayment(PaymentRequest paymentRequest) { - - Payment paymentRequestToEntity = paymentRequestToEntity(paymentRequest); + public PaymentResponse requestTossPayment(User user, PaymentRequest paymentRequest) { + User findUser = userRepository.findByIdentifier(user.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - if (paymentRequestToEntity.getAmount() < 100L) { + if (!findUser.getIdentifier().equals(paymentRequest.getUserEmail())) { + throw new BusinessException(ErrorCode.MEMBER_NOT_FOUND); + } + if (paymentRequest.getAmount() < 100L) { throw new BusinessException(ErrorCode.FAILED_POINT_PAYMENT); } if (!(paymentRequest.getAmount() == 1000L || paymentRequest.getAmount() == 3000L || paymentRequest.getAmount() == 5000L || paymentRequest.getAmount() == 7000L)) { throw new BusinessException(ErrorCode.FAILED_POINT_PAYMENT); } - - Payment savedPayment = paymentRepository.save(paymentRequestToEntity); - - return getPaymentResponse(savedPayment); - } - - private Payment paymentRequestToEntity(PaymentRequest paymentRequest) { - User findByEmailUser = userRepository.findByIdentifier(paymentRequest.getUserEmail()) - .orElseThrow(() -> new BusinessException( - ErrorCode.MEMBER_NOT_FOUND)); - - return getPayment(paymentRequest, findByEmailUser); + Payment requestToEntity = paymentRequest.paymentRequestToEntity(user, paymentRequest); + Payment savedPayment = paymentRepository.save(requestToEntity); + return PaymentResponse.createByEntity(savedPayment); } @Transactional @@ -165,17 +125,15 @@ public PaymentSuccessResponse requestPaymentAccept(PaymentSuccessRequest payment throw new BusinessException(ErrorCode.FAILED_FINAL_PAYMENT); } - return getPaymentSuccessResponse(payment); + return PaymentSuccessResponse.createByEntity(payment); } public Payment verifyPayment(String orderId, Long amount) { Payment payment = paymentRepository.findByOrderId(orderId).orElseThrow(() -> new BusinessException( ErrorCode.MEMBER_NOT_FOUND)); - if (amount < 100L) { throw new BusinessException(ErrorCode.FAILED_POINT_PAYMENT); } - if (payment.getAmount().equals(amount)) { Long pointAmount = payment.getPointAmount(); if (pointAmount == (amount / 10L) && (pointAmount * 10L) == amount) { diff --git a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java new file mode 100644 index 00000000..5ae75245 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java @@ -0,0 +1,127 @@ +package com.genius.gitget.profile.controller; + +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.exception.SuccessCode; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import com.genius.gitget.profile.dto.UserChallengeResultResponse; +import com.genius.gitget.profile.dto.UserDetailsInformationResponse; +import com.genius.gitget.profile.dto.UserInformationRequest; +import com.genius.gitget.profile.dto.UserInformationResponse; +import com.genius.gitget.profile.dto.UserInformationUpdateRequest; +import com.genius.gitget.profile.dto.UserInterestResponse; +import com.genius.gitget.profile.dto.UserPointResponse; +import com.genius.gitget.profile.dto.UserSignoutRequest; +import com.genius.gitget.profile.dto.UserTagsUpdateRequest; +import com.genius.gitget.profile.service.ProfileService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/profile") +public class ProfileController { + private final ProfileService profileService; + + // TODO 마이페이지 - 결제 내역 조회 + + // 마이페이지 - 사용자 상세 정보 조회 + @GetMapping + public ResponseEntity> getUserDetailsInformation( + @AuthenticationPrincipal + UserPrincipal userPrincipal) { + UserDetailsInformationResponse userInformation = profileService.getUserDetailsInformation( + userPrincipal.getUser()); + return ResponseEntity.ok() + .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + userInformation) + ); + } + + // 사용자 정보 조회 + @PostMapping + public ResponseEntity> getUserInformation( + @RequestBody UserInformationRequest userInformationRequest) { + UserInformationResponse userInformation = profileService.getUserInformation(userInformationRequest.getUserId()); + return ResponseEntity.ok() + .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + userInformation) + ); + } + + // 마이페이지 - 회원 정보 수정 + @PostMapping("/information") + public ResponseEntity updateUserInformation(@AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestPart(value = "data") UserInformationUpdateRequest userInformationUpdateRequest, + @RequestPart(value = "files", required = false) MultipartFile multipartFile, + @RequestPart(value = "type") String type) { + profileService.updateUserInformation(userPrincipal.getUser(), userInformationUpdateRequest, multipartFile, + type); + + return ResponseEntity.ok() + .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); + } + + // 마이페이지 - 관심사 조회 + @GetMapping("/interest") + public ResponseEntity> getUserInterest( + @AuthenticationPrincipal UserPrincipal userPrincipal) { + UserInterestResponse userInterest = profileService.getUserInterest(userPrincipal.getUser()); + + return ResponseEntity.ok() + .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + userInterest)); + } + + + // 마이페이지 - 관심사 수정 + @PostMapping("/interest") + public ResponseEntity updateUserTags(@AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody UserTagsUpdateRequest userTagsUpdateRequest) { + profileService.updateUserTags(userPrincipal.getUser(), userTagsUpdateRequest); + + return ResponseEntity.ok() + .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); + } + + // 마이페이지 - 챌린지 현황 + @GetMapping("/challenges") + public ResponseEntity> getUserChallengeResult( + @AuthenticationPrincipal UserPrincipal userPrincipal) { + UserChallengeResultResponse userChallengeResult = profileService.getUserChallengeResult( + userPrincipal.getUser()); + return ResponseEntity.ok() + .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + userChallengeResult)); + } + + // 마이페이지 - 탈퇴하기 + @DeleteMapping + public ResponseEntity deleteUserInformation(@AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody UserSignoutRequest userSignoutRequest) { + profileService.deleteUserInformation(userPrincipal.getUser(), userSignoutRequest.getReason()); + + return ResponseEntity.ok() + .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); + } + + // 포인트 조회 + @GetMapping("/point") + public ResponseEntity> getUserPoint( + @AuthenticationPrincipal UserPrincipal userPrincipal) { + UserPointResponse userPoint = profileService.getUserPoint(userPrincipal.getUser()); + + return ResponseEntity.ok() + .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + userPoint)); + } +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserChallengeResultResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserChallengeResultResponse.java new file mode 100644 index 00000000..4c4278e7 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserChallengeResultResponse.java @@ -0,0 +1,20 @@ +package com.genius.gitget.profile.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +public class UserChallengeResultResponse { + private Integer fail; + private Integer success; + private Integer processing; + private Integer beforeStart; + + @Builder + public UserChallengeResultResponse(Integer fail, Integer success, Integer processing, Integer beforeStart) { + this.fail = fail; + this.success = success; + this.processing = processing; + this.beforeStart = beforeStart; + } +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserDetailsInformationResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserDetailsInformationResponse.java new file mode 100644 index 00000000..f3a71885 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserDetailsInformationResponse.java @@ -0,0 +1,48 @@ +package com.genius.gitget.profile.dto; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import java.util.Optional; +import lombok.Builder; +import lombok.Data; + +@Data +public class UserDetailsInformationResponse { + private String identifier; + private String nickname; + private String information; + private Long point; + private int progressBar; + private FileResponse fileResponse; + + @Builder + public UserDetailsInformationResponse(String identifier, String nickname, String information, Long point, + Files files, + int progressBar) { + this.identifier = identifier; + this.nickname = nickname; + this.information = information; + this.point = point; + this.fileResponse = convertToFileResponse(Optional.ofNullable(files)); + this.progressBar = progressBar; + } + + public static UserDetailsInformationResponse createByEntity(User findUser, Files files, int participantCount) { + return UserDetailsInformationResponse.builder() + .identifier(findUser.getIdentifier()) + .nickname(findUser.getNickname()) + .information(findUser.getInformation()) + .point(findUser.getPoint()) + .files(files) + .progressBar(participantCount) + .build(); + } + + private static FileResponse convertToFileResponse(Optional files) { + if (files.isEmpty()) { + return FileResponse.createNotExistFile(); + } + return FileResponse.createExistFile(files.get()); + } +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserInformationRequest.java b/src/main/java/com/genius/gitget/profile/dto/UserInformationRequest.java new file mode 100644 index 00000000..b00360ab --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserInformationRequest.java @@ -0,0 +1,8 @@ +package com.genius.gitget.profile.dto; + +import lombok.Data; + +@Data +public class UserInformationRequest { + private Long userId; +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java new file mode 100644 index 00000000..70a984bd --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java @@ -0,0 +1,37 @@ +package com.genius.gitget.profile.dto; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import java.util.Optional; +import lombok.Builder; +import lombok.Data; + +@Data +public class UserInformationResponse { + private String identifier; + private String nickname; + private FileResponse fileResponse; + + @Builder + public UserInformationResponse(String identifier, String nickname, Files files) { + this.identifier = identifier; + this.nickname = nickname; + this.fileResponse = convertToFileResponse(Optional.ofNullable(files)); + } + + public static UserInformationResponse createByEntity(User findUser, Files files) { + return UserInformationResponse.builder() + .identifier(findUser.getIdentifier()) + .nickname(findUser.getNickname()) + .files(files) + .build(); + } + + private static FileResponse convertToFileResponse(Optional files) { + if (files.isEmpty()) { + return FileResponse.createNotExistFile(); + } + return FileResponse.createExistFile(files.get()); + } +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserInformationUpdateRequest.java b/src/main/java/com/genius/gitget/profile/dto/UserInformationUpdateRequest.java new file mode 100644 index 00000000..423e4630 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserInformationUpdateRequest.java @@ -0,0 +1,16 @@ +package com.genius.gitget.profile.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +public class UserInformationUpdateRequest { + private String nickname; + private String information; + + @Builder + public UserInformationUpdateRequest(String nickname, String information) { + this.nickname = nickname; + this.information = information; + } +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserInterestResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserInterestResponse.java new file mode 100644 index 00000000..f7e63691 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserInterestResponse.java @@ -0,0 +1,15 @@ +package com.genius.gitget.profile.dto; + +import java.util.List; +import lombok.Builder; +import lombok.Data; + +@Data +public class UserInterestResponse { + private List tags; + + @Builder + public UserInterestResponse(List tags) { + this.tags = tags; + } +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserPointResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserPointResponse.java new file mode 100644 index 00000000..8b2adae5 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserPointResponse.java @@ -0,0 +1,16 @@ +package com.genius.gitget.profile.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +public class UserPointResponse { + private String identifier; + private Long point; + + @Builder + public UserPointResponse(String identifier, Long point) { + this.identifier = identifier; + this.point = point; + } +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java b/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java new file mode 100644 index 00000000..7b885475 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java @@ -0,0 +1,14 @@ +package com.genius.gitget.profile.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +public class UserSignoutRequest { + private String reason; + + @Builder + public UserSignoutRequest(String reason) { + this.reason = reason; + } +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserTagsUpdateRequest.java b/src/main/java/com/genius/gitget/profile/dto/UserTagsUpdateRequest.java new file mode 100644 index 00000000..6d601ecc --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserTagsUpdateRequest.java @@ -0,0 +1,15 @@ +package com.genius.gitget.profile.dto; + +import java.util.List; +import lombok.Builder; +import lombok.Data; + +@Data +public class UserTagsUpdateRequest { + private List tags; + + @Builder + public UserTagsUpdateRequest(List tags) { + this.tags = tags; + } +} diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileService.java new file mode 100644 index 00000000..28330661 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/service/ProfileService.java @@ -0,0 +1,203 @@ +package com.genius.gitget.profile.service; + +import static com.genius.gitget.challenge.participantinfo.domain.JoinResult.FAIL; +import static com.genius.gitget.challenge.participantinfo.domain.JoinResult.PROCESSING; +import static com.genius.gitget.challenge.participantinfo.domain.JoinResult.SUCCESS; +import static com.genius.gitget.challenge.participantinfo.domain.JoinStatus.YES; + +import com.genius.gitget.admin.signout.Signout; +import com.genius.gitget.admin.signout.SignoutRepository; +import com.genius.gitget.challenge.participantinfo.domain.JoinResult; +import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.repository.FilesRepository; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.profile.dto.UserChallengeResultResponse; +import com.genius.gitget.profile.dto.UserDetailsInformationResponse; +import com.genius.gitget.profile.dto.UserInformationResponse; +import com.genius.gitget.profile.dto.UserInformationUpdateRequest; +import com.genius.gitget.profile.dto.UserInterestResponse; +import com.genius.gitget.profile.dto.UserPointResponse; +import com.genius.gitget.profile.dto.UserTagsUpdateRequest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class ProfileService { + private final UserRepository userRepository; + private final FilesRepository filesRepository; + private final FilesService filesService; + private final SignoutRepository signoutRepository; + + // 포인트 조회 + public UserPointResponse getUserPoint(User user) { + return UserPointResponse.builder() + .identifier(user.getIdentifier()) + .point(user.getPoint()) + .build(); + } + + // 사용자 정보 조회 + public UserInformationResponse getUserInformation(Long userId) { + User findUser = getUserById(userId); + Files files = getFiles(findUser); + if (isProfileFileType(files)) { + return UserInformationResponse.createByEntity(findUser, files); + } else { + return UserInformationResponse.createByEntity(findUser, null); + } + } + + // 마이페이지 - 사용자 정보 상세 조회 + public UserDetailsInformationResponse getUserDetailsInformation(User user) { + User findUser = getUserByIdentifier(user.getIdentifier()); + int participantCount = 0; + List participantInfoList = findUser.getParticipantInfoList(); + + for (int i = 0; i < participantInfoList.size(); i++) { + if (participantInfoList.get(i).getJoinStatus() == YES) { + JoinResult joinResult = participantInfoList.get(i).getJoinResult(); + participantCount = (joinResult == SUCCESS) ? participantCount + 1 : participantCount - 1; + } + } + Files files = getFiles(findUser); + if (isProfileFileType(files)) { + return UserDetailsInformationResponse.createByEntity(findUser, files, participantCount); + } else { + return UserDetailsInformationResponse.createByEntity(findUser, null, participantCount); + } + } + + // 마이페이지 - 사용자 정보 수정 + @Transactional + public void updateUserInformation(User user, UserInformationUpdateRequest userInformationUpdateRequest, + MultipartFile multipartFile, String type) { + User findUser = getUserByIdentifier(user.getIdentifier()); + findUser.updateUserInformation( + userInformationUpdateRequest.getNickname(), + userInformationUpdateRequest.getInformation()); + + if (multipartFile != null) { + if (findUser.getFiles() == null) { + Files uploadedFile = filesService.uploadFile(multipartFile, type); + findUser.setFiles(uploadedFile); + } else { + filesService.updateFile(findUser.getFiles().getId(), multipartFile); + } + } + userRepository.save(findUser); + } + + // 마이페이지 - 회원 탈퇴 + @Transactional + public void deleteUserInformation(User user, String reason) { + User findUser = getUserByIdentifier(user.getIdentifier()); + findUser.setFiles(null); + findUser.deleteLikesList(); + userRepository.deleteById(findUser.getId()); + signoutRepository.save( + Signout.builder() + .identifier(user.getIdentifier()) + .reason(reason) + .build() + ); + } + + // 마이페이지 - 관심사 수정 + @Transactional + public void updateUserTags(User user, UserTagsUpdateRequest userTagsUpdateRequest) { + if (userTagsUpdateRequest.getTags() == null) { + throw new BusinessException(); + } + User findUser = getUserByIdentifier(user.getIdentifier()); + String interest = String.join(",", userTagsUpdateRequest.getTags()); + findUser.updateUserTags(interest); + userRepository.save(findUser); + } + + // 관심사 조회 + public UserInterestResponse getUserInterest(User user) { + String tags = user.getTags(); + String[] tagsList = tags.split(","); + for (int i = 0; i < tagsList.length; i++) { + tagsList[i] = tagsList[i].trim(); + } + List interestList = new ArrayList<>(Arrays.asList(tagsList)); + return UserInterestResponse.builder() + .tags(interestList) + .build(); + } + + // 마이페이지 - 챌린지 현황 + public UserChallengeResultResponse getUserChallengeResult(User user) { + User findUser = getUserByIdentifier(user.getIdentifier()); + HashMap> participantHashMap = new HashMap<>() { + { + put(FAIL, new ArrayList<>()); + put(PROCESSING, new ArrayList<>()); + put(SUCCESS, new ArrayList<>()); + } + }; + List participantInfoList = findUser.getParticipantInfoList(); // 유저의 참여 정보를 담고 있는 리스트 + int participanTotalCount = participantInfoList.size(); + + for (int i = 0; i < participantInfoList.size(); i++) { // 각 참여 정보를 받아옴. + if (participantInfoList.get(i).getJoinStatus() == YES) { + JoinResult joinResult = participantInfoList.get(i).getJoinResult(); + if (participantHashMap.containsKey(joinResult)) { // hashmap에 저장된 key와 일치 여부 확인 + List participantIdList = participantHashMap.get(joinResult); + participantIdList.add(participantInfoList.get(i).getId()); // 일치한다면, 해당 key의 value인 list에 id 저장 + participantHashMap.put(joinResult, participantIdList); // 최종적으로 hashmap에 저장 + } + } + } + + int fail = participantHashMap.get(FAIL).size(); + int success = participantHashMap.get(SUCCESS).size(); + int processing = participantHashMap.get(PROCESSING).size(); + + return UserChallengeResultResponse.builder() + .fail(fail) + .success(success) + .processing(processing) + .beforeStart(participanTotalCount - fail - success - processing) + .build(); + } + + private User getUserByIdentifier(String identifier) { + return userRepository.findByIdentifier(identifier) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + } + + private Files getFiles(User findUser) { + if (findUser.getFiles() != null) { + return filesRepository.findById(findUser.getFiles().getId()).orElse(null); + } else { + return null; + } + } + + private User getUserById(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + } + + private static boolean isProfileFileType(Files files) { + return files != null && files.getFileType().equals(FileType.PROFILE); + } +} diff --git a/src/test/java/com/genius/gitget/GitgetApplicationTests.java b/src/test/java/com/genius/gitget/GitgetApplicationTests.java index e578a2f4..77159c82 100644 --- a/src/test/java/com/genius/gitget/GitgetApplicationTests.java +++ b/src/test/java/com/genius/gitget/GitgetApplicationTests.java @@ -1,14 +1,21 @@ package com.genius.gitget; +import com.genius.gitget.challenge.user.repository.UserRepository; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; @SpringBootTest +@Sql({"/data.sql"}) class GitgetApplicationTests { + @Autowired + private UserRepository userRepository; + @Test - void contextLoads() { + void test() { } } diff --git a/src/test/java/com/genius/gitget/challenge/hits/HitsTest.java b/src/test/java/com/genius/gitget/challenge/likes/LikesTest.java similarity index 88% rename from src/test/java/com/genius/gitget/challenge/hits/HitsTest.java rename to src/test/java/com/genius/gitget/challenge/likes/LikesTest.java index 205f757c..ac8bf0cf 100644 --- a/src/test/java/com/genius/gitget/challenge/hits/HitsTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/LikesTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.hits; +package com.genius.gitget.challenge.likes; import static com.genius.gitget.challenge.user.domain.Role.ADMIN; import static com.genius.gitget.challenge.user.domain.Role.USER; @@ -6,11 +6,11 @@ import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.challenge.hits.domain.Hits; -import com.genius.gitget.challenge.hits.repository.HitsRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.domain.Likes; +import com.genius.gitget.challenge.likes.repository.LikesRepository; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; @@ -24,14 +24,14 @@ @SpringBootTest @Transactional -public class HitsTest { +public class LikesTest { @Autowired UserRepository userRepository; @Autowired InstanceRepository instanceRepository; @Autowired - HitsRepository hitsRepository; + LikesRepository likesRepository; @Autowired TopicRepository topicRepository; @@ -84,10 +84,10 @@ public void setup() { @Test public void 사용자는_챌린지의_인스턴스를_관심목록에_저장한다() { - Hits like = new Hits(user1, instance1); - hitsRepository.save(like); + Likes like = new Likes(user1, instance1); + likesRepository.save(like); - int likeCount = instance1.getHitsList().size(); + int likeCount = instance1.getLikesList().size(); Assertions.assertEquals(1, likeCount); } diff --git a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java new file mode 100644 index 00000000..88132ffa --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java @@ -0,0 +1,208 @@ +package com.genius.gitget.challenge.likes.service; + +import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.domain.Likes; +import com.genius.gitget.challenge.likes.dto.UserLikesResponse; +import com.genius.gitget.challenge.likes.repository.LikesRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.repository.FilesRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.time.LocalDateTime; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Rollback +class LikesServiceTest { + static User user1; + static Topic topic1; + static Instance instance1, instance2, instance3; + static Files files1, files2, files3, files4; + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + LikesRepository likesRepository; + @Autowired + LikesService likesService; + @Autowired + FilesRepository filesRepository; + + @BeforeEach + void setup() { + files1 = getSavedFiles("originalFileName", "savedFileName", "fileURL", FileType.INSTANCE); + files2 = getSavedFiles("originalFileName", "savedFileName", "fileURL", FileType.TOPIC); + + user1 = getSavedUser("neo5188@gmail.com", GITHUB, "kimdozzi"); + + topic1 = getSavedTopic("1일 1커밋", "BE"); + + instance1 = getSavedInstance("1일 1커밋", "BE", 50, 100); + instance2 = getSavedInstance("1일 1커밋", "BE", 50, 150); + instance3 = getSavedInstance("1일 1알고리즘", "CS,BE,FE", 50, 200); + + //== 연관관계 ==// + instance1.setTopic(topic1); + instance2.setTopic(topic1); + instance3.setTopic(topic1); + +// topic1.setFiles(files1); +// topicRepository.save(topic1); +// +// instance1.setFiles(files2); +// instanceRepository.save(instance1); +// instance2.setFiles(files4); +// instanceRepository.save(instance2); +// +// user1.setFiles(files3); +// userRepository.save(user1); + + Likes likes1 = new Likes(user1, instance1); + Likes likes2 = new Likes(user1, instance2); + Likes likes3 = new Likes(user1, instance3); + likesRepository.save(likes1); + likesRepository.save(likes2); + likesRepository.save(likes3); + + } + + + @Test + void 유저_좋아요_목록_추가() { + List all = likesRepository.findAll(); + int cnt = 0; + for (Likes likes : all) { + if (likes.getUser().getIdentifier().equals("neo5188@gmail.com") && likes.getInstance().getTitle() + .equals("1일 1커밋")) { + cnt++; + } + } + Assertions.assertThat(all.size() - 1).isEqualTo(cnt); + } + + @Test + void 유저_좋아요_목록_삭제() { + List likes = likesRepository.findAll(); + Long likesId = likes.get(0).getId(); + + likesService.deleteLikes(user1, likesId); + org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, + () -> likesRepository.findById(likesId) + .orElseThrow(() -> new BusinessException(ErrorCode.LIKES_NOT_FOUND))); + + List all = likesRepository.findAll(); + + Assertions.assertThat(all.size()).isEqualTo(2); + } + + @Test + void 유저는_좋아요목록을_조회할_수_있다1() { + List all = likesRepository.findAll(); + + for (int i = 0; i < all.size(); i++) { + if (i <= 1) { + assertThat(all.get(i).getInstance().getTitle()).isEqualTo("1일 1커밋"); + } else { + assertThat(all.get(i).getInstance().getTitle()).isEqualTo("1일 1알고리즘"); + } + } + assertThat(all.size()).isEqualTo(3); + } + + @Test + void 유저는_좋아요목록을_조회할_수_있다2() { + + PageRequest pageRequest = PageRequest.of(0, 5); + Page likesResponses = likesService.getLikesList(user1, pageRequest); + for (UserLikesResponse likesResponse : likesResponses) { + System.out.println(likesResponse.getInstanceId() + " " + likesResponse.getTitle() + " " + + likesResponse.getPointPerPerson()); + } + assertThat(likesResponses.getContent().size()).isEqualTo(3); + assertThat(likesResponses.getContent().get(0).getTitle()).isEqualTo("1일 1커밋"); + assertThat(likesResponses.getContent().get(0).getPointPerPerson()).isEqualTo(100); + + assertThat(likesResponses.getContent().get(1).getTitle()).isEqualTo("1일 1커밋"); + assertThat(likesResponses.getContent().get(1).getPointPerPerson()).isEqualTo(150); + + assertThat(likesResponses.getContent().get(2).getTitle()).isEqualTo("1일 1알고리즘"); + assertThat(likesResponses.getContent().get(2).getPointPerPerson()).isEqualTo(200); + } + + + private User getSavedUser(String identifier, ProviderInfo providerInfo, String nickname) { + return userRepository.save( + User.builder() + .identifier(identifier) + .providerInfo(providerInfo) + .role(Role.ADMIN) + .nickname(nickname) + .build() + ); + } + + private Topic getSavedTopic(String title, String tags) { + return topicRepository.save( + Topic.builder() + .title(title) + .tags(tags) + .description("토픽 설명") + .pointPerPerson(100) + .build() + ); + } + + private Instance getSavedInstance(String title, String tags, int participantCnt, int pointPerPerson) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(pointPerPerson) + .certificationMethod("인증 방법") + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } + + private Files getSavedFiles(String originalFilename, String savedFilename, String fileURL, FileType fileType) { + return filesRepository.save( + Files.builder() + .originalFilename(originalFilename) + .savedFilename(savedFilename) + .fileURI(fileURL) + .fileType(fileType) + .build() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java b/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java index be99cf9b..caba1ac9 100644 --- a/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java @@ -12,10 +12,12 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; @SpringBootTest @Transactional +@Rollback public class UserTest { @Autowired @@ -41,9 +43,6 @@ public class UserTest { User savedUser2 = userRepository.save(userB()); List users = userRepository.findAll(); - for (User user : users) { - System.out.println("user = " + user); - } assertThat(count(users)).isEqualTo(2); assertThat(savedUser1).isNotSameAs(savedUser2); } diff --git a/src/test/java/com/genius/gitget/mock/MockTest.java b/src/test/java/com/genius/gitget/mock/MockTest.java deleted file mode 100644 index d254bbf9..00000000 --- a/src/test/java/com/genius/gitget/mock/MockTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.genius.gitget.mock; - - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -import java.util.List; - -import static org.mockito.Mockito.*; - -@SpringBootTest -public class MockTest { - - @Test - public void Mock_Test() { - List mockList = mock(List.class); - when(mockList.get(anyInt())).thenReturn("first"); - System.out.println(mockList.get(999)); - verify(mockList).get(anyInt()); - } -} diff --git a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java new file mode 100644 index 00000000..75cb0923 --- /dev/null +++ b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java @@ -0,0 +1,210 @@ +package com.genius.gitget.profile.service; + +import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.genius.gitget.admin.signout.Signout; +import com.genius.gitget.admin.signout.SignoutRepository; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.domain.Likes; +import com.genius.gitget.challenge.likes.repository.LikesRepository; +import com.genius.gitget.challenge.likes.service.LikesService; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.profile.dto.UserDetailsInformationResponse; +import com.genius.gitget.profile.dto.UserInformationResponse; +import com.genius.gitget.profile.dto.UserInformationUpdateRequest; +import com.genius.gitget.profile.dto.UserInterestResponse; +import com.genius.gitget.profile.dto.UserPointResponse; +import com.genius.gitget.profile.dto.UserTagsUpdateRequest; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Rollback +public class ProfileServiceTest { + static User user1, user2; + static Topic topic1; + static Instance instance1, instance2, instance3; + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + LikesRepository likesRepository; + @Autowired + LikesService likesService; + @Autowired + ProfileService profileService; + @Autowired + SignoutRepository signoutRepository; + + @BeforeEach + void setup() { + user1 = getSavedUser("neo5188@gmail.com", GITHUB, "alias1"); + user2 = getSavedUser("neo7269@naver.com", GITHUB, "alias2"); + + topic1 = getSavedTopic("1일 1커밋", "BE"); + + instance1 = getSavedInstance("1일 1커밋", "BE", 50); + instance2 = getSavedInstance("1일 1커밋", "BE", 100); + instance3 = getSavedInstance("1일 1알고리즘", "CS,BE,FE", 500); + + //== 연관관계 ==// + instance1.setTopic(topic1); + instance2.setTopic(topic1); + instance3.setTopic(topic1); + + Likes likes1 = new Likes(user1, instance1); + Likes likes2 = new Likes(user1, instance2); + Likes likes3 = new Likes(user1, instance3); + likesRepository.save(likes1); + likesRepository.save(likes2); + likesRepository.save(likes3); + } + + // TODO 챌린지 현황 조회 -> 코드 병합 후 테스트할 것 + + @Test + void 유저_상세_조회() { + UserDetailsInformationResponse userDetailsInformation = profileService.getUserDetailsInformation(user1); + Assertions.assertThat(userDetailsInformation.getIdentifier()).isEqualTo("neo5188@gmail.com"); + } + + @Test + void 유저_조회() { + List userIdList = new ArrayList<>(); + List all = userRepository.findAll(); + for (User user : all) { + Long id = user.getId(); + userIdList.add(id); + } + + for (int i = userIdList.size() - 1; i >= 0; i--) { + UserInformationResponse userInformation = profileService.getUserInformation(userIdList.get(i)); + Assertions.assertThat(userInformation.getNickname()).isEqualTo("alias" + (i + 1)); + } + } + + @Test + void 유저_정보_수정() { + profileService.updateUserInformation(user1, + UserInformationUpdateRequest.builder() + .nickname("수정된 nickname") + .information("수정된 information") + .build(), null, "profile"); + + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + + Assertions.assertThat(user.getNickname()).isEqualTo("수정된 nickname"); + } + + @Test + void 유저_관심사_조회() { + UserInterestResponse userInterest = profileService.getUserInterest(user1); + List tags = userInterest.getTags(); + String join = String.join(",", tags); + Assertions.assertThat(join).isEqualTo("BE,FE"); + } + + @Test + void 유저_관심사_수정() { + profileService.updateUserTags(user1, + UserTagsUpdateRequest.builder().tags(new ArrayList<>(Arrays.asList("FE", "BE"))).build()); + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + Assertions.assertThat(user.getTags()).isEqualTo("FE,BE"); + } + + + @Test + void 회원_탈퇴() { + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + String userIdentifier = user.getIdentifier(); + profileService.deleteUserInformation(user, "서비스 이용 불편"); + + assertThrows(BusinessException.class, + () -> userRepository.findByIdentifier(userIdentifier) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND))); + + Signout byIdentifier = signoutRepository.findByIdentifier(userIdentifier); + + Assertions.assertThat(byIdentifier.getReason()).isEqualTo("서비스 이용 불편"); + } + + @Test + void 유저_포인트_조회() { + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + user.setPoint(1500L); + userRepository.save(user); + UserPointResponse userPoint = profileService.getUserPoint(user1); + Assertions.assertThat(userPoint.getPoint()).isEqualTo(1500); + } + + + private User getSavedUser(String identifier, ProviderInfo providerInfo, String nickname) { + User user = userRepository.save( + User.builder() + .identifier(identifier) + .providerInfo(providerInfo) + .role(Role.ADMIN) + .tags("BE,FE") + .nickname(nickname) + .build() + ); + return user; + } + + private Topic getSavedTopic(String title, String tags) { + Topic topic = topicRepository.save( + Topic.builder() + .title(title) + .tags(tags) + .description("토픽 설명") + .pointPerPerson(100) + .build() + ); + return topic; + } + + private Instance getSavedInstance(String title, String tags, int participantCnt) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(100) + .certificationMethod("인증 방법") + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } +} diff --git a/src/test/resources/data.sql b/src/test/resources/data.sql new file mode 100644 index 00000000..00553f51 --- /dev/null +++ b/src/test/resources/data.sql @@ -0,0 +1,20 @@ +# User insert문 +insert into user (created_at, deleted_at, files_id, point, updated_at, nickname, information, identifier, tags, + provider_info, role) +values ('2023-03-04 11:01:48.594485', null, null, 1500, null, 'dohyungKim', '저는 백엔드 개발자입니다.', 'kimdozzi', 'BE, CS', + 'GITHUB', 'ADMIN'); +insert into user (created_at, deleted_at, files_id, point, updated_at, nickname, information, identifier, tags, + provider_info, role) +values ('2023-03-02 20:14:10.594123', null, null, 2500, null, 'yujinKim', '안녕하세요~ 저는 김유진이라고 합니다 잘부탁드려요 ^^.', 'yujin', + 'BE, CS, AI', + 'GITHUB', 'USER'); +insert into user (created_at, deleted_at, files_id, point, updated_at, nickname, information, identifier, tags, + provider_info, role) +values ('2023-03-01 07:01:00.600000', null, null, 50000, null, 'minsuPark', '', 'minsu', 'FE', + 'GITHUB', 'NOT_REGISTERED'); + + +# Topic insert문 +insert into topic (point_per_person, created_at, deleted_at, files_id, updated_at, description, notice, tags, title) +values (100, null, null, null, null, '1일 1알고리즘 챌린지입니다.', '85%이상 참여 시 포인트가 발행됩니다.', 'BE', '1일 1알고리즘 챌린지'); + From 71bbc4a516248cb8f4cc8e3dcb22b87b29afe409 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 9 Mar 2024 23:12:13 +0900 Subject: [PATCH 119/234] =?UTF-8?q?[FEAT]=20GITHUB=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20(#98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Github API for Java 의존성 추가 * feat: Github 토큰을 통해 깃허브 연결이 유효한지 확인하는 기능 추가 - 개인이 발급한 Github Personal Access Token을 전달받았을 때, 연결이 유효한지 확인하는 로직 추가 - 소셜로그인에 사용한 깃허브 계정과 같은지 확인 - 연결 과정에서 예외가 발생한 경우 BusinessException 발생 * feat: 깃허브 레포지토리 연결을 검증하는 서비스 코드 구현 - 깃허브 레포지토리 연결을 검증하는 서비스 코드 구현 - 위의 로직을 검증하는 테스트 코드 추가 * feat: 인증 관련 엔티티 작성 - Certification 엔티티 작성: 인증 현황, 인증 일자, 인증링크, 참여정보와 연관관계 설정 - User 엔티티에 Column 추가: 깃허브 personal access token을 암호화해서 저장할 column 추가 * feat: Certification repository 생성 - 인증 정보를 담고 있는 Certification 엔티티의 Repository 추가 - 리포지토리 관련 테스트 코드 작성 * feat: 암호화/복호화 유틸 개발 - Github Personal Access Token를 암호화/복호화할 때 사용할 유틸 클래스 개발 - 테스트 코드 작성 * feat: Github personal token 등록 로직 개발 - 사용자가 발급한 Github personal token을 등록하는 로직 구현 - 소셜로그인 시 사용했던 깃허브 계정과 일치하는지 확인 - DB에 암호화하여 저장 - 테스트 코드 작성 * feat: Github Repository 등록 기능 구현 - 레포지토리 등록 기능 구현 - Github Repository 등록 시 깃허브 토큰/레포지토리/인스턴스 관련 예외 처리 - 컨트롤러 관련 테스트 코드 작성 필요 * test: 인증 컨트롤러 테스트 코드 추가 * feat: 사용자 DB로부터 깃허브 토큰 받아오는 로직 추가 - UserService에 사용자 DB로부터 깃허브 토큰을 받아와 검증 및 복호화하여 반환하는 로직 추가 - 테스트 코드 작성 * feat: 참여정보를 저장한 DB로부터 레포지토리 이름 받아오는 기능 구현 - 참여정보를 저장한 DB로부터 저장해놓은 레포지토리 이름 받아오는 기능 구현 - 혼동을 막기 위해 ErrorCode의 에러 메세지 수정 * feat: 특정 조건에 충족하는 Pull Request를 가져오는 기능 구현 - 특정 Repository의 특정 일자에 존재하는 Pull Request 리스트들을 받아오는 기능 구현 - PR을 받아오는 코드 및 예외에 대한 테스트 코드 작성 * feat: 챌린지 참여 시도 시, 인증 연결 확인 기능 구현 - 사용자가 챌린지 참여 시도 시, 해당 레포지토리에 등록한 PR을 확인하는 기능 구현 - 관련 테스트 코드 작성 * fix: 영속성으로 인해 연관관계 설정 안되는 버그 픽스 * feat: 특정 날짜에 대해 인증 현황을 갱신하는 기능 구현 - Github API를 이용하여 특정 날짜에 대한 인증 현황을 갱신하는 기능 구현 - 해당 날짜에 등록된 PR을 통해 인증 현황 갱신 - 서비스단 코드에 대해 리팩토링 필요 * fix: 인증 PR 개수 반환 버그 픽스 - 인증에 사용한 PR 링크가 없을 때에도, 인증에 사용한 PR 개수를 1로 반환하던 버그 픽스 - 인증 갱신 관련 테스트 코드 작성 * refactor: 인증 객체 생성 메서드로 추출 * feat: 사용자의 레포지토리 목록을 반환하는 API 개발 - 사용자가 등록한 깃허브 계정에 있는 public repository 들을 불러오는 API 개발 - 관련 테스트 코드 작성 * feat: 특정 기간 내의 인증일자의 인증을 받아오는 기능 구현 - 등록되어 있는 인증들 중, 특정 기간 내의 인증 일자를 받아오는 Repository 코드 구현 - 관련 테스트 코드 작성 * feat: 현재 일자를 기준으로 일주일 간의 인증 정보를 받아오는 기능 구현 - 현재 일자를 기준으로 일주일 간(월요일~현재 요일)의 인증 정보를 받아오는 기능 구현 - 관련 서비스 코드 작성 * feat: 일주일 간 인증 정보 반환하는 API 구현 * refactor: Github 관련 서비스 코드들 클래스로 분리 * feat: 인증 수단 확인 시, PR 존재 X 시 예외 처리 로직 추가 - 인증 수단 확인 API 호출 시, 특정 레포지토리 내에 PR이 존재하지 않으면 PR이 존재하지 않는다는 예외를 발생시키도록 처리 * feat: 인증 정보 등록 시, 인증 회차 정보 추가 - 인증 정보 등록 시, 인증 회차 정보 추가하도록 설정 * refactor: 암호화 클래스에 대해 상속 불가로 변경 * refactor: 깃허브 관련 정보 검증 API 수정 - repository 검증 API 로직 변경: API endpoint 변경 및 참여 신청 관련 로직 분리 - pull request 검증 API 로직 변경: API endpoint 변경 - 관련 테스트 코드 수정 * feat: 챌린지 참여 API 로직 개발 * feat: 챌린지 참여, 참여 취소 API 개발 - 챌린지 참여&참여취소 API 개발 - 관련 테스트 코드 작성 - 챌린지 참여 취소에 대한 로직 보충 필요 (ex: 챌린지가 시작되었을 때 취소 불가 or 패널티) * feat: 챌린지 참여, 참여 취소 로직 보강 - 챌린지의 진행 상황(Progress)에 따라 다른 결과가 나오도록 로직 보강 - 관련 테스트 코드 작성 - InstanceDetailService 코드에 리팩토링 필요 * feat: 챌린지 상세 페이지 조회 API 개발 - 챌린지 상세페이지 진입 시 호출해야하는 API 개발 - 서비스 테스트 코드 작성 - 컨트롤러 테스트 코드 작성 필요 * feat: 특정 사용자 인증 전체 조회 API 개발 - 특정 사용자의 인증 전체 조회 API 개발 - 테스트 코드 작성 필요 * fix: 사용자의 레포지토리 반환 API 수정 - 레포지토리의 이름을 반환해야하는데, 객체의 toString을 통해 반환하던 문제점 해결 * feat: 사용자가 참여하고 있는 챌린지에 대한 현황 조회 API 구현 - 인증 상세 페이지에서 필요한 인증 현황 정보를 조회하는 API 개발 - 관련 테스트 코드 필요 - DateUtil의 메서드를 사용함에 따라 적절한 이름으로 변경 * refactor: 사용자를 식별하는 방법을 identifier로 변경 - 사용자를 식별하는 방법을 userId(PK)를 사용하는 방법에서, identifier(github id)로 식별하도록 변경 * feat: 인증 상세 페이지에서 챌린지 관련 정보 조회 API 개발 * refactor: 이미 참여한 챌린지에 대해 참여 API 요청 시 예외 발생 처리 - 이미 참여한 챌린지에 대해 참여 API 요청 시, 예외가 전달되도록 처리 * feat: 인증 결과 리스트 조회 시 더미 데이터 추가 - 인증 결과(일주일 간, 전체) 조회 API 요청 시, 없는 데이터인 경우 더미 데이터를 추가하도록 로직 보강 - CertificationRepository의 메서드 이름 변경: 불필요한 단어 삭제 - ParticipantInfoService에서 participantId를 통해 instance 를 받는 로직 추가 - 관련 테스트 코드 추가 작성 필요 * fix: 일주일 인증 현황 조회 시, 데이터의 개수가 잘못반환되는 버그 픽스 - 챌린지의 시작 요일에 따라 데이터의 개수가 잘못 반환되는 버그 픽스 - 관련 테스트 코드 작성 * refactor: 메서드 및 클래스 이름 변경 - 메서드 및 클래스의 이름을 다른 기능들과 구별이 쉽도록 변경 * refactor: 엔티티명 수정 - ParticipantInfo에서 Participant로 이름 변경 - 이름 변경에 따라 쿼리문 및 테스트 코드 변경 * refactor: CertificationProvider를 통해 Service layer 분리 - 기존의 CertificationService의 일부 기능을 CertificationProvider로 분리 - 관련 테스트 코드 작성 필요 * feat: 인증 관련 예외 사항 추가 처리 - 인증 시도 시, 해당 날짜가 진행 중인 챌린지인 경우에만 가능하도록 추가 처리 - 함수의 매개변수로 전달 시 필요한 정보만 전달하도록 변경 - 관련 테스트 코드 추가 * refactor: Module service에 의존하도록 설정 - InstanceDetailService에서 Module service인 ParticipantProvider에만 의존하도록 설정 * chore: 코드 정렬 * fix: 인증 정보 갱신 버그 픽스 - 인증 정보가 있음에도 불구하고 갱신 요청 시, 객체가 하나 더 저장되는 버그 픽스 * refactor: EncryptUtil의 메서드명을 일반적으로 변경 - EncryptUtil의 메서드명을 재사용하기 쉽도록 일반적인 이름으로 변경 * feat: 참여 요청 시, Github 연결 재확인 로직 추가 - 챌린지 참여 요청 시, Github repository 연결을 확인하는 로직 추가 * refactor: PR을 검증하는 메서드 추출 - PR 리스트를 받아서 controller에서 조건문을 통해 확인하던 로직을 service 내에 메서드로 추출 * refactor: 인증 상세 페이지 API 반환 값 변경 - repository의 위치가 변경됨에 따라 API 반환 값 변경 - githubProvider에 Github API에 요청할 repository full name을 반환하는 메서드 추가 * refactor: module service로 변경 - Instance 엔티티에 대해 module service(InstanceProvider)추가 * fix: 챌린지 상세 정보 조회 시, 제목 데이터가 포함되지 않은 버그 픽스 * fix: 인증 요청을 다시 했을 때 인증 정보가 업데이트되지 않는 버그 픽스 - 이전에 인증을 한 차례 진행하고, 다시 인증을 요청했을 때 DB에 저장하는 정보가 업데이트되지 않는 버그 픽스 - 테스트 코드 추가 작성 필요 * refactor: 인증을 갱신하는 로직에 대해 리팩토링 - CertificationService의 인증을 갱신하는 로직에 대해 리팩토링 진행 - 메서드로 따로 추출 - 테스트 코드 추가 * fix: 챌린지 참여 요청을 했을 때, 참여 상태가 변하지 않던 버그 픽스 - 특정 챌린지에 대해 참여 요청을 했을 때, 해당 챌린지의 참여 상태가 별하지 않는 버그 픽스 * feat: 챌린지 상세 정보 조회 시, 파일 데이터 추가 - 인증 상세 페이지에서 챌린지 상세 정보 조회 시, 전달하는 데이터에 파일 데이터 추가 * feat: 진행 중 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 진행 중인 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * fix: 챌린지 참여 조건 확인 버그 픽스 - 사용자가 챌린지에 참여할 수 있는 조건인지 확인하는 과정에서 발생하는 버그 픽스 - 조건이 잘못되어 있어 수정 * feat: 시작 전 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 시작 전인 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * feat: 완료 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 완료된 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * refactor: 챌린지 상세 정보 조회 응답 데이터 변경 * feat: 참여자 전체의 주간 인증 현황 조회 API 개발 - 특정 챌린지(인스턴스)에 참여한 참여자 전체의 주간 인증 현황 조회 API 개발 - 테스트 코드 작성 필요 - 리팩토링 필요 * refactor: DTO의 이름 변경 - DTO의 이름을 보다 쓰임에 맞는 이름으로 변경 * refactor: 코드 리팩토링 - 메서드 분리 진행 * feat: 사용자의 아이템 보유 개수 필드 추가 * feat: 인증 패스 아이템 API 개발 - 인증 패스 아이템 API 개발 - 리팩토링 필요 - 테스트 코드 작성 필요 * refactor: Participant의 패키지명 변경 - Participant의 패키지명을 ParticipantInfo에서 Participant로 변경 * feat: 완료된 챌린지에서 보상 수령 여부에 따른 응답 데이터 변경 - 마이챌린지의 완료 챌린지 목록에서, 챌린지 성공 보상 수령 여부에 따른 응답 데이터 변경 로직 추가 * feat: 완료 챌린지 응답 데이터에 아이템 개수 추가 - 완료 챌린지 리스트의 개별 응답 데이터에 '포인트 2배 수령 아이템'의 개수 정보 추가 * feat: 완료 챌린지 - 포인트 수령 API 개발 - 마이 챌린지의 완료 챌린지들 중, 챌린지에 성공했으나 포인트를 아직 수령하지 않은 챌린지에 대해 포인트를 수령하는 API 개발 - 아이템을 가지고 있는 경우, 포인트 2배 수령이 가능하도록 설정 * fix: 챌린지 참여 조건 버그 픽스 * refactor: 챌린지 시작 d-day 구하는 메서드 분리 * fix: 챌린지 참여 및 보유 아이템 정보 조회 시 버그 픽스 * refactor: 완료 챌린지의 인증 현황 반환 데이터 변경 - 마이 챌린지 - 완료 탭에서의 인증 현황 반환 데이터를 실제 서비스에서 보여줄 문자열로 변경 * refactor: 인증 패스 조건 검증 메서드 분리 - 인증 패스 아이템 사용이 가능한지 확인하는 메서드를 따로 분리 (조건문 수정) - 인증 관련 테스트 코드 추가 (CertificationProvider, CertificationService, GithubProvider) * refactor: UserItem 엔티티의 컬럼 변경 및 테스트 코드 추가 * test: 마이 챌린지 서비스 테스트 코드 작성 * refactor: 챌린지 상세 정보 조회 응답 데이터 변경 - 챌린지 상세 정보 조회 시, 시작 날짜, 끝 날짜를 따로 보내도록 변경 - FileResponse의 정적 팩토리 메서드 리팩토링 * refactor: 전체 주간 인증 현황 조회 시, 본인의 데이터 제외하도록 변경 - 인증 상세 페이지에서 전체 사용자들에 대한 주간 인증 현황 조회 시, 본인의 데이터는 제외하도록 변경 - 로직 변경에 따라 테스트 코드 변경 * refactor: 주간 인증 현황 조회 시, 사용자의 정보도 함께 전달 - 사용자가 본인의 주간 인증 현황 조회 시, 이후의 API 요청을 위해 사용자의 정보도 함께 전달하도록 변경 - 인증 정보가 저장되어 있지 않을 때, 날짜 정보까지 모두 null로 전달되던 버그 픽스 * feat: 주간 인증 현황 조회 시, 사용자 정보도 함께 반환 - 본인의 주간 인증 현황 조회 & 모든 사용자의 주간 인증 현황 조회 시, 사용자 정보(userId, nickname, file) 또한 포함하도록 변경 * feat: 전체 인증 현황 조회 응답 데이터 추가 - 전체 인증 현황 조회 시, 응답 데이터에 총 인증 횟수 데이터 추가 - 관련 테스트 코드 추가 * refactor: 전체 인증 현황 조회 시 query string 데이터 변경 - 전체 인증 현황 조회 시, 기존에 사용자의 identifier를 전달하는 것에서 userId로 전달하도록 변경 * feat: github token 등록 여부 확인 API 개발 * fix: Progress에 상관없이 인증 현황이 잘못 전달되던 버그 픽스 - 인스턴스의 Progress에 상관없이 성공/실패/남은 인증 개수가 잘못 전달되던 버그 픽스 - provider의 메서드 명을 명료하게 변경 - 관련 테스트 코드 변경 및 추가 작성 * refactor: 인증 응답 데이터 변경 - 인증 응답 데이터에서 DB에 저장되어있지 않은 인증에 대해 CertificateStatus를 NOT_YET으로 통일 * feat: report 기능 도메인 개발 - 개선 팔요 --------- Co-authored-by: kimdozzi --- build.gradle | 4 +- .../admin/topic/dto/TopicDetailResponse.java | 12 +- .../admin/topic/dto/TopicPagingResponse.java | 12 +- .../controller/CertificationController.java | 154 +++++ .../controller/GithubController.java | 92 +++ .../domain/CertificateStatus.java | 14 + .../certification/domain/Certification.java | 89 +++ .../dto/CertificationInformation.java | 16 + .../dto/CertificationRequest.java | 11 + .../dto/CertificationResponse.java | 57 ++ .../dto/InstancePreviewResponse.java | 32 + .../certification/dto/TotalResponse.java | 11 + .../certification/dto/WeekResponse.java | 34 + .../dto/github/GithubTokenRequest.java | 6 + .../dto/github/PullRequestResponse.java | 31 + .../repository/CertificationRepository.java | 27 + .../service/CertificationProvider.java | 86 +++ .../service/CertificationService.java | 274 ++++++++ .../certification/service/GithubProvider.java | 134 ++++ .../certification/service/GithubService.java | 82 +++ .../certification/util/DateUtil.java | 44 ++ .../certification/util/EncryptUtil.java | 42 ++ .../controller/InstanceDetailController.java | 73 +++ .../challenge/instance/domain/Instance.java | 13 +- .../challenge/instance/domain/Progress.java | 2 +- .../dto/crud/InstanceDetailResponse.java | 9 +- .../dto/crud/InstancePagingResponse.java | 9 +- .../instance/dto/detail/InstanceResponse.java | 46 ++ .../instance/dto/detail/JoinRequest.java | 10 + .../instance/dto/detail/JoinResponse.java | 29 + .../dto/home/HomeInstanceResponse.java | 9 +- .../dto/search/InstanceSearchResponse.java | 6 +- .../service/InstanceDetailService.java | 94 +++ .../instance/service/InstanceProvider.java | 22 + .../gitget/challenge/item/domain/Item.java | 9 + .../challenge/item/domain/ItemCategory.java | 2 +- .../challenge/item/domain/UserItem.java | 24 +- .../item/repository/UserItemRepository.java | 8 + .../challenge/item/service/ItemProvider.java | 13 + .../item/service/UserItemProvider.java | 31 + .../controller/MyChallengeController.java | 86 +++ .../myChallenge/dto/ActivatedResponse.java | 15 + .../myChallenge/dto/DoneResponse.java | 46 ++ .../myChallenge/dto/PreActivityResponse.java | 13 + .../myChallenge/dto/RewardRequest.java | 12 + .../service/MyChallengeService.java | 168 +++++ .../domain/JoinResult.java | 2 +- .../participant/domain/JoinStatus.java | 5 + .../participant/domain/Participant.java | 128 ++++ .../participant/domain/RewardStatus.java | 5 + .../repository/ParticipantRepository.java | 28 + .../service/ParticipantProvider.java | 76 +++ .../participantinfo/domain/JoinStatus.java | 5 - .../domain/ParticipantInfo.java | 68 -- .../repository/ParticipantInfoRepository.java | 7 - .../report/controller/ReportController.java | 7 + .../challenge/report/domain/Report.java | 31 + .../report/repository/ReportRepository.java | 7 + .../report/service/ReportService.java | 7 + .../gitget/challenge/user/domain/User.java | 24 +- .../challenge/user/service/UserService.java | 16 + .../gitget/global/file/dto/FileResponse.java | 8 +- .../global/file/service/FilesService.java | 13 +- .../security/config/SecurityConfig.java | 2 +- .../security/controller/AuthController.java | 9 - .../global/security/service/JwtService.java | 8 +- .../global/security/service/TokenService.java | 2 +- .../gitget/global/util/config/AppConfig.java | 14 + .../global/util/exception/ErrorCode.java | 28 +- .../global/util/exception/SuccessCode.java | 6 +- .../util/response/dto/ListResponse.java | 8 + .../payment/service/PaymentService.java | 2 +- .../profile/service/ProfileService.java | 32 +- .../topic/controller/TopicControllerTest.java | 2 +- .../CertificationControllerTest.java | 139 ++++ .../CertificationRepositoryTest.java | 117 ++++ .../service/CertificationProviderTest.java | 195 ++++++ .../service/CertificationServiceTest.java | 613 ++++++++++++++++++ .../service/GithubProviderTest.java | 152 +++++ .../service/GithubServiceTest.java | 269 ++++++++ .../certification/util/DateUtilTest.java | 96 +++ .../certification/util/EncryptUtilTest.java | 31 + .../service/InstanceDetailServiceTest.java | 325 ++++++++++ .../item/service/UserItemProviderTest.java | 123 ++++ .../service/MyChallengeServiceTest.java | 230 +++++++ .../service/ParticipantProviderTest.java | 120 ++++ .../user/controller/UserControllerTest.java | 2 +- .../user/service/UserServiceTest.java | 34 + .../controller/AuthControllerTest.java | 20 - .../security/service/JwtServiceTest.java | 4 +- ...hMockCustomUserSecurityContextFactory.java | 14 +- 91 files changed, 4837 insertions(+), 210 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/domain/CertificateStatus.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/dto/CertificationInformation.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/dto/CertificationRequest.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/dto/InstancePreviewResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/dto/TotalResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/dto/github/GithubTokenRequest.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/dto/github/PullRequestResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/service/GithubService.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/util/EncryptUtil.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/service/UserItemProvider.java create mode 100644 src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java create mode 100644 src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java create mode 100644 src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java rename src/main/java/com/genius/gitget/challenge/{participantinfo => participant}/domain/JoinResult.java (59%) create mode 100644 src/main/java/com/genius/gitget/challenge/participant/domain/JoinStatus.java create mode 100644 src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java create mode 100644 src/main/java/com/genius/gitget/challenge/participant/domain/RewardStatus.java create mode 100644 src/main/java/com/genius/gitget/challenge/participant/repository/ParticipantRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java delete mode 100644 src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinStatus.java delete mode 100644 src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java delete mode 100644 src/main/java/com/genius/gitget/challenge/participantinfo/repository/ParticipantInfoRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/report/controller/ReportController.java create mode 100644 src/main/java/com/genius/gitget/challenge/report/domain/Report.java create mode 100644 src/main/java/com/genius/gitget/challenge/report/repository/ReportRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/report/service/ReportService.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/repository/CertificationRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/service/CertificationProviderTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/util/EncryptUtilTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/item/service/UserItemProviderTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java diff --git a/build.gradle b/build.gradle index 35a743ec..b5bf3795 100644 --- a/build.gradle +++ b/build.gradle @@ -52,8 +52,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' // H2 - runtimeOnly 'com.h2database:h2:2.2.222'; + runtimeOnly 'com.h2database:h2:2.2.222' + // Github API for Java + implementation 'org.kohsuke:github-api:1.318' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java index aa330ec0..77f66661 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java @@ -3,10 +3,9 @@ import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import lombok.Builder; - import java.io.IOException; import java.util.Optional; +import lombok.Builder; @Builder public record TopicDetailResponse(Long topicId, String title, String tags, @@ -19,14 +18,7 @@ public static TopicDetailResponse createByEntity(Topic topic, Optional fi .description(topic.getDescription()) .notice(topic.getNotice()) .pointPerPerson(topic.getPointPerPerson()) - .fileResponse(convertToFileResponse(files)) + .fileResponse(FileResponse.create(files)) .build(); } - - private static FileResponse convertToFileResponse(Optional files) throws IOException { - if (files.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(files.get()); - } } diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java index 64caab46..a81e012f 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java @@ -3,10 +3,9 @@ import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import lombok.Builder; - import java.io.IOException; import java.util.Optional; +import lombok.Builder; @Builder public record TopicPagingResponse(Long topicId, String title, FileResponse fileResponse) { @@ -15,14 +14,7 @@ public static TopicPagingResponse createByEntity(Topic topic, Optional fi return TopicPagingResponse.builder() .topicId(topic.getId()) .title(topic.getTitle()) - .fileResponse(convertToFileResponse(files)) + .fileResponse(FileResponse.create(files)) .build(); } - - private static FileResponse convertToFileResponse(Optional files) throws IOException { - if (files.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(files.get()); - } } diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java new file mode 100644 index 00000000..7ff05754 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -0,0 +1,154 @@ +package com.genius.gitget.challenge.certification.controller; + +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.challenge.certification.dto.CertificationInformation; +import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import com.genius.gitget.challenge.certification.dto.CertificationResponse; +import com.genius.gitget.challenge.certification.dto.InstancePreviewResponse; +import com.genius.gitget.challenge.certification.dto.TotalResponse; +import com.genius.gitget.challenge.certification.dto.WeekResponse; +import com.genius.gitget.challenge.certification.service.CertificationService; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.service.InstanceProvider; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import com.genius.gitget.global.util.response.dto.SlicingResponse; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/certification") +public class CertificationController { + private final UserService userService; + private final CertificationService certificationService; + private final InstanceProvider instanceProvider; + private final ParticipantProvider participantProvider; + + + @GetMapping("/{instanceId}") + public ResponseEntity> getInstanceInformation( + @PathVariable Long instanceId + ) { + InstancePreviewResponse instancePreviewResponse = certificationService.getInstancePreview(instanceId); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instancePreviewResponse) + ); + } + + @PostMapping("/today") + public ResponseEntity> certificateByGithub( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody CertificationRequest certificationRequest + ) { + CertificationResponse certificationResponse = certificationService.updateCertification( + userPrincipal.getUser(), + new CertificationRequest(certificationRequest.instanceId(), LocalDate.now())); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certificationResponse) + ); + } + + @PostMapping("/pass") + public ResponseEntity> passCertification( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody CertificationRequest certificationRequest + ) { + User user = userPrincipal.getUser(); + CertificationResponse certificationResponse = certificationService.passCertification( + user.getId(), certificationRequest); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certificationResponse) + ); + } + + @GetMapping("/week/{instanceId}") + public ResponseEntity> getWeekCertification( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long instanceId + ) { + User user = userPrincipal.getUser(); + Participant participant = participantProvider.findByJoinInfo(user.getId(), instanceId); + List weekCertification = certificationService.getWeekCertification( + participant.getId(), LocalDate.now()); + FileResponse fileResponse = FileResponse.create(user.getFiles()); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), + WeekResponse.create(user, fileResponse, weekCertification)) + ); + } + + @GetMapping("/week/all/{instanceId}") + public ResponseEntity> getAllUserWeekCertification( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long instanceId, + @PageableDefault Pageable pageable + ) { + User user = userPrincipal.getUser(); + Slice certifications = certificationService.getAllWeekCertification( + user.getId(), instanceId, LocalDate.now(), pageable); + + return ResponseEntity.ok().body( + new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certifications) + ); + } + + @GetMapping("/total/{instanceId}") + public ResponseEntity> getTotalCertifications( + @PathVariable Long instanceId, + @RequestParam Long userId + ) { + User user = userService.findUserById(userId); + Participant participant = participantProvider.findByJoinInfo(user.getId(), instanceId); + TotalResponse totalResponse = certificationService.getTotalCertification( + participant.getId(), LocalDate.now()); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), totalResponse) + ); + } + + @GetMapping("/information/{instanceId}") + public ResponseEntity> getCertificationInformation( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long instanceId + ) { + + Instance instance = instanceProvider.findById(instanceId); + Participant participant = participantProvider.findByJoinInfo( + userPrincipal.getUser().getId(), + instanceId); + + CertificationInformation certificationInformation = certificationService.getCertificationInformation( + instance, participant, LocalDate.now()); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certificationInformation) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java new file mode 100644 index 00000000..ac371057 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java @@ -0,0 +1,92 @@ +package com.genius.gitget.challenge.certification.controller; + +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.challenge.certification.dto.github.GithubTokenRequest; +import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; +import com.genius.gitget.challenge.certification.service.GithubService; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.ListResponse; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/certification") +public class GithubController { + private final GithubService githubService; + + @PostMapping("/register/token") + public ResponseEntity registerGithubToken( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody GithubTokenRequest githubTokenRequest + ) { + githubService.registerGithubPersonalToken(userPrincipal.getUser(), githubTokenRequest.githubToken()); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } + + @GetMapping("/repositories") + public ResponseEntity> getPublicRepositories( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + List repositories = githubService.getPublicRepositories(userPrincipal.getUser()); + + return ResponseEntity.ok().body( + new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), repositories) + ); + } + + @GetMapping("/verify/token") + public ResponseEntity verifyGithubToken( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + githubService.verifyGithubToken(userPrincipal.getUser()); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } + + @GetMapping("/verify/repository") + public ResponseEntity verifyRepository( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestParam String repo + ) { + + githubService.verifyRepository(userPrincipal.getUser(), repo); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } + + @GetMapping("/verify/pull-request") + public ResponseEntity> verifyPullRequest( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestParam String repo + ) { + + List pullRequestResponses = githubService.verifyPullRequest( + userPrincipal.getUser(), repo, LocalDate.now() + ); + + return ResponseEntity.ok().body( + new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), pullRequestResponses) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/domain/CertificateStatus.java b/src/main/java/com/genius/gitget/challenge/certification/domain/CertificateStatus.java new file mode 100644 index 00000000..9ef152c3 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/domain/CertificateStatus.java @@ -0,0 +1,14 @@ +package com.genius.gitget.challenge.certification.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum CertificateStatus { + NOT_YET("인증 필요"), + CERTIFICATED("인증 갱신"), + PASSED("패스 완료"); + + private final String tag; +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java new file mode 100644 index 00000000..522ef647 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java @@ -0,0 +1,89 @@ +package com.genius.gitget.challenge.certification.domain; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; + +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.global.util.domain.BaseTimeEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DynamicInsert +public class Certification extends BaseTimeEntity { + @Id + @Column(name = "certification_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "participant_id") + private Participant participant; + + private int currentAttempt; + + private LocalDate certificatedAt; + + private String certificationLinks; + + @Enumerated(value = EnumType.STRING) + @ColumnDefault("'NOT_YET'") + private CertificateStatus certificationStatus; + + + @Builder + public Certification(int currentAttempt, LocalDate certificatedAt, String certificationLinks, + CertificateStatus certificationStatus) { + this.currentAttempt = currentAttempt; + this.certificatedAt = certificatedAt; + this.certificationLinks = certificationLinks; + this.certificationStatus = certificationStatus; + } + + public static Certification createPassed(LocalDate certificatedAt) { + return Certification.builder() + .certificatedAt(certificatedAt) + .certificationStatus(PASSED) + .certificationLinks("") + .build(); + } + + //=== 비지니스 로직 ===// + public void update(LocalDate certificatedAt, CertificateStatus status, String certificationLinks) { + this.certificatedAt = certificatedAt; + this.certificationStatus = status; + this.certificationLinks = certificationLinks; + } + + public void updateToPass(LocalDate certificatedAt) { + this.certificatedAt = certificatedAt; + this.certificationStatus = CertificateStatus.PASSED; + this.certificationLinks = ""; + } + + + //=== 연관관계 편의 메서드 ===// + public void setParticipant(Participant participant) { + this.participant = participant; + if (!participant.getCertificationList().contains(this)) { + participant.getCertificationList().add(this); + } + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationInformation.java b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationInformation.java new file mode 100644 index 00000000..9e4cc4da --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationInformation.java @@ -0,0 +1,16 @@ +package com.genius.gitget.challenge.certification.dto; + +import lombok.Builder; + +@Builder +public record CertificationInformation( + String repository, + double successPercent, + int totalAttempt, + int currentAttempt, + int pointPerPerson, + int successCount, + int failureCount, + int remainCount +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationRequest.java b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationRequest.java new file mode 100644 index 00000000..08fdc9de --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationRequest.java @@ -0,0 +1,11 @@ +package com.genius.gitget.challenge.certification.dto; + +import java.time.LocalDate; +import lombok.Builder; + +@Builder +public record CertificationRequest( + Long instanceId, + LocalDate targetDate +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java new file mode 100644 index 00000000..84aa60de --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java @@ -0,0 +1,57 @@ +package com.genius.gitget.challenge.certification.dto; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; + +@Builder +public record CertificationResponse( + Long certificationId, + int certificationAttempt, + DayOfWeek dayOfWeek, + LocalDate certificatedAt, + CertificateStatus certificateStatus, + int prCount, + List prLinks +) { + + public static CertificationResponse createNonExist(int currentAttempt, LocalDate certificatedAt) { + return CertificationResponse.builder() + .certificationId(0L) + .certificationAttempt(currentAttempt) + .dayOfWeek(certificatedAt.getDayOfWeek()) + .certificatedAt(certificatedAt) + .certificateStatus(NOT_YET) + .prLinks(null) + .prCount(0) + .build(); + } + + public static CertificationResponse createExist(Certification certification) { + List prLinks = getPrList(certification.getCertificationLinks()); + + return CertificationResponse.builder() + .certificationId(certification.getId()) + .certificationAttempt(certification.getCurrentAttempt()) + .dayOfWeek(certification.getCertificatedAt().getDayOfWeek()) + .certificatedAt(certification.getCertificatedAt()) + .certificateStatus(certification.getCertificationStatus()) + .prLinks(prLinks) + .prCount(prLinks.size()) + .build(); + } + + private static List getPrList(String prLink) { + List prLinkList = List.of(prLink.split(",")); + if (prLinkList.size() == 1 && prLinkList.get(0).isEmpty()) { + return new ArrayList<>(); + } + return prLinkList; + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/InstancePreviewResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/InstancePreviewResponse.java new file mode 100644 index 00000000..22899072 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/InstancePreviewResponse.java @@ -0,0 +1,32 @@ +package com.genius.gitget.challenge.certification.dto; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.dto.FileResponse; +import java.time.LocalDate; +import lombok.Builder; + +@Builder +public record InstancePreviewResponse( + Long instanceId, + String title, + int participantCount, + LocalDate startDate, + LocalDate completedDate, + int pointPerPerson, + String certificationMethod, + FileResponse fileResponse +) { + + public static InstancePreviewResponse createByEntity(Instance instance, FileResponse fileResponse) { + return InstancePreviewResponse.builder() + .instanceId(instance.getId()) + .title(instance.getTitle()) + .participantCount(instance.getParticipantCount()) + .startDate(instance.getStartedDate().toLocalDate()) + .completedDate(instance.getCompletedDate().toLocalDate()) + .pointPerPerson(instance.getPointPerPerson()) + .certificationMethod(instance.getCertificationMethod()) + .fileResponse(fileResponse) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/TotalResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/TotalResponse.java new file mode 100644 index 00000000..0311248e --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/TotalResponse.java @@ -0,0 +1,11 @@ +package com.genius.gitget.challenge.certification.dto; + +import java.util.List; +import lombok.Builder; + +@Builder +public record TotalResponse( + int totalAttempts, + List certifications +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java new file mode 100644 index 00000000..fdb85300 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java @@ -0,0 +1,34 @@ +package com.genius.gitget.challenge.certification.dto; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.file.dto.FileResponse; +import java.util.List; +import lombok.Builder; + +@Builder +public record WeekResponse( + Long userId, + String nickname, + List certifications, + FileResponse profile + +) { + + public static WeekResponse create(User user, List certifications) { + return WeekResponse.builder() + .userId(user.getId()) + .nickname(user.getNickname()) + .certifications(certifications) + .build(); + } + + public static WeekResponse create(User user, FileResponse fileResponse, + List certifications) { + return WeekResponse.builder() + .userId(user.getId()) + .nickname(user.getNickname()) + .certifications(certifications) + .profile(fileResponse) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/github/GithubTokenRequest.java b/src/main/java/com/genius/gitget/challenge/certification/dto/github/GithubTokenRequest.java new file mode 100644 index 00000000..f028f0d3 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/github/GithubTokenRequest.java @@ -0,0 +1,6 @@ +package com.genius.gitget.challenge.certification.dto.github; + +public record GithubTokenRequest( + String githubToken +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/github/PullRequestResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/github/PullRequestResponse.java new file mode 100644 index 00000000..b7a5decd --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/github/PullRequestResponse.java @@ -0,0 +1,31 @@ +package com.genius.gitget.challenge.certification.dto.github; + +import com.genius.gitget.global.util.exception.BusinessException; +import java.io.IOException; +import java.util.Date; +import lombok.Builder; +import org.kohsuke.github.GHPullRequest; + +@Builder +public record PullRequestResponse( + String prTitle, + String prLink, + String state, + Date createdAt, + Date closedAt +) { + + public static PullRequestResponse create(GHPullRequest ghPullRequest) { + try { + return PullRequestResponse.builder() + .prTitle(ghPullRequest.getTitle()) + .prLink(String.valueOf(ghPullRequest.getHtmlUrl())) + .state(ghPullRequest.getState().name()) + .createdAt(ghPullRequest.getCreatedAt()) + .closedAt(ghPullRequest.getClosedAt()) + .build(); + } catch (IOException e) { + throw new BusinessException(e); + } + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java b/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java new file mode 100644 index 00000000..2a2a2ffc --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java @@ -0,0 +1,27 @@ +package com.genius.gitget.challenge.certification.repository; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface CertificationRepository extends JpaRepository { + + @Query("select c from Certification c where c.certificatedAt = :targetDate and c.participant.id = :participantId") + Optional findByDate(@Param("targetDate") LocalDate targetDate, + @Param("participantId") Long participantId); + + @Query("select c from Certification c where c.participant.id = :participantId and c.certificatedAt >= :startDate and c.certificatedAt <= :endDate order by c.currentAttempt desc") + List findByDuration(@Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate, + @Param("participantId") Long participantId); + + @Query("select c from Certification c where c.participant.id = :participantId and c.certificationStatus = :status and c.certificatedAt <= :currentDate") + List findByStatus(@Param("participantId") Long participantId, + @Param("status") CertificateStatus status, + @Param("currentDate") LocalDate currentDate); +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java new file mode 100644 index 00000000..34ec34ad --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java @@ -0,0 +1,86 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.participant.domain.Participant; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class CertificationProvider { + private final CertificationRepository certificationRepository; + + + public List findByDuration(LocalDate startDate, LocalDate endDate, Long participantId) { + return certificationRepository.findByDuration(startDate, endDate, participantId); + } + + public Optional findByDate(LocalDate targetDate, Long participantId) { + return certificationRepository.findByDate(targetDate, participantId); + } + + public int countByStatus(Long participantId, CertificateStatus certificateStatus, + LocalDate targetDate) { + return certificationRepository.findByStatus(participantId, certificateStatus, targetDate).size(); + } + + @Transactional + public Certification save(Certification certification) { + return certificationRepository.save(certification); + } + + @Transactional + public Certification update(Certification certification, + LocalDate targetDate, + List pullRequests) { + certification.update( + targetDate, getCertificateStatus(pullRequests), getPrLinks(pullRequests) + ); + return certification; + } + + @Transactional + public Certification createCertification(Participant participant, + LocalDate targetDate, + List pullRequests) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), targetDate); + + Certification certification = Certification.builder() + .currentAttempt(attempt) + .certificatedAt(targetDate) + .certificationStatus(getCertificateStatus(pullRequests)) + .certificationLinks(getPrLinks(pullRequests)) + .build(); + + certification.setParticipant(participant); + + return certificationRepository.save(certification); + } + + private String getPrLinks(List pullRequests) { + StringBuilder prLinkBuilder = new StringBuilder(); + for (String pullRequest : pullRequests) { + prLinkBuilder.append(pullRequest); + prLinkBuilder.append(","); + } + return prLinkBuilder.toString(); + } + + private CertificateStatus getCertificateStatus(List pullRequests) { + if (pullRequests.isEmpty()) { + return NOT_YET; + } + return CERTIFICATED; + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java new file mode 100644 index 00000000..cf52a609 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -0,0 +1,274 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; +import static com.genius.gitget.global.util.exception.ErrorCode.CERTIFICATION_UNABLE; + +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.dto.CertificationInformation; +import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import com.genius.gitget.challenge.certification.dto.CertificationResponse; +import com.genius.gitget.challenge.certification.dto.InstancePreviewResponse; +import com.genius.gitget.challenge.certification.dto.TotalResponse; +import com.genius.gitget.challenge.certification.dto.WeekResponse; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.service.InstanceProvider; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.service.UserItemProvider; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GitHub; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class CertificationService { + private final FilesService filesService; + private final GithubProvider githubProvider; + private final CertificationProvider certificationProvider; + private final ParticipantProvider participantProvider; + private final InstanceProvider instanceProvider; + private final UserItemProvider userItemProvider; + + + public List getWeekCertification(Long participantId, LocalDate currentDate) { + LocalDate startDate = participantProvider.getInstanceStartDate(participantId); + int curAttempt = DateUtil.getWeekAttempt(startDate, currentDate); + + List certifications = certificationProvider.findByDuration( + DateUtil.getWeekStartDate(currentDate), + currentDate, + participantId); + + return convertToCertificationResponse(certifications, curAttempt, currentDate); + } + + public Slice getAllWeekCertification(Long userId, Long instanceId, + LocalDate currentDate, Pageable pageable) { + Slice participants = participantProvider.findAllByInstanceId(userId, instanceId, pageable); + return participants.map( + participant -> convertToWeekResponse(participant, currentDate) + ); + } + + private WeekResponse convertToWeekResponse(Participant participant, LocalDate currentDate) { + User user = participant.getUser(); + LocalDate startDate = participantProvider.getInstanceStartDate(participant.getId()); + + List certifications = certificationProvider.findByDuration( + DateUtil.getWeekStartDate(currentDate), + currentDate, + participant.getId()); + List certificationResponses = convertToCertificationResponse( + certifications, + DateUtil.getWeekAttempt(startDate, currentDate), + currentDate); + FileResponse fileResponse = FileResponse.create(user.getFiles()); + + return WeekResponse.create(user, fileResponse, certificationResponses); + } + + public TotalResponse getTotalCertification(Long participantId, LocalDate currentDate) { + Instance instance = participantProvider.getInstanceById(participantId); + LocalDate startDate = instance.getStartedDate().toLocalDate(); + int totalAttempts = instance.getTotalAttempt(); + + int curAttempt = DateUtil.getAttemptCount(startDate, currentDate); + + List certifications = certificationProvider.findByDuration( + startDate, currentDate, participantId); + + List certificationResponses = convertToCertificationResponse(certifications, curAttempt, + currentDate); + + return TotalResponse.builder() + .totalAttempts(totalAttempts) + .certifications(certificationResponses) + .build(); + } + + private List convertToCertificationResponse(List certifications, + int curAttempt, LocalDate currentDate) { + List result = new ArrayList<>(); + Map certificationMap = convertToMap(certifications); + currentDate = DateUtil.getWeekStartDate(currentDate).minusDays(1); + + for (int cur = 1; cur <= curAttempt; cur++) { + currentDate = currentDate.plusDays(1); + if (certificationMap.containsKey(cur)) { + result.add(CertificationResponse.createExist(certificationMap.get(cur))); + continue; + } + result.add(CertificationResponse.createNonExist(cur, currentDate)); + } + + return result; + } + + private Map convertToMap(List certifications) { + Map certificationMap = new HashMap<>(); + + for (Certification certification : certifications) { + certificationMap.put(certification.getCurrentAttempt(), certification); + } + return certificationMap; + } + + @Transactional + public CertificationResponse passCertification(Long userId, CertificationRequest certificationRequest) { + Instance instance = instanceProvider.findById(certificationRequest.instanceId()); + Participant participant = participantProvider.findByJoinInfo(userId, instance.getId()); + LocalDate targetDate = certificationRequest.targetDate(); + + UserItem userItem = userItemProvider.findUserItemByUser(userId, ItemCategory.CERTIFICATION_PASSER); + Optional optional = certificationProvider.findByDate(targetDate, participant.getId()); + + validatePassCondition(userItem, optional); + + //TODO: 리팩토링 시급... + if (optional.isPresent()) { + optional.get().updateToPass(targetDate); + return CertificationResponse.createExist(optional.get()); + } + + Certification certification = Certification.createPassed(targetDate); + certification.setParticipant(participant); + certificationProvider.save(certification); + + return CertificationResponse.createExist(certification); + } + + private void validatePassCondition(UserItem userItem, Optional optional) { + if (!userItem.hasItem()) { + throw new BusinessException(ErrorCode.USER_ITEM_NOT_FOUND); + } + if (optional.isEmpty() || optional.get().getCertificationStatus() == NOT_YET) { + return; + } + throw new BusinessException(ErrorCode.CAN_NOT_USE_PASS_ITEM); + } + + @Transactional + public CertificationResponse updateCertification(User user, CertificationRequest certificationRequest) { + GitHub gitHub = githubProvider.getGithubConnection(user); + Instance instance = instanceProvider.findById(certificationRequest.instanceId()); + Participant participant = participantProvider.findByJoinInfo(user.getId(), instance.getId()); + + if (!canCertificate(instance, certificationRequest.targetDate())) { + throw new BusinessException(CERTIFICATION_UNABLE); + } + + List pullRequests = getPullRequestLink( + gitHub, + participant.getRepositoryName(), + certificationRequest.targetDate()); + + Certification certification = createOrUpdate(participant, certificationRequest.targetDate(), pullRequests); + + return CertificationResponse.createExist(certification); + } + + private Certification createOrUpdate(Participant participant, LocalDate targetDate, List pullRequests) { + Optional optional = certificationProvider.findByDate(targetDate, participant.getId()); + if (optional.isPresent()) { + return certificationProvider.update(optional.get(), targetDate, pullRequests); + } + + return certificationProvider.createCertification(participant, targetDate, pullRequests); + } + + private boolean canCertificate(Instance instance, LocalDate targetDate) { + boolean isValidPeriod = targetDate.isAfter(instance.getStartedDate().toLocalDate()) && + targetDate.isBefore(instance.getCompletedDate().toLocalDate()); + + return ((instance.getProgress() == Progress.ACTIVITY) && isValidPeriod); + } + + private List getPullRequestLink(GitHub gitHub, String repositoryName, LocalDate targetDate) { + List ghPullRequests = githubProvider.getPullRequestByDate(gitHub, repositoryName, targetDate); + + return ghPullRequests.stream() + .map(pr -> pr.getHtmlUrl().toString()) + .toList(); + } + + public InstancePreviewResponse getInstancePreview(Long instanceId) { + Instance instance = instanceProvider.findById(instanceId); + FileResponse fileResponse = filesService.getEncodedFile(instance.getFiles()); + return InstancePreviewResponse.createByEntity(instance, fileResponse); + } + + @Transactional + public CertificationInformation getCertificationInformation(Instance instance, Participant participant, + LocalDate currentDate) { + int successCount = 0; + int failureCount = 0; + int remainCount = 0; + + int totalAttempt = instance.getTotalAttempt(); + int currentAttempt = 0; + + switch (instance.getProgress()) { + case PREACTIVITY -> { + remainCount = instance.getTotalAttempt(); + } + case ACTIVITY -> { + currentAttempt = DateUtil.getAttemptCount(instance.getStartedDate().toLocalDate(), currentDate); + successCount = calculateSuccess(participant.getId(), currentDate); + failureCount = currentAttempt - successCount; + remainCount = totalAttempt - currentAttempt; + + } + case DONE -> { + currentAttempt = totalAttempt; + successCount = calculateSuccess(participant.getId(), instance.getCompletedDate().toLocalDate()); + failureCount = totalAttempt - successCount; + } + } + + return CertificationInformation.builder() + .repository(participant.getRepositoryName()) + .successPercent(getSuccessPercent(successCount, currentAttempt)) + .totalAttempt(totalAttempt) + .currentAttempt(currentAttempt) + .pointPerPerson(instance.getPointPerPerson()) + .successCount(successCount) + .failureCount(failureCount) + .remainCount(remainCount) + .build(); + } + + private int calculateSuccess(Long participantId, LocalDate currentDate) { + int certificated = certificationProvider.countByStatus(participantId, CERTIFICATED, currentDate); + int passed = certificationProvider.countByStatus(participantId, PASSED, currentDate); + return certificated + passed; + } + + private double getSuccessPercent(int successCount, int currentAttempt) { + double successPercent = (double) successCount / (double) currentAttempt * 100; + return Math.round(successPercent * 100 / 100.0); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java b/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java new file mode 100644 index 00000000..e7b96305 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java @@ -0,0 +1,134 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_CONNECTION_FAILED; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_ID_INCORRECT; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_REPOSITORY_INCORRECT; + +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.exception.BusinessException; +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.kohsuke.github.GHDirection; +import org.kohsuke.github.GHFileNotFoundException; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHPullRequestSearchBuilder; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHRepositorySearchBuilder; +import org.kohsuke.github.GHRepositorySearchBuilder.Sort; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class GithubProvider { + private final UserService userService; + private final int PAGE_SIZE = 10; + + public GitHub getGithubConnection(String githubToken) { + try { + GitHub gitHub = new GitHubBuilder().withOAuthToken(githubToken).build(); + gitHub.checkApiUrlValidity(); + return gitHub; + } catch (IOException e) { + throw new BusinessException(GITHUB_CONNECTION_FAILED); + } + } + + public GitHub getGithubConnection(User user) { + try { + String githubToken = userService.getGithubToken(user); + GitHub gitHub = new GitHubBuilder().withOAuthToken(githubToken).build(); + gitHub.checkApiUrlValidity(); + return gitHub; + } catch (IOException e) { + throw new BusinessException(GITHUB_CONNECTION_FAILED); + } + } + + public void validateGithubConnection(GitHub gitHub, String githubId) { + try { + String accountId = gitHub.getMyself().getLogin(); + validateGithubAccount(githubId, accountId); + } catch (IOException e) { + throw new BusinessException(GITHUB_CONNECTION_FAILED); + } + } + + private void validateGithubAccount(String githubId, String accountId) { + if (!githubId.equals(accountId)) { + throw new BusinessException(GITHUB_ID_INCORRECT); + } + } + + public void validateGithubRepository(GitHub gitHub, String repositoryFullName) { + try { + gitHub.getRepository(repositoryFullName); + } catch (GHFileNotFoundException e) { + throw new BusinessException(GITHUB_REPOSITORY_INCORRECT); + } catch (IllegalArgumentException | IOException e) { + throw new BusinessException(e); + } + } + + public List getRepositoryList(GitHub gitHub) { + try { + GHRepositorySearchBuilder builder = gitHub.searchRepositories() + .user(getGHUser(gitHub).getLogin()) + .sort(Sort.UPDATED) + .order(GHDirection.DESC); + return builder.list().iterator().nextPage(); + } catch (IOException e) { + throw new BusinessException(e); + } + } + + public List getPullRequestByDate(GitHub gitHub, String repositoryName, + LocalDate kstDate) { + try { + GHRepository repository = gitHub.getRepository(getRepoFullName(gitHub, repositoryName)); + GHPullRequestSearchBuilder prSearchBuilder = gitHub.searchPullRequests() + .repo(repository) + .author(getGHUser(gitHub)) + .created(kstDate.minusDays(1), kstDate); + + return prSearchBuilder.list().iterator().nextPage().stream() + .filter(pr -> isEqualToKST(pr, kstDate)) + .toList(); + + } catch (GHFileNotFoundException e) { + throw new BusinessException(GITHUB_REPOSITORY_INCORRECT); + } catch (IOException e) { + throw new BusinessException(e); + } + } + + private boolean isEqualToKST(GHPullRequest ghPullRequest, LocalDate targetDate) { + try { + LocalDate kst = DateUtil.convertToLocalDate(ghPullRequest.getCreatedAt()); + return kst.isEqual(targetDate); + } catch (IOException e) { + throw new BusinessException(e); + } + } + + private GHUser getGHUser(GitHub gitHub) throws IOException { + String accountId = gitHub.getMyself().getLogin(); + return gitHub.getUser(accountId); + } + + public String getRepoFullName(GitHub gitHub, String repositoryName) { + try { + return gitHub.getMyself().getLogin() + "/" + repositoryName; + } catch (IOException e) { + throw new BusinessException(e); + } + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/GithubService.java b/src/main/java/com/genius/gitget/challenge/certification/service/GithubService.java new file mode 100644 index 00000000..94dfded1 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/service/GithubService.java @@ -0,0 +1,82 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_PR_NOT_FOUND; + +import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; +import com.genius.gitget.challenge.certification.util.EncryptUtil; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.exception.BusinessException; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class GithubService { + private final UserService userService; + private final GithubProvider githubProvider; + private final EncryptUtil encryptUtil; + + @Transactional + public void registerGithubPersonalToken(User user, String githubToken) { + GitHub gitHub = githubProvider.getGithubConnection(githubToken); + githubProvider.validateGithubConnection(gitHub, user.getIdentifier()); + + String encryptedToken = encryptUtil.encrypt(githubToken); + user.updateGithubPersonalToken(encryptedToken); + userService.save(user); + } + + public void verifyGithubToken(User user) { + String githubToken = encryptUtil.decrypt(user.getGithubToken()); + + GitHub gitHub = githubProvider.getGithubConnection(githubToken); + githubProvider.validateGithubConnection(gitHub, user.getIdentifier()); + } + + @Transactional + public void verifyRepository(User user, String repository) { + GitHub gitHub = githubProvider.getGithubConnection(user); + + String repositoryFullName = githubProvider.getRepoFullName(gitHub, repository); + githubProvider.validateGithubRepository(gitHub, repositoryFullName); + } + + public List getPublicRepositories(User user) { + GitHub gitHub = githubProvider.getGithubConnection(user); + List repositoryList = githubProvider.getRepositoryList(gitHub); + return repositoryList.stream() + .map(GHRepository::getName) + .toList(); + } + + //TODO: PR이 날라온 브랜치의 이름이 정해진 규칙에 맞는지 여부 확인 필요 + public List verifyPullRequest(User user, String repositoryName, LocalDate targetDate) { + List responses = getPullRequestListByDate(user, repositoryName, targetDate); + + if (responses.isEmpty()) { + throw new BusinessException(GITHUB_PR_NOT_FOUND); + } + return responses; + } + + public List getPullRequestListByDate(User user, String repositoryName, + LocalDate targetDate) { + GitHub gitHub = githubProvider.getGithubConnection(user); + + List pullRequest = githubProvider.getPullRequestByDate(gitHub, repositoryName, targetDate); + + return pullRequest.stream() + .map(PullRequestResponse::create) + .toList(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java new file mode 100644 index 00000000..09f99641 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java @@ -0,0 +1,44 @@ +package com.genius.gitget.challenge.certification.util; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +public final class DateUtil { + + public static int getRemainDaysToStart(LocalDate startDate, LocalDate targetDate) { + if (targetDate.isBefore(startDate)) { + return (int) ChronoUnit.DAYS.between(targetDate, startDate); + } + return 0; + } + + public static int getAttemptCount(LocalDate startDate, LocalDate targetDate) { + return Math.toIntExact(ChronoUnit.DAYS.between(startDate, targetDate)) + 1; + } + + public static int getWeekAttempt(LocalDate challengeStartDate, LocalDate targetDate) { + int weekAttempt = targetDate.getDayOfWeek().ordinal() + 1; + int totalAttempt = getAttemptCount(challengeStartDate, targetDate); + + if ((challengeStartDate.getDayOfWeek() != DayOfWeek.MONDAY) && (totalAttempt < 8)) { + return totalAttempt; + } + + return weekAttempt; + } + + public static LocalDate getWeekStartDate(LocalDate currentDate) { + return currentDate.minusDays(currentDate.getDayOfWeek().ordinal()); + } + + public static LocalDate convertToLocalDate(Date date) { + return LocalDate.ofInstant( + date.toInstant(), + ZoneId.of("Asia/Seoul") + ); + } +} + diff --git a/src/main/java/com/genius/gitget/challenge/certification/util/EncryptUtil.java b/src/main/java/com/genius/gitget/challenge/certification/util/EncryptUtil.java new file mode 100644 index 00000000..e5c1bb4c --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/util/EncryptUtil.java @@ -0,0 +1,42 @@ +package com.genius.gitget.challenge.certification.util; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.encrypt.AesBytesEncryptor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public final class EncryptUtil { + private final AesBytesEncryptor encryptor; + + public String encrypt(String target) { + byte[] encrypt = encryptor.encrypt(target.getBytes(StandardCharsets.UTF_8)); + return byteArrayToString(encrypt); + } + + public String decrypt(String encrypted) { + byte[] decryptBytes = stringToByteArray(encrypted); + byte[] decrypt = encryptor.decrypt(decryptBytes); + return new String(decrypt, StandardCharsets.UTF_8); + } + + private String byteArrayToString(byte[] bytes) { + StringBuilder builder = new StringBuilder(); + for (byte aByte : bytes) { + builder.append(aByte); + builder.append(" "); + } + return builder.toString(); + } + + private byte[] stringToByteArray(String byteString) { + String[] split = byteString.split("\\s"); + ByteBuffer buffer = ByteBuffer.allocate(split.length); + for (String single : split) { + buffer.put((byte) Integer.parseInt(single)); + } + return buffer.array(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java new file mode 100644 index 00000000..88b64242 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java @@ -0,0 +1,73 @@ +package com.genius.gitget.challenge.instance.controller; + +import static com.genius.gitget.global.util.exception.SuccessCode.JOIN_SUCCESS; +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; +import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; +import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; +import com.genius.gitget.challenge.instance.service.InstanceDetailService; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/challenges") +public class InstanceDetailController { + private final InstanceDetailService instanceDetailService; + + + @GetMapping("/{instanceId}") + public ResponseEntity> getInstanceDetail( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long instanceId + ) { + InstanceResponse instanceDetailInformation = instanceDetailService.getInstanceDetailInformation( + userPrincipal.getUser(), instanceId); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instanceDetailInformation) + ); + } + + @PostMapping("/{instanceId}") + public ResponseEntity> joinChallenge( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long instanceId, + @RequestParam String repo + ) { + JoinRequest joinRequest = JoinRequest.builder() + .instanceId(instanceId) + .repository(repo) + .build(); + JoinResponse joinResponse = instanceDetailService.joinNewChallenge(userPrincipal.getUser(), joinRequest); + + return ResponseEntity.ok().body( + new SingleResponse<>(JOIN_SUCCESS.getStatus(), JOIN_SUCCESS.getMessage(), joinResponse) + ); + } + + @DeleteMapping("/{instanceId}") + public ResponseEntity> quitChallenge( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long instanceId + ) { + JoinResponse joinResponse = instanceDetailService.quitChallenge(userPrincipal.getUser(), instanceId); + + return ResponseEntity.ok().body( + new SingleResponse<>(JOIN_SUCCESS.getStatus(), JOIN_SUCCESS.getMessage(), joinResponse) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 6d1dcf5a..4d480fba 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -2,8 +2,9 @@ import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.likes.domain.Likes; -import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.Files; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -49,7 +50,7 @@ public class Instance { private List likesList = new ArrayList<>(); @OneToMany(mappedBy = "instance") - private List participantInfoList = new ArrayList<>(); + private List participantList = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "topic_id") @@ -109,6 +110,10 @@ public void updateParticipantCount(int amount) { this.participantCount += amount; } + public void updateProgress(Progress progress) { + this.progress = progress; + } + public Optional getFiles() { return Optional.ofNullable(this.files); } @@ -117,6 +122,10 @@ public void setFiles(Files files) { this.files = files; } + public int getTotalAttempt() { + return DateUtil.getAttemptCount(startedDate.toLocalDate(), completedDate.toLocalDate()); + } + //== 연관관계 편의 메서드 ==// public void setTopic(Topic topic) { this.topic = topic; diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java index 6aa7f917..e44ee421 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Progress.java @@ -3,5 +3,5 @@ public enum Progress { PREACTIVITY, ACTIVITY, - DONE + DONE; } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java index 4ba3e5ce..0779d056 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java @@ -25,14 +25,7 @@ public static InstanceDetailResponse createByEntity(Instance instance, Optional< .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()) .certificationMethod(instance.getCertificationMethod()) - .fileResponse(convertToFileResponse(files)) + .fileResponse(FileResponse.create(files)) .build(); } - - private static FileResponse convertToFileResponse(Optional files) { - if (files.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(files.get()); - } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java index e8dab50d..9ffa1d31 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java @@ -18,14 +18,7 @@ public static InstancePagingResponse createByEntity(Instance instance, Optional< .title(instance.getTitle()) .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()) - .fileResponse(convertToFileResponse(files)) + .fileResponse(FileResponse.create(files)) .build(); } - - private static FileResponse convertToFileResponse(Optional files) { - if (files.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(files.get()); - } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java new file mode 100644 index 00000000..b8d6b2e5 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java @@ -0,0 +1,46 @@ +package com.genius.gitget.challenge.instance.dto.detail; + +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.global.file.dto.FileResponse; +import java.time.LocalDate; +import lombok.Builder; + +@Builder +public record InstanceResponse( + Long instanceId, + String title, + int remainDays, + LocalDate startedDate, + LocalDate completedDate, + int participantCount, + int pointPerPerson, + String description, + String notice, + String certificationMethod, + JoinStatus joinStatus, + int hitCount, + FileResponse fileResponse +) { + + public static InstanceResponse createByEntity(Instance instance, JoinStatus joinStatus) { + LocalDate startedLocalDate = instance.getStartedDate().toLocalDate(); + LocalDate completedLocalDate = instance.getCompletedDate().toLocalDate(); + return InstanceResponse.builder() + .title(instance.getTitle()) + .instanceId(instance.getId()) + .remainDays(DateUtil.getRemainDaysToStart(startedLocalDate, LocalDate.now())) + .startedDate(startedLocalDate) + .completedDate(completedLocalDate) + .participantCount(instance.getParticipantCount()) + .pointPerPerson(instance.getPointPerPerson()) + .description(instance.getDescription()) + .notice(instance.getNotice()) + .certificationMethod(instance.getCertificationMethod()) + .joinStatus(joinStatus) + .hitCount(instance.getLikesList().size()) + .fileResponse(FileResponse.create(instance.getFiles())) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java new file mode 100644 index 00000000..bd31983b --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java @@ -0,0 +1,10 @@ +package com.genius.gitget.challenge.instance.dto.detail; + +import lombok.Builder; + +@Builder +public record JoinRequest( + Long instanceId, + String repository +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinResponse.java new file mode 100644 index 00000000..5ffaefe2 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinResponse.java @@ -0,0 +1,29 @@ +package com.genius.gitget.challenge.instance.dto.detail; + +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import lombok.Builder; + +@Builder +public record JoinResponse( + Long participantId, + JoinStatus joinStatus, + JoinResult joinResult +) { + public static JoinResponse createJoinResponse(Participant participant) { + return JoinResponse.builder() + .participantId(participant.getId()) + .joinStatus(participant.getJoinStatus()) + .joinResult(participant.getJoinResult()) + .build(); + } + + public static JoinResponse createQuitResponse() { + return JoinResponse.builder() + .participantId(null) + .joinResult(null) + .joinStatus(null) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java index 5717806d..479d12c3 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java @@ -21,14 +21,7 @@ public static HomeInstanceResponse createByEntity(Instance instance, Optional files) throws IOException { - if (files.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(files.get()); - } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index b9deaae3..16c45961 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -3,7 +3,6 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; import com.querydsl.core.annotations.QueryProjection; -import java.io.IOException; import java.util.Optional; import lombok.Builder; import lombok.Data; @@ -22,16 +21,17 @@ public class InstanceSearchResponse { @Builder @QueryProjection public InstanceSearchResponse(Long topicId, Long instanceId, String keyword, int pointPerPerson, - int participantCount, Files files) throws IOException { + int participantCount, Files files) { this.topicId = topicId; this.instanceId = instanceId; this.keyword = keyword; this.pointPerPerson = pointPerPerson; this.participantCount = participantCount; + this.fileResponse = FileResponse.create(Optional.of(files)); this.fileResponse = convertToFileResponse(Optional.ofNullable(files)); } - private static FileResponse convertToFileResponse(Optional files) throws IOException { + private static FileResponse convertToFileResponse(Optional files) { if (files.isEmpty()) { return FileResponse.createNotExistFile(); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java new file mode 100644 index 00000000..3378fac0 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -0,0 +1,94 @@ +package com.genius.gitget.challenge.instance.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_JOIN_INSTANCE; +import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_QUIT_INSTANCE; + +import com.genius.gitget.challenge.certification.service.GithubProvider; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; +import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; +import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.exception.BusinessException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GitHub; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class InstanceDetailService { + private final UserService userService; + private final InstanceProvider instanceProvider; + private final ParticipantProvider participantProvider; + private final GithubProvider githubProvider; + + + public InstanceResponse getInstanceDetailInformation(User user, Long instanceId) { + Instance instance = instanceProvider.findById(instanceId); + if (participantProvider.hasParticipant(user.getId(), instanceId)) { + return InstanceResponse.createByEntity(instance, JoinStatus.YES); + } + + return InstanceResponse.createByEntity(instance, JoinStatus.NO); + } + + @Transactional + public JoinResponse joinNewChallenge(User user, JoinRequest joinRequest) { + User persistUser = userService.findUserById(user.getId()); + Instance instance = instanceProvider.findById(joinRequest.instanceId()); + + String repository = joinRequest.repository(); + + if (!verifyGithub(persistUser, repository) || !canJoinChallenge(persistUser, instance)) { + throw new BusinessException(CAN_NOT_JOIN_INSTANCE); + } + + instance.updateParticipantCount(1); + Participant participant = Participant.createDefaultParticipant(repository); + participant.setUserAndInstance(persistUser, instance); + participant.joinChallenge(); + return JoinResponse.createJoinResponse(participantProvider.save(participant)); + } + + private boolean canJoinChallenge(User user, Instance instance) { + boolean b = !participantProvider.hasParticipant(user.getId(), instance.getId()); + return (instance.getProgress() == Progress.PREACTIVITY) && + !participantProvider.hasParticipant(user.getId(), instance.getId()); + } + + private boolean verifyGithub(User user, String repository) { + GitHub gitHub = githubProvider.getGithubConnection(user); + String repositoryFullName = githubProvider.getRepoFullName(gitHub, repository); + githubProvider.validateGithubRepository(gitHub, repositoryFullName); + return true; + } + + @Transactional + public JoinResponse quitChallenge(User user, Long instanceId) { + Instance instance = instanceProvider.findById(instanceId); + Participant participant = participantProvider.findByJoinInfo(user.getId(), instanceId); + + if (instance.getProgress() == Progress.DONE) { + throw new BusinessException(CAN_NOT_QUIT_INSTANCE); + } + + if (instance.getProgress() == Progress.PREACTIVITY) { + instance.updateParticipantCount(-1); + participantProvider.delete(participant); + return JoinResponse.createQuitResponse(); + } + + instance.updateParticipantCount(-1); + participant.quitChallenge(); + return JoinResponse.createJoinResponse(participant); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java new file mode 100644 index 00000000..0d87fc42 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java @@ -0,0 +1,22 @@ +package com.genius.gitget.challenge.instance.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class InstanceProvider { + private final InstanceRepository instanceRepository; + + public Instance findById(Long instanceId) { + return instanceRepository.findById(instanceId) + .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java b/src/main/java/com/genius/gitget/challenge/item/domain/Item.java index 14747c51..add2e147 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/Item.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -35,4 +36,12 @@ public class Item extends BaseTimeEntity { private ItemCategory itemCategory; private String details; + + @Builder + public Item(String name, int cost, ItemCategory itemCategory, String details) { + this.name = name; + this.cost = cost; + this.itemCategory = itemCategory; + this.details = details; + } } diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java b/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java index 24cc1ac7..c9254f35 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java @@ -7,6 +7,6 @@ @Getter public enum ItemCategory { PROFILE_FRAME, - CERTIFICATION_SKIPPER, + CERTIFICATION_PASSER, POINT_MULTIPLIER } diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java b/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java index 37c4934a..96ada28c 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java @@ -1,9 +1,13 @@ package com.genius.gitget.challenge.item.domain; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -16,8 +20,9 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserItem { @Id - @GeneratedValue - Long userItemId; + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_item_id") + Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "item_id") @@ -29,6 +34,21 @@ public class UserItem { private int count; + public UserItem(int count) { + this.count = count; + } + + //=== 비지니스 로직 ===// + public boolean hasItem() { + return this.count > 0; + } + + public void useItem() { + if (!hasItem()) { + throw new BusinessException(ErrorCode.HAS_NO_ITEM); + } + this.count -= 1; + } //=== 연관관계 편의 메서드 ===// public void setUser(User user) { diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java b/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java index 20d3f55c..75cea564 100644 --- a/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java +++ b/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java @@ -1,7 +1,15 @@ package com.genius.gitget.challenge.item.repository; +import com.genius.gitget.challenge.item.domain.ItemCategory; import com.genius.gitget.challenge.item.domain.UserItem; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface UserItemRepository extends JpaRepository { + + @Query("select u from UserItem u where u.user.id = :userId and u.item.itemCategory = :itemCategory") + Optional findUserItemByUser(@Param("userId") Long userId, + @Param("itemCategory") ItemCategory itemCategory); } diff --git a/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java b/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java new file mode 100644 index 00000000..865d1e3d --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java @@ -0,0 +1,13 @@ +package com.genius.gitget.challenge.item.service; + +import com.genius.gitget.challenge.item.repository.ItemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ItemProvider { + private final ItemRepository itemRepository; +} diff --git a/src/main/java/com/genius/gitget/challenge/item/service/UserItemProvider.java b/src/main/java/com/genius/gitget/challenge/item/service/UserItemProvider.java new file mode 100644 index 00000000..06dd57d6 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/service/UserItemProvider.java @@ -0,0 +1,31 @@ +package com.genius.gitget.challenge.item.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.USER_ITEM_NOT_FOUND; + +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.repository.UserItemRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.exception.BusinessException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class UserItemProvider { + private final UserItemRepository userItemRepository; + + public UserItem findUserItemByUser(Long userId, ItemCategory itemCategory) { + return userItemRepository.findUserItemByUser(userId, itemCategory) + .orElseThrow(() -> new BusinessException(USER_ITEM_NOT_FOUND)); + } + + public int countNumOfItem(User user, ItemCategory itemCategory) { + Optional optionalUserItem = userItemRepository.findUserItemByUser(user.getId(), itemCategory); + return optionalUserItem.map(UserItem::getCount) + .orElse(0); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java new file mode 100644 index 00000000..894c6d0a --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java @@ -0,0 +1,86 @@ +package com.genius.gitget.challenge.myChallenge.controller; + +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; +import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; +import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; +import com.genius.gitget.challenge.myChallenge.service.MyChallengeService; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.response.dto.ListResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/challenges") +@RequiredArgsConstructor +public class MyChallengeController { + private final MyChallengeService myChallengeService; + + + @GetMapping("/my/pre-activity") + public ResponseEntity> getPreActivityChallenges( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + List preActivityInstances = myChallengeService.getPreActivityInstances( + userPrincipal.getUser(), + LocalDate.now()); + + return ResponseEntity.ok().body( + new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), preActivityInstances) + ); + } + + + @GetMapping("/my/activity") + public ResponseEntity> getActivatedChallenges( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + List activatedInstances = myChallengeService.getActivatedInstances( + userPrincipal.getUser(), + LocalDate.now()); + + return ResponseEntity.ok().body( + new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), activatedInstances) + ); + } + + @GetMapping("/my/done") + public ResponseEntity> getDoneChallenges( + @AuthenticationPrincipal UserPrincipal userPrincipal + ) { + List doneInstances = myChallengeService.getDoneInstances( + userPrincipal.getUser(), + LocalDate.now()); + + return ResponseEntity.ok().body( + new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), doneInstances) + ); + } + + // /api/challenges/reward/1?item=true + @GetMapping("/reward/{instanceId}") + public ResponseEntity> getRewards( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long instanceId, + @RequestParam(value = "item") boolean useItem + ) { + + RewardRequest rewardRequest = new RewardRequest(userPrincipal.getUser(), instanceId, useItem, LocalDate.now()); + DoneResponse doneResponse = myChallengeService.getRewards(rewardRequest); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), doneResponse) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java new file mode 100644 index 00000000..f2558a23 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java @@ -0,0 +1,15 @@ +package com.genius.gitget.challenge.myChallenge.dto; + +import lombok.Builder; + +@Builder +public record ActivatedResponse( + Long instanceId, + String title, + int pointPerPerson, + String repository, + String certificateStatus, + int numOfPassItem, + boolean canUsePassItem +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java new file mode 100644 index 00000000..ccd947d8 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java @@ -0,0 +1,46 @@ +package com.genius.gitget.challenge.myChallenge.dto; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.domain.RewardStatus; +import lombok.Builder; + +@Builder +public record DoneResponse( + Long instanceId, + int pointPerPerson, + JoinResult joinResult, + boolean canGetReward, + int numOfPointItem, + int rewardedPoints, + double achievementRate +) { + public static DoneResponse createNotRewarded(Instance instance, Participant participant, + int numOfPointItem) { + return DoneResponse.builder() + .instanceId(instance.getId()) + .pointPerPerson(instance.getPointPerPerson()) + .joinResult(participant.getJoinResult()) + .canGetReward(canGetReward(participant)) + .numOfPointItem(numOfPointItem) + .build(); + } + + public static DoneResponse createRewarded(Instance instance, Participant participant, + double achievementRate) { + return DoneResponse.builder() + .instanceId(instance.getId()) + .pointPerPerson(instance.getPointPerPerson()) + .joinResult(participant.getJoinResult()) + .canGetReward(false) + .rewardedPoints(participant.getRewardPoints()) + .achievementRate(achievementRate) + .build(); + } + + private static boolean canGetReward(Participant participant) { + return (participant.getRewardStatus() == RewardStatus.NO) && + (participant.getJoinResult() == JoinResult.SUCCESS); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java new file mode 100644 index 00000000..809289bb --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java @@ -0,0 +1,13 @@ +package com.genius.gitget.challenge.myChallenge.dto; + +import lombok.Builder; + +@Builder +public record PreActivityResponse( + Long instanceId, + String title, + int participantCount, + int pointPerPerson, + int remainDays +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java new file mode 100644 index 00000000..0d72cb8b --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java @@ -0,0 +1,12 @@ +package com.genius.gitget.challenge.myChallenge.dto; + +import com.genius.gitget.challenge.user.domain.User; +import java.time.LocalDate; + +public record RewardRequest( + User user, + Long instanceId, + Boolean useItem, + LocalDate targetDate +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java new file mode 100644 index 00000000..f5947a6e --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -0,0 +1,168 @@ +package com.genius.gitget.challenge.myChallenge.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; +import static com.genius.gitget.challenge.participant.domain.RewardStatus.NO; +import static com.genius.gitget.challenge.participant.domain.RewardStatus.YES; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.service.CertificationProvider; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.service.UserItemProvider; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; +import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; +import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class MyChallengeService { + private final UserService userService; + private final ParticipantProvider participantProvider; + private final CertificationProvider certificationProvider; + private final UserItemProvider userItemProvider; + + + public List getPreActivityInstances(User user, LocalDate targetDate) { + List preActivity = new ArrayList<>(); + List participants = participantProvider.findJoinedByProgress(user.getId(), Progress.PREACTIVITY); + + for (Participant participant : participants) { + Instance instance = participant.getInstance(); + + PreActivityResponse preActivityResponse = PreActivityResponse.builder() + .instanceId(instance.getId()) + .title(instance.getTitle()) + .participantCount(instance.getParticipantCount()) + .pointPerPerson(instance.getPointPerPerson()) + .remainDays(DateUtil.getRemainDaysToStart(participant.getStartedDate(), targetDate)) + .build(); + preActivity.add(preActivityResponse); + } + + return preActivity; + } + + //TODO: 사용자의 달성률이 85%가 되지 않았을 때에는 joinResult가 Fail이어야 함 + public List getDoneInstances(User user, LocalDate targetDate) { + List done = new ArrayList<>(); + List participants = participantProvider.findJoinedByProgress(user.getId(), Progress.DONE); + + for (Participant participant : participants) { + Instance instance = participant.getInstance(); + + // 포인트를 아직 수령하지 않았을 때 + if (participant.getRewardStatus() == NO) { + int numOfPassItem = userItemProvider.countNumOfItem(user, ItemCategory.POINT_MULTIPLIER); + DoneResponse doneResponse = DoneResponse.createNotRewarded(instance, participant, numOfPassItem); + done.add(doneResponse); + continue; + } + + // 포인트를 수령했을 때 + double achievementRate = getAchievementRate(instance, participant.getId(), targetDate); + DoneResponse doneResponse = DoneResponse.createRewarded(instance, participant, achievementRate); + done.add(doneResponse); + } + + return done; + } + + private double getAchievementRate(Instance instance, Long participantId, LocalDate targetDate) { + int totalAttempt = instance.getTotalAttempt(); + int successCount = certificationProvider.countByStatus(participantId, CERTIFICATED, + targetDate); + + double successPercent = (double) successCount / (double) totalAttempt * 100; + return Math.round(successPercent * 100 / 100.0); + } + + public List getActivatedInstances(User user, LocalDate targetDate) { + List activated = new ArrayList<>(); + List participants = participantProvider.findJoinedByProgress(user.getId(), Progress.ACTIVITY); + + for (Participant participant : participants) { + Instance instance = participant.getInstance(); + Certification certification = certificationProvider.findByDate(targetDate, participant.getId()) + .orElse(getDummyCertification()); + int numOfPassItem = userItemProvider.countNumOfItem(user, ItemCategory.CERTIFICATION_PASSER); + boolean canUseItem = checkItemCondition(certification.getCertificationStatus(), numOfPassItem); + + ActivatedResponse activatedResponse = ActivatedResponse.builder() + .instanceId(instance.getId()) + .title(instance.getTitle()) + .pointPerPerson(instance.getPointPerPerson()) + .repository(participant.getRepositoryName()) + .certificateStatus(certification.getCertificationStatus().getTag()) + .canUsePassItem(canUseItem) + .numOfPassItem(canUseItem ? numOfPassItem : 0) + .build(); + activated.add(activatedResponse); + } + return activated; + } + + private boolean checkItemCondition(CertificateStatus certificateStatus, int numOfPassItem) { + return (certificateStatus == CertificateStatus.NOT_YET) && (numOfPassItem > 0); + } + + private Certification getDummyCertification() { + return Certification.builder() + .currentAttempt(0) + .certificationStatus(CertificateStatus.NOT_YET) + .certificatedAt(null) + .certificationLinks(null) + .build(); + } + + @Transactional + public DoneResponse getRewards(RewardRequest rewardRequest) { + User user = userService.findUserById(rewardRequest.user().getId()); + Participant participant = participantProvider.findByJoinInfo(user.getId(), rewardRequest.instanceId()); + Instance instance = participant.getInstance(); + + validRewardCondition(participant); + + int pointPerPerson = instance.getPointPerPerson(); + int rewardPoints = pointPerPerson; + + if (rewardRequest.useItem()) { + UserItem userItem = userItemProvider.findUserItemByUser(user.getId(), ItemCategory.POINT_MULTIPLIER); + userItem.useItem(); + rewardPoints = pointPerPerson * 2; + } + + user.updatePoints((long) rewardPoints); + double achievementRate = getAchievementRate(instance, participant.getId(), rewardRequest.targetDate()); + + participant.getRewards(rewardPoints); + return DoneResponse.createRewarded(instance, participant, achievementRate); + } + + private void validRewardCondition(Participant participant) { + if (participant.getJoinResult() != SUCCESS) { + throw new BusinessException(ErrorCode.CAN_NOT_GET_REWARDS); + } + if (participant.getRewardStatus() == YES) { + throw new BusinessException(ErrorCode.ALREADY_REWARDED); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinResult.java b/src/main/java/com/genius/gitget/challenge/participant/domain/JoinResult.java similarity index 59% rename from src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinResult.java rename to src/main/java/com/genius/gitget/challenge/participant/domain/JoinResult.java index febec45d..1a35f193 100644 --- a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinResult.java +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/JoinResult.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.participantinfo.domain; +package com.genius.gitget.challenge.participant.domain; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/challenge/participant/domain/JoinStatus.java b/src/main/java/com/genius/gitget/challenge/participant/domain/JoinStatus.java new file mode 100644 index 00000000..aba5972c --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/JoinStatus.java @@ -0,0 +1,5 @@ +package com.genius.gitget.challenge.participant.domain; + +public enum JoinStatus { + NO, YES +} diff --git a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java new file mode 100644 index 00000000..2d63a9a7 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java @@ -0,0 +1,128 @@ +package com.genius.gitget.challenge.participant.domain; + +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.user.domain.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DynamicInsert +@Table(name = "participant") +public class Participant { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "participant_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "instance_id") + private Instance instance; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @OneToMany(mappedBy = "participant") + private List certificationList = new ArrayList<>(); + + @Enumerated(EnumType.STRING) + @NotNull + @ColumnDefault("'YES'") + private JoinStatus joinStatus; + + @Enumerated(EnumType.STRING) + private JoinResult joinResult; + + private String repositoryName; + + @Enumerated(EnumType.STRING) + @ColumnDefault("'NO'") + private RewardStatus rewardStatus; + + private int rewardPoints; + + @Builder + private Participant(JoinStatus joinStatus, JoinResult joinResult, String repositoryName) { + this.joinStatus = joinStatus; + this.joinResult = joinResult; + this.repositoryName = repositoryName; + this.rewardStatus = RewardStatus.NO; + this.rewardPoints = 0; + } + + public static Participant createDefaultParticipant(String repositoryName) { + return Participant.builder() + .joinStatus(JoinStatus.YES) + .joinResult(JoinResult.PROCESSING) + .repositoryName(repositoryName) + .build(); + } + + //=== 비지니스 로직 ===// + public void joinChallenge() { + this.joinStatus = JoinStatus.YES; + this.joinResult = JoinResult.PROCESSING; + } + + public void quitChallenge() { + this.joinStatus = JoinStatus.NO; + this.joinResult = JoinResult.FAIL; + } + + public void getRewards(int rewardPoints) { + this.rewardStatus = RewardStatus.YES; + this.rewardPoints = rewardPoints; + } + + public void updateRepository(String repository) { + this.repositoryName = repository; + } + + public LocalDate getStartedDate() { + return this.getInstance().getStartedDate().toLocalDate(); + } + + + /*== 연관관계 편의 메서드 ==*/ + public void setUserAndInstance(User user, Instance instance) { + addParticipantInfoForUser(user); + addParticipantInfoForInstance(instance); + } + + private void addParticipantInfoForUser(User user) { + this.user = user; + if (!(user.getParticipantList().contains(this))) { + user.getParticipantList().add(this); + } + } + + private void addParticipantInfoForInstance(Instance instance) { + this.instance = instance; + if (!(instance.getParticipantList().contains(this))) { + instance.getParticipantList().add(this); + } + } +} diff --git a/src/main/java/com/genius/gitget/challenge/participant/domain/RewardStatus.java b/src/main/java/com/genius/gitget/challenge/participant/domain/RewardStatus.java new file mode 100644 index 00000000..54f59dd3 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/RewardStatus.java @@ -0,0 +1,5 @@ +package com.genius.gitget.challenge.participant.domain; + +public enum RewardStatus { + NO, YES +} diff --git a/src/main/java/com/genius/gitget/challenge/participant/repository/ParticipantRepository.java b/src/main/java/com/genius/gitget/challenge/participant/repository/ParticipantRepository.java new file mode 100644 index 00000000..50ccee77 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/participant/repository/ParticipantRepository.java @@ -0,0 +1,28 @@ +package com.genius.gitget.challenge.participant.repository; + +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import java.util.List; +import java.util.Optional; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ParticipantRepository extends JpaRepository { + + @Query("select p from Participant p where p.instance.id = :instanceId and p.user.id = :userId") + Optional findByJoinInfo(@Param("userId") Long userId, + @Param("instanceId") Long instanceId); + + @Query("select p from Participant p where p.user.id = :userId and p.instance.progress = :progress and p.joinStatus = 'YES'") + List findAllJoinedByProgress(@Param("userId") Long userId, + @Param("progress") Progress progress); + + @Query("select p from Participant p where p.instance.id = :instanceId and p.joinStatus = :joinStatus") + Slice findAllByInstanceId(@Param("instanceId") Long instanceId, + @Param("joinStatus") JoinStatus joinStatus, + Pageable pageable); +} diff --git a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java new file mode 100644 index 00000000..203379db --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java @@ -0,0 +1,76 @@ +package com.genius.gitget.challenge.participant.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.PARTICIPANT_INFO_NOT_FOUND; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ParticipantProvider { + private final ParticipantRepository participantRepository; + + @Transactional + public Participant save(Participant participant) { + return participantRepository.save(participant); + } + + @Transactional + public void delete(Participant participant) { + participantRepository.delete(participant); + } + + public Participant findByJoinInfo(Long userId, Long instanceId) { + return participantRepository.findByJoinInfo(userId, instanceId) + .orElseThrow(() -> new BusinessException(PARTICIPANT_INFO_NOT_FOUND)); + } + + public Participant findById(Long participantInfoId) { + return participantRepository.findById(participantInfoId) + .orElseThrow(() -> new BusinessException(PARTICIPANT_INFO_NOT_FOUND)); + } + + public Slice findAllByInstanceId(Long userId, Long instanceId, Pageable pageable) { + Slice participants = participantRepository.findAllByInstanceId(instanceId, JoinStatus.YES, + pageable); + List filtered = participants.stream() + .filter(participant -> participant.getUser().getId() != userId) + .toList(); + + return new SliceImpl<>(filtered, pageable, participants.hasNext()); + } + + public Instance getInstanceById(Long participantId) { + return participantRepository.findById(participantId) + .orElseThrow(() -> new BusinessException(PARTICIPANT_INFO_NOT_FOUND)) + .getInstance(); + } + + public List findJoinedByProgress(Long userId, Progress progress) { + return participantRepository.findAllJoinedByProgress(userId, progress); + } + + public boolean hasParticipant(Long userId, Long instanceId) { + return participantRepository.findByJoinInfo(userId, instanceId).isPresent(); + } + + public LocalDate getInstanceStartDate(Long participantId) { + return getInstanceById(participantId).getStartedDate().toLocalDate(); + } + +} diff --git a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinStatus.java b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinStatus.java deleted file mode 100644 index 6f5240c9..00000000 --- a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/JoinStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.genius.gitget.challenge.participantinfo.domain; - -public enum JoinStatus { - NO, YES -} diff --git a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java b/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java deleted file mode 100644 index 93e042ca..00000000 --- a/src/main/java/com/genius/gitget/challenge/participantinfo/domain/ParticipantInfo.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.genius.gitget.challenge.participantinfo.domain; - -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.user.domain.User; -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.DynamicInsert; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DynamicInsert -@Table(name = "participantInfo") -public class ParticipantInfo { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "participantInfo_id") - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "instance_id") - private Instance instance; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - - @Enumerated(EnumType.STRING) - @Column(name = "join_status") - @NotNull - @ColumnDefault("'YES'") - private JoinStatus joinStatus; - - @Enumerated(EnumType.STRING) - @Column(name = "join_result") - private JoinResult joinResult; - - @Builder - public ParticipantInfo(JoinStatus joinStatus, JoinResult joinResult) { - this.joinStatus = joinStatus; - this.joinResult = joinResult; - } - - /*== 연관관계 편의 메서드 ==*/ - public void setUserAndInstance(User user, Instance instance) { - addParticipantInfoForUser(user); - addParticipantInfoForInstance(instance); - } - - private void addParticipantInfoForUser(User user) { - this.user = user; - if (!(user.getParticipantInfoList().contains(this))) { - user.getParticipantInfoList().add(this); - } - } - - private void addParticipantInfoForInstance(Instance instance) { - this.instance = instance; - if (!(instance.getParticipantInfoList().contains(this))) { - instance.getParticipantInfoList().add(this); - } - } -} diff --git a/src/main/java/com/genius/gitget/challenge/participantinfo/repository/ParticipantInfoRepository.java b/src/main/java/com/genius/gitget/challenge/participantinfo/repository/ParticipantInfoRepository.java deleted file mode 100644 index d1c316ed..00000000 --- a/src/main/java/com/genius/gitget/challenge/participantinfo/repository/ParticipantInfoRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.genius.gitget.challenge.participantinfo.repository; - -import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ParticipantInfoRepository extends JpaRepository { -} diff --git a/src/main/java/com/genius/gitget/challenge/report/controller/ReportController.java b/src/main/java/com/genius/gitget/challenge/report/controller/ReportController.java new file mode 100644 index 00000000..40d15522 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/report/controller/ReportController.java @@ -0,0 +1,7 @@ +package com.genius.gitget.challenge.report.controller; + +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ReportController { +} diff --git a/src/main/java/com/genius/gitget/challenge/report/domain/Report.java b/src/main/java/com/genius/gitget/challenge/report/domain/Report.java new file mode 100644 index 00000000..e3b857d5 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/report/domain/Report.java @@ -0,0 +1,31 @@ +package com.genius.gitget.challenge.report.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "report") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class Report { + // TODO PR URL 주소 및 효율적인 방법 조사해보기 -> 1차 배포 후 진행으로 변경 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "report_id") + private Long id; + + private String reporter; + + private String receiver; + + private String reportUrl; + + private String reportReason; +} diff --git a/src/main/java/com/genius/gitget/challenge/report/repository/ReportRepository.java b/src/main/java/com/genius/gitget/challenge/report/repository/ReportRepository.java new file mode 100644 index 00000000..591c5d1b --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/report/repository/ReportRepository.java @@ -0,0 +1,7 @@ +package com.genius.gitget.challenge.report.repository; + +import com.genius.gitget.challenge.report.domain.Report; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReportRepository extends JpaRepository { +} diff --git a/src/main/java/com/genius/gitget/challenge/report/service/ReportService.java b/src/main/java/com/genius/gitget/challenge/report/service/ReportService.java new file mode 100644 index 00000000..37662cc3 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/report/service/ReportService.java @@ -0,0 +1,7 @@ +package com.genius.gitget.challenge.report.service; + +import org.springframework.stereotype.Service; + +@Service +public class ReportService { +} diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 806a8708..1060a3c7 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -2,7 +2,7 @@ import com.genius.gitget.challenge.item.domain.UserItem; import com.genius.gitget.challenge.likes.domain.Likes; -import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.domain.BaseTimeEntity; @@ -23,10 +23,12 @@ import jakarta.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; @Entity @Getter @@ -46,7 +48,7 @@ public class User extends BaseTimeEntity { private List likesList = new ArrayList<>(); @OneToMany(mappedBy = "user") - private List participantInfoList = new ArrayList<>(); + private List participantList = new ArrayList<>(); @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List payment = new ArrayList<>(); @@ -73,6 +75,10 @@ public class User extends BaseTimeEntity { @Column(length = 100) private String information; + @Column(columnDefinition = "TEXT") + private String githubToken; + + @ColumnDefault(value = "0") private Long point = 0L; @Builder @@ -86,6 +92,7 @@ public User(ProviderInfo providerInfo, String identifier, Role role, String nick this.information = information; } + //=== 비지니스 로직 ===// public void updateUserInformation(String nickname, String information) { this.nickname = nickname; this.information = information; @@ -99,6 +106,19 @@ public void updateRole(Role role) { this.role = role; } + public void updateGithubPersonalToken(String encryptedToken) { + this.githubToken = encryptedToken; + } + + public long updatePoints(Long amount) { + this.point += amount; + return this.point; + } + + public Optional getFiles() { + return Optional.ofNullable(this.files); + } + //=== 연관관계 편의 메서드 ===// public void setFiles(Files files) { this.files = files; diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 9817171f..4c8eeed1 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -2,8 +2,10 @@ import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REGISTERED; import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; +import com.genius.gitget.challenge.certification.util.EncryptUtil; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; @@ -20,6 +22,7 @@ @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + private final EncryptUtil encryptUtil; public User findUserById(Long id) { @@ -32,6 +35,11 @@ public User findUserByIdentifier(String identifier) { .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); } + @Transactional + public Long save(User user) { + return userRepository.saveAndFlush(user).getId(); + } + @Transactional public Long signup(SignupRequest requestUser) { User user = findUserByIdentifier(requestUser.identifier()); @@ -54,6 +62,14 @@ public void isNicknameDuplicate(String nickname) { } } + public String getGithubToken(User user) { + String githubToken = user.getGithubToken(); + if (githubToken == null || githubToken.isEmpty() || githubToken.isBlank()) { + throw new BusinessException(GITHUB_TOKEN_NOT_FOUND); + } + return encryptUtil.decrypt(githubToken); + } + public void isAlreadyRegistered(User user) { if (user.getRole() != Role.NOT_REGISTERED) { throw new BusinessException(ALREADY_REGISTERED); diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index fb3874bd..650dbc41 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -8,11 +8,11 @@ public record FileResponse( Long fileId, String encodedFile) { - public static FileResponse create(Optional files) { - if (files.isEmpty()) { - return createNotExistFile(); + public static FileResponse create(Optional optionalFiles) { + if (optionalFiles.isEmpty()) { + return FileResponse.createNotExistFile(); } - return createExistFile(files.get()); + return FileResponse.createExistFile(optionalFiles.get()); } public static FileResponse createExistFile(Files files) { diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index eb8d9289..1b34e735 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -111,10 +111,15 @@ private void deleteFilesInStorage(Files files) { public FileResponse getEncodedFile(Long fileId) { Optional optionalFiles = filesRepository.findById(fileId); - if (optionalFiles.isEmpty()) { - return FileResponse.createNotExistFile(); - } + return optionalFiles + .map(FileResponse::createExistFile) + .orElseGet(FileResponse::createNotExistFile); + + } - return FileResponse.createExistFile(optionalFiles.get()); + public FileResponse getEncodedFile(Optional optionalFiles) { + return optionalFiles + .map(FileResponse::createExistFile) + .orElseGet(FileResponse::createNotExistFile); } } diff --git a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java index 5b34a047..b189ab5c 100644 --- a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java @@ -27,7 +27,7 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**", "/login"}; + public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**", "/login", "/favicon.ico"}; private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index a32f83ec..642983c8 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -13,7 +13,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -51,12 +50,4 @@ public ResponseEntity logout( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); } - - @GetMapping("/test") - public ResponseEntity test() { - - return ResponseEntity.ok().body( - new CommonResponse(SUCCESS.getStatus(), "TEST 성공") - ); - } } diff --git a/src/main/java/com/genius/gitget/global/security/service/JwtService.java b/src/main/java/com/genius/gitget/global/security/service/JwtService.java index 36af4d15..4929f570 100644 --- a/src/main/java/com/genius/gitget/global/security/service/JwtService.java +++ b/src/main/java/com/genius/gitget/global/security/service/JwtService.java @@ -3,15 +3,15 @@ import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.gitget.global.security.constants.JwtRule.JWT_ISSUE_HEADER; import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.global.util.exception.ErrorCode.JWT_TOKEN_NOT_FOUND; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; -import static com.genius.gitget.global.util.exception.ErrorCode.TOKEN_NOT_FOUND; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.security.constants.JwtRule; import com.genius.gitget.global.security.constants.TokenStatus; import com.genius.gitget.global.security.domain.Token; import com.genius.gitget.global.security.repository.TokenRepository; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import io.jsonwebtoken.Jwts; @@ -107,7 +107,7 @@ public boolean validateRefreshToken(String token, String identifier) { public String resolveTokenFromCookie(HttpServletRequest request, JwtRule tokenPrefix) { Cookie[] cookies = request.getCookies(); if (cookies == null) { - throw new BusinessException(TOKEN_NOT_FOUND); + throw new BusinessException(JWT_TOKEN_NOT_FOUND); } return jwtUtil.resolveTokenFromCookie(cookies, tokenPrefix); } diff --git a/src/main/java/com/genius/gitget/global/security/service/TokenService.java b/src/main/java/com/genius/gitget/global/security/service/TokenService.java index ca596fbf..4b4126c6 100644 --- a/src/main/java/com/genius/gitget/global/security/service/TokenService.java +++ b/src/main/java/com/genius/gitget/global/security/service/TokenService.java @@ -18,7 +18,7 @@ public class TokenService { public Token findTokenByIdentifier(String identifier) { return tokenRepository.findById(identifier) - .orElseThrow(() -> new BusinessException(ErrorCode.TOKEN_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(ErrorCode.JWT_TOKEN_NOT_FOUND)); } public boolean isRefreshHijacked(String identifier, String refreshToken) { diff --git a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java index 6caed22f..37261d0b 100644 --- a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java @@ -2,11 +2,25 @@ import com.genius.gitget.global.util.formatter.LocalDateFormatter; import com.genius.gitget.global.util.formatter.LocalDateTimeFormatter; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.security.crypto.encrypt.AesBytesEncryptor; @Configuration +@PropertySource("classpath:application-github.yml") +@RequiredArgsConstructor public class AppConfig { + private final Environment env; + + @Bean + public AesBytesEncryptor aesBytesEncryptor() { + return new AesBytesEncryptor( + env.getProperty("github.encryptSecretKey"), + env.getProperty("github.salt")); + } @Bean public LocalDateFormatter localDateFormatter() { diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index a576ad33..95c4dfdc 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -19,10 +19,12 @@ public enum ErrorCode { TOPIC_HAVE_INSTANCE(HttpStatus.BAD_REQUEST, "해당 토픽은 인스턴스를 가지고 있으므로 삭제할 수 없습니다."), INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), + PARTICIPANT_INFO_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 참여 정보를 찾을 수 없습니다."), + CERTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인증 정보를 찾을 수 없습니다."), ALREADY_REGISTERED(HttpStatus.BAD_REQUEST, "이미 회원가입이 완료된 사용자입니다."), MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."), - DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "이미 존재하는 닉네임입니다"), + DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "이미 존재하는 닉네임입니다."), NOT_AUTHENTICATED_USER(HttpStatus.BAD_REQUEST, "인증 가능한 사용자가 아닙니다."), INVALID_EXPIRED_JWT(HttpStatus.BAD_REQUEST, "이미 만료된 JWT 입니다."), @@ -32,15 +34,33 @@ public enum ErrorCode { INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."), INVALID_PROGRESS(HttpStatus.BAD_REQUEST, "존재하지 않는 정보입니다."), - TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."), + JWT_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."), FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "해당 파일(이미지)이 존재하지 않습니다."), NOT_SUPPORTED_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 확장자입니다."), NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 타입입니다."), - IMAGE_NOT_ENCODED(HttpStatus.BAD_REQUEST, "이미지를 인코딩하는 과정에서 오류가 발생했습니다."), + FILE_NOT_DELETED(HttpStatus.BAD_REQUEST, "파일(이미지)이 정상적으로 삭제되지 않았습니다."), FILE_NOT_SAVED(HttpStatus.BAD_REQUEST, "파일(이미지)가 정상적으로 저장되지 않았습니다."), FILE_NOT_COPIED(HttpStatus.BAD_REQUEST, "파일(이미지)가 정상적으로 복사되지 않았습니다."), - FILE_NOT_DELETED(HttpStatus.BAD_REQUEST, "파일(이미지)이 정상적으로 삭제되지 않았습니다."); + IMAGE_NOT_ENCODED(HttpStatus.BAD_REQUEST, "이미지를 인코딩하는 과정에서 오류가 발생했습니다."), + + GITHUB_CONNECTION_FAILED(HttpStatus.BAD_REQUEST, "Github 연결이 실패했습니다."), + GITHUB_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "저장된 Github Token을 찾을 수 없습니다."), + GITHUB_ID_INCORRECT(HttpStatus.BAD_REQUEST, "소셜로그인에 사용한 Github 계정과 일치하지 않습니다."), + GITHUB_REPOSITORY_INCORRECT(HttpStatus.BAD_REQUEST, "해당 레포지토리와 연결이 되지 않습니다."), + GITHUB_PR_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 레포지토리에 PR이 존재하지 않습니다."), + + CAN_NOT_JOIN_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스에 참여할 수 없습니다."), + CAN_NOT_QUIT_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스의 참여를 취소할 수 없습니다."), + + CERTIFICATION_UNABLE(HttpStatus.BAD_REQUEST, "해당 챌린지는 인증을 할 수 없는 상태입니다."), + + USER_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자의 아이템 보유 정보를 찾을 수 없습니다."), + HAS_NO_ITEM(HttpStatus.NOT_FOUND, "해당 아이템을 보유하고 있지 않습니다."), + CAN_NOT_USE_PASS_ITEM(HttpStatus.BAD_REQUEST, "인증 패스 아이템을 사용할 수 없는 조건입니다."), + + CAN_NOT_GET_REWARDS(HttpStatus.BAD_REQUEST, "챌린지 보상을 받을 수 있는 조건이 아닙니다."), + ALREADY_REWARDED(HttpStatus.BAD_REQUEST, "해당 챌린지 보상은 이미 지급되었습니다."); private final HttpStatus status; diff --git a/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java b/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java index edee8294..05cdd534 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java @@ -9,14 +9,14 @@ public enum SuccessCode { // 200 OK - SUCCESS(HttpStatus.OK, "OK", "요청이 정상적으로 처리되었습니다.") + SUCCESS(HttpStatus.OK, "요청이 정상적으로 처리되었습니다."), + JOIN_SUCCESS(HttpStatus.OK, "챌린지 참여가 정상적으로 처리되었습니다."), // 201 CREATED - , CREATED(HttpStatus.CREATED, "CREATED", "정상적으로 생성되었습니다."); + CREATED(HttpStatus.CREATED, "정상적으로 생성되었습니다."); private final HttpStatus status; - private final String key; private final String message; } diff --git a/src/main/java/com/genius/gitget/global/util/response/dto/ListResponse.java b/src/main/java/com/genius/gitget/global/util/response/dto/ListResponse.java index bf86871d..441132a4 100644 --- a/src/main/java/com/genius/gitget/global/util/response/dto/ListResponse.java +++ b/src/main/java/com/genius/gitget/global/util/response/dto/ListResponse.java @@ -3,6 +3,7 @@ import java.util.List; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; @Getter @@ -11,6 +12,13 @@ public class ListResponse extends CommonResponse { private List dataList; private int count; + + public ListResponse(HttpStatus status, String message, List dataList) { + super(status, message); + this.dataList = dataList; + this.count = dataList.size(); + } + public ListResponse(List dataList) { this.dataList = dataList; this.count = dataList.size(); diff --git a/src/main/java/com/genius/gitget/payment/service/PaymentService.java b/src/main/java/com/genius/gitget/payment/service/PaymentService.java index b36d9352..13c44276 100644 --- a/src/main/java/com/genius/gitget/payment/service/PaymentService.java +++ b/src/main/java/com/genius/gitget/payment/service/PaymentService.java @@ -69,7 +69,7 @@ public PaymentSuccessResponse tossPaymentSuccess(PaymentSuccessRequest paymentSu User user = userRepository.findByIdentifier(payment.getUser().getIdentifier()) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - user.setPoint(payment.getPointAmount()); + user.updatePoints(payment.getPointAmount()); return result; } diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileService.java index 28330661..e30ed0ee 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileService.java @@ -1,14 +1,14 @@ package com.genius.gitget.profile.service; -import static com.genius.gitget.challenge.participantinfo.domain.JoinResult.FAIL; -import static com.genius.gitget.challenge.participantinfo.domain.JoinResult.PROCESSING; -import static com.genius.gitget.challenge.participantinfo.domain.JoinResult.SUCCESS; -import static com.genius.gitget.challenge.participantinfo.domain.JoinStatus.YES; +import static com.genius.gitget.challenge.participant.domain.JoinResult.FAIL; +import static com.genius.gitget.challenge.participant.domain.JoinResult.PROCESSING; +import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; +import static com.genius.gitget.challenge.participant.domain.JoinStatus.YES; import com.genius.gitget.admin.signout.Signout; import com.genius.gitget.admin.signout.SignoutRepository; -import com.genius.gitget.challenge.participantinfo.domain.JoinResult; -import com.genius.gitget.challenge.participantinfo.domain.ParticipantInfo; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.file.domain.FileType; @@ -44,6 +44,10 @@ public class ProfileService { private final FilesService filesService; private final SignoutRepository signoutRepository; + private static boolean isProfileFileType(Files files) { + return files != null && files.getFileType().equals(FileType.PROFILE); + } + // 포인트 조회 public UserPointResponse getUserPoint(User user) { return UserPointResponse.builder() @@ -67,7 +71,7 @@ public UserInformationResponse getUserInformation(Long userId) { public UserDetailsInformationResponse getUserDetailsInformation(User user) { User findUser = getUserByIdentifier(user.getIdentifier()); int participantCount = 0; - List participantInfoList = findUser.getParticipantInfoList(); + List participantInfoList = findUser.getParticipantList(); for (int i = 0; i < participantInfoList.size(); i++) { if (participantInfoList.get(i).getJoinStatus() == YES) { @@ -93,11 +97,11 @@ public void updateUserInformation(User user, UserInformationUpdateRequest userIn userInformationUpdateRequest.getInformation()); if (multipartFile != null) { - if (findUser.getFiles() == null) { + if (findUser.getFiles().isEmpty()) { Files uploadedFile = filesService.uploadFile(multipartFile, type); findUser.setFiles(uploadedFile); } else { - filesService.updateFile(findUser.getFiles().getId(), multipartFile); + filesService.updateFile(findUser.getFiles().get().getId(), multipartFile); } } userRepository.save(findUser); @@ -153,7 +157,7 @@ public UserChallengeResultResponse getUserChallengeResult(User user) { put(SUCCESS, new ArrayList<>()); } }; - List participantInfoList = findUser.getParticipantInfoList(); // 유저의 참여 정보를 담고 있는 리스트 + List participantInfoList = findUser.getParticipantList(); // 유저의 참여 정보를 담고 있는 리스트 int participanTotalCount = participantInfoList.size(); for (int i = 0; i < participantInfoList.size(); i++) { // 각 참여 정보를 받아옴. @@ -185,8 +189,8 @@ private User getUserByIdentifier(String identifier) { } private Files getFiles(User findUser) { - if (findUser.getFiles() != null) { - return filesRepository.findById(findUser.getFiles().getId()).orElse(null); + if (findUser.getFiles().isPresent()) { + return filesRepository.findById(findUser.getFiles().get().getId()).orElse(null); } else { return null; } @@ -196,8 +200,4 @@ private User getUserById(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); } - - private static boolean isProfileFileType(Files files) { - return files != null && files.getFileType().equals(FileType.PROFILE); - } } diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java index ff89781f..2965814a 100644 --- a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java @@ -46,5 +46,5 @@ public void setup() { // .apply(springSecurity()) // .build(); } - + } diff --git a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java new file mode 100644 index 00000000..954c9315 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java @@ -0,0 +1,139 @@ +package com.genius.gitget.challenge.certification.controller; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.genius.gitget.challenge.certification.service.CertificationService; +import com.genius.gitget.challenge.certification.service.GithubService; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +@Slf4j +@Transactional +@SpringBootTest +class CertificationControllerTest { + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + @Autowired + TokenTestUtil tokenTestUtil; + @Autowired + CertificationService certificationService; + @Autowired + GithubService githubService; + @Autowired + InstanceRepository instanceRepository; + @Autowired + UserRepository userRepository; + + @Value("${github.personalKey}") + private String githubToken; + + @Value("${github.githubId}") + private String githubId; + + @Value("${github.repository}") + private String targetRepo; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @DisplayName("Github token을 전달받아서 검증하여 데이터베이스에 저장할 수 있다.") + @WithMockCustomUser + public void should_saveToken_when_tokenValid() throws Exception { + //given + String requestBody = "{\"githubToken\": \"" + githubToken + "\"}"; + + //when + + //then + mockMvc.perform(post("/api/certification/register/token") + .cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().is2xxSuccessful()); + } + + @Test + @DisplayName("가입하지 않은 사용자인 경우 4xx Client error가 발생한다.") + @WithMockCustomUser(role = Role.NOT_REGISTERED) + public void should_throwException_when_unregisteredUser() throws Exception { + mockMvc.perform(post("/api/certification/register/token")) + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("가입한 사용자이나, JWT가 발급되지 않은 경우 4xx client error가 발생한다.") + @WithMockCustomUser(role = Role.NOT_REGISTERED) + public void should_throwException_when_JWTNonExist() throws Exception { + mockMvc.perform(post("/api/certification/register/token") + .cookie(tokenTestUtil.createAccessCookie())) + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("회원가입 시 사용한 깃허브 계정과 토큰 계정이 같지 않으면 4xx client error가 발생한다.") + @WithMockCustomUser(identifier = "test") + public void should_throwException_when_accountIncorrect() throws Exception { + //given + String requestBody = "{\"githubToken\": \"" + githubToken + "\"}"; + + //when & then + mockMvc.perform(post("/api/certification/register/token") + .cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("Repository 이름을 전달받아서 검증 후, 데이터베이스에 저장할 수 있다.") + @WithMockCustomUser + public void should_saveToken_when_repositoryValid() throws Exception { + //given + Instance savedInstance = getSavedInstance(); + + //when + User user = userRepository.findByIdentifier(githubId).get(); + githubService.registerGithubPersonalToken(user, githubToken); + + //then + mockMvc.perform(get("/api/certification/verify/repository?repo=" + targetRepo) + .cookie(tokenTestUtil.createAccessCookie())) + .andExpect(status().is2xxSuccessful()); + } + + private Instance getSavedInstance() { + return instanceRepository.save( + Instance.builder() + .progress(Progress.PREACTIVITY) + .build() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/repository/CertificationRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/certification/repository/CertificationRepositoryTest.java new file mode 100644 index 00000000..ba3b2a33 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/repository/CertificationRepositoryTest.java @@ -0,0 +1,117 @@ +package com.genius.gitget.challenge.certification.repository; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import java.time.LocalDate; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class CertificationRepositoryTest { + @Autowired + CertificationRepository certificationRepository; + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + ParticipantRepository participantRepository; + + @Test + @DisplayName("Certification 객체를 만들어서 저장할 수 있다.") + public void should() { + //given + LocalDate certificatedDate = LocalDate.of(2024, 2, 1); + String certificationLinks = "https://test.com"; + Participant savedParticipant = getSavedParticipant(getSavedUser(), getSavedInstance()); + + //when + Certification savedCertification = getSavedCertification(NOT_YET, certificatedDate, certificationLinks, + savedParticipant); + + //then + assertThat(savedCertification.getCertificationStatus()).isEqualTo(NOT_YET); + assertThat(savedCertification.getCertificatedAt()).isEqualTo(certificatedDate); + assertThat(savedCertification.getCertificationLinks()).isEqualTo(certificationLinks); + } + + @Test + @DisplayName("인증 일자가 특정 기간에 포함된 Certification 객체들을 찾을 수 있다.") + public void should_returnCertifications_byDuration() { + //given + String certificationLinks = "https://test.com"; + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 2, 4); + Participant participant = getSavedParticipant(getSavedUser(), getSavedInstance()); + + //when + getSavedCertification(NOT_YET, startDate, certificationLinks, participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(1), certificationLinks, participant); + getSavedCertification(CERTIFICATED, endDate.minusDays(1), certificationLinks, participant); + getSavedCertification(CERTIFICATED, endDate, certificationLinks, participant); + + List certifications = certificationRepository.findByDuration(startDate, endDate, + participant.getId()); + + //then + assertThat(certifications.size()).isEqualTo(4); + } + + private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, + String certificationLink, Participant participant) { + Certification certification = Certification.builder() + .certificationStatus(status) + .certificatedAt(certificatedAt) + .certificationLinks(certificationLink) + .build(); + certification.setParticipant(participant); + return certificationRepository.save(certification); + } + + private User getSavedUser() { + return userRepository.save( + User.builder() + .providerInfo(ProviderInfo.GITHUB) + .identifier("identifier") + .role(Role.USER) + .build() + ); + } + + private Instance getSavedInstance() { + return instanceRepository.save( + Instance.builder() + .progress(Progress.ACTIVITY) + .build() + ); + } + + private Participant getSavedParticipant(User user, Instance instance) { + Participant participant = Participant.builder() + .joinStatus(JoinStatus.YES) + .build(); + participant.setUserAndInstance(user, instance); + return participantRepository.save(participant); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationProviderTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationProviderTest.java new file mode 100644 index 00000000..69d7f6e8 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationProviderTest.java @@ -0,0 +1,195 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class CertificationProviderTest { + @Autowired + private CertificationProvider certificationProvider; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private UserRepository userRepository; + + @Test + @DisplayName("DB에서 특정 기간 내의 인증 객체 리스트들을 받아올 수 있다.") + public void should_returnList_when_passDuration() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 2, 5); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + + getSavedCertification(startDate, CERTIFICATED, "link1", participant); + getSavedCertification(startDate.plusDays(1), CERTIFICATED, "link1", participant); + getSavedCertification(endDate.minusDays(1), NOT_YET, null, participant); + getSavedCertification(endDate.minusDays(2), CERTIFICATED, "link1", participant); + + //when + List certifications = certificationProvider.findByDuration(startDate, endDate, + participant.getId()); + + //then + assertThat(certifications.size()).isEqualTo(4); + } + + @Test + @DisplayName("특정 일자에 저장된 인증 객체를 받아올 수 있다.") + public void should_getCertification_when_passDate() { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + + //when + getSavedCertification(targetDate, CERTIFICATED, "link1", participant); + Optional byDate = certificationProvider.findByDate(targetDate, participant.getId()); + + //then + assertThat(byDate).isPresent(); + } + + @Test + @DisplayName("특정 기간 이내에 특정 인증 상태인 인증 객체의 개수를 받아올 수 있다.") + public void should_count_when_passStatus() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 2, 5); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + + getSavedCertification(startDate, CERTIFICATED, "link1", participant); + getSavedCertification(startDate.plusDays(1), CERTIFICATED, "link1", participant); + getSavedCertification(endDate.minusDays(1), NOT_YET, null, participant); + getSavedCertification(endDate.minusDays(2), CERTIFICATED, "link1", participant); + + //when + int certificated = certificationProvider.countByStatus(participant.getId(), CERTIFICATED, + endDate); + + //then + assertThat(certificated).isEqualTo(3); + } + + @Test + @DisplayName("사용자가 인증을 생성/갱신할 수 있다.") + public void should_renewCertification() { + //given + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + LocalDate targetDate = LocalDate.of(2024, 2, 1); + List pullRequests = List.of("pr link1", "pr link2"); + + //when + Certification certification = certificationProvider.createCertification(participant, targetDate, + pullRequests); + + //then + assertThat(certification.getCertificatedAt()).isEqualTo(targetDate); + } + + @Test + @DisplayName("인증과 관련된 정보를 전달했을 때, 객체의 정보를 업데이트할 수 있다.") + public void should_update_when_passInfo() { + //given + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + LocalDate targetDate = LocalDate.of(2024, 2, 1); + Certification certification = getSavedCertification(targetDate, NOT_YET, "", participant); + List pullRequests = List.of("pr link1", "pr link2"); + + //when + Certification updatedCertification = certificationProvider.update(certification, targetDate, pullRequests); + + //then + assertThat(updatedCertification.getId()).isEqualTo(certification.getId()); + assertThat(updatedCertification.getCertificatedAt()).isEqualTo(targetDate); + assertThat(updatedCertification.getCertificationStatus()).isEqualTo(CERTIFICATED); + assertThat(updatedCertification.getCertificationLinks()).isEqualTo("pr link1,pr link2,"); + } + + private Certification getSavedCertification(LocalDate certificatedAt, CertificateStatus status, + String link, Participant participant) { + Certification certification = certificationProvider.save( + Certification.builder() + .certificatedAt(certificatedAt) + .certificationStatus(status) + .certificationLinks(link) + .build() + ); + certification.setParticipant(participant); + return certification; + } + + private Participant getSavedParticipant(User user, Instance instance) { + Participant participant = participantRepository.save( + Participant.createDefaultParticipant("repo") + ); + participant.setUserAndInstance(user, instance); + return participant; + } + + private Participant getSavedParticipant(Instance instance) { + Participant participant = participantRepository.save( + Participant.createDefaultParticipant("repo") + ); + participant.setUserAndInstance(null, instance); + return participant; + } + + private Instance getSavedInstance() { + return instanceRepository.save( + Instance.builder() + .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .build() + ); + } + + private User getSavedUser() { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("githubId") + .information("information") + .tags("BE,FE") + .build() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java new file mode 100644 index 00000000..ddf88f81 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -0,0 +1,613 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; +import static com.genius.gitget.global.util.exception.ErrorCode.CERTIFICATION_UNABLE; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.dto.CertificationInformation; +import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import com.genius.gitget.challenge.certification.dto.CertificationResponse; +import com.genius.gitget.challenge.certification.dto.InstancePreviewResponse; +import com.genius.gitget.challenge.certification.dto.TotalResponse; +import com.genius.gitget.challenge.certification.dto.WeekResponse; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.repository.ItemRepository; +import com.genius.gitget.challenge.item.repository.UserItemRepository; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +@ActiveProfiles({"github"}) +class CertificationServiceTest { + @Autowired + private CertificationService certificationService; + @Autowired + private GithubService githubService; + @Autowired + private UserRepository userRepository; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private CertificationRepository certificationRepository; + @Autowired + private ItemRepository itemRepository; + @Autowired + private UserItemRepository userItemRepository; + + @Value("${github.personalKey}") + private String personalKey; + + @Value("${github.githubId}") + private String githubId; + + @Value("${github.repository}") + private String targetRepo; + + + @Test + @DisplayName("사용자가 연결한 레포지토리에 특정 날짜의 PR이 있으면 인증으로 간주한다") + public void should_certificate_when_prExist() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + getParticipantInfo(user, instance); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 2, 5); + + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(targetDate) + .build(); + instance.updateProgress(Progress.ACTIVITY); + + //when + CertificationResponse certificationResponse = certificationService.updateCertification(user, + certificationRequest); + Certification certification = certificationRepository.findById(certificationResponse.certificationId()) + .get(); + + //then + assertThat(certification.getId()).isEqualTo(certificationResponse.certificationId()); + assertThat(certificationResponse.certificateStatus()).isEqualTo(CERTIFICATED); + assertThat(certificationResponse.certificatedAt()).isEqualTo(targetDate); + assertThat(certificationResponse.prCount()).isEqualTo(1); + } + + @Test + @DisplayName("인증을 시도한 날짜가 챌린지의 진행 기간과 겹치지 않는다면 예외를 발생한다.") + public void should_throwException_when_progressIsNotActivity() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + getParticipantInfo(user, instance); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 12, 6); + + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(targetDate) + .build(); + instance.updateProgress(Progress.ACTIVITY); + + //when && then + assertThatThrownBy(() -> certificationService.updateCertification(user, certificationRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CERTIFICATION_UNABLE.getMessage()); + } + + @Test + @DisplayName("사용자가 연결한 레포지토리에 특정 날짜의 PR이 존재하지 않으면 인증이 아직 안된 것으로 간주한다.") + public void should_notCertificate_when_prNotExist() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + getParticipantInfo(user, instance); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 2, 6); + + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(targetDate) + .build(); + instance.updateProgress(Progress.ACTIVITY); + + //when + CertificationResponse certificationResponse = certificationService.updateCertification(user, + certificationRequest); + Certification certification = certificationRepository.findById(certificationResponse.certificationId()) + .get(); + + //then + assertThat(certification.getId()).isEqualTo(certificationResponse.certificationId()); + assertThat(certificationResponse.certificateStatus()).isEqualTo(CertificateStatus.NOT_YET); + assertThat(certificationResponse.certificatedAt()).isEqualTo(targetDate); + assertThat(certificationResponse.prCount()).isEqualTo(0); + } + + @Test + @DisplayName("github를 통해 public repository 정보들을 받아올 수 있다.") + public void should_returnRepositoryList_when_passGitHubToken() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + githubService.registerGithubPersonalToken(user, personalKey); + + //when + List repositoryList = githubService.getPublicRepositories(user); + + //then + assertThat(repositoryList.size()).isGreaterThan(0); + } + + @Test + @DisplayName("repository 정보를 불러올 때 github token이 제대로 설정되어있지 않다면 예외를 발생해야 한다.") + public void should_throwException_when_loadRepository() { + //given + User user = getSavedUser(githubId); + + //when & then + assertThatThrownBy(() -> githubService.getPublicRepositories(user)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("특정 사용자가 일주일 간 인증한 현황들을 받아올 수 있다.") + public void should_returnCertifications_when_passDuration() { + //given + LocalDate currentDate = LocalDate.of(2024, 2, 3); + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 2, 4); + Participant participant = getParticipantInfo(getSavedUser(githubId), getSavedInstance()); + + //when + getSavedCertification(NOT_YET, startDate, participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); + getSavedCertification(CERTIFICATED, endDate.minusDays(1), participant); + getSavedCertification(CERTIFICATED, endDate, participant); + + List weekCertification = certificationService.getWeekCertification( + participant.getId(), currentDate); + + //then + assertThat(weekCertification.size()).isEqualTo(3); + } + + @Test + @DisplayName("특정 사용자가 일주일 간 인증한 현황들을 받아올 때, 더미 데이터를 포함하여 연속적인 데이터로 받아올 수 있다.") + public void should_returnList_when_dataIsNotContinuous() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 2, 29); + LocalDate currentDate = LocalDate.of(2024, 2, 8); + + Participant participant = getParticipantInfo(getSavedUser(githubId), getSavedInstance()); + + //when + getSavedCertification(NOT_YET, startDate, participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(6), participant); + + List weekCertification = certificationService.getWeekCertification( + participant.getId(), currentDate); + + //then + assertThat(weekCertification.size()).isEqualTo(4); + assertThat(weekCertification.get(0).certificationAttempt()).isEqualTo(1); + assertThat(weekCertification.get(1).certificationAttempt()).isEqualTo(2); + assertThat(weekCertification.get(2).certificationAttempt()).isEqualTo(3); + assertThat(weekCertification.get(3).certificationAttempt()).isEqualTo(4); + } + + @Test + @DisplayName("현재 일자까지의 인증 현황들을 받아올 수 있다.") + public void should_returnList_when_passDate() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 2, 29); + LocalDate currentDate = LocalDate.of(2024, 2, 8); + + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(getSavedUser(githubId), instance); + + //when + getSavedCertification(NOT_YET, startDate, participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(6), participant); + + TotalResponse totalResponse = certificationService.getTotalCertification(participant.getId(), + currentDate); + + //then + assertThat(totalResponse.certifications().size()).isEqualTo(8); + assertThat(totalResponse.totalAttempts()).isEqualTo(instance.getTotalAttempt()); + } + + @Test + @DisplayName("사용자가 참여한 챌린지에 대한 상세 정보를 받을 수 있다.") + public void should_returnDetailInfo_when_participate() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + + //when + InstancePreviewResponse instancePreviewResponse = certificationService.getInstancePreview(instance.getId()); + + //then + assertThat(instancePreviewResponse.instanceId()).isEqualTo(instance.getId()); + } + + @Test + @DisplayName("사용자가 참여한 챌린지가 아직 시작 전이라면, 성공/실패의 값이 모두 0이어야한다.") + public void should_getInformation_when_progressIsPreActivity() { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 8); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + + //when + CertificationInformation information = certificationService.getCertificationInformation(instance, + participant, targetDate); + + //then + assertThat(information.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(information.remainCount()).isEqualTo(information.totalAttempt()); + assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); + assertThat(information.currentAttempt()).isEqualTo(0); + assertThat(information.successCount()).isEqualTo(0); + assertThat(information.failureCount()).isEqualTo(0); + assertThat(information.remainCount()).isEqualTo(instance.getTotalAttempt()); + } + + @Test + @DisplayName("사용자가 참여한 챌린지가 진행 중이라면, 성공/실패/남은 일자의 값의 제대로 나와야 한다.") + public void should_getInformation_when_progressIsActivity() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate targetDate = LocalDate.of(2024, 2, 8); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + + //when + instance.updateProgress(Progress.ACTIVITY); + getSavedCertification(NOT_YET, startDate, participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); + getSavedCertification(PASSED, startDate.plusDays(6), participant); + CertificationInformation information = certificationService.getCertificationInformation(instance, + participant, targetDate); + + //then + assertThat(information.repository()).isEqualTo(participant.getRepositoryName()); + assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); + assertThat(information.currentAttempt()).isEqualTo(8); + assertThat(information.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(information.successCount()).isEqualTo(3); + assertThat(information.failureCount()).isEqualTo(information.currentAttempt() - 3); + assertThat(information.remainCount()).isEqualTo(instance.getTotalAttempt() - 8); + } + + @Test + @DisplayName("사용자가 참여한 챌린지가 완료이라면, 성공/실패/남은 일자의 값의 제대로 나와야 한다.") + public void should_getInformation_when_progressIsDone() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate targetDate = LocalDate.of(2024, 2, 8); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + + //when + instance.updateProgress(Progress.DONE); + getSavedCertification(NOT_YET, startDate, participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); + getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); + getSavedCertification(PASSED, startDate.plusDays(6), participant); + CertificationInformation information = certificationService.getCertificationInformation(instance, + participant, targetDate); + + //then + assertThat(information.repository()).isEqualTo(participant.getRepositoryName()); + assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); + assertThat(information.currentAttempt()).isEqualTo(instance.getTotalAttempt()); + assertThat(information.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(information.successCount()).isEqualTo(3); + assertThat(information.failureCount()).isEqualTo(information.totalAttempt() - 3); + assertThat(information.remainCount()).isEqualTo(0); + } + + @Test + @DisplayName("챌린지에 참여한 모든 사용자들의 일주일 간 인증 현황을 받아올 수 있다. 단, 본인의 값을 제외한다.") + public void should_getWeekCertification_aboutAllParticipants() { + //given + PageRequest pageRequest = PageRequest.of(0, 10); + LocalDate currentDate = LocalDate.of(2024, 3, 6); + User user1 = getSavedUser(githubId, "nickname1"); + User user2 = getSavedUser(githubId, "nickname2"); + Instance instance = getSavedInstance(); + Participant participant1 = getParticipantInfo(user1, instance); + Participant participant2 = getParticipantInfo(user2, instance); + + //when + Slice certification = certificationService.getAllWeekCertification( + user1.getId(), instance.getId(), currentDate, pageRequest); + + //then + assertThat(certification.getContent().size()).isEqualTo(1); + assertThat(certification.getContent().get(0).certifications().size()).isEqualTo(3); + } + + @Test + @DisplayName("아직 인증을 하지 않았고, 패스 아이템이 있을 때 해당 일자의 인증을 패스할 수 있다.") + public void should_passCertification_when_conditionIsValid() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(currentDate) + .build(); + + //when + getSavedCertification(NOT_YET, currentDate, participant); + getSavedCertification(CERTIFICATED, currentDate.plusDays(1), participant); + getSavedCertification(CERTIFICATED, currentDate.plusDays(4), participant); + getSavedCertification(CERTIFICATED, currentDate.plusDays(6), participant); + + CertificationResponse certificationResponse = certificationService.passCertification( + user.getId(), + certificationRequest); + + //then + assertThat(certificationResponse.certificationId()).isNotNull(); + assertThat(certificationResponse.certificateStatus()).isEqualTo(CertificateStatus.PASSED); + assertThat(certificationResponse.certificatedAt()).isEqualTo(currentDate); + assertThat(certificationResponse.prCount()).isEqualTo(0); + assertThat(certificationResponse.prLinks()).isEmpty(); + } + + @Test + @DisplayName("UserItem 정보가 DB에 존재하지 않을 때 인증 패스를 요청하면 예외가 발생해야 한다.") + public void should_throwException_when_userItemInfoNotExist() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(currentDate) + .build(); + + //when + getSavedCertification(NOT_YET, currentDate, participant); + + //then + assertThatThrownBy(() -> certificationService.passCertification(instance.getId(), certificationRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("UserItem 정보는 있으나 아이템의 개수가 0 이하일 때 인증 패스를 요청하면 예외가 발생해야 한다.") + public void should_throwException_when_outOfStock() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 0); + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(currentDate) + .build(); + + //when && then + assertThatThrownBy(() -> certificationService.passCertification(instance.getId(), certificationRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); + } + + @ParameterizedTest + @DisplayName("패스 아이템은 있으나, 챌린지가 인증 필요 상태가 아니라면 예외가 발생해야 한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"CERTIFICATED", "PASSED"}) + public void should_throwException_when_challengeIsNotNOT_YET(CertificateStatus certificateStatus) { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(currentDate) + .build(); + + //when + getSavedCertification(certificateStatus, currentDate, participant); + + //then + assertThatThrownBy(() -> certificationService.passCertification(user.getId(), certificationRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.CAN_NOT_USE_PASS_ITEM.getMessage()); + } + + @Test + @DisplayName("패스 아이템을 사용할 수 있고, 기존에 인증을 한 차례 시도했다면 PASSED로 데이터가 덮어진다.") + public void should_overwriteData_when_certificatedBefore() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(currentDate) + .build(); + + //when + getSavedCertification(NOT_YET, currentDate, participant); + CertificationResponse certificationResponse = certificationService.passCertification(user.getId(), + certificationRequest); + + //then + assertThat(certificationResponse.certificateStatus()).isEqualTo(PASSED); + assertThat(certificationResponse.certificationId()).isNotZero(); + assertThat(certificationResponse.prCount()).isZero(); + assertThat(certificationResponse.prLinks().size()).isZero(); + assertThat(certificationResponse.certificatedAt()).isEqualTo(currentDate); + } + + @Test + @DisplayName("패스 아이템을 가지고 있고, 이전에 인증을 한차례도 시도하지 않았을 때에도 아이템 사용이 가능하다") + public void should_usePassItem_when_conditionIsValid() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(currentDate) + .build(); + + //when + CertificationResponse certificationResponse = certificationService.passCertification(user.getId(), + certificationRequest); + + //then + assertThat(certificationResponse.certificateStatus()).isEqualTo(PASSED); + assertThat(certificationResponse.certificationId()).isNotZero(); + assertThat(certificationResponse.prCount()).isZero(); + assertThat(certificationResponse.prLinks().size()).isZero(); + assertThat(certificationResponse.certificatedAt()).isEqualTo(currentDate); + } + + + private User getSavedUser(String githubId) { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier(githubId) + .information("information") + .tags("BE,FE") + .build() + ); + } + + private User getSavedUser(String githubId, String nickname) { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname(nickname) + .providerInfo(ProviderInfo.GITHUB) + .identifier(githubId) + .information("information") + .tags("BE,FE") + .build() + ); + } + + private Instance getSavedInstance() { + return instanceRepository.save( + Instance.builder() + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) + .completedDate(LocalDateTime.of(2024, 3, 29, 0, 0)) + .build() + ); + } + + private Participant getParticipantInfo(User user, Instance instance) { + Participant participant = participantRepository.save( + Participant.builder() + .joinResult(JoinResult.PROCESSING) + .joinStatus(JoinStatus.YES) + .build() + ); + participant.setUserAndInstance(user, instance); + participant.updateRepository(targetRepo); + + return participant; + } + + + private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, + Participant participant) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); + Certification certification = Certification.builder() + .certificationStatus(status) + .currentAttempt(attempt) + .certificatedAt(certificatedAt) + .certificationLinks("certificationLink") + .build(); + certification.setParticipant(participant); + return certificationRepository.save(certification); + } + + private UserItem getSavedUserItem(User user, ItemCategory itemCategory, int count) { + Item item = itemRepository.save(Item.builder() + .itemCategory(itemCategory) + .build()); + UserItem userItem = new UserItem(count); + userItem.setItem(item); + userItem.setUser(user); + return userItemRepository.save(userItem); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java new file mode 100644 index 00000000..fcb55eb9 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java @@ -0,0 +1,152 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_ID_INCORRECT; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_REPOSITORY_INCORRECT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.global.util.exception.BusinessException; +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles({"github"}) +class GithubProviderTest { + @Autowired + private GithubProvider githubProvider; + + @Value("${github.personalKey}") + private String personalKey; + + @Value("${github.githubId}") + private String githubId; + + @Value("${github.repository}") + private String repository; + + @Test + @DisplayName("정상적인 github token을 전달받았을 때, API를 통해 GitHub 객체를 반환받을 수 있다.") + public void should_returnGitHubInstance_when_passValidToken() { + //given + + //when + GitHub gitHub = githubProvider.getGithubConnection(personalKey); + + //then + assertThat(gitHub).isNotNull(); + } + + @Test + @DisplayName("github token을 전달받았을 때, 사용자가 소셜로그인할 때 사용했던 깃허브 계정 아이디와 일치한다면 연결 성공으로 간주한다.") + public void should_checkConnection_when_passPersonalToken() { + //given + GitHub gitHub = getGitHub(); + + //when + githubProvider.validateGithubConnection(gitHub, githubId); + } + + @Test + @DisplayName("github token을 전달받았을 때, 소셜로그인 깃허브 계정 아이디와 일치하지 않는다면 예외가 발생한다.") + public void should_throwException_when_idIncorrect() { + //given + GitHub gitHub = getGitHub(); + String githubId = "fake Id"; + + //when & then + assertThatThrownBy(() -> githubProvider.validateGithubConnection(gitHub, githubId)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_ID_INCORRECT.getMessage()); + } + + @Test + @DisplayName("특정 사용자의 특정 repository가 연결됨을 검증할 수 있다.") + public void should_findRepository_when_passToken() throws IOException { + // given + GitHub gitHub = getGitHub(); + + //when & then + githubProvider.validateGithubRepository(gitHub, githubId + "/" + repository); + } + + @Test + @DisplayName("전달받은 Repository명이 명확하지 않는다면 예외가 발생한다.") + public void should_throwException_when_repositoryNameInvalid() { + //given + GitHub gitHub = getGitHub(); + String repositoryName = "fake repository"; + + //when & then + assertThatThrownBy(() -> githubProvider.validateGithubRepository(gitHub, repositoryName)) + .isInstanceOf(BusinessException.class); + } + + @Test + @DisplayName("해당 레포지토리에 있는 PR을 확인할 수 있다.") + public void should_checkPR_when_validRepo() { + //given + GitHub gitHub = getGitHub(); + LocalDate createdAt = LocalDate.of(2024, 2, 5); + + //when + List pullRequest = githubProvider.getPullRequestByDate(gitHub, repository, createdAt); + + //then + assertThat(pullRequest.size()).isEqualTo(1); + } + + @Test + @DisplayName("특정 레포지토리에 연결이 되지 않으면 예외를 발생한다.") + public void should_throwException_when_repoConnectionInvalid() { + //given + GitHub gitHub = getGitHub(); + String repositoryName = "Fake"; + LocalDate createdAt = LocalDate.of(2024, 2, 5); + + //when & then + assertThatThrownBy(() -> githubProvider.getPullRequestByDate(gitHub, repositoryName, createdAt)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_REPOSITORY_INCORRECT.getMessage()); + } + + @Test + @DisplayName("사용자가 가지고 있는 레포지토리 리스트들을 반환할 수 있다.") + public void should_returnRepositories() { + //given + GitHub gitHub = getGitHub(); + + //when + List repositoryList = githubProvider.getRepositoryList(gitHub); + + //then + assertThat(repositoryList.size()).isGreaterThan(0); + } + + @Test + @DisplayName("Pr 인증을 시도 했을 때, KST 기준으로 생성된 PR 리스트를 불러올 수 있다.") + public void should_searchPR_when_tryToCertificate() { + //given + GitHub gitHub = getGitHub(); + LocalDate kstDate = LocalDate.of(2024, 2, 25); + + //when + List pullRequests = githubProvider.getPullRequestByDate(gitHub, repository, kstDate); + + //then + assertThat(pullRequests.size()).isEqualTo(2); + } + + private GitHub getGitHub() { + return githubProvider.getGithubConnection(personalKey); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java new file mode 100644 index 00000000..e98d8065 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java @@ -0,0 +1,269 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_REPOSITORY_INCORRECT; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.certification.util.EncryptUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class GithubServiceTest { + @Autowired + private EncryptUtil encryptUtil; + @Autowired + private GithubService githubService; + @Autowired + private UserRepository userRepository; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private CertificationRepository certificationRepository; + + @Value("${github.personalKey}") + private String personalKey; + @Value("${github.githubId}") + private String githubId; + @Value("${github.repository}") + private String targetRepo; + + @Test + @DisplayName("Github personal access token 연결에 이상이 없다면, 암호화하여 User 엔티티에 저장한다.") + public void should_updateTokenInfo_when_tokenValid() { + //given + User user = getSavedUser(githubId); + String encrypted = encryptUtil.encrypt(personalKey); + + //when + githubService.registerGithubPersonalToken(user, personalKey); + User updatedUser = userRepository.findByIdentifier(githubId).get(); + + //then + assertThat(updatedUser.getGithubToken()).isEqualTo(encrypted); + } + + @Test + @DisplayName("Github personal access token과 소셜로그인 시의 계정이 일치하지 않으면 예외를 발생시킨다.") + public void should_throwException_when_accountIncorrect() { + //given + User user = getSavedUser("incorrect Id"); + encryptUtil.encrypt(personalKey); + + //when & then + assertThatThrownBy(() -> githubService.registerGithubPersonalToken(user, personalKey)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.GITHUB_ID_INCORRECT.getMessage()); + } + + @Test + @DisplayName("repository 이름을 전달했을 때, 해당 깃허브 계정에 레포지토리가 있어야 한다.") + public void should_repositoryExist_when_passRepositoryName() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + githubService.registerGithubPersonalToken(user, personalKey); + + //when + githubService.verifyRepository(user, targetRepo); + + } + + @Test + @DisplayName("repository 등록 시, 해당 레포지토리가 없다면 예외가 발생해야 한다.") + public void should_throw_Exception_when_thereIsNoRepository() { + //given + String fakeRepositoryName = "Fake"; + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + githubService.registerGithubPersonalToken(user, personalKey); + + //when & then + assertThatThrownBy(() -> githubService.verifyRepository(user, fakeRepositoryName)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_REPOSITORY_INCORRECT.getMessage()); + } + + @Test + @DisplayName("repository 등록 시, 사용자에게 Github token이 저장되어있지 않다면 예외가 발생해야 한다.") + public void should_throwException_when_userDontHaveToken() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getParticipantInfo(user, instance); + + //when & then + assertThatThrownBy(() -> githubService.verifyRepository(user, targetRepo)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("특정 일자에 PR이 존재하지 않는다면 빈 리스트를 반환한다.") + public void should_returnEmptyList_when_prNotExist() throws IOException { + //given + User user = getSavedUser(githubId); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 1, 4); + + //when + List pullRequestResponses = githubService.getPullRequestListByDate( + user, targetRepo, targetDate); + + //then + assertThat(pullRequestResponses.size()).isEqualTo(0); + } + + @Test + @DisplayName("사용자의 github token이 저장되어있지 않을 때 예외가 발생해야 한다.") + public void should_throwException_when_githubTokenNotSaved() { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 5); + User user = getSavedUser(githubId); + + //when & then + assertThatThrownBy(() -> githubService.getPullRequestListByDate(user, targetRepo, targetDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("repository가 등록되어있지 않을 때 예외가 발생해야 한다.") + public void should_throwException_when_repositoryNotRegistered() { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 5); + User user = getSavedUser(githubId); + String fakeRepo = "fake Repo"; + githubService.registerGithubPersonalToken(user, personalKey); + + //when & then + assertThatThrownBy(() -> githubService.getPullRequestListByDate(user, fakeRepo, targetDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_REPOSITORY_INCORRECT.getMessage()); + } + + @Test + @DisplayName("특정 레포지토리에 특정 날짜에 생성된 PR 목록을 불러올 수 있다.") + public void should_loadPRList_when_tryJoin() { + //given + User user = getSavedUser(githubId); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 2, 5); + + //when + List pullRequestResponses = githubService.getPullRequestListByDate( + user, targetRepo, targetDate); + + //then + assertThat(pullRequestResponses.size()).isEqualTo(1); + log.info(pullRequestResponses.get(0).toString()); + } + + @Test + @DisplayName("특정 일자에 특정 브랜치에 PR이 있는지 확인할 수 있다.") + public void should_checkPR_when_tryToVerify() { + //given + User user = getSavedUser(githubId); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 2, 5); + + //when + List pullRequestResponses = githubService.verifyPullRequest(user, targetRepo, targetDate); + + //then + assertThat(pullRequestResponses.size()).isNotZero(); + } + + @Test + @DisplayName("PR 검증을 요청했을 때, PR이 해당 브랜치에 존재하지 않는다면 예외를 발생한다.") + public void should_throwException_when_PRNotExist() { + //given + User user = getSavedUser(githubId); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 3, 5); + + //when & then + assertThatThrownBy(() -> githubService.verifyPullRequest(user, targetRepo, targetDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.GITHUB_PR_NOT_FOUND.getMessage()); + } + + + private User getSavedUser(String githubId) { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier(githubId) + .information("information") + .tags("BE,FE") + .build() + ); + } + + private Instance getSavedInstance() { + return instanceRepository.save( + Instance.builder() + .progress(Progress.PREACTIVITY) + .build() + ); + } + + private Participant getParticipantInfo(User user, Instance instance) { + Participant participant = participantRepository.save( + Participant.builder() + .joinResult(JoinResult.PROCESSING) + .joinStatus(JoinStatus.YES) + .build() + ); + participant.setUserAndInstance(user, instance); + + return participant; + } + + private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, + Participant participant) { + Certification certification = Certification.builder() + .certificationStatus(status) + .certificatedAt(certificatedAt) + .certificationLinks("certificationLink") + .build(); + certification.setParticipant(participant); + return certificationRepository.save(certification); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java b/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java new file mode 100644 index 00000000..db8675d3 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java @@ -0,0 +1,96 @@ +package com.genius.gitget.challenge.certification.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +import java.util.Date; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@Slf4j +class DateUtilTest { + + @Test + @DisplayName("시작 날짜와 현재 날짜를 전달했을 때, 오늘의 날짜가 몇 번째 회차인지 구할 수 있다.") + public void should_getAttempt_when_passDate() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 3, 4); + + //when + int attempt = DateUtil.getAttemptCount(startDate, endDate); + + //then + assertThat(attempt).isEqualTo(33); + } + + @Test + @DisplayName("첫 주차의 인증 현황을 조회할 때 챌린지의 시작 요일이 월요일이 아니라면, 시작 날짜를 기준으로 계산한다.") + public void should_calculateByStartDate_when_startDateIsNotMonday() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 2, 3); + + //when + int weekAttempt = DateUtil.getWeekAttempt(startDate, endDate); + + //then + assertThat(weekAttempt).isEqualTo(3); + } + + @Test + @DisplayName("일반적으로 주차별 인증 현황을 조회할 때, 요일에 따라 계산한다.") + public void should_calculateByDay_when_getListGenerally() { + //given + LocalDate startDate = LocalDate.of(2024, 2, 1); + LocalDate endDate = LocalDate.of(2024, 2, 15); + + //when + int weekAttempt = DateUtil.getWeekAttempt(startDate, endDate); + + //then + assertThat(weekAttempt).isEqualTo(4); + } + + @Test + @DisplayName("Date를 전달했을 때 LocalDate로 변환할 수 있다.") + public void should_convertToLocalDate_when_passDate() { + //given + Date date = new Date(1725000000000L); + + //when + LocalDate localDate = DateUtil.convertToLocalDate(date); + + //then + assertThat(localDate).isEqualTo(LocalDate.of(2024, 8, 30)); + } + + @Test + @DisplayName("시작일자과 현재일자를 전달했을 때, 챌린지 시작까지 몇 일 남았는지 구할 수 있다.") + public void should_getRemainDays_when_passStartDate() { + //given + LocalDate startDate = LocalDate.of(2024, 3, 10); + LocalDate targetDate = LocalDate.of(2024, 3, 1); + + //when + int remainDays = DateUtil.getRemainDaysToStart(startDate, targetDate); + + //then + assertThat(remainDays).isEqualTo(9); + } + + @Test + @DisplayName("현재일자가 시작일자보다 더 이후의 날짜일 때, 남은 일수를 0으로 반환한다.") + public void should_returnMinus_when_startDateBeforeThenTargetDate() { + //given + LocalDate targetDate = LocalDate.of(2024, 3, 10); + LocalDate startDate = LocalDate.of(2024, 3, 1); + + //when + int remainDays = DateUtil.getRemainDaysToStart(startDate, targetDate); + + //then + assertThat(remainDays).isEqualTo(0); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/util/EncryptUtilTest.java b/src/test/java/com/genius/gitget/challenge/certification/util/EncryptUtilTest.java new file mode 100644 index 00000000..166098ff --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/util/EncryptUtilTest.java @@ -0,0 +1,31 @@ +package com.genius.gitget.challenge.certification.util; + +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class EncryptUtilTest { + @Autowired + EncryptUtil encryptUtil; + + @Test + @DisplayName("특정 문자열에 대해서 암호화하고 복호화했을 때, 원래의 값과 일치해야 한다.") + public void should_returnOrigin_when_decrypt() { + //given + String target = "target token"; + + //when + String encrypted = encryptUtil.encrypt(target); + String decrypted = encryptUtil.decrypt(encrypted); + + //then + Assertions.assertThat(decrypted).isEqualTo(target); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java new file mode 100644 index 00000000..e7e05b21 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -0,0 +1,325 @@ +package com.genius.gitget.challenge.instance.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_JOIN_INSTANCE; +import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_QUIT_INSTANCE; +import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.PARTICIPANT_INFO_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.certification.service.GithubService; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; +import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; +import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class InstanceDetailServiceTest { + @Autowired + InstanceDetailService instanceDetailService; + @Autowired + ParticipantProvider participantProvider; + @Autowired + GithubService githubService; + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + ParticipantRepository participantRepository; + + @Value("${github.personalKey}") + private String githubToken; + @Value("${github.githubId}") + private String githubId; + @Value("${github.repository}") + private String targetRepo; + + + @Test + @DisplayName("챌린지 참여에 필요한 정보를 전달했을 때, 참여 정보가 저장이 되어야 한다.") + public void should_saveParticipantInfo_when_passInfo() { + //given + User savedUser = getSavedUser(githubId); + Instance instance = getSavedInstance(Progress.PREACTIVITY); + JoinRequest joinRequest = JoinRequest.builder() + .instanceId(instance.getId()) + .repository(targetRepo) + .build(); + + //when + JoinResponse joinResponse = instanceDetailService.joinNewChallenge(savedUser, joinRequest); + + //then + assertThat(joinResponse.joinStatus()).isEqualTo(JoinStatus.YES); + assertThat(joinResponse.joinResult()).isEqualTo(JoinResult.PROCESSING); + assertThat(instance.getParticipantCount()).isEqualTo(1); + } + + @Test + @DisplayName("챌린지 참여 요청을 했을 때, 인스턴스가 존재하지 않는다면 예외가 발생한다.") + public void should_throwException_when_instanceNotExist() { + //given + User savedUser = getSavedUser(githubId); + JoinRequest joinRequest = JoinRequest.builder() + .instanceId(1L) + .repository(targetRepo) + .build(); + + //when & then + assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(savedUser, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); + } + + @ParameterizedTest + @DisplayName("챌린지 참여 요청을 했을 때, 인스턴스의 상태가 시작 전이 아니라면 예외가 발생한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"ACTIVITY", "DONE"}) + public void should_throwException_when_instanceProgressNotPreactivity(Progress progress) { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(progress); + JoinRequest joinRequest = JoinRequest.builder() + .repository(targetRepo) + .instanceId(savedInstance.getId()) + .build(); + + //when & then + assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(savedUser, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); + } + + @Test + @DisplayName("챌린지 참여 요청을 했을 때, 사용자가 이미 참여한 챌린지인 경우 예외가 발생한다.") + public void should_throwException_when_userAlreadyJoined() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(Progress.PREACTIVITY); + JoinRequest joinRequest = JoinRequest.builder() + .repository(targetRepo) + .instanceId(instance.getId()) + .build(); + + //when + instanceDetailService.joinNewChallenge(user, joinRequest); + + //then + assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(user, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); + } + + @Test + @DisplayName("아직 시작하지 않은 챌린지에 대해 취소 요청을 하면 ParticipantInfo가 삭제된다.") + public void should_joinStatusIsNo_when_quitChallenge() { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + + //when + instanceDetailService.joinNewChallenge(savedUser, new JoinRequest(savedInstance.getId(), targetRepo)); + JoinResponse joinResponse = instanceDetailService.quitChallenge(savedUser, savedInstance.getId()); + Optional byJoinInfo = participantRepository.findByJoinInfo(savedUser.getId(), + savedInstance.getId()); + + //then + assertThat(byJoinInfo).isEmpty(); + assertThat(savedInstance.getParticipantCount()).isEqualTo(0); + assertThat(joinResponse.participantId()).isEqualTo(null); + assertThat(joinResponse.joinResult()).isEqualTo(null); + assertThat(joinResponse.joinStatus()).isEqualTo(null); + } + + @Test + @DisplayName("진행 중인 챌린지에 대해 취소 요청을 하면 인스턴스의 참여 인원 수가 줄어들고, 참여 정보가 변경된다") + public void should_changeParticipantInfo_when_requestQuitInstance() { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + JoinRequest joinRequest = JoinRequest.builder() + .instanceId(savedInstance.getId()) + .repository(targetRepo) + .build(); + + //when + instanceDetailService.joinNewChallenge(savedUser, joinRequest); + savedInstance.updateProgress(Progress.ACTIVITY); + instanceDetailService.quitChallenge(savedUser, savedInstance.getId()); + Participant participant = participantProvider.findByJoinInfo(savedUser.getId(), + savedInstance.getId()); + + //then + assertThat(savedInstance.getParticipantCount()).isEqualTo(0); + assertThat(participant.getJoinResult()).isEqualTo(JoinResult.FAIL); + assertThat(participant.getJoinStatus()).isEqualTo(JoinStatus.NO); + } + + @Test + @DisplayName("챌린지 취소 요청을 할 때 인스턴스가 존재하지 않으면 예외가 발생한다.") + public void should_throwException_when_instanceNotExist_quitChallenge() { + //given + User savedUser = getSavedUser(githubId); + + //when & then + assertThatThrownBy(() -> instanceDetailService.quitChallenge(savedUser, 1L)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("챌린지 취소 요청을 할 때 참여 정보가 존재하지 않으면 예외가 발생한다.") + public void should_throwException_when_participantInfoNotExist() { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + + //when & then + assertThatThrownBy(() -> instanceDetailService.quitChallenge(savedUser, savedInstance.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(PARTICIPANT_INFO_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("챌린지 취소 요청을 할 때, 인스턴스의 진행 상황이 DONE이면 예외가 발생한다.") + public void should_throwException_when_progressIsDONE() { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + JoinRequest joinRequest = JoinRequest.builder() + .instanceId(savedInstance.getId()) + .repository(targetRepo) + .build(); + + //when + instanceDetailService.joinNewChallenge(savedUser, joinRequest); + savedInstance.updateProgress(Progress.DONE); + + //then + assertThatThrownBy(() -> instanceDetailService.quitChallenge(savedUser, savedInstance.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_QUIT_INSTANCE.getMessage()); + } + + @Test + @DisplayName("사용자가 참여한 인스턴스에 대해 상세 조회를 하면 상세 페이지에 필요한 데이터들을 반환해야 한다.") + public void should_returnValues_when_joinedInstance() { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); + JoinRequest joinRequest = JoinRequest.builder() + .instanceId(savedInstance.getId()) + .repository(targetRepo) + .build(); + + //when + instanceDetailService.joinNewChallenge(savedUser, joinRequest); + InstanceResponse instanceResponse = instanceDetailService.getInstanceDetailInformation(savedUser, + savedInstance.getId()); + + //then + assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); + assertThat(instanceResponse.remainDays()).isEqualTo(2); + assertThat(instanceResponse.participantCount()).isEqualTo(1); + assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); + assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); + assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.YES); + assertThat(instanceResponse.hitCount()).isEqualTo(0); + } + + @Test + @DisplayName("사용자가 참여하지 않은 챌린지에 대해 상세 조회를 하면 상세 페이지에 필요한 정보들을 반환할 수 있다.") + public void should_returnData_when_notJoinedInstance() { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); + + //when + InstanceResponse instanceResponse = instanceDetailService.getInstanceDetailInformation(savedUser, + savedInstance.getId()); + + //then + assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); + assertThat(instanceResponse.remainDays()).isEqualTo(2); + assertThat(instanceResponse.participantCount()).isEqualTo(0); + assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); + assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); + assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.NO); + assertThat(instanceResponse.hitCount()).isEqualTo(0); + } + + @Test + @DisplayName("상세 정보를 요청하는 인스턴스가 존재하지 않는다면 예외가 발생해야 한다.") + public void should_throwException_when_requestDetail_instanceNotExist() { + //given + User savedUser = getSavedUser(githubId); + + //when & then + assertThatThrownBy(() -> instanceDetailService.getInstanceDetailInformation(savedUser, 1L)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); + } + + private User getSavedUser(String githubId) { + User user = userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier(githubId) + .information("information") + .tags("BE,FE") + .build() + ); + githubService.registerGithubPersonalToken(user, githubToken); + return user; + } + + private Instance getSavedInstance(Progress progress) { + return instanceRepository.save( + Instance.builder() + .progress(progress) + .startedDate(LocalDateTime.of(2024, 2, 1, 11, 3)) + .build() + ); + } + + private Instance getSavedInstance(Progress progress, LocalDate startedDate) { + return instanceRepository.save( + Instance.builder() + .progress(progress) + .description("description") + .notice("notice") + .certificationMethod("certification method") + .pointPerPerson(100) + .startedDate(startedDate.atTime(12, 12)) + .completedDate(startedDate.plusDays(30).atTime(12, 12)) + .build() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/item/service/UserItemProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/UserItemProviderTest.java new file mode 100644 index 00000000..0073be15 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/item/service/UserItemProviderTest.java @@ -0,0 +1,123 @@ +package com.genius.gitget.challenge.item.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.repository.ItemRepository; +import com.genius.gitget.challenge.item.repository.UserItemRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class UserItemProviderTest { + @Autowired + private UserRepository userRepository; + @Autowired + private ItemRepository itemRepository; + @Autowired + private UserItemRepository userItemRepository; + @Autowired + private UserItemProvider userItemProvider; + + @Test + @DisplayName("사용자가 특정 아이템을 보유하고 있는지 정보를 조회할 수 있다.") + public void should_checkItem_when_userHaveItem() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + getSavedUserItem(user, item, 1); + + //when + UserItem userItemByUser = userItemProvider.findUserItemByUser(user.getId(), ItemCategory.PROFILE_FRAME); + + //then + Assertions.assertThat(userItemByUser.getCount()).isEqualTo(1); + } + + @Test + @DisplayName("사용자의 아이템 보유 정보가 없다면 예외가 발생한다.") + public void should_throwException_when_userItemNotExist() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + + //when & then + assertThatThrownBy(() -> userItemProvider.findUserItemByUser(user.getId(), ItemCategory.PROFILE_FRAME)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("사용자가 특정 아이템을 보유하고 있을 때, 보유하고 있는 아이템의 개수를 반환받을 수 있다.") + public void should_returnItemCount_when_haveItem() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + getSavedUserItem(user, item, 1); + + //when + int numOfItem = userItemProvider.countNumOfItem(user, ItemCategory.PROFILE_FRAME); + + //then + assertThat(numOfItem).isEqualTo(1); + } + + @Test + @DisplayName("사용자의 아이템 보유 정보가 DB에 저장되어있지 않을 때, 보유하고 있는 아이템의 개수를 요청하면 0을 반환한다.") + public void should_returnZero_when_dataNotSaved() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + + //when + int numOfItem = userItemProvider.countNumOfItem(user, ItemCategory.PROFILE_FRAME); + + //then + assertThat(numOfItem).isEqualTo(0); + } + + + private User getSavedUser() { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("githubId") + .information("information") + .tags("BE,FE") + .build() + ); + } + + private Item getSavedItem(ItemCategory itemCategory) { + return itemRepository.save( + Item.builder() + .itemCategory(itemCategory) + .build() + ); + } + + private UserItem getSavedUserItem(User user, Item item, int count) { + UserItem userItem = new UserItem(count); + userItem.setUser(user); + userItem.setItem(item); + return userItemRepository.save(userItem); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java new file mode 100644 index 00000000..cbdac328 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java @@ -0,0 +1,230 @@ +package com.genius.gitget.challenge.myChallenge.service; + +import static com.genius.gitget.challenge.participant.domain.JoinResult.PROCESSING; +import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.repository.ItemRepository; +import com.genius.gitget.challenge.item.repository.UserItemRepository; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; +import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class MyChallengeServiceTest { + @Autowired + private MyChallengeService myChallengeService; + @Autowired + private UserRepository userRepository; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private ItemRepository itemRepository; + @Autowired + private UserItemRepository userItemRepository; + @Autowired + private CertificationRepository certificationRepository; + + @Test + @DisplayName("사용자가 참여한 챌린지들 중, 시작 전인 챌린지들을 받아올 수 있다.") + public void should_getPreActivities_when_userJoinChallenges() { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 14); + User user = getSavedUser(); + Instance instance1 = getSavedInstance(Progress.PREACTIVITY); + Instance instance2 = getSavedInstance(Progress.PREACTIVITY); + Instance instance3 = getSavedInstance(Progress.PREACTIVITY); + Participant participant1 = getSavedParticipant(user, instance1, PROCESSING); + Participant participant2 = getSavedParticipant(user, instance2, PROCESSING); + Participant participant3 = getSavedParticipant(user, instance3, PROCESSING); + + //when + List instances = myChallengeService.getPreActivityInstances(user, targetDate); + + //then + assertThat(instances.size()).isEqualTo(3); + assertThat(instances.get(0).instanceId()).isEqualTo(instance1.getId()); + assertThat(instances.get(1).instanceId()).isEqualTo(instance2.getId()); + assertThat(instances.get(2).instanceId()).isEqualTo(instance3.getId()); + } + + @Test + @DisplayName("진행 중인 챌린지 목록 조회 시, 패스 아이템을 사용할 수 있는 조건이라면 아이템 사용 가능하다는 데이터를 반환한다.") + public void should_getActivatedList_when_userJoinChallenges() { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 14); + User user = getSavedUser(); + Instance instance1 = getSavedInstance(Progress.ACTIVITY); + Instance instance2 = getSavedInstance(Progress.ACTIVITY); + Participant participant1 = getSavedParticipant(user, instance1, PROCESSING); + Participant participant2 = getSavedParticipant(user, instance2, PROCESSING); + getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 3); + + //when + getSavedCertification(CertificateStatus.NOT_YET, targetDate, participant2); + List instances = myChallengeService.getActivatedInstances(user, targetDate); + + //then + assertThat(instances.size()).isEqualTo(2); + assertThat(instances.get(0).canUsePassItem()).isTrue(); + assertThat(instances.get(0).numOfPassItem()).isEqualTo(3); + assertThat(instances.get(1).canUsePassItem()).isTrue(); + assertThat(instances.get(1).numOfPassItem()).isEqualTo(3); + } + + @ParameterizedTest + @DisplayName("진행 중인 챌린지 목록 조회 시, NOT_YET을 제외한 챌린지들은 아이템 사용 가능 여부가 false여야 한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"CERTIFICATED", "PASSED"}) + public void should_canUserPassItemIsFalse_when_AlreadyCertificated(CertificateStatus certificateStatus) { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 14); + User user = getSavedUser(); + Instance instance1 = getSavedInstance(Progress.ACTIVITY); + Participant participant1 = getSavedParticipant(user, instance1, PROCESSING); + getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 3); + + //when + getSavedCertification(certificateStatus, targetDate, participant1); + List instances = myChallengeService.getActivatedInstances(user, targetDate); + + //then + assertThat(instances.size()).isEqualTo(1); + assertThat(instances.get(0).certificateStatus()).isEqualTo(certificateStatus.getTag()); + assertThat(instances.get(0).canUsePassItem()).isFalse(); + assertThat(instances.get(0).numOfPassItem()).isEqualTo(0); + assertThat(instances.get(0).pointPerPerson()).isEqualTo(instance1.getPointPerPerson()); + } + + @Test + @DisplayName("완료된 챌린지 목록 조회 시, 포인트를 아직 수령하지 않은 챌린지에 대해서는 보상 가능 정보를 전달해야 한다.") + public void should_returnTrue_when_ableToReward() { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 14); + User user = getSavedUser(); + Instance instance1 = getSavedInstance(Progress.DONE); + Participant participant1 = getSavedParticipant(user, instance1, SUCCESS); + getSavedUserItem(user, ItemCategory.POINT_MULTIPLIER, 3); + + //when + List doneResponses = myChallengeService.getDoneInstances(user, targetDate); + + //then + assertThat(doneResponses.size()).isEqualTo(1); + assertThat(doneResponses.get(0).canGetReward()).isTrue(); + assertThat(doneResponses.get(0).numOfPointItem()).isEqualTo(3); + } + + @Test + @DisplayName("완료된 챌린지 목록 조회 시, 포인트를 수령한 챌린지에 대해서는 포인트 수령 정보를 전달해야 한다.") + public void should_returnRewardInfo_when_alreadyRewarded() { + LocalDate targetDate = LocalDate.of(2024, 2, 14); + User user = getSavedUser(); + Instance instance1 = getSavedInstance(Progress.DONE); + Participant participant1 = getSavedParticipant(user, instance1, SUCCESS); + getSavedUserItem(user, ItemCategory.POINT_MULTIPLIER, 3); + + //when + List doneResponses = myChallengeService.getDoneInstances(user, targetDate); + + //then + assertThat(doneResponses.size()).isEqualTo(1); + assertThat(doneResponses.get(0).canGetReward()).isTrue(); + assertThat(doneResponses.get(0).numOfPointItem()).isEqualTo(3); + } + + + private User getSavedUser() { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("githubId") + .information("information") + .tags("BE,FE") + .build() + ); + } + + private Instance getSavedInstance(Progress progress) { + return instanceRepository.save( + Instance.builder() + .progress(progress) + .pointPerPerson(100) + .title("title") + .startedDate(LocalDateTime.of(2024, 2, 1, 11, 3)) + .completedDate(LocalDateTime.of(2024, 3, 29, 23, 59)) + .build() + ); + } + + private Participant getSavedParticipant(User user, Instance instance, JoinResult joinResult) { + Participant participant = participantRepository.save( + Participant.builder() + .joinResult(joinResult) + .joinStatus(JoinStatus.YES) + .build() + ); + participant.setUserAndInstance(user, instance); + instance.updateParticipantCount(1); + return participant; + } + + + private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, + Participant participant) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); + Certification certification = Certification.builder() + .certificationStatus(status) + .currentAttempt(attempt) + .certificatedAt(certificatedAt) + .certificationLinks("certificationLink") + .build(); + certification.setParticipant(participant); + return certificationRepository.save(certification); + } + + private UserItem getSavedUserItem(User user, ItemCategory itemCategory, int count) { + Item item = itemRepository.save(Item.builder() + .itemCategory(itemCategory) + .build()); + UserItem userItem = new UserItem(count); + userItem.setItem(item); + userItem.setUser(user); + return userItemRepository.save(userItem); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java b/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java new file mode 100644 index 00000000..65712780 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java @@ -0,0 +1,120 @@ +package com.genius.gitget.challenge.participant.service; + +import static com.genius.gitget.challenge.instance.domain.Progress.ACTIVITY; +import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class ParticipantProviderTest { + @Autowired + ParticipantProvider participantProvider; + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + ParticipantRepository participantRepository; + + + @Test + @DisplayName("userId와 instanceId를 통해 저장되어 있는 ParticipantInfo를 받아올 수 있다.") + public void should_getParticipantInfo_when_passUserIdAndInstanceId() { + //given + User savedUser = getSavedUser(); + Instance savedInstance = getSavedInstance(PREACTIVITY); + getSavedParticipant(savedUser, savedInstance); + + //when + Participant participant = participantProvider.findByJoinInfo(savedUser.getId(), + savedInstance.getId()); + + //then + assertThat(participant.getUser().getId()).isEqualTo(savedUser.getId()); + assertThat(participant.getInstance().getId()).isEqualTo(savedInstance.getId()); + } + + @Test + @DisplayName("Participant를 Participant의 PK를 통해 찾을 수 있다.") + public void should_getParticipant_when_passPK() { + //given + User savedUser = getSavedUser(); + Instance savedInstance = getSavedInstance(PREACTIVITY); + Participant participant = getSavedParticipant(savedUser, savedInstance); + + //when + Participant foundParticipant = participantProvider.findById(participant.getId()); + + //then + assertThat(foundParticipant.getId()).isEqualTo(participant.getId()); + assertThat(foundParticipant.getJoinStatus()).isEqualTo(participant.getJoinStatus()); + assertThat(foundParticipant.getUser()).isEqualTo(savedUser); + assertThat(foundParticipant.getInstance()).isEqualTo(savedInstance); + } + + @Test + @DisplayName("Participant들 중 Progress(진행 상황)과 사용자 정보 조건에 맞는 정보들을 불러올 수 있다.") + public void should_returnList_when_passProgress() { + //given + User user = getSavedUser(); + Instance instance1 = getSavedInstance(PREACTIVITY); + Instance instance2 = getSavedInstance(PREACTIVITY); + Instance instance3 = getSavedInstance(ACTIVITY); + Participant participant1 = getSavedParticipant(user, instance1); + Participant participant2 = getSavedParticipant(user, instance2); + Participant participant3 = getSavedParticipant(user, instance3); + + //when + List participants = participantProvider.findJoinedByProgress(user.getId(), PREACTIVITY); + + //then + assertThat(participants.size()).isEqualTo(2); + } + + + private User getSavedUser() { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("identifier") + .information("information") + .tags("BE,FE") + .build() + ); + } + + private Instance getSavedInstance(Progress progress) { + return instanceRepository.save( + Instance.builder() + .progress(progress) + .build() + ); + } + + private Participant getSavedParticipant(User user, Instance instance) { + Participant participant = Participant.builder() + .joinStatus(JoinStatus.YES) + .build(); + participant.setUserAndInstance(user, instance); + return participantRepository.save(participant); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java b/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java index 8091c136..53b6c067 100644 --- a/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java @@ -61,7 +61,7 @@ public void should_return4XX_when_nicknameDuplicated() throws Exception { public void should_return2XX_when_nicknameNotDuplicated() throws Exception { mockMvc.perform(get("/api/auth/check-nickname?nickname=" + "nickname")) .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("$.code").value(SUCCESS.getKey())) + .andExpect(jsonPath("$.code").value("OK")) .andExpect(jsonPath("$.resultCode").value(SUCCESS.getStatus().value())) .andExpect(jsonPath("$.message").value(SUCCESS.getMessage())); } diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index 98230693..b878e824 100644 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.user.service; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -14,6 +15,8 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; @@ -127,6 +130,37 @@ public void should_throwException_when_nicknameIsDuplicated() { .hasMessageContaining(ErrorCode.DUPLICATED_NICKNAME.getMessage()); } + @ParameterizedTest + @DisplayName("User 엔티티로부터 깃허브 토큰을 불러올 때 길이가 0이거나, 공백으로 이루어져 있다면 예외가 발생한다.") + @ValueSource(strings = {"", " "}) + public void should_throwException_when_githubTokenInvalid(String githubToken) { + //given + User user = getSavedUser(); + + //when + user.updateGithubPersonalToken(githubToken); + + //then + assertThatThrownBy(() -> userService.getGithubToken(user)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("User 엔티티로부터 깃허브 토큰을 불러올 때 null 이라면 예외가 발생한다.") + public void should_throwException_when_githubTokenNull() { + //given + User user = getSavedUser(); + + //when + user.updateGithubPersonalToken(null); + + //then + assertThatThrownBy(() -> userService.getGithubToken(user)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + } + private void saveUnsignedUser() { userRepository.save(User.builder() diff --git a/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java b/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java index e78c5815..f03a1890 100644 --- a/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java +++ b/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java @@ -1,17 +1,10 @@ package com.genius.gitget.global.security.controller; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; -import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; @@ -37,17 +30,4 @@ public void setup() { .apply(springSecurity()) .build(); } - - @Test - @DisplayName("anotation test") - @WithMockCustomUser(role = Role.USER) - public void test() throws Exception { - //given - Cookie cookie = tokenTestUtil.createAccessCookie(); - - //when&then - mockMvc.perform(get("/api/test") - .cookie(cookie)) - .andExpect(status().isOk()); - } } diff --git a/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java index bae38396..c00402f7 100644 --- a/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java @@ -3,8 +3,8 @@ import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.gitget.global.util.exception.ErrorCode.JWT_TOKEN_NOT_FOUND; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; -import static com.genius.gitget.global.util.exception.ErrorCode.TOKEN_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -171,7 +171,7 @@ public void should_throwException_when_noTokens() { //then assertThatThrownBy(() -> jwtService.resolveTokenFromCookie(request, refreshTokenPrefix)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(TOKEN_NOT_FOUND.getMessage()); + .hasMessageContaining(JWT_TOKEN_NOT_FOUND.getMessage()); } @Test diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java index 64265ab8..d019d7b2 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java @@ -7,8 +7,10 @@ import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.service.CustomUserDetailsService; import java.util.List; +import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -23,16 +25,24 @@ public class WithMockCustomUserSecurityContextFactory implements WithSecurityCon private final UserService userService; private final CustomUserDetailsService customUserDetailsService; + @Value("${github.githubId}") + private String githubId; + @Override public SecurityContext createSecurityContext(WithMockCustomUser customUser) { + String identifier = githubId; + if (!Objects.equals(customUser.identifier(), "identifier")) { + identifier = customUser.identifier(); + } + User user = User.builder() .providerInfo(customUser.providerInfo()) - .identifier(customUser.identifier()) + .identifier(identifier) .role(Role.NOT_REGISTERED) .build(); SignupRequest signupRequest = SignupRequest.builder() - .identifier(customUser.identifier()) + .identifier(identifier) .interest(List.of("FE", "BE")) .nickname(customUser.nickname()) .information(customUser.information()) From ad2c09e150cc72bab6b32795d885150fe8de2189 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 9 Mar 2024 23:44:59 +0900 Subject: [PATCH 120/234] =?UTF-8?q?[FEAT]=20=EB=A7=88=EC=9D=B4=20=EC=B1=8C?= =?UTF-8?q?=EB=A6=B0=EC=A7=80=20=EA=B0=9C=EB=B0=9C=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Github API for Java 의존성 추가 * feat: Github 토큰을 통해 깃허브 연결이 유효한지 확인하는 기능 추가 - 개인이 발급한 Github Personal Access Token을 전달받았을 때, 연결이 유효한지 확인하는 로직 추가 - 소셜로그인에 사용한 깃허브 계정과 같은지 확인 - 연결 과정에서 예외가 발생한 경우 BusinessException 발생 * feat: 깃허브 레포지토리 연결을 검증하는 서비스 코드 구현 - 깃허브 레포지토리 연결을 검증하는 서비스 코드 구현 - 위의 로직을 검증하는 테스트 코드 추가 * feat: 인증 관련 엔티티 작성 - Certification 엔티티 작성: 인증 현황, 인증 일자, 인증링크, 참여정보와 연관관계 설정 - User 엔티티에 Column 추가: 깃허브 personal access token을 암호화해서 저장할 column 추가 * feat: Certification repository 생성 - 인증 정보를 담고 있는 Certification 엔티티의 Repository 추가 - 리포지토리 관련 테스트 코드 작성 * feat: 암호화/복호화 유틸 개발 - Github Personal Access Token를 암호화/복호화할 때 사용할 유틸 클래스 개발 - 테스트 코드 작성 * feat: Github personal token 등록 로직 개발 - 사용자가 발급한 Github personal token을 등록하는 로직 구현 - 소셜로그인 시 사용했던 깃허브 계정과 일치하는지 확인 - DB에 암호화하여 저장 - 테스트 코드 작성 * feat: Github Repository 등록 기능 구현 - 레포지토리 등록 기능 구현 - Github Repository 등록 시 깃허브 토큰/레포지토리/인스턴스 관련 예외 처리 - 컨트롤러 관련 테스트 코드 작성 필요 * test: 인증 컨트롤러 테스트 코드 추가 * feat: 사용자 DB로부터 깃허브 토큰 받아오는 로직 추가 - UserService에 사용자 DB로부터 깃허브 토큰을 받아와 검증 및 복호화하여 반환하는 로직 추가 - 테스트 코드 작성 * feat: 참여정보를 저장한 DB로부터 레포지토리 이름 받아오는 기능 구현 - 참여정보를 저장한 DB로부터 저장해놓은 레포지토리 이름 받아오는 기능 구현 - 혼동을 막기 위해 ErrorCode의 에러 메세지 수정 * feat: 특정 조건에 충족하는 Pull Request를 가져오는 기능 구현 - 특정 Repository의 특정 일자에 존재하는 Pull Request 리스트들을 받아오는 기능 구현 - PR을 받아오는 코드 및 예외에 대한 테스트 코드 작성 * feat: 챌린지 참여 시도 시, 인증 연결 확인 기능 구현 - 사용자가 챌린지 참여 시도 시, 해당 레포지토리에 등록한 PR을 확인하는 기능 구현 - 관련 테스트 코드 작성 * fix: 영속성으로 인해 연관관계 설정 안되는 버그 픽스 * feat: 특정 날짜에 대해 인증 현황을 갱신하는 기능 구현 - Github API를 이용하여 특정 날짜에 대한 인증 현황을 갱신하는 기능 구현 - 해당 날짜에 등록된 PR을 통해 인증 현황 갱신 - 서비스단 코드에 대해 리팩토링 필요 * fix: 인증 PR 개수 반환 버그 픽스 - 인증에 사용한 PR 링크가 없을 때에도, 인증에 사용한 PR 개수를 1로 반환하던 버그 픽스 - 인증 갱신 관련 테스트 코드 작성 * refactor: 인증 객체 생성 메서드로 추출 * feat: 사용자의 레포지토리 목록을 반환하는 API 개발 - 사용자가 등록한 깃허브 계정에 있는 public repository 들을 불러오는 API 개발 - 관련 테스트 코드 작성 * feat: 특정 기간 내의 인증일자의 인증을 받아오는 기능 구현 - 등록되어 있는 인증들 중, 특정 기간 내의 인증 일자를 받아오는 Repository 코드 구현 - 관련 테스트 코드 작성 * feat: 현재 일자를 기준으로 일주일 간의 인증 정보를 받아오는 기능 구현 - 현재 일자를 기준으로 일주일 간(월요일~현재 요일)의 인증 정보를 받아오는 기능 구현 - 관련 서비스 코드 작성 * feat: 일주일 간 인증 정보 반환하는 API 구현 * refactor: Github 관련 서비스 코드들 클래스로 분리 * feat: 인증 수단 확인 시, PR 존재 X 시 예외 처리 로직 추가 - 인증 수단 확인 API 호출 시, 특정 레포지토리 내에 PR이 존재하지 않으면 PR이 존재하지 않는다는 예외를 발생시키도록 처리 * feat: 인증 정보 등록 시, 인증 회차 정보 추가 - 인증 정보 등록 시, 인증 회차 정보 추가하도록 설정 * refactor: 암호화 클래스에 대해 상속 불가로 변경 * refactor: 깃허브 관련 정보 검증 API 수정 - repository 검증 API 로직 변경: API endpoint 변경 및 참여 신청 관련 로직 분리 - pull request 검증 API 로직 변경: API endpoint 변경 - 관련 테스트 코드 수정 * feat: 챌린지 참여 API 로직 개발 * feat: 챌린지 참여, 참여 취소 API 개발 - 챌린지 참여&참여취소 API 개발 - 관련 테스트 코드 작성 - 챌린지 참여 취소에 대한 로직 보충 필요 (ex: 챌린지가 시작되었을 때 취소 불가 or 패널티) * feat: 챌린지 참여, 참여 취소 로직 보강 - 챌린지의 진행 상황(Progress)에 따라 다른 결과가 나오도록 로직 보강 - 관련 테스트 코드 작성 - InstanceDetailService 코드에 리팩토링 필요 * feat: 챌린지 상세 페이지 조회 API 개발 - 챌린지 상세페이지 진입 시 호출해야하는 API 개발 - 서비스 테스트 코드 작성 - 컨트롤러 테스트 코드 작성 필요 * feat: 특정 사용자 인증 전체 조회 API 개발 - 특정 사용자의 인증 전체 조회 API 개발 - 테스트 코드 작성 필요 * fix: 사용자의 레포지토리 반환 API 수정 - 레포지토리의 이름을 반환해야하는데, 객체의 toString을 통해 반환하던 문제점 해결 * feat: 사용자가 참여하고 있는 챌린지에 대한 현황 조회 API 구현 - 인증 상세 페이지에서 필요한 인증 현황 정보를 조회하는 API 개발 - 관련 테스트 코드 필요 - DateUtil의 메서드를 사용함에 따라 적절한 이름으로 변경 * refactor: 사용자를 식별하는 방법을 identifier로 변경 - 사용자를 식별하는 방법을 userId(PK)를 사용하는 방법에서, identifier(github id)로 식별하도록 변경 * feat: 인증 상세 페이지에서 챌린지 관련 정보 조회 API 개발 * refactor: 이미 참여한 챌린지에 대해 참여 API 요청 시 예외 발생 처리 - 이미 참여한 챌린지에 대해 참여 API 요청 시, 예외가 전달되도록 처리 * feat: 인증 결과 리스트 조회 시 더미 데이터 추가 - 인증 결과(일주일 간, 전체) 조회 API 요청 시, 없는 데이터인 경우 더미 데이터를 추가하도록 로직 보강 - CertificationRepository의 메서드 이름 변경: 불필요한 단어 삭제 - ParticipantInfoService에서 participantId를 통해 instance 를 받는 로직 추가 - 관련 테스트 코드 추가 작성 필요 * fix: 일주일 인증 현황 조회 시, 데이터의 개수가 잘못반환되는 버그 픽스 - 챌린지의 시작 요일에 따라 데이터의 개수가 잘못 반환되는 버그 픽스 - 관련 테스트 코드 작성 * refactor: 메서드 및 클래스 이름 변경 - 메서드 및 클래스의 이름을 다른 기능들과 구별이 쉽도록 변경 * refactor: 엔티티명 수정 - ParticipantInfo에서 Participant로 이름 변경 - 이름 변경에 따라 쿼리문 및 테스트 코드 변경 * refactor: CertificationProvider를 통해 Service layer 분리 - 기존의 CertificationService의 일부 기능을 CertificationProvider로 분리 - 관련 테스트 코드 작성 필요 * feat: 인증 관련 예외 사항 추가 처리 - 인증 시도 시, 해당 날짜가 진행 중인 챌린지인 경우에만 가능하도록 추가 처리 - 함수의 매개변수로 전달 시 필요한 정보만 전달하도록 변경 - 관련 테스트 코드 추가 * refactor: Module service에 의존하도록 설정 - InstanceDetailService에서 Module service인 ParticipantProvider에만 의존하도록 설정 * chore: 코드 정렬 * fix: 인증 정보 갱신 버그 픽스 - 인증 정보가 있음에도 불구하고 갱신 요청 시, 객체가 하나 더 저장되는 버그 픽스 * refactor: EncryptUtil의 메서드명을 일반적으로 변경 - EncryptUtil의 메서드명을 재사용하기 쉽도록 일반적인 이름으로 변경 * feat: 참여 요청 시, Github 연결 재확인 로직 추가 - 챌린지 참여 요청 시, Github repository 연결을 확인하는 로직 추가 * refactor: PR을 검증하는 메서드 추출 - PR 리스트를 받아서 controller에서 조건문을 통해 확인하던 로직을 service 내에 메서드로 추출 * refactor: 인증 상세 페이지 API 반환 값 변경 - repository의 위치가 변경됨에 따라 API 반환 값 변경 - githubProvider에 Github API에 요청할 repository full name을 반환하는 메서드 추가 * refactor: module service로 변경 - Instance 엔티티에 대해 module service(InstanceProvider)추가 * fix: 챌린지 상세 정보 조회 시, 제목 데이터가 포함되지 않은 버그 픽스 * fix: 인증 요청을 다시 했을 때 인증 정보가 업데이트되지 않는 버그 픽스 - 이전에 인증을 한 차례 진행하고, 다시 인증을 요청했을 때 DB에 저장하는 정보가 업데이트되지 않는 버그 픽스 - 테스트 코드 추가 작성 필요 * refactor: 인증을 갱신하는 로직에 대해 리팩토링 - CertificationService의 인증을 갱신하는 로직에 대해 리팩토링 진행 - 메서드로 따로 추출 - 테스트 코드 추가 * fix: 챌린지 참여 요청을 했을 때, 참여 상태가 변하지 않던 버그 픽스 - 특정 챌린지에 대해 참여 요청을 했을 때, 해당 챌린지의 참여 상태가 별하지 않는 버그 픽스 * feat: 챌린지 상세 정보 조회 시, 파일 데이터 추가 - 인증 상세 페이지에서 챌린지 상세 정보 조회 시, 전달하는 데이터에 파일 데이터 추가 * feat: 진행 중 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 진행 중인 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * fix: 챌린지 참여 조건 확인 버그 픽스 - 사용자가 챌린지에 참여할 수 있는 조건인지 확인하는 과정에서 발생하는 버그 픽스 - 조건이 잘못되어 있어 수정 * feat: 시작 전 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 시작 전인 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * feat: 완료 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 완료된 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * refactor: 챌린지 상세 정보 조회 응답 데이터 변경 * feat: 참여자 전체의 주간 인증 현황 조회 API 개발 - 특정 챌린지(인스턴스)에 참여한 참여자 전체의 주간 인증 현황 조회 API 개발 - 테스트 코드 작성 필요 - 리팩토링 필요 * refactor: DTO의 이름 변경 - DTO의 이름을 보다 쓰임에 맞는 이름으로 변경 * refactor: 코드 리팩토링 - 메서드 분리 진행 * feat: 사용자의 아이템 보유 개수 필드 추가 * feat: 인증 패스 아이템 API 개발 - 인증 패스 아이템 API 개발 - 리팩토링 필요 - 테스트 코드 작성 필요 * refactor: Participant의 패키지명 변경 - Participant의 패키지명을 ParticipantInfo에서 Participant로 변경 * feat: 완료된 챌린지에서 보상 수령 여부에 따른 응답 데이터 변경 - 마이챌린지의 완료 챌린지 목록에서, 챌린지 성공 보상 수령 여부에 따른 응답 데이터 변경 로직 추가 * feat: 완료 챌린지 응답 데이터에 아이템 개수 추가 - 완료 챌린지 리스트의 개별 응답 데이터에 '포인트 2배 수령 아이템'의 개수 정보 추가 * feat: 완료 챌린지 - 포인트 수령 API 개발 - 마이 챌린지의 완료 챌린지들 중, 챌린지에 성공했으나 포인트를 아직 수령하지 않은 챌린지에 대해 포인트를 수령하는 API 개발 - 아이템을 가지고 있는 경우, 포인트 2배 수령이 가능하도록 설정 * fix: 챌린지 참여 조건 버그 픽스 * refactor: 챌린지 시작 d-day 구하는 메서드 분리 * fix: 챌린지 참여 및 보유 아이템 정보 조회 시 버그 픽스 * refactor: 완료 챌린지의 인증 현황 반환 데이터 변경 - 마이 챌린지 - 완료 탭에서의 인증 현황 반환 데이터를 실제 서비스에서 보여줄 문자열로 변경 * refactor: 인증 패스 조건 검증 메서드 분리 - 인증 패스 아이템 사용이 가능한지 확인하는 메서드를 따로 분리 (조건문 수정) - 인증 관련 테스트 코드 추가 (CertificationProvider, CertificationService, GithubProvider) * refactor: UserItem 엔티티의 컬럼 변경 및 테스트 코드 추가 * test: 마이 챌린지 서비스 테스트 코드 작성 * refactor: 챌린지 상세 정보 조회 응답 데이터 변경 - 챌린지 상세 정보 조회 시, 시작 날짜, 끝 날짜를 따로 보내도록 변경 - FileResponse의 정적 팩토리 메서드 리팩토링 * refactor: 전체 주간 인증 현황 조회 시, 본인의 데이터 제외하도록 변경 - 인증 상세 페이지에서 전체 사용자들에 대한 주간 인증 현황 조회 시, 본인의 데이터는 제외하도록 변경 - 로직 변경에 따라 테스트 코드 변경 * refactor: 주간 인증 현황 조회 시, 사용자의 정보도 함께 전달 - 사용자가 본인의 주간 인증 현황 조회 시, 이후의 API 요청을 위해 사용자의 정보도 함께 전달하도록 변경 - 인증 정보가 저장되어 있지 않을 때, 날짜 정보까지 모두 null로 전달되던 버그 픽스 * feat: 주간 인증 현황 조회 시, 사용자 정보도 함께 반환 - 본인의 주간 인증 현황 조회 & 모든 사용자의 주간 인증 현황 조회 시, 사용자 정보(userId, nickname, file) 또한 포함하도록 변경 * feat: 전체 인증 현황 조회 응답 데이터 추가 - 전체 인증 현황 조회 시, 응답 데이터에 총 인증 횟수 데이터 추가 - 관련 테스트 코드 추가 * refactor: 챌린지 상세 정보 조회 응답 데이터 변경 - 챌린지 상세 정보 조회 시, 시작 날짜, 끝 날짜를 따로 보내도록 변경 - FileResponse의 정적 팩토리 메서드 리팩토링 * refactor: 전체 인증 현황 조회 시 query string 데이터 변경 - 전체 인증 현황 조회 시, 기존에 사용자의 identifier를 전달하는 것에서 userId로 전달하도록 변경 * refactor: 전체 인증 현황 조회 시 query string 데이터 변경 - 전체 인증 현황 조회 시, 기존에 사용자의 identifier를 전달하는 것에서 userId로 전달하도록 변경 * refactor: 진행 전 챌린지 응답 데이터 변경 - 마이 챌린지의 진행 전 챌린지 목록 응답 데이터에서 파일 데이터(FileResponse) 추가 * refactor: 완료 챌린지 응답 데이터 변경 - 완료 챌린지의 응답 데이터에 '제목'과 '파일' 정보 추가 - 관련 테스트 코드 추가 * refactor: 진행 중 챌린지의 응답데이터 변경 - 진행 중 챌린지의 응답 데이터 변경 - 인증 현황 데이터를 전달하던 이전과 달리, 진행 중 리스트 반환 시 사용하는 데이터로 반환하도록 변경 - 관련 테스트 코드 작성 * feat: github token 등록 여부 확인 API 개발 * fix: Progress에 상관없이 인증 현황이 잘못 전달되던 버그 픽스 - 인스턴스의 Progress에 상관없이 성공/실패/남은 인증 개수가 잘못 전달되던 버그 픽스 - provider의 메서드 명을 명료하게 변경 - 관련 테스트 코드 변경 및 추가 작성 * refactor: 인증 응답 데이터 변경 - 인증 응답 데이터에서 DB에 저장되어있지 않은 인증에 대해 CertificateStatus를 NOT_YET으로 통일 * fix: 마이챌린지 진행 중 문구 변경 * feat: report 기능 도메인 개발 - 개선 팔요 * Update InstanceDetailServiceTest.java --------- Co-authored-by: kimdozzi --- .../controller/CertificationController.java | 10 ++-- .../domain/CertificateStatus.java | 2 +- .../service/CertificationService.java | 14 ++++-- .../instance/dto/detail/InstanceResponse.java | 4 +- .../dto/search/InstanceSearchResponse.java | 8 ---- .../myChallenge/dto/ActivatedResponse.java | 26 ++++++++++- .../myChallenge/dto/DoneResponse.java | 12 ++++- .../myChallenge/dto/PreActivityResponse.java | 4 +- .../service/MyChallengeService.java | 20 +++----- .../service/CertificationServiceTest.java | 46 +++++++++++-------- .../service/InstanceDetailServiceTest.java | 4 +- .../service/MyChallengeServiceTest.java | 18 ++++++-- 12 files changed, 106 insertions(+), 62 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index 7ff05754..cefd6003 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -11,6 +11,7 @@ import com.genius.gitget.challenge.certification.service.CertificationService; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.service.InstanceProvider; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantProvider; import com.genius.gitget.challenge.user.domain.User; @@ -73,16 +74,17 @@ public ResponseEntity> certificateByGithub } @PostMapping("/pass") - public ResponseEntity> passCertification( + public ResponseEntity> passCertification( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody CertificationRequest certificationRequest ) { User user = userPrincipal.getUser(); - CertificationResponse certificationResponse = certificationService.passCertification( - user.getId(), certificationRequest); + ActivatedResponse activatedResponse = certificationService.passCertification( + user.getId(), + new CertificationRequest(certificationRequest.instanceId(), LocalDate.now())); return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certificationResponse) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), activatedResponse) ); } diff --git a/src/main/java/com/genius/gitget/challenge/certification/domain/CertificateStatus.java b/src/main/java/com/genius/gitget/challenge/certification/domain/CertificateStatus.java index 9ef152c3..073411b0 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/domain/CertificateStatus.java +++ b/src/main/java/com/genius/gitget/challenge/certification/domain/CertificateStatus.java @@ -6,7 +6,7 @@ @Getter @RequiredArgsConstructor public enum CertificateStatus { - NOT_YET("인증 필요"), + NOT_YET("인증하기"), CERTIFICATED("인증 갱신"), PASSED("패스 완료"); diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index cf52a609..fded2460 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -19,6 +19,7 @@ import com.genius.gitget.challenge.item.domain.ItemCategory; import com.genius.gitget.challenge.item.domain.UserItem; import com.genius.gitget.challenge.item.service.UserItemProvider; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantProvider; import com.genius.gitget.challenge.user.domain.User; @@ -138,7 +139,7 @@ private Map convertToMap(List certificati } @Transactional - public CertificationResponse passCertification(Long userId, CertificationRequest certificationRequest) { + public ActivatedResponse passCertification(Long userId, CertificationRequest certificationRequest) { Instance instance = instanceProvider.findById(certificationRequest.instanceId()); Participant participant = participantProvider.findByJoinInfo(userId, instance.getId()); LocalDate targetDate = certificationRequest.targetDate(); @@ -150,15 +151,18 @@ public CertificationResponse passCertification(Long userId, CertificationRequest //TODO: 리팩토링 시급... if (optional.isPresent()) { - optional.get().updateToPass(targetDate); - return CertificationResponse.createExist(optional.get()); + Certification certification = optional.get(); + certification.updateToPass(targetDate); + return ActivatedResponse.create(instance, certification.getCertificationStatus(), + 0, participant.getRepositoryName()); } Certification certification = Certification.createPassed(targetDate); certification.setParticipant(participant); certificationProvider.save(certification); - - return CertificationResponse.createExist(certification); + + return ActivatedResponse.create(instance, certification.getCertificationStatus(), + 0, participant.getRepositoryName()); } private void validatePassCondition(UserItem userItem, Optional optional) { diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java index b8d6b2e5..e15cf80a 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java @@ -20,7 +20,7 @@ public record InstanceResponse( String notice, String certificationMethod, JoinStatus joinStatus, - int hitCount, + int likesCount, FileResponse fileResponse ) { @@ -39,7 +39,7 @@ public static InstanceResponse createByEntity(Instance instance, JoinStatus join .notice(instance.getNotice()) .certificationMethod(instance.getCertificationMethod()) .joinStatus(joinStatus) - .hitCount(instance.getLikesList().size()) + .likesCount(instance.getLikesList().size()) .fileResponse(FileResponse.create(instance.getFiles())) .build(); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index 16c45961..6dcd98b5 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -28,13 +28,5 @@ public InstanceSearchResponse(Long topicId, Long instanceId, String keyword, int this.pointPerPerson = pointPerPerson; this.participantCount = participantCount; this.fileResponse = FileResponse.create(Optional.of(files)); - this.fileResponse = convertToFileResponse(Optional.ofNullable(files)); - } - - private static FileResponse convertToFileResponse(Optional files) { - if (files.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(files.get()); } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java index f2558a23..0ca84552 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java @@ -1,5 +1,8 @@ package com.genius.gitget.challenge.myChallenge.dto; +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.dto.FileResponse; import lombok.Builder; @Builder @@ -10,6 +13,27 @@ public record ActivatedResponse( String repository, String certificateStatus, int numOfPassItem, - boolean canUsePassItem + boolean canUsePassItem, + FileResponse fileResponse ) { + + public static ActivatedResponse create(Instance instance, CertificateStatus certificateStatus, + int numOfPassItem, String repository) { + boolean canUseItem = checkItemCondition(certificateStatus, numOfPassItem); + + return ActivatedResponse.builder() + .instanceId(instance.getId()) + .title(instance.getTitle()) + .pointPerPerson(instance.getPointPerPerson()) + .repository(repository) + .certificateStatus(certificateStatus.getTag()) + .canUsePassItem(canUseItem) + .numOfPassItem(canUseItem ? numOfPassItem : 0) + .fileResponse(FileResponse.create(instance.getFiles())) + .build(); + } + + private static boolean checkItemCondition(CertificateStatus certificateStatus, int numOfPassItem) { + return (certificateStatus == CertificateStatus.NOT_YET) && (numOfPassItem > 0); + } } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java index ccd947d8..ee80a97a 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java @@ -4,38 +4,46 @@ import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.domain.RewardStatus; +import com.genius.gitget.global.file.dto.FileResponse; import lombok.Builder; @Builder public record DoneResponse( Long instanceId, + String title, int pointPerPerson, JoinResult joinResult, boolean canGetReward, int numOfPointItem, int rewardedPoints, - double achievementRate + double achievementRate, + FileResponse fileResponse ) { - public static DoneResponse createNotRewarded(Instance instance, Participant participant, + public static DoneResponse createNotRewarded(Instance instance, + Participant participant, int numOfPointItem) { return DoneResponse.builder() + .title(instance.getTitle()) .instanceId(instance.getId()) .pointPerPerson(instance.getPointPerPerson()) .joinResult(participant.getJoinResult()) .canGetReward(canGetReward(participant)) .numOfPointItem(numOfPointItem) + .fileResponse(FileResponse.create(instance.getFiles())) .build(); } public static DoneResponse createRewarded(Instance instance, Participant participant, double achievementRate) { return DoneResponse.builder() + .title(instance.getTitle()) .instanceId(instance.getId()) .pointPerPerson(instance.getPointPerPerson()) .joinResult(participant.getJoinResult()) .canGetReward(false) .rewardedPoints(participant.getRewardPoints()) .achievementRate(achievementRate) + .fileResponse(FileResponse.create(instance.getFiles())) .build(); } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java index 809289bb..a00f16c3 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.myChallenge.dto; +import com.genius.gitget.global.file.dto.FileResponse; import lombok.Builder; @Builder @@ -8,6 +9,7 @@ public record PreActivityResponse( String title, int participantCount, int pointPerPerson, - int remainDays + int remainDays, + FileResponse fileResponse ) { } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index f5947a6e..8cc013f4 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -22,6 +22,7 @@ import com.genius.gitget.challenge.participant.service.ParticipantProvider; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import java.time.LocalDate; @@ -54,6 +55,7 @@ public List getPreActivityInstances(User user, LocalDate ta .participantCount(instance.getParticipantCount()) .pointPerPerson(instance.getPointPerPerson()) .remainDays(DateUtil.getRemainDaysToStart(participant.getStartedDate(), targetDate)) + .fileResponse(FileResponse.create(instance.getFiles())) .build(); preActivity.add(preActivityResponse); } @@ -104,26 +106,16 @@ public List getActivatedInstances(User user, LocalDate target Certification certification = certificationProvider.findByDate(targetDate, participant.getId()) .orElse(getDummyCertification()); int numOfPassItem = userItemProvider.countNumOfItem(user, ItemCategory.CERTIFICATION_PASSER); - boolean canUseItem = checkItemCondition(certification.getCertificationStatus(), numOfPassItem); - ActivatedResponse activatedResponse = ActivatedResponse.builder() - .instanceId(instance.getId()) - .title(instance.getTitle()) - .pointPerPerson(instance.getPointPerPerson()) - .repository(participant.getRepositoryName()) - .certificateStatus(certification.getCertificationStatus().getTag()) - .canUsePassItem(canUseItem) - .numOfPassItem(canUseItem ? numOfPassItem : 0) - .build(); + ActivatedResponse activatedResponse = ActivatedResponse.create( + instance, certification.getCertificationStatus(), + numOfPassItem, participant.getRepositoryName() + ); activated.add(activatedResponse); } return activated; } - private boolean checkItemCondition(CertificateStatus certificateStatus, int numOfPassItem) { - return (certificateStatus == CertificateStatus.NOT_YET) && (numOfPassItem > 0); - } - private Certification getDummyCertification() { return Certification.builder() .currentAttempt(0) diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index ddf88f81..484a93a2 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -26,6 +26,7 @@ import com.genius.gitget.challenge.item.domain.UserItem; import com.genius.gitget.challenge.item.repository.ItemRepository; import com.genius.gitget.challenge.item.repository.UserItemRepository; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; @@ -406,16 +407,19 @@ public void should_passCertification_when_conditionIsValid() { getSavedCertification(CERTIFICATED, currentDate.plusDays(4), participant); getSavedCertification(CERTIFICATED, currentDate.plusDays(6), participant); - CertificationResponse certificationResponse = certificationService.passCertification( + ActivatedResponse activatedResponse = certificationService.passCertification( user.getId(), certificationRequest); //then - assertThat(certificationResponse.certificationId()).isNotNull(); - assertThat(certificationResponse.certificateStatus()).isEqualTo(CertificateStatus.PASSED); - assertThat(certificationResponse.certificatedAt()).isEqualTo(currentDate); - assertThat(certificationResponse.prCount()).isEqualTo(0); - assertThat(certificationResponse.prLinks()).isEmpty(); + assertThat(activatedResponse.instanceId()).isEqualTo(instance.getId()); + assertThat(activatedResponse.title()).isEqualTo(instance.getTitle()); + assertThat(activatedResponse.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(activatedResponse.repository()).isEqualTo(participant.getRepositoryName()); + assertThat(activatedResponse.certificateStatus()).isEqualTo(PASSED.getTag()); + assertThat(activatedResponse.numOfPassItem()).isEqualTo(0); + assertThat(activatedResponse.canUsePassItem()).isFalse(); + assertThat(activatedResponse.fileResponse()).isNotNull(); } @Test @@ -500,15 +504,18 @@ public void should_overwriteData_when_certificatedBefore() { //when getSavedCertification(NOT_YET, currentDate, participant); - CertificationResponse certificationResponse = certificationService.passCertification(user.getId(), + ActivatedResponse activatedResponse = certificationService.passCertification(user.getId(), certificationRequest); //then - assertThat(certificationResponse.certificateStatus()).isEqualTo(PASSED); - assertThat(certificationResponse.certificationId()).isNotZero(); - assertThat(certificationResponse.prCount()).isZero(); - assertThat(certificationResponse.prLinks().size()).isZero(); - assertThat(certificationResponse.certificatedAt()).isEqualTo(currentDate); + assertThat(activatedResponse.instanceId()).isEqualTo(instance.getId()); + assertThat(activatedResponse.title()).isEqualTo(instance.getTitle()); + assertThat(activatedResponse.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(activatedResponse.repository()).isEqualTo(participant.getRepositoryName()); + assertThat(activatedResponse.certificateStatus()).isEqualTo(PASSED.getTag()); + assertThat(activatedResponse.numOfPassItem()).isEqualTo(0); + assertThat(activatedResponse.canUsePassItem()).isFalse(); + assertThat(activatedResponse.fileResponse()).isNotNull(); } @Test @@ -526,15 +533,18 @@ public void should_usePassItem_when_conditionIsValid() { .build(); //when - CertificationResponse certificationResponse = certificationService.passCertification(user.getId(), + ActivatedResponse activatedResponse = certificationService.passCertification(user.getId(), certificationRequest); //then - assertThat(certificationResponse.certificateStatus()).isEqualTo(PASSED); - assertThat(certificationResponse.certificationId()).isNotZero(); - assertThat(certificationResponse.prCount()).isZero(); - assertThat(certificationResponse.prLinks().size()).isZero(); - assertThat(certificationResponse.certificatedAt()).isEqualTo(currentDate); + assertThat(activatedResponse.instanceId()).isEqualTo(instance.getId()); + assertThat(activatedResponse.title()).isEqualTo(instance.getTitle()); + assertThat(activatedResponse.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(activatedResponse.repository()).isEqualTo(participant.getRepositoryName()); + assertThat(activatedResponse.certificateStatus()).isEqualTo(PASSED.getTag()); + assertThat(activatedResponse.numOfPassItem()).isEqualTo(0); + assertThat(activatedResponse.canUsePassItem()).isFalse(); + assertThat(activatedResponse.fileResponse()).isNotNull(); } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index e7e05b21..44619b96 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -249,7 +249,7 @@ public void should_returnValues_when_joinedInstance() { assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.YES); - assertThat(instanceResponse.hitCount()).isEqualTo(0); + assertThat(instanceResponse.likesCount()).isEqualTo(0); } @Test @@ -270,7 +270,7 @@ public void should_returnData_when_notJoinedInstance() { assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.NO); - assertThat(instanceResponse.hitCount()).isEqualTo(0); + assertThat(instanceResponse.likesCount()).isEqualTo(0); } @Test diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java index cbdac328..c0e2048d 100644 --- a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java @@ -78,8 +78,12 @@ public void should_getPreActivities_when_userJoinChallenges() { //then assertThat(instances.size()).isEqualTo(3); assertThat(instances.get(0).instanceId()).isEqualTo(instance1.getId()); + + assertThat(instances.get(0).fileResponse()).isNotNull(); assertThat(instances.get(1).instanceId()).isEqualTo(instance2.getId()); + assertThat(instances.get(1).fileResponse()).isNotNull(); assertThat(instances.get(2).instanceId()).isEqualTo(instance3.getId()); + assertThat(instances.get(2).fileResponse()).isNotNull(); } @Test @@ -135,17 +139,23 @@ public void should_returnTrue_when_ableToReward() { //given LocalDate targetDate = LocalDate.of(2024, 2, 14); User user = getSavedUser(); - Instance instance1 = getSavedInstance(Progress.DONE); - Participant participant1 = getSavedParticipant(user, instance1, SUCCESS); + Instance instance = getSavedInstance(Progress.DONE); + Participant participant = getSavedParticipant(user, instance, SUCCESS); getSavedUserItem(user, ItemCategory.POINT_MULTIPLIER, 3); //when List doneResponses = myChallengeService.getDoneInstances(user, targetDate); //then + DoneResponse doneResponse = doneResponses.get(0); assertThat(doneResponses.size()).isEqualTo(1); - assertThat(doneResponses.get(0).canGetReward()).isTrue(); - assertThat(doneResponses.get(0).numOfPointItem()).isEqualTo(3); + assertThat(doneResponse.title()).isEqualTo(instance.getTitle()); + assertThat(doneResponse.instanceId()).isEqualTo(instance.getId()); + assertThat(doneResponse.rewardedPoints()).isZero(); + assertThat(doneResponse.joinResult()).isEqualTo(SUCCESS); + assertThat(doneResponse.fileResponse()).isNotNull(); + assertThat(doneResponse.canGetReward()).isTrue(); + assertThat(doneResponse.numOfPointItem()).isEqualTo(3); } @Test From 314aec9b81945c970d5f51a25b7361734462c5cf Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 10 Mar 2024 00:08:05 +0900 Subject: [PATCH 121/234] =?UTF-8?q?[FEAT]=20=EC=8B=9C=EC=9E=91/=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20=EA=B8=B0=EA=B0=84=EC=9D=B4=20=EB=90=9C=20=EC=B1=8C?= =?UTF-8?q?=EB=A6=B0=EC=A7=80=EC=97=90=20=EB=8C=80=ED=95=B4=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A5=BC=20=EB=B3=80=ED=99=94=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20(#100)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Github API for Java 의존성 추가 * feat: Github 토큰을 통해 깃허브 연결이 유효한지 확인하는 기능 추가 - 개인이 발급한 Github Personal Access Token을 전달받았을 때, 연결이 유효한지 확인하는 로직 추가 - 소셜로그인에 사용한 깃허브 계정과 같은지 확인 - 연결 과정에서 예외가 발생한 경우 BusinessException 발생 * feat: 깃허브 레포지토리 연결을 검증하는 서비스 코드 구현 - 깃허브 레포지토리 연결을 검증하는 서비스 코드 구현 - 위의 로직을 검증하는 테스트 코드 추가 * feat: 인증 관련 엔티티 작성 - Certification 엔티티 작성: 인증 현황, 인증 일자, 인증링크, 참여정보와 연관관계 설정 - User 엔티티에 Column 추가: 깃허브 personal access token을 암호화해서 저장할 column 추가 * feat: Certification repository 생성 - 인증 정보를 담고 있는 Certification 엔티티의 Repository 추가 - 리포지토리 관련 테스트 코드 작성 * feat: 암호화/복호화 유틸 개발 - Github Personal Access Token를 암호화/복호화할 때 사용할 유틸 클래스 개발 - 테스트 코드 작성 * feat: Github personal token 등록 로직 개발 - 사용자가 발급한 Github personal token을 등록하는 로직 구현 - 소셜로그인 시 사용했던 깃허브 계정과 일치하는지 확인 - DB에 암호화하여 저장 - 테스트 코드 작성 * feat: Github Repository 등록 기능 구현 - 레포지토리 등록 기능 구현 - Github Repository 등록 시 깃허브 토큰/레포지토리/인스턴스 관련 예외 처리 - 컨트롤러 관련 테스트 코드 작성 필요 * test: 인증 컨트롤러 테스트 코드 추가 * feat: 사용자 DB로부터 깃허브 토큰 받아오는 로직 추가 - UserService에 사용자 DB로부터 깃허브 토큰을 받아와 검증 및 복호화하여 반환하는 로직 추가 - 테스트 코드 작성 * feat: 참여정보를 저장한 DB로부터 레포지토리 이름 받아오는 기능 구현 - 참여정보를 저장한 DB로부터 저장해놓은 레포지토리 이름 받아오는 기능 구현 - 혼동을 막기 위해 ErrorCode의 에러 메세지 수정 * feat: 특정 조건에 충족하는 Pull Request를 가져오는 기능 구현 - 특정 Repository의 특정 일자에 존재하는 Pull Request 리스트들을 받아오는 기능 구현 - PR을 받아오는 코드 및 예외에 대한 테스트 코드 작성 * feat: 챌린지 참여 시도 시, 인증 연결 확인 기능 구현 - 사용자가 챌린지 참여 시도 시, 해당 레포지토리에 등록한 PR을 확인하는 기능 구현 - 관련 테스트 코드 작성 * fix: 영속성으로 인해 연관관계 설정 안되는 버그 픽스 * feat: 특정 날짜에 대해 인증 현황을 갱신하는 기능 구현 - Github API를 이용하여 특정 날짜에 대한 인증 현황을 갱신하는 기능 구현 - 해당 날짜에 등록된 PR을 통해 인증 현황 갱신 - 서비스단 코드에 대해 리팩토링 필요 * fix: 인증 PR 개수 반환 버그 픽스 - 인증에 사용한 PR 링크가 없을 때에도, 인증에 사용한 PR 개수를 1로 반환하던 버그 픽스 - 인증 갱신 관련 테스트 코드 작성 * refactor: 인증 객체 생성 메서드로 추출 * feat: 사용자의 레포지토리 목록을 반환하는 API 개발 - 사용자가 등록한 깃허브 계정에 있는 public repository 들을 불러오는 API 개발 - 관련 테스트 코드 작성 * feat: 특정 기간 내의 인증일자의 인증을 받아오는 기능 구현 - 등록되어 있는 인증들 중, 특정 기간 내의 인증 일자를 받아오는 Repository 코드 구현 - 관련 테스트 코드 작성 * feat: 현재 일자를 기준으로 일주일 간의 인증 정보를 받아오는 기능 구현 - 현재 일자를 기준으로 일주일 간(월요일~현재 요일)의 인증 정보를 받아오는 기능 구현 - 관련 서비스 코드 작성 * feat: 일주일 간 인증 정보 반환하는 API 구현 * refactor: Github 관련 서비스 코드들 클래스로 분리 * feat: 인증 수단 확인 시, PR 존재 X 시 예외 처리 로직 추가 - 인증 수단 확인 API 호출 시, 특정 레포지토리 내에 PR이 존재하지 않으면 PR이 존재하지 않는다는 예외를 발생시키도록 처리 * feat: 인증 정보 등록 시, 인증 회차 정보 추가 - 인증 정보 등록 시, 인증 회차 정보 추가하도록 설정 * refactor: 암호화 클래스에 대해 상속 불가로 변경 * refactor: 깃허브 관련 정보 검증 API 수정 - repository 검증 API 로직 변경: API endpoint 변경 및 참여 신청 관련 로직 분리 - pull request 검증 API 로직 변경: API endpoint 변경 - 관련 테스트 코드 수정 * feat: 챌린지 참여 API 로직 개발 * feat: 챌린지 참여, 참여 취소 API 개발 - 챌린지 참여&참여취소 API 개발 - 관련 테스트 코드 작성 - 챌린지 참여 취소에 대한 로직 보충 필요 (ex: 챌린지가 시작되었을 때 취소 불가 or 패널티) * feat: 챌린지 참여, 참여 취소 로직 보강 - 챌린지의 진행 상황(Progress)에 따라 다른 결과가 나오도록 로직 보강 - 관련 테스트 코드 작성 - InstanceDetailService 코드에 리팩토링 필요 * feat: 챌린지 상세 페이지 조회 API 개발 - 챌린지 상세페이지 진입 시 호출해야하는 API 개발 - 서비스 테스트 코드 작성 - 컨트롤러 테스트 코드 작성 필요 * feat: 특정 사용자 인증 전체 조회 API 개발 - 특정 사용자의 인증 전체 조회 API 개발 - 테스트 코드 작성 필요 * fix: 사용자의 레포지토리 반환 API 수정 - 레포지토리의 이름을 반환해야하는데, 객체의 toString을 통해 반환하던 문제점 해결 * feat: 사용자가 참여하고 있는 챌린지에 대한 현황 조회 API 구현 - 인증 상세 페이지에서 필요한 인증 현황 정보를 조회하는 API 개발 - 관련 테스트 코드 필요 - DateUtil의 메서드를 사용함에 따라 적절한 이름으로 변경 * refactor: 사용자를 식별하는 방법을 identifier로 변경 - 사용자를 식별하는 방법을 userId(PK)를 사용하는 방법에서, identifier(github id)로 식별하도록 변경 * feat: 인증 상세 페이지에서 챌린지 관련 정보 조회 API 개발 * refactor: 이미 참여한 챌린지에 대해 참여 API 요청 시 예외 발생 처리 - 이미 참여한 챌린지에 대해 참여 API 요청 시, 예외가 전달되도록 처리 * feat: 인증 결과 리스트 조회 시 더미 데이터 추가 - 인증 결과(일주일 간, 전체) 조회 API 요청 시, 없는 데이터인 경우 더미 데이터를 추가하도록 로직 보강 - CertificationRepository의 메서드 이름 변경: 불필요한 단어 삭제 - ParticipantInfoService에서 participantId를 통해 instance 를 받는 로직 추가 - 관련 테스트 코드 추가 작성 필요 * fix: 일주일 인증 현황 조회 시, 데이터의 개수가 잘못반환되는 버그 픽스 - 챌린지의 시작 요일에 따라 데이터의 개수가 잘못 반환되는 버그 픽스 - 관련 테스트 코드 작성 * refactor: 메서드 및 클래스 이름 변경 - 메서드 및 클래스의 이름을 다른 기능들과 구별이 쉽도록 변경 * refactor: 엔티티명 수정 - ParticipantInfo에서 Participant로 이름 변경 - 이름 변경에 따라 쿼리문 및 테스트 코드 변경 * refactor: CertificationProvider를 통해 Service layer 분리 - 기존의 CertificationService의 일부 기능을 CertificationProvider로 분리 - 관련 테스트 코드 작성 필요 * feat: 인증 관련 예외 사항 추가 처리 - 인증 시도 시, 해당 날짜가 진행 중인 챌린지인 경우에만 가능하도록 추가 처리 - 함수의 매개변수로 전달 시 필요한 정보만 전달하도록 변경 - 관련 테스트 코드 추가 * refactor: Module service에 의존하도록 설정 - InstanceDetailService에서 Module service인 ParticipantProvider에만 의존하도록 설정 * chore: 코드 정렬 * fix: 인증 정보 갱신 버그 픽스 - 인증 정보가 있음에도 불구하고 갱신 요청 시, 객체가 하나 더 저장되는 버그 픽스 * refactor: EncryptUtil의 메서드명을 일반적으로 변경 - EncryptUtil의 메서드명을 재사용하기 쉽도록 일반적인 이름으로 변경 * feat: 참여 요청 시, Github 연결 재확인 로직 추가 - 챌린지 참여 요청 시, Github repository 연결을 확인하는 로직 추가 * refactor: PR을 검증하는 메서드 추출 - PR 리스트를 받아서 controller에서 조건문을 통해 확인하던 로직을 service 내에 메서드로 추출 * refactor: 인증 상세 페이지 API 반환 값 변경 - repository의 위치가 변경됨에 따라 API 반환 값 변경 - githubProvider에 Github API에 요청할 repository full name을 반환하는 메서드 추가 * refactor: module service로 변경 - Instance 엔티티에 대해 module service(InstanceProvider)추가 * fix: 챌린지 상세 정보 조회 시, 제목 데이터가 포함되지 않은 버그 픽스 * fix: 인증 요청을 다시 했을 때 인증 정보가 업데이트되지 않는 버그 픽스 - 이전에 인증을 한 차례 진행하고, 다시 인증을 요청했을 때 DB에 저장하는 정보가 업데이트되지 않는 버그 픽스 - 테스트 코드 추가 작성 필요 * refactor: 인증을 갱신하는 로직에 대해 리팩토링 - CertificationService의 인증을 갱신하는 로직에 대해 리팩토링 진행 - 메서드로 따로 추출 - 테스트 코드 추가 * fix: 챌린지 참여 요청을 했을 때, 참여 상태가 변하지 않던 버그 픽스 - 특정 챌린지에 대해 참여 요청을 했을 때, 해당 챌린지의 참여 상태가 별하지 않는 버그 픽스 * feat: 챌린지 상세 정보 조회 시, 파일 데이터 추가 - 인증 상세 페이지에서 챌린지 상세 정보 조회 시, 전달하는 데이터에 파일 데이터 추가 * feat: 진행 중 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 진행 중인 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * fix: 챌린지 참여 조건 확인 버그 픽스 - 사용자가 챌린지에 참여할 수 있는 조건인지 확인하는 과정에서 발생하는 버그 픽스 - 조건이 잘못되어 있어 수정 * feat: 시작 전 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 시작 전인 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * feat: 완료 챌린지 리스트 조회 API 구현 - 사용자가 참여한 챌린지들 중, 완료된 챌린지들에 대한 정보를 받아오는 API 구현 - 테스트 코드 작성 필요 * refactor: 챌린지 상세 정보 조회 응답 데이터 변경 * feat: 참여자 전체의 주간 인증 현황 조회 API 개발 - 특정 챌린지(인스턴스)에 참여한 참여자 전체의 주간 인증 현황 조회 API 개발 - 테스트 코드 작성 필요 - 리팩토링 필요 * refactor: DTO의 이름 변경 - DTO의 이름을 보다 쓰임에 맞는 이름으로 변경 * refactor: 코드 리팩토링 - 메서드 분리 진행 * feat: 사용자의 아이템 보유 개수 필드 추가 * feat: 인증 패스 아이템 API 개발 - 인증 패스 아이템 API 개발 - 리팩토링 필요 - 테스트 코드 작성 필요 * refactor: Participant의 패키지명 변경 - Participant의 패키지명을 ParticipantInfo에서 Participant로 변경 * feat: 완료된 챌린지에서 보상 수령 여부에 따른 응답 데이터 변경 - 마이챌린지의 완료 챌린지 목록에서, 챌린지 성공 보상 수령 여부에 따른 응답 데이터 변경 로직 추가 * feat: 완료 챌린지 응답 데이터에 아이템 개수 추가 - 완료 챌린지 리스트의 개별 응답 데이터에 '포인트 2배 수령 아이템'의 개수 정보 추가 * feat: 완료 챌린지 - 포인트 수령 API 개발 - 마이 챌린지의 완료 챌린지들 중, 챌린지에 성공했으나 포인트를 아직 수령하지 않은 챌린지에 대해 포인트를 수령하는 API 개발 - 아이템을 가지고 있는 경우, 포인트 2배 수령이 가능하도록 설정 * fix: 챌린지 참여 조건 버그 픽스 * refactor: 챌린지 시작 d-day 구하는 메서드 분리 * fix: 챌린지 참여 및 보유 아이템 정보 조회 시 버그 픽스 * refactor: 완료 챌린지의 인증 현황 반환 데이터 변경 - 마이 챌린지 - 완료 탭에서의 인증 현황 반환 데이터를 실제 서비스에서 보여줄 문자열로 변경 * refactor: 인증 패스 조건 검증 메서드 분리 - 인증 패스 아이템 사용이 가능한지 확인하는 메서드를 따로 분리 (조건문 수정) - 인증 관련 테스트 코드 추가 (CertificationProvider, CertificationService, GithubProvider) * refactor: UserItem 엔티티의 컬럼 변경 및 테스트 코드 추가 * test: 마이 챌린지 서비스 테스트 코드 작성 * refactor: 챌린지 상세 정보 조회 응답 데이터 변경 - 챌린지 상세 정보 조회 시, 시작 날짜, 끝 날짜를 따로 보내도록 변경 - FileResponse의 정적 팩토리 메서드 리팩토링 * refactor: 전체 주간 인증 현황 조회 시, 본인의 데이터 제외하도록 변경 - 인증 상세 페이지에서 전체 사용자들에 대한 주간 인증 현황 조회 시, 본인의 데이터는 제외하도록 변경 - 로직 변경에 따라 테스트 코드 변경 * refactor: 주간 인증 현황 조회 시, 사용자의 정보도 함께 전달 - 사용자가 본인의 주간 인증 현황 조회 시, 이후의 API 요청을 위해 사용자의 정보도 함께 전달하도록 변경 - 인증 정보가 저장되어 있지 않을 때, 날짜 정보까지 모두 null로 전달되던 버그 픽스 * feat: 주간 인증 현황 조회 시, 사용자 정보도 함께 반환 - 본인의 주간 인증 현황 조회 & 모든 사용자의 주간 인증 현황 조회 시, 사용자 정보(userId, nickname, file) 또한 포함하도록 변경 * feat: 전체 인증 현황 조회 응답 데이터 추가 - 전체 인증 현황 조회 시, 응답 데이터에 총 인증 횟수 데이터 추가 - 관련 테스트 코드 추가 * refactor: 전체 인증 현황 조회 시 query string 데이터 변경 - 전체 인증 현황 조회 시, 기존에 사용자의 identifier를 전달하는 것에서 userId로 전달하도록 변경 * feat: github token 등록 여부 확인 API 개발 * fix: Progress에 상관없이 인증 현황이 잘못 전달되던 버그 픽스 - 인스턴스의 Progress에 상관없이 성공/실패/남은 인증 개수가 잘못 전달되던 버그 픽스 - provider의 메서드 명을 명료하게 변경 - 관련 테스트 코드 변경 및 추가 작성 * feat: 조건에 충족하는 경우 Progress를 ACTIVITY로 변경하도록 구현 - 인스턴스의 시작일자가 현재일자보다 이전이고, 종료일자가 현재일자보다 이후라면 해당 인스턴스를 ACTIVITY로 변경하는 로직 추가 - 관련 테스트 코드 작성 * feat: 조건에 충족하는 경우 Progress를 DONE으로 변경 - 시작일자/완료일자 모두 현재 일자보다 이전 일자라면 Progress를 DONE으로 변경 - 테스트 코드 작성 필요 * fix: 완료 챌린지로 전환하는 조건 버그 픽스 - 완료 챌린지로 전환하는 조건 버그 픽스 - 메서드명을 통일성있게 변경 - 관련 테스트 코드 작성 * feat: 인스턴스 생성 시 시작/완료 일자 검증 로직 추가 - 인스턴스 생성 API 요청 시, 시작/완료 일자가 현재 일자보다 이후의 일자인지 검증하는 로직 추가 - 관련 테스트 코드 추가 * feat: 인스턴스 상세 조회 시, 인스턴스의 상태 데이터 전달 - 인스턴스 상세 조회 시, 인스턴스의 상태 데이터(Progress)도 전달하도록 변경 - 관련 테스트 코드 추가 --- .../dto/CertificationResponse.java | 1 + .../controller/InstanceController.java | 3 +- .../controller/ProgressUpdateController.java | 30 +++ .../challenge/instance/domain/Instance.java | 15 ++ .../instance/dto/detail/InstanceResponse.java | 5 +- .../repository/InstanceRepository.java | 5 +- .../instance/service/InstanceHomeService.java | 2 +- .../instance/service/InstanceProvider.java | 6 + .../instance/service/InstanceService.java | 27 +-- .../instance/service/ProgressUpdater.java | 84 ++++++++ .../participant/domain/Participant.java | 4 + .../challenge/user/service/UserService.java | 2 +- .../global/util/exception/ErrorCode.java | 1 + .../repository/InstanceRepositoryTest.java | 4 +- .../InstanceSearchRepositoryTest.java | 43 +++- .../service/InstanceDetailServiceTest.java | 1 + .../service/InstanceSearchServiceTest.java | 3 +- .../instance/service/InstanceServiceTest.java | 10 +- .../instance/service/ProgressUpdaterTest.java | 191 ++++++++++++++++++ 19 files changed, 411 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/instance/controller/ProgressUpdateController.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java create mode 100644 src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java index 84aa60de..22c2acf0 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java @@ -21,6 +21,7 @@ public record CertificationResponse( List prLinks ) { + public static CertificationResponse createNonExist(int currentAttempt, LocalDate certificatedAt) { return CertificationResponse.builder() .certificationId(0L) diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index 6b9e550d..ff46756e 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -9,6 +9,7 @@ import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; +import java.time.LocalDate; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -74,7 +75,7 @@ public ResponseEntity createInstance( @RequestPart(value = "data") InstanceCreateRequest instanceCreateRequest, @RequestPart(value = "files", required = false) MultipartFile multipartFile, @RequestPart(value = "type", required = false) String type) { - instanceService.createInstance(instanceCreateRequest, multipartFile, type); + instanceService.createInstance(instanceCreateRequest, multipartFile, type, LocalDate.now()); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.CREATED.getStatus(), SuccessCode.CREATED.getMessage()) ); diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/ProgressUpdateController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/ProgressUpdateController.java new file mode 100644 index 00000000..ebe34d65 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/ProgressUpdateController.java @@ -0,0 +1,30 @@ +package com.genius.gitget.challenge.instance.controller; + +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.challenge.instance.service.ProgressUpdater; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class ProgressUpdateController { + private final ProgressUpdater progressUpdater; + + @GetMapping("/challenges/update") + public ResponseEntity updateProgress() { + LocalDate currentDate = LocalDate.now(); + progressUpdater.updateToActivity(currentDate); + progressUpdater.updateToDone(currentDate); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 4d480fba..36d3194b 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -3,6 +3,7 @@ import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.Files; @@ -95,6 +96,20 @@ public Instance(String title, String description, String tags, int pointPerPerso this.completedDate = completedDate; } + public static Instance createByRequest(InstanceCreateRequest instanceCreateRequest) { + return Instance.builder() + .title(instanceCreateRequest.title()) + .tags(instanceCreateRequest.tags()) + .description(instanceCreateRequest.description()) + .pointPerPerson(instanceCreateRequest.pointPerPerson()) + .notice(instanceCreateRequest.notice()) + .startedDate(instanceCreateRequest.startedAt()) + .completedDate(instanceCreateRequest.completedAt()) + .certificationMethod(instanceCreateRequest.certificationMethod()) + .progress(Progress.PREACTIVITY) + .build(); + } + //== 비지니스 로직 ==// public void updateInstance(String description, String notice, int pointPerPerson, LocalDateTime startedDate, LocalDateTime completedDate, String certificationMethod) { diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java index e15cf80a..af5fcfa4 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java @@ -2,6 +2,7 @@ import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.global.file.dto.FileResponse; import java.time.LocalDate; @@ -10,6 +11,7 @@ @Builder public record InstanceResponse( Long instanceId, + Progress progress, String title, int remainDays, LocalDate startedDate, @@ -28,8 +30,9 @@ public static InstanceResponse createByEntity(Instance instance, JoinStatus join LocalDate startedLocalDate = instance.getStartedDate().toLocalDate(); LocalDate completedLocalDate = instance.getCompletedDate().toLocalDate(); return InstanceResponse.builder() - .title(instance.getTitle()) .instanceId(instance.getId()) + .progress(instance.getProgress()) + .title(instance.getTitle()) .remainDays(DateUtil.getRemainDaysToStart(startedLocalDate, LocalDate.now())) .startedDate(startedLocalDate) .completedDate(completedLocalDate) diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java index e15e8244..d3500c3a 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java @@ -22,5 +22,8 @@ public interface InstanceRepository extends JpaRepository { Slice findRecommendations(@Param("userTags") List userTags, Progress progress, Pageable pageable); @Query("select i from Instance i where i.progress = :progress") - Slice findInstanceByCondition(@Param("progress") Progress progress, Pageable pageable); + Slice findPagesByProgress(@Param("progress") Progress progress, Pageable pageable); + + @Query("select i from Instance i where i.progress = :progress") + List findAllByProgress(@Param("progress") Progress progress); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java index 7e942fb1..a5694c07 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java @@ -35,7 +35,7 @@ public Slice getRecommendations(User user, Pageable pageab public Slice getInstancesByCondition(Pageable pageable) { - Slice instances = instanceRepository.findInstanceByCondition(PREACTIVITY, pageable); + Slice instances = instanceRepository.findPagesByProgress(PREACTIVITY, pageable); return instances.map(this::mapToHomeInstanceResponse); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java index 0d87fc42..3992c34e 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java @@ -3,8 +3,10 @@ import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.global.util.exception.BusinessException; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,4 +21,8 @@ public Instance findById(Long instanceId) { return instanceRepository.findById(instanceId) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); } + + public List findAllByProgress(Progress progress) { + return instanceRepository.findAllByProgress(progress); + } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index c75c5b04..c53c9c69 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -1,12 +1,12 @@ package com.genius.gitget.challenge.instance.service; import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_INSTANCE_DATE; import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; @@ -17,6 +17,7 @@ import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import java.io.IOException; +import java.time.LocalDate; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -36,24 +37,15 @@ public class InstanceService { // 인스턴스 생성 @Transactional public Long createInstance(InstanceCreateRequest instanceCreateRequest, - MultipartFile multipartFile, String type) { + MultipartFile multipartFile, String type, LocalDate currentDate) { Topic topic = topicRepository.findById(instanceCreateRequest.topicId()) .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); Files uploadedFile = filesService.uploadFile(topic.getFiles(), multipartFile, type); - Instance instance = Instance.builder() - .title(instanceCreateRequest.title()) - .tags(instanceCreateRequest.tags()) - .description(instanceCreateRequest.description()) - .pointPerPerson(instanceCreateRequest.pointPerPerson()) - .notice(instanceCreateRequest.notice()) - .startedDate(instanceCreateRequest.startedAt()) - .completedDate(instanceCreateRequest.completedAt()) - .certificationMethod(instanceCreateRequest.certificationMethod()) - .progress(Progress.PREACTIVITY) - .build(); + validatePeriod(instanceCreateRequest, currentDate); + Instance instance = Instance.createByRequest(instanceCreateRequest); instance.setTopic(topic); instance.setFiles(uploadedFile); @@ -62,6 +54,15 @@ public Long createInstance(InstanceCreateRequest instanceCreateRequest, return savedInstance.getId(); } + private void validatePeriod(InstanceCreateRequest instanceCreateRequest, LocalDate currentDate) { + LocalDate startedDate = instanceCreateRequest.startedAt().toLocalDate(); + LocalDate completedDate = instanceCreateRequest.completedAt().toLocalDate(); + + if (currentDate.isAfter(startedDate) || currentDate.isAfter(completedDate)) { + throw new BusinessException(INVALID_INSTANCE_DATE); + } + } + // 인스턴스 리스트 조회 public Page getAllInstances(Pageable pageable) { Page instances = instanceRepository.findAllById(pageable); diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java b/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java new file mode 100644 index 00000000..2c75904a --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java @@ -0,0 +1,84 @@ +package com.genius.gitget.challenge.instance.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; + +import com.genius.gitget.challenge.certification.service.CertificationProvider; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.Participant; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ProgressUpdater { + private final InstanceProvider instanceProvider; + private final CertificationProvider certificationProvider; + private final double SUCCESS_THRESHOLD = 85; + + @Transactional + public void updateToActivity(LocalDate currentDate) { + List preActivities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + for (Instance preActivity : preActivities) { + LocalDate startedDate = preActivity.getStartedDate().toLocalDate(); + LocalDate completedDate = preActivity.getCompletedDate().toLocalDate(); + + if (currentDate.isAfter(startedDate) && currentDate.isBefore(completedDate)) { + preActivity.updateProgress(Progress.ACTIVITY); + } + } + } + + @Transactional + public void updateToDone(LocalDate currentDate) { + List instances = new ArrayList<>(); + instances.addAll(instanceProvider.findAllByProgress(Progress.PREACTIVITY)); + instances.addAll(instanceProvider.findAllByProgress(Progress.ACTIVITY)); + + for (Instance instance : instances) { + LocalDate startedDate = instance.getStartedDate().toLocalDate(); + LocalDate completedDate = instance.getCompletedDate().toLocalDate(); + + if (currentDate.isAfter(startedDate) && currentDate.isAfter(completedDate)) { + updateParticipants(instance, currentDate); + } + } + } + + private void updateParticipants(Instance instance, LocalDate currentDate) { + instance.updateProgress(Progress.DONE); + for (Participant participant : instance.getParticipantList()) { + int totalAttempt = instance.getTotalAttempt(); + int successAttempt = getSuccessAttempt(participant.getId(), currentDate); + + JoinResult joinResult = getJoinResult(totalAttempt, successAttempt); + participant.updateJoinResult(joinResult); + } + } + + private JoinResult getJoinResult(int totalAttempt, int successAttempt) { + double successPercent = getSuccessPercent(successAttempt, totalAttempt); + if (successPercent >= SUCCESS_THRESHOLD) { + return JoinResult.SUCCESS; + } + return JoinResult.FAIL; + } + + private int getSuccessAttempt(Long participantId, LocalDate currentDate) { + int certificated = certificationProvider.countByStatus(participantId, CERTIFICATED, currentDate); + int passed = certificationProvider.countByStatus(participantId, PASSED, currentDate); + return certificated + passed; + } + + private double getSuccessPercent(int successCount, int totalCount) { + double successPercent = (double) successCount / (double) totalCount * 100; + return Math.round(successPercent * 100 / 100.0); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java index 2d63a9a7..f98124c7 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java @@ -97,6 +97,10 @@ public void getRewards(int rewardPoints) { this.rewardPoints = rewardPoints; } + public void updateJoinResult(JoinResult joinResult) { + this.joinResult = joinResult; + } + public void updateRepository(String repository) { this.repositoryName = repository; } diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 4c8eeed1..bbf6367a 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -69,7 +69,7 @@ public String getGithubToken(User user) { } return encryptUtil.decrypt(githubToken); } - + public void isAlreadyRegistered(User user) { if (user.getRole() != Role.NOT_REGISTERED) { throw new BusinessException(ALREADY_REGISTERED); diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 95c4dfdc..a090ad48 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -18,6 +18,7 @@ public enum ErrorCode { TOPIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 토픽을 찾을 수 없습니다."), TOPIC_HAVE_INSTANCE(HttpStatus.BAD_REQUEST, "해당 토픽은 인스턴스를 가지고 있으므로 삭제할 수 없습니다."), + INVALID_INSTANCE_DATE(HttpStatus.BAD_REQUEST, "인스턴스 생성/종료 일자는 현재 일자 이후여야 합니다."), INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), PARTICIPANT_INFO_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 참여 정보를 찾을 수 없습니다."), CERTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인증 정보를 찾을 수 없습니다."), diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java index 9668ef45..d9f78d89 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -167,7 +167,7 @@ public void should_returnInstances_orderByStartedDate() { getSavedInstance("title1", "BE", 10); getSavedInstance("title2", "BE", 3); getSavedInstance("title3", "BE", 20); - Slice instances = instanceRepository.findInstanceByCondition(Progress.PREACTIVITY, pageRequest); + Slice instances = instanceRepository.findPagesByProgress(Progress.PREACTIVITY, pageRequest); //then assertThat(instances.getContent().size()).isEqualTo(3); @@ -194,7 +194,7 @@ public void should_returnInstances_orderByParticipantCnt() { getSavedInstance("title1", "BE", 10); getSavedInstance("title2", "BE", 3); getSavedInstance("title3", "BE", 20); - Slice instances = instanceRepository.findInstanceByCondition(Progress.PREACTIVITY, pageRequest); + Slice instances = instanceRepository.findPagesByProgress(Progress.PREACTIVITY, pageRequest); //then assertThat(instances.getContent().size()).isEqualTo(3); diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index 77085116..89272f0f 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -1,5 +1,7 @@ package com.genius.gitget.challenge.instance.repository; +import static com.genius.gitget.challenge.instance.domain.Progress.ACTIVITY; +import static com.genius.gitget.challenge.instance.domain.Progress.DONE; import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; import com.genius.gitget.admin.topic.domain.Topic; @@ -103,6 +105,18 @@ public void setup() throws IOException { } + @Test + public void 검색_조건_없이_테스트() throws Exception { + for (int i = 0; i < 5; i++) { + PageRequest pageRequest = PageRequest.of(i, 2); + Page result = searchRepository.search(null, null, pageRequest); + for (InstanceSearchResponse instanceSearchResponse : result) { + System.out.println("instanceSearchResponse = " + instanceSearchResponse.getInstanceId()); + } + System.out.println("========== " + i + 1 + " 번째 끝 ========="); + } + } + @Test public void 챌린지_제목으로_검색_테스트() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); @@ -130,6 +144,31 @@ public void setup() throws IOException { Assertions.assertThat(cnt).isEqualTo(7); } + @Test + public void 챌린지_현황으로_검색_테스트2() throws Exception { + PageRequest pageRequest = PageRequest.of(0, 10); + Page result = searchRepository.search(DONE, null, pageRequest); + int cnt = 0; + for (InstanceSearchResponse instanceSearchResponse : result) { + if (instanceSearchResponse != null) { + cnt++; + } + } + Assertions.assertThat(cnt).isEqualTo(1); + } + + @Test + public void 챌린지_현황으로_검색_테스트3() throws Exception { + PageRequest pageRequest = PageRequest.of(0, 10); + Page result = searchRepository.search(ACTIVITY, null, pageRequest); + int cnt = 0; + for (InstanceSearchResponse instanceSearchResponse : result) { + if (instanceSearchResponse != null) { + cnt++; + } + } + Assertions.assertThat(cnt).isEqualTo(0); + } @Test public void 챌린지_현황과_챌린지_제목으로_검색_테스트() throws Exception { @@ -151,12 +190,12 @@ private void createInstance(Topic savedTopic, Instance instance, String title) { .topicId(savedTopic.getId()) .title(title) .tags(instance.getTags()) - .description(instance.getDescription()) .notice(instance.getNotice()) .pointPerPerson(instance.getPointPerPerson()) .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()).build(), - FileTestUtil.getMultipartFile("name"), "instance"); + FileTestUtil.getMultipartFile("name"), "instance", + instance.getStartedDate().minusDays(3).toLocalDate()); } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 44619b96..860b6551 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -244,6 +244,7 @@ public void should_returnValues_when_joinedInstance() { //then assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); + assertThat(instanceResponse.progress()).isEqualTo(Progress.PREACTIVITY); assertThat(instanceResponse.remainDays()).isEqualTo(2); assertThat(instanceResponse.participantCount()).isEqualTo(1); assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java index 4e07b142..cfb6b883 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java @@ -91,6 +91,7 @@ private void createInstance(Topic savedTopic, Instance instance, String title) t .pointPerPerson(instance.getPointPerPerson()) .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()).build(), - FileTestUtil.getMultipartFile("name"), "instance"); + FileTestUtil.getMultipartFile("name"), "instance", + instance.getCompletedDate().minusDays(3).toLocalDate()); } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java index 4e0e8d5f..e72454b6 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java @@ -10,6 +10,7 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.util.file.FileTestUtil; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Optional; import org.assertj.core.api.Assertions; @@ -60,12 +61,13 @@ public void setup() { @Test public void 인스턴스_생성() throws Exception { //given + LocalDate currentDate = instance.getCompletedDate().minusDays(3).toLocalDate(); Topic savedTopic = topicRepository.save(topic); InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); //when Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, - FileTestUtil.getMultipartFile("name"), fileType); + FileTestUtil.getMultipartFile("name"), fileType, currentDate); //then Optional byId = instanceRepository.findById(savedInstanceId); @@ -75,11 +77,12 @@ public void setup() { @Test public void 인스턴스_수정() throws Exception { //given + LocalDate currentDate = instance.getCompletedDate().minusDays(3).toLocalDate(); Topic savedTopic = topicRepository.save(topic); InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, - FileTestUtil.getMultipartFile("name"), fileType); + FileTestUtil.getMultipartFile("name"), fileType, currentDate); InstanceUpdateRequest instanceUpdateRequest = InstanceUpdateRequest.builder() .topicId(savedTopic.getId()) @@ -101,11 +104,12 @@ public void setup() { @Test public void 인스턴스_단건_조회() throws Exception { //given + LocalDate currentDate = instance.getCompletedDate().minusDays(3).toLocalDate(); Topic savedTopic = topicRepository.save(topic); InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, - FileTestUtil.getMultipartFile("name"), fileType); + FileTestUtil.getMultipartFile("name"), fileType, currentDate); //when InstanceDetailResponse instanceById = instanceService.getInstanceById(savedInstanceId); diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java new file mode 100644 index 00000000..5a8a6568 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java @@ -0,0 +1,191 @@ +package com.genius.gitget.challenge.instance.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import java.time.LocalDate; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class ProgressUpdaterTest { + @Autowired + private ProgressUpdater progressUpdater; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private UserRepository userRepository; + @Autowired + private CertificationRepository certificationRepository; + @Autowired + private InstanceProvider instanceProvider; + + @Test + @DisplayName("PRE_ACTIVITY 인스턴스들 중, 특정 조건에 해당하는 인스턴스들을 ACTIVITY로 상태를 바꿀 수 있다.") + public void should_updateToActivity_when_conditionMatches() { + //given + LocalDate startedDate = LocalDate.of(2024, 3, 1); + LocalDate completedDate = LocalDate.of(2024, 3, 30); + LocalDate currentDate = LocalDate.of(2024, 3, 6); + + getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + + //when + List preActivities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + progressUpdater.updateToActivity(currentDate); + List activities = instanceProvider.findAllByProgress(Progress.ACTIVITY); + + //then + assertThat(preActivities.size()).isEqualTo(3); + assertThat(activities.size()).isEqualTo(3); + } + + @Test + @DisplayName("ACTIVITY 인스턴스들 중, 특정 조건에 해당하는 인스턴스들을 DONE 상태로 바꿀 수 있다.") + public void should_updateToDone_when_conditionMatches() { + //given + LocalDate startedDate = LocalDate.of(2024, 3, 1); + LocalDate completedDate = LocalDate.of(2024, 3, 30); + LocalDate currentDate = LocalDate.of(2024, 4, 1); + + getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + + //when + List activities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + progressUpdater.updateToDone(currentDate); + List done = instanceProvider.findAllByProgress(Progress.DONE); + + //then + assertThat(activities.size()).isEqualTo(3); + assertThat(done.size()).isEqualTo(3); + } + + @Test + @DisplayName("DONE으로 상태를 바꾸면서 성공률이 85.5% 이상이라면 JoinResult를 SUCCESS로 변경한다.") + public void should_updateToSuccess_then_rateOverThreshold() { + //given + LocalDate startedDate = LocalDate.of(2024, 3, 1); + LocalDate completedDate = LocalDate.of(2024, 3, 2); + LocalDate currentDate = LocalDate.of(2024, 4, 1); + + Instance instance = getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + + Participant participant1 = getSavedParticipant(getSavedUser("nickname1"), instance); + + getSavedCertification(CertificateStatus.CERTIFICATED, startedDate, participant1); + getSavedCertification(CertificateStatus.PASSED, completedDate, participant1); + + //when + progressUpdater.updateToDone(currentDate); + + //then + List done = instanceProvider.findAllByProgress(Progress.DONE); + assertThat(done.size()).isEqualTo(3); + assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.SUCCESS); + } + + @Test + @DisplayName("DONE으로 상태를 바꾸면서 성공률이 85% 이하라면 JoinResult를 FAIL로 변경한다.") + public void should_updateToFail_when_rateUnderThreshold() { + //given + LocalDate startedDate = LocalDate.of(2024, 3, 1); + LocalDate completedDate = LocalDate.of(2024, 3, 2); + LocalDate currentDate = LocalDate.of(2024, 4, 1); + + Instance instance = getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + + Participant participant1 = getSavedParticipant(getSavedUser("nickname1"), instance); + + getSavedCertification(CertificateStatus.NOT_YET, startedDate, participant1); + getSavedCertification(CertificateStatus.PASSED, completedDate, participant1); + + //when + progressUpdater.updateToDone(currentDate); + + //then + List done = instanceProvider.findAllByProgress(Progress.DONE); + assertThat(done.size()).isEqualTo(3); + assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.FAIL); + } + + private Instance getSavedInstance(LocalDate startedDate, LocalDate completedDate) { + return instanceRepository.save( + Instance.builder() + .title("title") + .progress(Progress.PREACTIVITY) + .pointPerPerson(100) + .startedDate(startedDate.atTime(0, 0)) + .completedDate(completedDate.atTime(0, 0)) + .build() + ); + } + + private Participant getSavedParticipant(User user, Instance instance) { + Participant participant = participantRepository.save( + Participant.builder() + .joinResult(JoinResult.PROCESSING) + .joinStatus(JoinStatus.YES) + .build() + ); + participant.setUserAndInstance(user, instance); + return participant; + } + + private User getSavedUser(String nickname) { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname(nickname) + .providerInfo(ProviderInfo.GITHUB) + .identifier("identifier") + .information("information") + .tags("BE,FE") + .build() + ); + } + + private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, + Participant participant) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); + Certification certification = Certification.builder() + .certificationStatus(status) + .currentAttempt(attempt) + .certificatedAt(certificatedAt) + .certificationLinks("certificationLink") + .build(); + certification.setParticipant(participant); + return certificationRepository.save(certification); + } + +} \ No newline at end of file From 943498c61673238b248076116ffa2ba95ebc70d9 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Sun, 10 Mar 2024 16:25:52 +0900 Subject: [PATCH 122/234] =?UTF-8?q?chore:=20item=20=EA=B4=80=EB=A0=A8=20sq?= =?UTF-8?q?l=20=EC=8B=A4=ED=96=89=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/import.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/resources/import.sql diff --git a/src/main/resources/import.sql b/src/main/resources/import.sql new file mode 100644 index 00000000..93667031 --- /dev/null +++ b/src/main/resources/import.sql @@ -0,0 +1,5 @@ +# 단독 실행 시 정상 작동하지만, ddl-auto를 create로 해놓고 실행하면 오류 발생 +INSERT INTO todoffin.item (cost, created_at, deleted_at, updated_at, details, name, item_category) +VALUES (100, NULL, NULL, NULL, '프로필 프레임', 'profile frame', 'PROFILE_FRAME'), + (100, NULL, NULL, NULL, '인증 패스 아이템', 'certification passer', 'CERTIFICATION_PASSER'), + (100, NULL, NULL, NULL, '포인트 2배 획득 아이템', 'point multiplier', 'POINT_MULTIPLIER'); From ac45c03eac0a2db0f83c52f7d16bf4eb11dc3e40 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 10 Mar 2024 21:50:04 +0900 Subject: [PATCH 123/234] =?UTF-8?q?[BUG/FIX]=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=ED=98=84=ED=99=A9=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C,=20=EA=B8=B0=EA=B0=84=EC=9D=B4=20=EC=A0=9C=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=84=EB=8B=AC=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=EA=B7=B8=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 전체 인증 현황 조회 시 기간이 맞지 않는 버그 픽스 - 전체 인증 현황 조회 시, 인스턴스 시작 일자부터 기간이 시작해야 하는데 해당 주만 전달하던 버그 픽스 - 인스턴스 시작 일자부터 현재 일자까지 전달하도록 변경 * refactor: 검증 및 예외 처리 메서드 추출 --- .../service/CertificationService.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index fded2460..6296f882 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -58,13 +58,14 @@ public class CertificationService { public List getWeekCertification(Long participantId, LocalDate currentDate) { LocalDate startDate = participantProvider.getInstanceStartDate(participantId); int curAttempt = DateUtil.getWeekAttempt(startDate, currentDate); + LocalDate weekStartDate = DateUtil.getWeekStartDate(currentDate); List certifications = certificationProvider.findByDuration( - DateUtil.getWeekStartDate(currentDate), + weekStartDate, currentDate, participantId); - return convertToCertificationResponse(certifications, curAttempt, currentDate); + return convertToCertificationResponse(certifications, curAttempt, weekStartDate); } public Slice getAllWeekCertification(Long userId, Long instanceId, @@ -78,15 +79,18 @@ public Slice getAllWeekCertification(Long userId, Long instanceId, private WeekResponse convertToWeekResponse(Participant participant, LocalDate currentDate) { User user = participant.getUser(); LocalDate startDate = participantProvider.getInstanceStartDate(participant.getId()); + LocalDate weekStartDate = DateUtil.getWeekStartDate(currentDate); List certifications = certificationProvider.findByDuration( - DateUtil.getWeekStartDate(currentDate), + weekStartDate, currentDate, participant.getId()); + List certificationResponses = convertToCertificationResponse( certifications, DateUtil.getWeekAttempt(startDate, currentDate), - currentDate); + weekStartDate); + FileResponse fileResponse = FileResponse.create(user.getFiles()); return WeekResponse.create(user, fileResponse, certificationResponses); @@ -102,8 +106,8 @@ public TotalResponse getTotalCertification(Long participantId, LocalDate current List certifications = certificationProvider.findByDuration( startDate, currentDate, participantId); - List certificationResponses = convertToCertificationResponse(certifications, curAttempt, - currentDate); + List certificationResponses = convertToCertificationResponse( + certifications, curAttempt, startDate); return TotalResponse.builder() .totalAttempts(totalAttempts) @@ -112,18 +116,19 @@ public TotalResponse getTotalCertification(Long participantId, LocalDate current } private List convertToCertificationResponse(List certifications, - int curAttempt, LocalDate currentDate) { + int curAttempt, LocalDate startedDate) { List result = new ArrayList<>(); Map certificationMap = convertToMap(certifications); - currentDate = DateUtil.getWeekStartDate(currentDate).minusDays(1); + + startedDate = startedDate.minusDays(1); for (int cur = 1; cur <= curAttempt; cur++) { - currentDate = currentDate.plusDays(1); + startedDate = startedDate.plusDays(1); if (certificationMap.containsKey(cur)) { result.add(CertificationResponse.createExist(certificationMap.get(cur))); continue; } - result.add(CertificationResponse.createNonExist(cur, currentDate)); + result.add(CertificationResponse.createNonExist(cur, startedDate)); } return result; @@ -160,7 +165,7 @@ public ActivatedResponse passCertification(Long userId, CertificationRequest cer Certification certification = Certification.createPassed(targetDate); certification.setParticipant(participant); certificationProvider.save(certification); - + return ActivatedResponse.create(instance, certification.getCertificationStatus(), 0, participant.getRepositoryName()); } @@ -181,9 +186,7 @@ public CertificationResponse updateCertification(User user, CertificationRequest Instance instance = instanceProvider.findById(certificationRequest.instanceId()); Participant participant = participantProvider.findByJoinInfo(user.getId(), instance.getId()); - if (!canCertificate(instance, certificationRequest.targetDate())) { - throw new BusinessException(CERTIFICATION_UNABLE); - } + validCertificationCondition(instance, certificationRequest.targetDate()); List pullRequests = getPullRequestLink( gitHub, @@ -204,11 +207,13 @@ private Certification createOrUpdate(Participant participant, LocalDate targetDa return certificationProvider.createCertification(participant, targetDate, pullRequests); } - private boolean canCertificate(Instance instance, LocalDate targetDate) { + private void validCertificationCondition(Instance instance, LocalDate targetDate) { boolean isValidPeriod = targetDate.isAfter(instance.getStartedDate().toLocalDate()) && targetDate.isBefore(instance.getCompletedDate().toLocalDate()); - return ((instance.getProgress() == Progress.ACTIVITY) && isValidPeriod); + if ((instance.getProgress() != Progress.ACTIVITY) || !isValidPeriod) { + throw new BusinessException(CERTIFICATION_UNABLE); + } } private List getPullRequestLink(GitHub gitHub, String repositoryName, LocalDate targetDate) { From 3b5a612f46e6e80151d69a08f4e187c89b7a4133 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 10 Mar 2024 23:42:33 +0900 Subject: [PATCH 124/234] =?UTF-8?q?fix:=20=EC=A3=BC=EA=B0=84=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=ED=98=84=ED=99=A9=20=EC=A1=B0=ED=9A=8C=20=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인스턴스(챌린지)의 시작일자가 월요일이 아니고, 첫째주 주간 인증 현황 API를 요청하는 경우 해당 주의 첫째날(월요일)이 아닌 시작일자부터 기간이 시작해야 하는데, 월요일부터 시작하는 버그 발생 - 첫째주이며 시작일자가 월요일이 아닌 경우, 시작일자를 반환하도록 하여 버그 픽스 --- .../service/CertificationService.java | 8 +++----- .../challenge/certification/util/DateUtil.java | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 6296f882..16213733 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -58,7 +58,7 @@ public class CertificationService { public List getWeekCertification(Long participantId, LocalDate currentDate) { LocalDate startDate = participantProvider.getInstanceStartDate(participantId); int curAttempt = DateUtil.getWeekAttempt(startDate, currentDate); - LocalDate weekStartDate = DateUtil.getWeekStartDate(currentDate); + LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate); List certifications = certificationProvider.findByDuration( weekStartDate, @@ -79,12 +79,10 @@ public Slice getAllWeekCertification(Long userId, Long instanceId, private WeekResponse convertToWeekResponse(Participant participant, LocalDate currentDate) { User user = participant.getUser(); LocalDate startDate = participantProvider.getInstanceStartDate(participant.getId()); - LocalDate weekStartDate = DateUtil.getWeekStartDate(currentDate); + LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate); List certifications = certificationProvider.findByDuration( - weekStartDate, - currentDate, - participant.getId()); + weekStartDate, currentDate, participant.getId()); List certificationResponses = convertToCertificationResponse( certifications, diff --git a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java index 09f99641..ed109dba 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java +++ b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java @@ -23,14 +23,18 @@ public static int getWeekAttempt(LocalDate challengeStartDate, LocalDate targetD int weekAttempt = targetDate.getDayOfWeek().ordinal() + 1; int totalAttempt = getAttemptCount(challengeStartDate, targetDate); - if ((challengeStartDate.getDayOfWeek() != DayOfWeek.MONDAY) && (totalAttempt < 8)) { + if (isNotStartWithMonday(challengeStartDate, targetDate)) { return totalAttempt; } return weekAttempt; } - public static LocalDate getWeekStartDate(LocalDate currentDate) { + public static LocalDate getWeekStartDate(LocalDate challengeStartDate, LocalDate currentDate) { + if (isNotStartWithMonday(challengeStartDate, currentDate)) { + return challengeStartDate; + } + return currentDate.minusDays(currentDate.getDayOfWeek().ordinal()); } @@ -40,5 +44,14 @@ public static LocalDate convertToLocalDate(Date date) { ZoneId.of("Asia/Seoul") ); } + + private static boolean isNotStartWithMonday(LocalDate challengeStartDate, LocalDate currentDate) { + int totalAttempt = getAttemptCount(challengeStartDate, currentDate); + // 첫째주이고 && 시작일이 월요일이 아닐 때 + if ((challengeStartDate.getDayOfWeek() != DayOfWeek.MONDAY) && (totalAttempt < 8)) { + return true; + } + return false; + } } From bf34a8499390aa9019499a13f52d8118235109ee Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 11 Mar 2024 01:21:10 +0900 Subject: [PATCH 125/234] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=20=ED=8C=A8?= =?UTF-8?q?=EC=8A=A4=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=8B=9C,=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EA=B0=9C=EC=88=98?= =?UTF-8?q?=EA=B0=80=20=EC=A4=84=EC=96=B4=EB=93=A4=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=95=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20?= =?UTF-8?q?(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 인증 패스 아이템 사용 시, 아이템 개수가 줄어들지 않았던 버그 픽스 * fix: 인증 패스 사용 가능 조건 로직 보강 - 인증 패스를 사용할 수 있는 조건에 인스턴스의 진행 상황이 ACTIVITY이어야 하는데, 해당 부분이 빠져있어 로직 보강 진행 - 관련 테스트 코드 보강 --- .../certification/service/CertificationService.java | 13 +++++++++---- .../challenge/myChallenge/dto/RewardRequest.java | 2 +- .../myChallenge/service/MyChallengeService.java | 2 +- .../gitget/global/util/exception/ErrorCode.java | 3 ++- .../service/CertificationServiceTest.java | 11 +++++++++-- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 16213733..949001a3 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -3,7 +3,6 @@ import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; -import static com.genius.gitget.global.util.exception.ErrorCode.CERTIFICATION_UNABLE; import com.genius.gitget.challenge.certification.domain.Certification; import com.genius.gitget.challenge.certification.dto.CertificationInformation; @@ -150,8 +149,11 @@ public ActivatedResponse passCertification(Long userId, CertificationRequest cer UserItem userItem = userItemProvider.findUserItemByUser(userId, ItemCategory.CERTIFICATION_PASSER); Optional optional = certificationProvider.findByDate(targetDate, participant.getId()); + validCertificationCondition(instance, targetDate); validatePassCondition(userItem, optional); + userItem.useItem(); + //TODO: 리팩토링 시급... if (optional.isPresent()) { Certification certification = optional.get(); @@ -206,11 +208,14 @@ private Certification createOrUpdate(Participant participant, LocalDate targetDa } private void validCertificationCondition(Instance instance, LocalDate targetDate) { + if (instance.getProgress() != Progress.ACTIVITY) { + throw new BusinessException(ErrorCode.NOT_ACTIVITY_INSTANCE); + } + boolean isValidPeriod = targetDate.isAfter(instance.getStartedDate().toLocalDate()) && targetDate.isBefore(instance.getCompletedDate().toLocalDate()); - - if ((instance.getProgress() != Progress.ACTIVITY) || !isValidPeriod) { - throw new BusinessException(CERTIFICATION_UNABLE); + if (!isValidPeriod) { + throw new BusinessException(ErrorCode.NOT_CERTIFICATE_PERIOD); } } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java index 0d72cb8b..3bba8175 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java @@ -6,7 +6,7 @@ public record RewardRequest( User user, Long instanceId, - Boolean useItem, + Boolean canUseItem, LocalDate targetDate ) { } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index 8cc013f4..acd69799 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -136,7 +136,7 @@ public DoneResponse getRewards(RewardRequest rewardRequest) { int pointPerPerson = instance.getPointPerPerson(); int rewardPoints = pointPerPerson; - if (rewardRequest.useItem()) { + if (rewardRequest.canUseItem()) { UserItem userItem = userItemProvider.findUserItemByUser(user.getId(), ItemCategory.POINT_MULTIPLIER); userItem.useItem(); rewardPoints = pointPerPerson * 2; diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index a090ad48..bd3aaa3e 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -54,7 +54,8 @@ public enum ErrorCode { CAN_NOT_JOIN_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스에 참여할 수 없습니다."), CAN_NOT_QUIT_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스의 참여를 취소할 수 없습니다."), - CERTIFICATION_UNABLE(HttpStatus.BAD_REQUEST, "해당 챌린지는 인증을 할 수 없는 상태입니다."), + NOT_ACTIVITY_INSTANCE(HttpStatus.BAD_REQUEST, "진행 중인 챌린지에 대해서만 인증이 가능합니다."), + NOT_CERTIFICATE_PERIOD(HttpStatus.BAD_REQUEST, "챌린지 인증은 챌린지 진행 기간 내에만 가능합니다. 챌린지 진행 기간인지 확인해주세요."), USER_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자의 아이템 보유 정보를 찾을 수 없습니다."), HAS_NO_ITEM(HttpStatus.NOT_FOUND, "해당 아이템을 보유하고 있지 않습니다."), diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index 484a93a2..b0f8f464 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -3,8 +3,8 @@ import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; -import static com.genius.gitget.global.util.exception.ErrorCode.CERTIFICATION_UNABLE; import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_CERTIFICATE_PERIOD; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -136,7 +136,7 @@ public void should_throwException_when_progressIsNotActivity() { //when && then assertThatThrownBy(() -> certificationService.updateCertification(user, certificationRequest)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(CERTIFICATION_UNABLE.getMessage()); + .hasMessageContaining(NOT_CERTIFICATE_PERIOD.getMessage()); } @Test @@ -402,6 +402,7 @@ public void should_passCertification_when_conditionIsValid() { .build(); //when + instance.updateProgress(Progress.ACTIVITY); getSavedCertification(NOT_YET, currentDate, participant); getSavedCertification(CERTIFICATED, currentDate.plusDays(1), participant); getSavedCertification(CERTIFICATED, currentDate.plusDays(4), participant); @@ -420,6 +421,7 @@ public void should_passCertification_when_conditionIsValid() { assertThat(activatedResponse.numOfPassItem()).isEqualTo(0); assertThat(activatedResponse.canUsePassItem()).isFalse(); assertThat(activatedResponse.fileResponse()).isNotNull(); + assertThat(userItem.getCount()).isEqualTo(0); } @Test @@ -458,6 +460,8 @@ public void should_throwException_when_outOfStock() { .targetDate(currentDate) .build(); + instance.updateProgress(Progress.ACTIVITY); + //when && then assertThatThrownBy(() -> certificationService.passCertification(instance.getId(), certificationRequest)) .isInstanceOf(BusinessException.class) @@ -480,6 +484,7 @@ public void should_throwException_when_challengeIsNotNOT_YET(CertificateStatus c .build(); //when + instance.updateProgress(Progress.ACTIVITY); getSavedCertification(certificateStatus, currentDate, participant); //then @@ -503,6 +508,7 @@ public void should_overwriteData_when_certificatedBefore() { .build(); //when + instance.updateProgress(Progress.ACTIVITY); getSavedCertification(NOT_YET, currentDate, participant); ActivatedResponse activatedResponse = certificationService.passCertification(user.getId(), certificationRequest); @@ -533,6 +539,7 @@ public void should_usePassItem_when_conditionIsValid() { .build(); //when + instance.updateProgress(Progress.ACTIVITY); ActivatedResponse activatedResponse = certificationService.passCertification(user.getId(), certificationRequest); From 7c4eb85f353edcb5615a57c216cfde1a7b5f691a Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 11 Mar 2024 01:28:46 +0900 Subject: [PATCH 126/234] =?UTF-8?q?[FEAT]=20=EC=B0=B8=EC=97=AC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=ED=85=8C=EC=9D=B4=EB=B8=94=EC=97=90=20'=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EC=A0=84'=20=EC=A0=95=EB=B3=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#108)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 에러 코드 이름 변경 - PARTICIPANT_INFO_NOT_FOUND에서 PARTICIPANT_NOT_FOUND로 변경 * feat: JoinResult에 READY 데이터 추가 - JoinResult에 '시작 전'을 나타내는 READY 데이터 추가 - 챌린지 참여 직후, READY 값을 가지도록 변경 --- .../service/InstanceDetailService.java | 1 - .../instance/service/ProgressUpdater.java | 13 +++-- .../participant/domain/JoinResult.java | 5 +- .../participant/domain/Participant.java | 16 +++---- .../service/ParticipantProvider.java | 8 ++-- .../global/util/exception/ErrorCode.java | 2 +- .../service/InstanceDetailServiceTest.java | 4 +- .../instance/service/ProgressUpdaterTest.java | 48 ++++++++++++++++++- 8 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index 3378fac0..f2fb1de6 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -55,7 +55,6 @@ public JoinResponse joinNewChallenge(User user, JoinRequest joinRequest) { instance.updateParticipantCount(1); Participant participant = Participant.createDefaultParticipant(repository); participant.setUserAndInstance(persistUser, instance); - participant.joinChallenge(); return JoinResponse.createJoinResponse(participantProvider.save(participant)); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java b/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java index 2c75904a..fd45395b 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java @@ -31,11 +31,18 @@ public void updateToActivity(LocalDate currentDate) { LocalDate completedDate = preActivity.getCompletedDate().toLocalDate(); if (currentDate.isAfter(startedDate) && currentDate.isBefore(completedDate)) { - preActivity.updateProgress(Progress.ACTIVITY); + updateActivityInstance(preActivity); } } } + private void updateActivityInstance(Instance preActivity) { + preActivity.updateProgress(Progress.ACTIVITY); + for (Participant participant : preActivity.getParticipantList()) { + participant.updateJoinResult(JoinResult.PROCESSING); + } + } + @Transactional public void updateToDone(LocalDate currentDate) { List instances = new ArrayList<>(); @@ -47,12 +54,12 @@ public void updateToDone(LocalDate currentDate) { LocalDate completedDate = instance.getCompletedDate().toLocalDate(); if (currentDate.isAfter(startedDate) && currentDate.isAfter(completedDate)) { - updateParticipants(instance, currentDate); + updateDoneInstance(instance, currentDate); } } } - private void updateParticipants(Instance instance, LocalDate currentDate) { + private void updateDoneInstance(Instance instance, LocalDate currentDate) { instance.updateProgress(Progress.DONE); for (Participant participant : instance.getParticipantList()) { int totalAttempt = instance.getTotalAttempt(); diff --git a/src/main/java/com/genius/gitget/challenge/participant/domain/JoinResult.java b/src/main/java/com/genius/gitget/challenge/participant/domain/JoinResult.java index 1a35f193..590fbd6e 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/domain/JoinResult.java +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/JoinResult.java @@ -4,5 +4,8 @@ @Getter public enum JoinResult { - FAIL, SUCCESS, PROCESSING + READY, + PROCESSING, + FAIL, + SUCCESS } diff --git a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java index f98124c7..bf5d719c 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java @@ -65,28 +65,26 @@ public class Participant { private int rewardPoints; @Builder - private Participant(JoinStatus joinStatus, JoinResult joinResult, String repositoryName) { + private Participant(JoinStatus joinStatus, JoinResult joinResult, String repositoryName, + RewardStatus rewardStatus, int rewardPoints) { this.joinStatus = joinStatus; this.joinResult = joinResult; this.repositoryName = repositoryName; - this.rewardStatus = RewardStatus.NO; - this.rewardPoints = 0; + this.rewardStatus = rewardStatus; + this.rewardPoints = rewardPoints; } public static Participant createDefaultParticipant(String repositoryName) { return Participant.builder() .joinStatus(JoinStatus.YES) - .joinResult(JoinResult.PROCESSING) + .joinResult(JoinResult.READY) .repositoryName(repositoryName) + .rewardStatus(RewardStatus.NO) + .rewardPoints(0) .build(); } //=== 비지니스 로직 ===// - public void joinChallenge() { - this.joinStatus = JoinStatus.YES; - this.joinResult = JoinResult.PROCESSING; - } - public void quitChallenge() { this.joinStatus = JoinStatus.NO; this.joinResult = JoinResult.FAIL; diff --git a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java index 203379db..ef418e7a 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java +++ b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java @@ -1,6 +1,6 @@ package com.genius.gitget.challenge.participant.service; -import static com.genius.gitget.global.util.exception.ErrorCode.PARTICIPANT_INFO_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.PARTICIPANT_NOT_FOUND; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; @@ -37,12 +37,12 @@ public void delete(Participant participant) { public Participant findByJoinInfo(Long userId, Long instanceId) { return participantRepository.findByJoinInfo(userId, instanceId) - .orElseThrow(() -> new BusinessException(PARTICIPANT_INFO_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(PARTICIPANT_NOT_FOUND)); } public Participant findById(Long participantInfoId) { return participantRepository.findById(participantInfoId) - .orElseThrow(() -> new BusinessException(PARTICIPANT_INFO_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(PARTICIPANT_NOT_FOUND)); } public Slice findAllByInstanceId(Long userId, Long instanceId, Pageable pageable) { @@ -57,7 +57,7 @@ public Slice findAllByInstanceId(Long userId, Long instanceId, Page public Instance getInstanceById(Long participantId) { return participantRepository.findById(participantId) - .orElseThrow(() -> new BusinessException(PARTICIPANT_INFO_NOT_FOUND)) + .orElseThrow(() -> new BusinessException(PARTICIPANT_NOT_FOUND)) .getInstance(); } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index bd3aaa3e..2efc20d1 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -20,7 +20,7 @@ public enum ErrorCode { INVALID_INSTANCE_DATE(HttpStatus.BAD_REQUEST, "인스턴스 생성/종료 일자는 현재 일자 이후여야 합니다."), INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), - PARTICIPANT_INFO_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 참여 정보를 찾을 수 없습니다."), + PARTICIPANT_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 참여 정보를 찾을 수 없습니다."), CERTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인증 정보를 찾을 수 없습니다."), ALREADY_REGISTERED(HttpStatus.BAD_REQUEST, "이미 회원가입이 완료된 사용자입니다."), diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 860b6551..13993012 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -3,7 +3,7 @@ import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_JOIN_INSTANCE; import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_QUIT_INSTANCE; import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; -import static com.genius.gitget.global.util.exception.ErrorCode.PARTICIPANT_INFO_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.PARTICIPANT_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -202,7 +202,7 @@ public void should_throwException_when_participantInfoNotExist() { //when & then assertThatThrownBy(() -> instanceDetailService.quitChallenge(savedUser, savedInstance.getId())) .isInstanceOf(BusinessException.class) - .hasMessageContaining(PARTICIPANT_INFO_NOT_FOUND.getMessage()); + .hasMessageContaining(PARTICIPANT_NOT_FOUND.getMessage()); } @Test diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java index 5a8a6568..aaa0f8c7 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java @@ -5,9 +5,11 @@ import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.certification.domain.Certification; import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.certification.service.GithubService; import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; @@ -17,22 +19,31 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import java.time.LocalDate; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; @Slf4j @SpringBootTest @Transactional +@ActiveProfiles({"github"}) class ProgressUpdaterTest { + @Autowired + private InstanceDetailService instanceDetailService; @Autowired private ProgressUpdater progressUpdater; @Autowired + private GithubService githubService; + @Autowired private InstanceRepository instanceRepository; @Autowired private ParticipantRepository participantRepository; @@ -43,6 +54,13 @@ class ProgressUpdaterTest { @Autowired private InstanceProvider instanceProvider; + @Value("${github.personalKey}") + private String personalKey; + @Value("${github.githubId}") + private String githubId; + @Value("${github.repository}") + private String targetRepo; + @Test @DisplayName("PRE_ACTIVITY 인스턴스들 중, 특정 조건에 해당하는 인스턴스들을 ACTIVITY로 상태를 바꿀 수 있다.") public void should_updateToActivity_when_conditionMatches() { @@ -51,18 +69,33 @@ public void should_updateToActivity_when_conditionMatches() { LocalDate completedDate = LocalDate.of(2024, 3, 30); LocalDate currentDate = LocalDate.of(2024, 3, 6); + User user = getSavedUser("nickname1", githubId); + Instance instance1 = getSavedInstance(startedDate, completedDate); getSavedInstance(startedDate, completedDate); getSavedInstance(startedDate, completedDate); - getSavedInstance(startedDate, completedDate); + + githubService.registerGithubPersonalToken(user, personalKey); + instanceDetailService.joinNewChallenge( + user, + JoinRequest.builder() + .repository(targetRepo) + .instanceId(instance1.getId()) + .build() + ); + + Participant participant1 = participantRepository.findByJoinInfo(user.getId(), instance1.getId()) + .orElseThrow(() -> new BusinessException(ErrorCode.PARTICIPANT_NOT_FOUND)); //when List preActivities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.READY); progressUpdater.updateToActivity(currentDate); List activities = instanceProvider.findAllByProgress(Progress.ACTIVITY); //then assertThat(preActivities.size()).isEqualTo(3); assertThat(activities.size()).isEqualTo(3); + assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.PROCESSING); } @Test @@ -162,6 +195,19 @@ private Participant getSavedParticipant(User user, Instance instance) { return participant; } + private User getSavedUser(String nickname, String githubId) { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname(nickname) + .providerInfo(ProviderInfo.GITHUB) + .identifier(githubId) + .information("information") + .tags("BE,FE") + .build() + ); + } + private User getSavedUser(String nickname) { return userRepository.save( User.builder() From 78e8e2f4514026365fab52ae1b860eca91a18594 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 11 Mar 2024 01:32:00 +0900 Subject: [PATCH 127/234] =?UTF-8?q?test:=20=EC=B1=8C=EB=A6=B0=EC=A7=80?= =?UTF-8?q?=EC=9D=98=20=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge/myChallenge/service/MyChallengeService.java | 1 - .../challenge/instance/service/InstanceDetailServiceTest.java | 2 +- .../challenge/myChallenge/service/MyChallengeServiceTest.java | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index acd69799..bb485c63 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -63,7 +63,6 @@ public List getPreActivityInstances(User user, LocalDate ta return preActivity; } - //TODO: 사용자의 달성률이 85%가 되지 않았을 때에는 joinResult가 Fail이어야 함 public List getDoneInstances(User user, LocalDate targetDate) { List done = new ArrayList<>(); List participants = participantProvider.findJoinedByProgress(user.getId(), Progress.DONE); diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 13993012..7875389d 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -77,7 +77,7 @@ public void should_saveParticipantInfo_when_passInfo() { //then assertThat(joinResponse.joinStatus()).isEqualTo(JoinStatus.YES); - assertThat(joinResponse.joinResult()).isEqualTo(JoinResult.PROCESSING); + assertThat(joinResponse.joinResult()).isEqualTo(JoinResult.READY); assertThat(instance.getParticipantCount()).isEqualTo(1); } diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java index c0e2048d..ad73fee3 100644 --- a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java @@ -22,6 +22,7 @@ import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.domain.RewardStatus; import com.genius.gitget.challenge.participant.repository.ParticipantRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; @@ -207,6 +208,7 @@ private Participant getSavedParticipant(User user, Instance instance, JoinResult Participant.builder() .joinResult(joinResult) .joinStatus(JoinStatus.YES) + .rewardStatus(RewardStatus.NO) .build() ); participant.setUserAndInstance(user, instance); From 094febf3c7b5631f492c51da6850c5ade69c320d Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:56:00 +0900 Subject: [PATCH 128/234] =?UTF-8?q?fix:=20=EC=84=9C=EB=B2=84=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EC=8B=9C,=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=B6=94=EA=B0=80=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/{import.sql => data.sql} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename src/main/resources/{import.sql => data.sql} (60%) diff --git a/src/main/resources/import.sql b/src/main/resources/data.sql similarity index 60% rename from src/main/resources/import.sql rename to src/main/resources/data.sql index 93667031..1c2e8e43 100644 --- a/src/main/resources/import.sql +++ b/src/main/resources/data.sql @@ -1,5 +1,4 @@ -# 단독 실행 시 정상 작동하지만, ddl-auto를 create로 해놓고 실행하면 오류 발생 -INSERT INTO todoffin.item (cost, created_at, deleted_at, updated_at, details, name, item_category) +INSERT INTO item (cost, created_at, deleted_at, updated_at, details, name, item_category) VALUES (100, NULL, NULL, NULL, '프로필 프레임', 'profile frame', 'PROFILE_FRAME'), (100, NULL, NULL, NULL, '인증 패스 아이템', 'certification passer', 'CERTIFICATION_PASSER'), (100, NULL, NULL, NULL, '포인트 2배 획득 아이템', 'point multiplier', 'POINT_MULTIPLIER'); From c416e8ea2566c0d2b908ea3717c0f738efb28d58 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Tue, 12 Mar 2024 09:55:38 +0900 Subject: [PATCH 129/234] =?UTF-8?q?hotfix:=20type=20definition=20error=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20(#111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gitget/profile/controller/ProfileController.java | 6 +++--- ...sUpdateRequest.java => UserInterestUpdateRequest.java} | 6 ++++-- .../com/genius/gitget/profile/service/ProfileService.java | 8 ++++---- src/main/resources/data.sql | 4 ---- .../genius/gitget/profile/service/ProfileServiceTest.java | 4 ++-- 5 files changed, 13 insertions(+), 15 deletions(-) rename src/main/java/com/genius/gitget/profile/dto/{UserTagsUpdateRequest.java => UserInterestUpdateRequest.java} (55%) delete mode 100644 src/main/resources/data.sql diff --git a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java index 5ae75245..8967b3c7 100644 --- a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java +++ b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java @@ -10,9 +10,9 @@ import com.genius.gitget.profile.dto.UserInformationResponse; import com.genius.gitget.profile.dto.UserInformationUpdateRequest; import com.genius.gitget.profile.dto.UserInterestResponse; +import com.genius.gitget.profile.dto.UserInterestUpdateRequest; import com.genius.gitget.profile.dto.UserPointResponse; import com.genius.gitget.profile.dto.UserSignoutRequest; -import com.genius.gitget.profile.dto.UserTagsUpdateRequest; import com.genius.gitget.profile.service.ProfileService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -86,8 +86,8 @@ public ResponseEntity> getUserInterest( // 마이페이지 - 관심사 수정 @PostMapping("/interest") public ResponseEntity updateUserTags(@AuthenticationPrincipal UserPrincipal userPrincipal, - @RequestBody UserTagsUpdateRequest userTagsUpdateRequest) { - profileService.updateUserTags(userPrincipal.getUser(), userTagsUpdateRequest); + @RequestBody UserInterestUpdateRequest userInterestUpdateRequest) { + profileService.updateUserTags(userPrincipal.getUser(), userInterestUpdateRequest); return ResponseEntity.ok() .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); diff --git a/src/main/java/com/genius/gitget/profile/dto/UserTagsUpdateRequest.java b/src/main/java/com/genius/gitget/profile/dto/UserInterestUpdateRequest.java similarity index 55% rename from src/main/java/com/genius/gitget/profile/dto/UserTagsUpdateRequest.java rename to src/main/java/com/genius/gitget/profile/dto/UserInterestUpdateRequest.java index 6d601ecc..5872d917 100644 --- a/src/main/java/com/genius/gitget/profile/dto/UserTagsUpdateRequest.java +++ b/src/main/java/com/genius/gitget/profile/dto/UserInterestUpdateRequest.java @@ -3,13 +3,15 @@ import java.util.List; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data -public class UserTagsUpdateRequest { +@NoArgsConstructor +public class UserInterestUpdateRequest { private List tags; @Builder - public UserTagsUpdateRequest(List tags) { + public UserInterestUpdateRequest(List tags) { this.tags = tags; } } diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileService.java index e30ed0ee..64edded7 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileService.java @@ -22,8 +22,8 @@ import com.genius.gitget.profile.dto.UserInformationResponse; import com.genius.gitget.profile.dto.UserInformationUpdateRequest; import com.genius.gitget.profile.dto.UserInterestResponse; +import com.genius.gitget.profile.dto.UserInterestUpdateRequest; import com.genius.gitget.profile.dto.UserPointResponse; -import com.genius.gitget.profile.dto.UserTagsUpdateRequest; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -124,12 +124,12 @@ public void deleteUserInformation(User user, String reason) { // 마이페이지 - 관심사 수정 @Transactional - public void updateUserTags(User user, UserTagsUpdateRequest userTagsUpdateRequest) { - if (userTagsUpdateRequest.getTags() == null) { + public void updateUserTags(User user, UserInterestUpdateRequest userInterestUpdateRequest) { + if (userInterestUpdateRequest.getTags() == null) { throw new BusinessException(); } User findUser = getUserByIdentifier(user.getIdentifier()); - String interest = String.join(",", userTagsUpdateRequest.getTags()); + String interest = String.join(",", userInterestUpdateRequest.getTags()); findUser.updateUserTags(interest); userRepository.save(findUser); } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql deleted file mode 100644 index 1c2e8e43..00000000 --- a/src/main/resources/data.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO item (cost, created_at, deleted_at, updated_at, details, name, item_category) -VALUES (100, NULL, NULL, NULL, '프로필 프레임', 'profile frame', 'PROFILE_FRAME'), - (100, NULL, NULL, NULL, '인증 패스 아이템', 'certification passer', 'CERTIFICATION_PASSER'), - (100, NULL, NULL, NULL, '포인트 2배 획득 아이템', 'point multiplier', 'POINT_MULTIPLIER'); diff --git a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java index 75cb0923..1e04e365 100644 --- a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java +++ b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java @@ -23,8 +23,8 @@ import com.genius.gitget.profile.dto.UserInformationResponse; import com.genius.gitget.profile.dto.UserInformationUpdateRequest; import com.genius.gitget.profile.dto.UserInterestResponse; +import com.genius.gitget.profile.dto.UserInterestUpdateRequest; import com.genius.gitget.profile.dto.UserPointResponse; -import com.genius.gitget.profile.dto.UserTagsUpdateRequest; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -131,7 +131,7 @@ void setup() { @Test void 유저_관심사_수정() { profileService.updateUserTags(user1, - UserTagsUpdateRequest.builder().tags(new ArrayList<>(Arrays.asList("FE", "BE"))).build()); + UserInterestUpdateRequest.builder().tags(new ArrayList<>(Arrays.asList("FE", "BE"))).build()); User user = userRepository.findByIdentifier(user1.getIdentifier()) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); Assertions.assertThat(user.getTags()).isEqualTo("FE,BE"); From ac2d461fbf1088bd5997117730cdf97eb873b0e0 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:26:15 +0900 Subject: [PATCH 130/234] =?UTF-8?q?test:=20github=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B4=80=EB=A0=A8=20=ED=99=98=EA=B2=BD=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CertificationControllerTest.java | 6 +-- .../service/CertificationServiceTest.java | 48 +++++++++---------- .../service/GithubProviderTest.java | 6 +-- .../service/GithubServiceTest.java | 6 +-- .../service/InstanceDetailServiceTest.java | 6 +-- .../instance/service/ProgressUpdaterTest.java | 6 +-- ...hMockCustomUserSecurityContextFactory.java | 2 +- 7 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java index 954c9315..37c61370 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java @@ -46,13 +46,13 @@ class CertificationControllerTest { @Autowired UserRepository userRepository; - @Value("${github.personalKey}") + @Value("${github.yeon-personalKey}") private String githubToken; - @Value("${github.githubId}") + @Value("${github.yeon-githubId}") private String githubId; - @Value("${github.repository}") + @Value("${github.yeon-repository}") private String targetRepo; @BeforeEach diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index b0f8f464..a36384ce 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -76,13 +76,13 @@ class CertificationServiceTest { @Autowired private UserItemRepository userItemRepository; - @Value("${github.personalKey}") + @Value("${github.yeon-personalKey}") private String personalKey; - @Value("${github.githubId}") + @Value("${github.yeon-githubId}") private String githubId; - @Value("${github.repository}") + @Value("${github.yeon-repository}") private String targetRepo; @@ -92,7 +92,7 @@ public void should_certificate_when_prExist() { //given User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - getParticipantInfo(user, instance); + getSavedParticipant(user, instance); githubService.registerGithubPersonalToken(user, personalKey); LocalDate targetDate = LocalDate.of(2024, 2, 5); @@ -122,7 +122,7 @@ public void should_throwException_when_progressIsNotActivity() { //given User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - getParticipantInfo(user, instance); + getSavedParticipant(user, instance); githubService.registerGithubPersonalToken(user, personalKey); LocalDate targetDate = LocalDate.of(2024, 12, 6); @@ -145,7 +145,7 @@ public void should_notCertificate_when_prNotExist() { //given User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - getParticipantInfo(user, instance); + getSavedParticipant(user, instance); githubService.registerGithubPersonalToken(user, personalKey); LocalDate targetDate = LocalDate.of(2024, 2, 6); @@ -203,7 +203,7 @@ public void should_returnCertifications_when_passDuration() { LocalDate currentDate = LocalDate.of(2024, 2, 3); LocalDate startDate = LocalDate.of(2024, 2, 1); LocalDate endDate = LocalDate.of(2024, 2, 4); - Participant participant = getParticipantInfo(getSavedUser(githubId), getSavedInstance()); + Participant participant = getSavedParticipant(getSavedUser(githubId), getSavedInstance()); //when getSavedCertification(NOT_YET, startDate, participant); @@ -226,7 +226,7 @@ public void should_returnList_when_dataIsNotContinuous() { LocalDate endDate = LocalDate.of(2024, 2, 29); LocalDate currentDate = LocalDate.of(2024, 2, 8); - Participant participant = getParticipantInfo(getSavedUser(githubId), getSavedInstance()); + Participant participant = getSavedParticipant(getSavedUser(githubId), getSavedInstance()); //when getSavedCertification(NOT_YET, startDate, participant); @@ -254,7 +254,7 @@ public void should_returnList_when_passDate() { LocalDate currentDate = LocalDate.of(2024, 2, 8); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(getSavedUser(githubId), instance); + Participant participant = getSavedParticipant(getSavedUser(githubId), instance); //when getSavedCertification(NOT_YET, startDate, participant); @@ -276,7 +276,7 @@ public void should_returnDetailInfo_when_participate() { //given User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); //when InstancePreviewResponse instancePreviewResponse = certificationService.getInstancePreview(instance.getId()); @@ -292,7 +292,7 @@ public void should_getInformation_when_progressIsPreActivity() { LocalDate targetDate = LocalDate.of(2024, 2, 8); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); //when CertificationInformation information = certificationService.getCertificationInformation(instance, @@ -316,7 +316,7 @@ public void should_getInformation_when_progressIsActivity() { LocalDate targetDate = LocalDate.of(2024, 2, 8); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); //when instance.updateProgress(Progress.ACTIVITY); @@ -345,7 +345,7 @@ public void should_getInformation_when_progressIsDone() { LocalDate targetDate = LocalDate.of(2024, 2, 8); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); //when instance.updateProgress(Progress.DONE); @@ -375,8 +375,8 @@ public void should_getWeekCertification_aboutAllParticipants() { User user1 = getSavedUser(githubId, "nickname1"); User user2 = getSavedUser(githubId, "nickname2"); Instance instance = getSavedInstance(); - Participant participant1 = getParticipantInfo(user1, instance); - Participant participant2 = getParticipantInfo(user2, instance); + Participant participant1 = getSavedParticipant(user1, instance); + Participant participant2 = getSavedParticipant(user2, instance); //when Slice certification = certificationService.getAllWeekCertification( @@ -394,7 +394,7 @@ public void should_passCertification_when_conditionIsValid() { LocalDate currentDate = LocalDate.of(2024, 3, 1); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) @@ -431,7 +431,7 @@ public void should_throwException_when_userItemInfoNotExist() { LocalDate currentDate = LocalDate.of(2024, 3, 1); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) .targetDate(currentDate) @@ -441,7 +441,7 @@ public void should_throwException_when_userItemInfoNotExist() { getSavedCertification(NOT_YET, currentDate, participant); //then - assertThatThrownBy(() -> certificationService.passCertification(instance.getId(), certificationRequest)) + assertThatThrownBy(() -> certificationService.passCertification(user.getId(), certificationRequest)) .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); } @@ -453,7 +453,7 @@ public void should_throwException_when_outOfStock() { LocalDate currentDate = LocalDate.of(2024, 3, 1); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 0); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) @@ -463,7 +463,7 @@ public void should_throwException_when_outOfStock() { instance.updateProgress(Progress.ACTIVITY); //when && then - assertThatThrownBy(() -> certificationService.passCertification(instance.getId(), certificationRequest)) + assertThatThrownBy(() -> certificationService.passCertification(user.getId(), certificationRequest)) .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); } @@ -476,7 +476,7 @@ public void should_throwException_when_challengeIsNotNOT_YET(CertificateStatus c LocalDate currentDate = LocalDate.of(2024, 3, 1); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) @@ -500,7 +500,7 @@ public void should_overwriteData_when_certificatedBefore() { LocalDate currentDate = LocalDate.of(2024, 3, 1); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) @@ -531,7 +531,7 @@ public void should_usePassItem_when_conditionIsValid() { LocalDate currentDate = LocalDate.of(2024, 3, 1); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + Participant participant = getSavedParticipant(user, instance); UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) @@ -591,7 +591,7 @@ private Instance getSavedInstance() { ); } - private Participant getParticipantInfo(User user, Instance instance) { + private Participant getSavedParticipant(User user, Instance instance) { Participant participant = participantRepository.save( Participant.builder() .joinResult(JoinResult.PROCESSING) diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java index fcb55eb9..f8f437f4 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java @@ -25,13 +25,13 @@ class GithubProviderTest { @Autowired private GithubProvider githubProvider; - @Value("${github.personalKey}") + @Value("${github.yeon-personalKey}") private String personalKey; - @Value("${github.githubId}") + @Value("${github.yeon-githubId}") private String githubId; - @Value("${github.repository}") + @Value("${github.yeon-repository}") private String repository; @Test diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java index e98d8065..45d72a60 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java @@ -51,11 +51,11 @@ class GithubServiceTest { @Autowired private CertificationRepository certificationRepository; - @Value("${github.personalKey}") + @Value("${github.yeon-personalKey}") private String personalKey; - @Value("${github.githubId}") + @Value("${github.yeon-githubId}") private String githubId; - @Value("${github.repository}") + @Value("${github.yeon-repository}") private String targetRepo; @Test diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 7875389d..676d0613 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -53,11 +53,11 @@ class InstanceDetailServiceTest { @Autowired ParticipantRepository participantRepository; - @Value("${github.personalKey}") + @Value("${github.yeon-personalKey}") private String githubToken; - @Value("${github.githubId}") + @Value("${github.yeon-githubId}") private String githubId; - @Value("${github.repository}") + @Value("${github.yeon-repository}") private String targetRepo; diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java index aaa0f8c7..5077727c 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java @@ -54,11 +54,11 @@ class ProgressUpdaterTest { @Autowired private InstanceProvider instanceProvider; - @Value("${github.personalKey}") + @Value("${github.yeon-personalKey}") private String personalKey; - @Value("${github.githubId}") + @Value("${github.yeon-githubId}") private String githubId; - @Value("${github.repository}") + @Value("${github.yeon-repository}") private String targetRepo; @Test diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java index d019d7b2..6b76ae5c 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java @@ -25,7 +25,7 @@ public class WithMockCustomUserSecurityContextFactory implements WithSecurityCon private final UserService userService; private final CustomUserDetailsService customUserDetailsService; - @Value("${github.githubId}") + @Value("${github.yeon-githubId}") private String githubId; @Override From 6d72ed8258c53996526f2799fb3289dcf3beeb31 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:44:07 +0900 Subject: [PATCH 131/234] =?UTF-8?q?feat:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C,=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=EB=8F=84=20=ED=8F=AC=ED=95=A8=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20(#117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 챌린지 상세 정보 조회 시, 좋아요 관련 정보(likesId, likesCount, isLiked)도 전달하도록 변경 - Instance에 좋아요의 수를 반환하는 비니지스 로직 작성 - 관련 테스트 코드 작성 --- .../challenge/instance/domain/Instance.java | 4 ++ .../instance/dto/detail/InstanceResponse.java | 6 +-- .../instance/dto/detail/LikesInfo.java | 27 ++++++++++++++ .../service/InstanceDetailService.java | 21 ++++++++++- .../likes/repository/LikesRepository.java | 7 ++++ .../service/InstanceDetailServiceTest.java | 37 ++++++++++++++++++- 6 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 36d3194b..a215b2be 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -129,6 +129,10 @@ public void updateProgress(Progress progress) { this.progress = progress; } + public int getLikesCount() { + return this.likesList.size(); + } + public Optional getFiles() { return Optional.ofNullable(this.files); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java index af5fcfa4..925e4de6 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java @@ -22,11 +22,11 @@ public record InstanceResponse( String notice, String certificationMethod, JoinStatus joinStatus, - int likesCount, + LikesInfo likesInfo, FileResponse fileResponse ) { - public static InstanceResponse createByEntity(Instance instance, JoinStatus joinStatus) { + public static InstanceResponse createByEntity(Instance instance, LikesInfo likesInfo, JoinStatus joinStatus) { LocalDate startedLocalDate = instance.getStartedDate().toLocalDate(); LocalDate completedLocalDate = instance.getCompletedDate().toLocalDate(); return InstanceResponse.builder() @@ -42,7 +42,7 @@ public static InstanceResponse createByEntity(Instance instance, JoinStatus join .notice(instance.getNotice()) .certificationMethod(instance.getCertificationMethod()) .joinStatus(joinStatus) - .likesCount(instance.getLikesList().size()) + .likesInfo(likesInfo) .fileResponse(FileResponse.create(instance.getFiles())) .build(); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java new file mode 100644 index 00000000..a46a43f9 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java @@ -0,0 +1,27 @@ +package com.genius.gitget.challenge.instance.dto.detail; + +import lombok.Builder; + +@Builder +public record LikesInfo( + Long likesId, + boolean isLiked, + int likesCount +) { + + public static LikesInfo createExist(Long likesId, int likesCount) { + return LikesInfo.builder() + .likesId(likesId) + .isLiked(true) + .likesCount(likesCount) + .build(); + } + + public static LikesInfo createNotExist() { + return LikesInfo.builder() + .likesId(0L) + .isLiked(false) + .likesCount(0) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index f2fb1de6..085cf009 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -9,12 +9,16 @@ import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; +import com.genius.gitget.challenge.instance.dto.detail.LikesInfo; +import com.genius.gitget.challenge.likes.domain.Likes; +import com.genius.gitget.challenge.likes.repository.LikesRepository; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantProvider; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.util.exception.BusinessException; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.kohsuke.github.GitHub; @@ -30,15 +34,28 @@ public class InstanceDetailService { private final InstanceProvider instanceProvider; private final ParticipantProvider participantProvider; private final GithubProvider githubProvider; + private final LikesRepository likesRepository; public InstanceResponse getInstanceDetailInformation(User user, Long instanceId) { Instance instance = instanceProvider.findById(instanceId); + LikesInfo likesInfo = getLikesInfo(user.getId(), instance); + if (participantProvider.hasParticipant(user.getId(), instanceId)) { - return InstanceResponse.createByEntity(instance, JoinStatus.YES); + return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.YES); + } + + return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.NO); + } + + private LikesInfo getLikesInfo(Long userId, Instance instance) { + Optional optionalLikes = likesRepository.findSpecificLike(userId, instance.getId()); + if (optionalLikes.isPresent()) { + Likes likes = optionalLikes.get(); + return LikesInfo.createExist(likes.getId(), instance.getLikesCount()); } - return InstanceResponse.createByEntity(instance, JoinStatus.NO); + return LikesInfo.createNotExist(); } @Transactional diff --git a/src/main/java/com/genius/gitget/challenge/likes/repository/LikesRepository.java b/src/main/java/com/genius/gitget/challenge/likes/repository/LikesRepository.java index 324ad4fc..c246b0ad 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/repository/LikesRepository.java +++ b/src/main/java/com/genius/gitget/challenge/likes/repository/LikesRepository.java @@ -1,7 +1,14 @@ package com.genius.gitget.challenge.likes.repository; import com.genius.gitget.challenge.likes.domain.Likes; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface LikesRepository extends JpaRepository { + + @Query("select l from Likes l where l.user.id = :userId and l.instance.id = :instanceId") + Optional findSpecificLike(@Param("userId") Long userId, + @Param("instanceId") Long instanceId); } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 676d0613..dcbbf218 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -14,6 +14,8 @@ import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; +import com.genius.gitget.challenge.likes.service.LikesService; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; @@ -43,6 +45,8 @@ class InstanceDetailServiceTest { @Autowired InstanceDetailService instanceDetailService; @Autowired + LikesService likesService; + @Autowired ParticipantProvider participantProvider; @Autowired GithubService githubService; @@ -250,7 +254,9 @@ public void should_returnValues_when_joinedInstance() { assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.YES); - assertThat(instanceResponse.likesCount()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().likesId()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().isLiked()).isFalse(); } @Test @@ -271,7 +277,34 @@ public void should_returnData_when_notJoinedInstance() { assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.NO); - assertThat(instanceResponse.likesCount()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().likesId()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().isLiked()).isFalse(); + } + + @Test + @DisplayName("시용자가 좋아요를 한 이후, 상세 정보를 요청하면 좋아요 관련된 정보를 받을 수 있다.") + public void should_returnLikesData_when_userPushLikes() { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); + + //when + UserLikesAddResponse userLikesAddResponse = likesService.addLikes(savedUser, savedUser.getIdentifier(), + savedInstance.getId()); + InstanceResponse instanceResponse = instanceDetailService.getInstanceDetailInformation(savedUser, + savedInstance.getId()); + + //then + assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); + assertThat(instanceResponse.remainDays()).isEqualTo(2); + assertThat(instanceResponse.participantCount()).isEqualTo(0); + assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); + assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); + assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.NO); + assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(1); + assertThat(instanceResponse.likesInfo().likesId()).isEqualTo(userLikesAddResponse.getLikesId()); + assertThat(instanceResponse.likesInfo().isLiked()).isTrue(); } @Test From a194b92f4214d6f80d40c60fbe164ea000620d0b Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:44:17 +0900 Subject: [PATCH 132/234] =?UTF-8?q?feat:=20JWT=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=97=90=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#116)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JWT API의 응답 데이터에 사용자의 데이터(역할) 추가 --- .../global/security/controller/AuthController.java | 9 ++++++--- .../genius/gitget/global/security/dto/AuthResponse.java | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 642983c8..836af150 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -5,9 +5,11 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.security.dto.TokenDTO; import com.genius.gitget.global.security.service.JwtService; import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -27,16 +29,17 @@ public class AuthController { private final JwtService jwtService; @PostMapping("/auth") - public ResponseEntity generateToken(HttpServletResponse response, - @RequestBody TokenDTO tokenRequest) { + public ResponseEntity> generateToken(HttpServletResponse response, + @RequestBody TokenDTO tokenRequest) { User requestUser = userService.findUserByIdentifier(tokenRequest.identifier()); jwtService.validateUser(requestUser); jwtService.generateAccessToken(response, requestUser); jwtService.generateRefreshToken(response, requestUser); + AuthResponse authResponse = new AuthResponse(requestUser.getRole()); return ResponseEntity.ok().body( - new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), authResponse) ); } diff --git a/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java b/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java new file mode 100644 index 00000000..e983b7a1 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java @@ -0,0 +1,8 @@ +package com.genius.gitget.global.security.dto; + +import com.genius.gitget.challenge.user.domain.Role; + +public record AuthResponse( + Role role +) { +} From d9cc3d724cb548d493a97542f640ddb63c6032fe Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Tue, 12 Mar 2024 16:50:19 +0900 Subject: [PATCH 133/234] =?UTF-8?q?feat:=20instance=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EC=8B=9C=20uuid=20=EC=B6=94=EA=B0=80=20(#118)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - instanceUUID entity 추가 - 테스트 코드 작성 Co-authored-by: HEY <50323157+SSung023@users.noreply.github.com> --- .../challenge/instance/domain/Instance.java | 49 +++++++++++++++---- .../instance/service/InstanceService.java | 28 +++++++---- .../global/util/exception/ErrorCode.java | 4 ++ .../repository/InstanceRepositoryTest.java | 29 +++++++++++ .../instance/service/InstanceServiceTest.java | 8 +-- 5 files changed, 97 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index a215b2be..1fffb042 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -7,6 +7,8 @@ import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -81,6 +83,8 @@ public class Instance { @Column(name = "completed_at") private LocalDateTime completedDate; + private String instanceUUID; + @Builder public Instance(String title, String description, String tags, int pointPerPerson, Progress progress, String notice, String certificationMethod, @@ -110,7 +114,23 @@ public static Instance createByRequest(InstanceCreateRequest instanceCreateReque .build(); } + //== 연관관계 편의 메서드 ==// + public void setTopic(Topic topic) { + this.topic = topic; + if (!topic.getInstanceList().contains(this)) { + topic.getInstanceList().add(this); + } + } + + public void setFiles(Files files) { + this.files = files; + } + //== 비지니스 로직 ==// + + /* + * 인스턴스 수정 + * */ public void updateInstance(String description, String notice, int pointPerPerson, LocalDateTime startedDate, LocalDateTime completedDate, String certificationMethod) { this.description = description; @@ -121,10 +141,16 @@ public void updateInstance(String description, String notice, int pointPerPerson this.certificationMethod = certificationMethod; } + /* + * 참가자 수 정보 수정 + * */ public void updateParticipantCount(int amount) { this.participantCount += amount; } + /* + * 진행 상황 수정 + * */ public void updateProgress(Progress progress) { this.progress = progress; } @@ -133,23 +159,28 @@ public int getLikesCount() { return this.likesList.size(); } + /* + * 파일 조회 + * */ public Optional getFiles() { return Optional.ofNullable(this.files); } - public void setFiles(Files files) { - this.files = files; - } - + /* + * 챌린지 전체 인증 일자 조회 + * */ public int getTotalAttempt() { return DateUtil.getAttemptCount(startedDate.toLocalDate(), completedDate.toLocalDate()); } - //== 연관관계 편의 메서드 ==// - public void setTopic(Topic topic) { - this.topic = topic; - if (!topic.getInstanceList().contains(this)) { - topic.getInstanceList().add(this); + /* + * 인스턴스 고유 uuid 설정 + * */ + public void setInstanceUUID(String instanceUUID) { + if (this.instanceUUID != null) { + throw new BusinessException(ErrorCode.UUID_ALREADY_EXISTS); + } else { + this.instanceUUID = instanceUUID; } } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index c53c9c69..aa6c7bb1 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.time.LocalDate; import java.util.Optional; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -37,21 +38,31 @@ public class InstanceService { // 인스턴스 생성 @Transactional public Long createInstance(InstanceCreateRequest instanceCreateRequest, - MultipartFile multipartFile, String type, LocalDate currentDate) { + MultipartFile multipartFile, String type, + LocalDate currentDate) { + // 토픽 조회 Topic topic = topicRepository.findById(instanceCreateRequest.topicId()) .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); + // 파일 업로드 Files uploadedFile = filesService.uploadFile(topic.getFiles(), multipartFile, type); + // 인스턴스 생성 일자 검증 validatePeriod(instanceCreateRequest, currentDate); + // 인스턴스 고유 uuid 생성 + String uuid = UUID.randomUUID().toString(); + uuid = uuid.replaceAll("-", "").substring(0, 16); + + // from dto to entity 변환 및 uuid 설정 Instance instance = Instance.createByRequest(instanceCreateRequest); + instance.setInstanceUUID(uuid); + + // 연관 관계 설정 instance.setTopic(topic); instance.setFiles(uploadedFile); - Instance savedInstance = instanceRepository.save(instance); - - return savedInstance.getId(); + return instanceRepository.save(instance).getId(); } private void validatePeriod(InstanceCreateRequest instanceCreateRequest, LocalDate currentDate) { @@ -103,8 +114,8 @@ public void deleteInstance(Long id) { // 인스턴스 수정 @Transactional - public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, - MultipartFile multipartFile, String type) { + public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, MultipartFile multipartFile, + String type) { Instance existingInstance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); @@ -113,9 +124,8 @@ public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, filesService.updateFile(findInstanceFileId, multipartFile); existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.notice(), - instanceUpdateRequest.pointPerPerson(), - instanceUpdateRequest.startedAt(), instanceUpdateRequest.completedAt(), - instanceUpdateRequest.certificationMethod()); + instanceUpdateRequest.pointPerPerson(), instanceUpdateRequest.startedAt(), + instanceUpdateRequest.completedAt(), instanceUpdateRequest.certificationMethod()); Instance savedInstance = instanceRepository.save(existingInstance); diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 2efc20d1..b3f67051 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -8,8 +8,12 @@ @RequiredArgsConstructor public enum ErrorCode { + UUID_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "이미 존재하는 uuid입니다."), + MEMBER_NOT_UPDATED(HttpStatus.BAD_REQUEST, "유저 정보가 업데이트되지 않았습니다."), + LIKES_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 좋아요 목록을 찾을 수 없습니다"), + FAILED_POINT_PAYMENT(HttpStatus.BAD_REQUEST, "최소 충전 금액은 100원 이상입니다."), INVALID_PAYMENT_AMOUNT(HttpStatus.BAD_REQUEST, "최초 결제 요청 금액과 일치하지 않습니다."), FAILED_FINAL_PAYMENT(HttpStatus.BAD_REQUEST, "최종 결제가 승인되지 않았습니다"), diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java index d9f78d89..f494bf12 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -4,8 +4,10 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.global.util.exception.BusinessException; import java.time.LocalDateTime; import java.util.List; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -39,6 +41,9 @@ class InstanceRepositoryTest { .completedDate(LocalDateTime.now().plusDays(3)) .build(); + String uuid = UUID.randomUUID().toString(); + uuid = uuid.replaceAll("-", "").substring(0, 16); + instance.setInstanceUUID(uuid); //when Instance savedInstance = instanceRepository.save(instance); @@ -46,6 +51,30 @@ class InstanceRepositoryTest { Assertions.assertThat(savedInstance.getTitle()).isEqualTo("1일 1알고리즘"); } + @Test + public void 인스턴스_uuid를_수정할_수_없다() { + //given + Instance instance = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(3)) + .build(); + + String uuid = UUID.randomUUID().toString(); + uuid = uuid.replaceAll("-", "").substring(0, 16); + instance.setInstanceUUID(uuid); + //when + Instance savedInstance = instanceRepository.save(instance); + + org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, () -> + savedInstance.setInstanceUUID(UUID.randomUUID().toString())); + } + + @Test public void 인스턴스_수정() throws Exception { //given diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java index e72454b6..8edd6c1e 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java @@ -12,6 +12,7 @@ import com.genius.gitget.util.file.FileTestUtil; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -66,14 +67,15 @@ public void setup() { InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); //when - Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, + instanceService.createInstance(instanceCreateRequest, FileTestUtil.getMultipartFile("name"), fileType, currentDate); //then - Optional byId = instanceRepository.findById(savedInstanceId); - Assertions.assertThat(byId.get().getId()).isEqualTo(savedInstanceId); + List all = instanceRepository.findAll(); + Assertions.assertThat(all.size()).isEqualTo(1); } + @Test public void 인스턴스_수정() throws Exception { //given From e5f4604f660010c668299af795b8131cc686dcaa Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:32:37 +0900 Subject: [PATCH 134/234] =?UTF-8?q?[FEAT]=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=83=81=EC=A0=90=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?(#124)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: UserItem 테이블에 장착 정보를 나타내는 컬럼 추가 * feat: 아이템 조회 API 개발 - 프로필 아이템 조회 API 개발 * feat: 아이템 카테고리에 따른 정보를 반환하는 API 개발 * fix: Items에 이미 값이 존재하는 경우 데이터를 삽입하지 않도록 변경 * refactor: 아이템 목록 조회 API 공통 로직 메서드로 추출 * refactor: 사용자의 아이템 보유 정보를 찾는 메서드 이름 변경 * feat: 사용자의 아이템 구매 API 구현 - 특정 아이템을 구매하는 API 개발 - 테스트 코드 작성 필요 * feat: 아이템 구매 API 로직 보강 - 프로필 프레임인 경우 재구매 시도 시 예외 처리 - 아이템 구매 시 사용자의 포인트가 감소하도록 처리 - ErrorCode에 예외 처리 코드 추가 - 테스트 코드 작성 필요 - Item을 Category를 통해 검색하는 거에 대한 로직 보강 필요 * feat: 아이템 목록 전체 조회 옵션 추가 - 아이템 목록 조회 API에서 query string을 통해 아이템의 전체 목록을 조회할 수 있는 기능 추가 * refactor: 아이템 초기 설정 값 변경 * test: github 테스트 관련 환경 설정 * feat: 사용자의 아이템 사용 API 구현 중 - 사용자의 아이템 사용 API 구현 중(인증 패스 아이템, 보상 2배 획득 아이템 구현 필요) - 아이템 보유정보 조회 메서드명 변경 - 관련 테스트 코드 작성 * feat: 사용자 아이템 사용 API 구현 - 인증 패스 아이템, 포인트 2배 획득 기능 구현 - 프로필 프레임 사용했을 때와 인증 패스/포인트2배획득 아이템 사용 때와 다른 응답 반환 - 코드 리팩토링 필요 - 테스트 코드 추가 작성 필요 * refactor: 진행 중 인스턴스 조회 시, 아이템 정보도 전달하도록 수정 - 진행 중 인스턴스 목록 조회 시, 인증 패스 아이템에 대한 정보(PK) 추가 전달 * refactor: 챌린지 일반 보상 API 분리 * fix: compile error fix * feat: 마이챌린지 완료응답 데이터에 아이템 정보 추가 * fix: 아이템 사용 API 버그 픽스 - 불필요한 검증 조건 제거 - 아이템 사용 시 아이템 개수 감소 - 포인트 2배 획득 로직 버그 픽스 * refactor: 아이템 관련 코드 리팩토링 - 아이탬 구매 메서드 내의 switch문에서 각 case에 대해 메서드로 추출 - 예외 상황 추가 처리 - 관련 테스트 코드 작성 및 돌아가지 않는 테스트 수정 * feat: 아이템 장착 해제 API 개발 - 아이템 장착 해제 API 개발 - 관련 테스트 코드 작성 * fix: 아이템의 개수를 새는 로직 버그 픽스 - 카테고리를 통해 개수를 확인하면 제대로 된 개수를 확인하지 못하는 버그 픽스 - 아이템 PK를 받아 처리하도록 설정 * refactor: 엔티티의 이름 변경 - UserItem에서 Order로 변경 --- .../service/CertificationService.java | 21 +- .../item/controller/ItemController.java | 92 ++++ .../challenge/item/domain/EquipStatus.java | 14 + .../gitget/challenge/item/domain/Item.java | 2 +- .../challenge/item/domain/ItemCategory.java | 21 +- .../item/domain/{UserItem.java => Order.java} | 39 +- .../challenge/item/dto/ItemResponse.java | 26 + .../challenge/item/dto/ItemUseResponse.java | 22 + .../challenge/item/dto/ProfileResponse.java | 20 + .../item/repository/ItemRepository.java | 7 + .../item/repository/OrderRepository.java | 26 + .../item/repository/UserItemRepository.java | 15 - .../challenge/item/service/ItemProvider.java | 14 + .../challenge/item/service/ItemService.java | 173 +++++++ .../challenge/item/service/OrderProvider.java | 53 ++ .../item/service/UserItemProvider.java | 31 -- .../controller/MyChallengeController.java | 9 +- .../myChallenge/dto/ActivatedResponse.java | 36 +- .../myChallenge/dto/DoneResponse.java | 38 +- .../myChallenge/dto/RewardRequest.java | 1 - .../service/MyChallengeService.java | 33 +- .../gitget/challenge/user/domain/User.java | 4 +- .../global/util/exception/ErrorCode.java | 7 + src/main/resources/data.sql | 14 + .../service/CertificationServiceTest.java | 117 ++--- .../item/service/ItemProviderTest.java | 78 +++ .../item/service/ItemServiceTest.java | 474 ++++++++++++++++++ ...oviderTest.java => OrderProviderTest.java} | 58 +-- .../service/MyChallengeServiceTest.java | 96 ++-- 29 files changed, 1259 insertions(+), 282 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/item/controller/ItemController.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/domain/EquipStatus.java rename src/main/java/com/genius/gitget/challenge/item/domain/{UserItem.java => Order.java} (57%) create mode 100644 src/main/java/com/genius/gitget/challenge/item/dto/ItemResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/dto/ItemUseResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/dto/ProfileResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/repository/OrderRepository.java delete mode 100644 src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/service/ItemService.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/service/OrderProvider.java delete mode 100644 src/main/java/com/genius/gitget/challenge/item/service/UserItemProvider.java create mode 100644 src/main/resources/data.sql create mode 100644 src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java rename src/test/java/com/genius/gitget/challenge/item/service/{UserItemProviderTest.java => OrderProviderTest.java} (55%) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 949001a3..c2ca4987 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -15,9 +15,7 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.service.InstanceProvider; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.UserItem; -import com.genius.gitget.challenge.item.service.UserItemProvider; +import com.genius.gitget.challenge.item.service.OrderProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantProvider; @@ -51,7 +49,7 @@ public class CertificationService { private final CertificationProvider certificationProvider; private final ParticipantProvider participantProvider; private final InstanceProvider instanceProvider; - private final UserItemProvider userItemProvider; + private final OrderProvider orderProvider; public List getWeekCertification(Long participantId, LocalDate currentDate) { @@ -146,13 +144,10 @@ public ActivatedResponse passCertification(Long userId, CertificationRequest cer Participant participant = participantProvider.findByJoinInfo(userId, instance.getId()); LocalDate targetDate = certificationRequest.targetDate(); - UserItem userItem = userItemProvider.findUserItemByUser(userId, ItemCategory.CERTIFICATION_PASSER); Optional optional = certificationProvider.findByDate(targetDate, participant.getId()); validCertificationCondition(instance, targetDate); - validatePassCondition(userItem, optional); - - userItem.useItem(); + validatePassCondition(optional); //TODO: 리팩토링 시급... if (optional.isPresent()) { @@ -170,14 +165,10 @@ public ActivatedResponse passCertification(Long userId, CertificationRequest cer 0, participant.getRepositoryName()); } - private void validatePassCondition(UserItem userItem, Optional optional) { - if (!userItem.hasItem()) { - throw new BusinessException(ErrorCode.USER_ITEM_NOT_FOUND); - } - if (optional.isEmpty() || optional.get().getCertificationStatus() == NOT_YET) { - return; + private void validatePassCondition(Optional optional) { + if (optional.isPresent() && optional.get().getCertificationStatus() != NOT_YET) { + throw new BusinessException(ErrorCode.CAN_NOT_USE_PASS_ITEM); } - throw new BusinessException(ErrorCode.CAN_NOT_USE_PASS_ITEM); } @Transactional diff --git a/src/main/java/com/genius/gitget/challenge/item/controller/ItemController.java b/src/main/java/com/genius/gitget/challenge/item/controller/ItemController.java new file mode 100644 index 00000000..1bba8cc0 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/controller/ItemController.java @@ -0,0 +1,92 @@ +package com.genius.gitget.challenge.item.controller; + +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.dto.ItemResponse; +import com.genius.gitget.challenge.item.dto.ItemUseResponse; +import com.genius.gitget.challenge.item.dto.ProfileResponse; +import com.genius.gitget.challenge.item.service.ItemService; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.ListResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class ItemController { + private final ItemService itemService; + + @GetMapping("/items") + public ResponseEntity> getItemList( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestParam String category + ) { + List itemResponses; + if (category.trim().equalsIgnoreCase("all")) { + itemResponses = itemService.getAllItems(userPrincipal.getUser()); + } else { + ItemCategory itemCategory = ItemCategory.findCategory(category); + itemResponses = itemService.getItemsByCategory(userPrincipal.getUser(), itemCategory); + } + + return ResponseEntity.ok().body( + new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemResponses) + ); + } + + @PostMapping("/items/order/{itemId}") + public ResponseEntity> purchaseItem( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long itemId + ) { + ItemResponse itemResponse = itemService.orderItem(userPrincipal.getUser(), itemId); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemResponse) + ); + } + + @PostMapping("/items/use/{itemId}") + public ResponseEntity useItem( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long itemId, + @RequestParam(required = false) Long instanceId + ) { + ItemUseResponse itemUseResponse = itemService.useItem( + userPrincipal.getUser(), itemId, instanceId, LocalDate.now()); + if (itemUseResponse.isFrameResponse()) { + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemUseResponse) + ); + } + + @PostMapping("/items/unuse/{itemId}") + public ResponseEntity> unmountItem( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @PathVariable Long itemId + ) { + ProfileResponse profileResponse = itemService.unmountFrame(userPrincipal.getUser(), itemId); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), profileResponse) + ); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/EquipStatus.java b/src/main/java/com/genius/gitget/challenge/item/domain/EquipStatus.java new file mode 100644 index 00000000..91726105 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/domain/EquipStatus.java @@ -0,0 +1,14 @@ +package com.genius.gitget.challenge.item.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EquipStatus { + UNAVAILABLE("장착 불가"), + AVAILABLE("장착 가능"), + IN_USE("장착 중"); + + private final String tag; +} diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java b/src/main/java/com/genius/gitget/challenge/item/domain/Item.java index add2e147..8f55ce24 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/Item.java @@ -26,7 +26,7 @@ public class Item extends BaseTimeEntity { private Long id; @OneToMany(mappedBy = "item") - private List userItemList = new ArrayList<>(); + private List orderList = new ArrayList<>(); private String name; diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java b/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java index c9254f35..098a069c 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java @@ -1,12 +1,27 @@ package com.genius.gitget.challenge.item.domain; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.util.Arrays; import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Getter public enum ItemCategory { - PROFILE_FRAME, - CERTIFICATION_PASSER, - POINT_MULTIPLIER + PROFILE_FRAME("profile-frame", "프로필 프레임"), + CERTIFICATION_PASSER("certification-passer", "인증 패스 아이템"), + POINT_MULTIPLIER("point-multiplier", "포인트 2배 획득 아이템"); + + private final String tag; + private final String name; + + public static ItemCategory findCategory(String category) { + String lowerCase = category.trim().toLowerCase(); + + return Arrays.stream(ItemCategory.values()) + .filter(itemCategory -> itemCategory.tag.equals(lowerCase)) + .findFirst() + .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_CATEGORY_NOT_FOUND)); + } } diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java b/src/main/java/com/genius/gitget/challenge/item/domain/Order.java similarity index 57% rename from src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java rename to src/main/java/com/genius/gitget/challenge/item/domain/Order.java index 96ada28c..9e5b4bd3 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/UserItem.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/Order.java @@ -5,6 +5,8 @@ import com.genius.gitget.global.util.exception.ErrorCode; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -18,10 +20,10 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class UserItem { +public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_item_id") + @Column(name = "order_id") Long id; @ManyToOne(fetch = FetchType.LAZY) @@ -34,8 +36,19 @@ public class UserItem { private int count; - public UserItem(int count) { + @Enumerated(value = EnumType.STRING) + private EquipStatus equipStatus; + + private Order(int count, EquipStatus equipStatus) { this.count = count; + this.equipStatus = equipStatus; + } + + public static Order createDefault(int count, ItemCategory itemCategory) { + if (itemCategory == ItemCategory.PROFILE_FRAME) { + return new Order(count, EquipStatus.AVAILABLE); + } + return new Order(count, EquipStatus.UNAVAILABLE); } //=== 비지니스 로직 ===// @@ -43,6 +56,14 @@ public boolean hasItem() { return this.count > 0; } + public int purchase() { + if (this.item.getItemCategory() == ItemCategory.PROFILE_FRAME && hasItem()) { + throw new BusinessException(ErrorCode.ALREADY_PURCHASED); + } + this.count++; + return count; + } + public void useItem() { if (!hasItem()) { throw new BusinessException(ErrorCode.HAS_NO_ITEM); @@ -50,18 +71,22 @@ public void useItem() { this.count -= 1; } + public void updateEquipStatus(EquipStatus equipStatus) { + this.equipStatus = equipStatus; + } + //=== 연관관계 편의 메서드 ===// public void setUser(User user) { this.user = user; - if (!user.getUserItemList().contains(this)) { - user.getUserItemList().add(this); + if (!user.getOrderList().contains(this)) { + user.getOrderList().add(this); } } public void setItem(Item item) { this.item = item; - if (!item.getUserItemList().contains(this)) { - item.getUserItemList().add(this); + if (!item.getOrderList().contains(this)) { + item.getOrderList().add(this); } } } diff --git a/src/main/java/com/genius/gitget/challenge/item/dto/ItemResponse.java b/src/main/java/com/genius/gitget/challenge/item/dto/ItemResponse.java new file mode 100644 index 00000000..ff8555b1 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/dto/ItemResponse.java @@ -0,0 +1,26 @@ +package com.genius.gitget.challenge.item.dto; + +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import lombok.Data; + +@Data +public class ItemResponse { + private Long itemId; + private ItemCategory itemCategory; + private String name; + private int cost; + private int count; + + protected ItemResponse(Item item, int count) { + this.itemId = item.getId(); + this.itemCategory = item.getItemCategory(); + this.name = item.getName(); + this.cost = item.getCost(); + this.count = count; + } + + public static ItemResponse create(Item item, int count) { + return new ItemResponse(item, count); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/item/dto/ItemUseResponse.java b/src/main/java/com/genius/gitget/challenge/item/dto/ItemUseResponse.java new file mode 100644 index 00000000..6428f337 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/dto/ItemUseResponse.java @@ -0,0 +1,22 @@ +package com.genius.gitget.challenge.item.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ItemUseResponse { + private Long instanceId; + private String title; + private int pointPerPerson; + + public ItemUseResponse(Long instanceId, String title, int pointPerPerson) { + this.instanceId = instanceId; + this.title = title; + this.pointPerPerson = pointPerPerson; + } + + public boolean isFrameResponse() { + return this.instanceId == 0L; + } +} diff --git a/src/main/java/com/genius/gitget/challenge/item/dto/ProfileResponse.java b/src/main/java/com/genius/gitget/challenge/item/dto/ProfileResponse.java new file mode 100644 index 00000000..31005902 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/dto/ProfileResponse.java @@ -0,0 +1,20 @@ +package com.genius.gitget.challenge.item.dto; + +import com.genius.gitget.challenge.item.domain.Item; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ProfileResponse extends ItemResponse { + private String equipStatus; + + public ProfileResponse(Item item, int numOfItem, String equipStatus) { + super(item, numOfItem); + this.equipStatus = equipStatus; + } + + public static ProfileResponse create(Item item, int numOfItem, String equipStatus) { + return new ProfileResponse(item, numOfItem, equipStatus); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java b/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java index 549f6f32..1e04a4a8 100644 --- a/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java +++ b/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java @@ -1,7 +1,14 @@ package com.genius.gitget.challenge.item.repository; import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface ItemRepository extends JpaRepository { + + @Query("select i from Item i where i.itemCategory = :category") + List findAllByCategory(@Param("category") ItemCategory itemCategory); } diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/OrderRepository.java b/src/main/java/com/genius/gitget/challenge/item/repository/OrderRepository.java new file mode 100644 index 00000000..a5ce8610 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/repository/OrderRepository.java @@ -0,0 +1,26 @@ +package com.genius.gitget.challenge.item.repository; + +import com.genius.gitget.challenge.item.domain.EquipStatus; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.Order; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface OrderRepository extends JpaRepository { + + @Query("select u from Order u where u.user.id = :userId and u.item.itemCategory = :itemCategory") + List findByCategory(@Param("userId") Long userId, + @Param("itemCategory") ItemCategory itemCategory); + + @Query("select u from Order u where u.user.id = :userId and u.item.id = :itemId") + Optional findByOrderInfo(@Param("userId") Long userId, + @Param("itemId") Long itemId); + + @Query("select u from Order u where u.user.id = :userId and u.item.itemCategory = :category and u.equipStatus = :equipStatus") + Optional findByEquipStatus(@Param("userId") Long userId, + @Param("category") ItemCategory category, + @Param("equipStatus") EquipStatus equipStatus); +} diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java b/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java deleted file mode 100644 index 75cea564..00000000 --- a/src/main/java/com/genius/gitget/challenge/item/repository/UserItemRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.genius.gitget.challenge.item.repository; - -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.UserItem; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface UserItemRepository extends JpaRepository { - - @Query("select u from UserItem u where u.user.id = :userId and u.item.itemCategory = :itemCategory") - Optional findUserItemByUser(@Param("userId") Long userId, - @Param("itemCategory") ItemCategory itemCategory); -} diff --git a/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java b/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java index 865d1e3d..bba8bd9b 100644 --- a/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java +++ b/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java @@ -1,6 +1,11 @@ package com.genius.gitget.challenge.item.service; +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; import com.genius.gitget.challenge.item.repository.ItemRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -10,4 +15,13 @@ @RequiredArgsConstructor public class ItemProvider { private final ItemRepository itemRepository; + + public Item findById(Long itemId) { + return itemRepository.findById(itemId) + .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); + } + + public List findAllByCategory(ItemCategory itemCategory) { + return itemRepository.findAllByCategory(itemCategory); + } } diff --git a/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java b/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java new file mode 100644 index 00000000..310a79da --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java @@ -0,0 +1,173 @@ +package com.genius.gitget.challenge.item.service; + +import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import com.genius.gitget.challenge.certification.service.CertificationService; +import com.genius.gitget.challenge.item.domain.EquipStatus; +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.dto.ItemResponse; +import com.genius.gitget.challenge.item.dto.ItemUseResponse; +import com.genius.gitget.challenge.item.dto.ProfileResponse; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; +import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; +import com.genius.gitget.challenge.myChallenge.service.MyChallengeService; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ItemService { + private final UserService userService; + private final ItemProvider itemProvider; + private final OrderProvider orderProvider; + + //TODO: Service를 의존하는게 맘에 들지 않는다 provider만을 의존하게 할 순 없을까? + private final CertificationService certificationService; + private final MyChallengeService myChallengeService; + + public List getAllItems(User user) { + List itemResponses = new ArrayList<>(); + for (ItemCategory itemCategory : ItemCategory.values()) { + itemResponses.addAll(getItemsByCategory(user, itemCategory)); + } + return itemResponses; + } + + public List getItemsByCategory(User user, ItemCategory itemCategory) { + List itemResponses = new ArrayList<>(); + List items = itemProvider.findAllByCategory(itemCategory); + + for (Item item : items) { + int numOfItem = orderProvider.countNumOfItem(user, item.getId()); + ItemResponse itemResponse = getItemResponse(user, item, numOfItem); + itemResponses.add(itemResponse); + } + + return itemResponses; + } + + @Transactional + public ItemResponse orderItem(User user, Long itemId) { + User persistUser = userService.findUserById(user.getId()); + Item item = itemProvider.findById(itemId); + + validateUserPoint(persistUser.getPoint(), item.getCost()); + + Order order = orderProvider.findOptionalByOrderInfo(persistUser.getId(), itemId) + .orElseGet(() -> createNew(persistUser, item)); + int numOfItem = order.purchase(); + persistUser.updatePoints((long) item.getCost() * -1); + + return getItemResponse(persistUser, item, numOfItem); + } + + private void validateUserPoint(long userPoint, int itemCost) { + if (userPoint < itemCost) { + throw new BusinessException(ErrorCode.NOT_ENOUGH_POINT); + } + } + + private Order createNew(User user, Item item) { + Order order = Order.createDefault(0, item.getItemCategory()); + order.setUser(user); + order.setItem(item); + return orderProvider.save(order); + } + + @Transactional + public ProfileResponse unmountFrame(User user, Long itemId) { + Order order = orderProvider.findByOrderInfo(user.getId(), itemId); + validateUnmountCondition(order); + + order.updateEquipStatus(EquipStatus.AVAILABLE); + + return ProfileResponse.create( + order.getItem(), order.getCount(), order.getEquipStatus().getTag()); + } + + private void validateUnmountCondition(Order order) { + if (order.getItem().getItemCategory() != ItemCategory.PROFILE_FRAME) { + throw new BusinessException(ErrorCode.ITEM_NOT_FOUND); + } + if (order.getEquipStatus() != EquipStatus.IN_USE) { + throw new BusinessException(ErrorCode.IN_USE_FRAME_NOT_FOUND); + } + } + + @Transactional + public ItemUseResponse useItem(User user, Long itemId, Long instanceId, LocalDate currentDate) { + Item item = itemProvider.findById(itemId); + Order order = orderProvider.findByOrderInfo(user.getId(), itemId); + + if (!order.hasItem()) { + throw new BusinessException(ErrorCode.HAS_NO_ITEM); + } + + switch (item.getItemCategory()) { + case PROFILE_FRAME -> { + return useProfileFrameItem(order); + } + case CERTIFICATION_PASSER -> { + return usePasserItem(order, instanceId, currentDate); + } + case POINT_MULTIPLIER -> { + return usePointMultiplierItem(order, instanceId, currentDate); + } + } + throw new BusinessException(ErrorCode.USER_ITEM_NOT_FOUND); + } + + private ItemUseResponse useProfileFrameItem(Order order) { + validateFrameEquip(order); + order.updateEquipStatus(EquipStatus.IN_USE); + return new ItemUseResponse(0L, "", 0); + } + + private void validateFrameEquip(Order order) { + if (!order.hasItem()) { + throw new BusinessException(ErrorCode.HAS_NO_ITEM); + } + if (order.getEquipStatus() != EquipStatus.AVAILABLE) { + throw new BusinessException(ErrorCode.INVALID_EQUIP_CONDITION); + } + } + + private ItemUseResponse usePasserItem(Order order, Long instanceId, LocalDate currentDate) { + Long userId = order.getUser().getId(); + Long itemId = order.getItem().getId(); + ActivatedResponse activatedResponse = certificationService.passCertification( + userId, + new CertificationRequest(instanceId, currentDate)); + activatedResponse.setItemId(itemId); + order.useItem(); + return activatedResponse; + } + + private ItemUseResponse usePointMultiplierItem(Order order, Long instanceId, LocalDate currentDate) { + User user = order.getUser(); + DoneResponse doneResponse = myChallengeService.getRewards( + new RewardRequest(user, instanceId, currentDate), true + ); + order.useItem(); + return doneResponse; + } + + private ItemResponse getItemResponse(User user, Item item, int numOfItem) { + if (item.getItemCategory() == ItemCategory.PROFILE_FRAME) { + EquipStatus equipStatus = orderProvider.getEquipStatus(user.getId(), item.getId()); + return ProfileResponse.create(item, numOfItem, equipStatus.getTag()); + } + return ItemResponse.create(item, numOfItem); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/item/service/OrderProvider.java b/src/main/java/com/genius/gitget/challenge/item/service/OrderProvider.java new file mode 100644 index 00000000..a3196210 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/service/OrderProvider.java @@ -0,0 +1,53 @@ +package com.genius.gitget.challenge.item.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.USER_ITEM_NOT_FOUND; + +import com.genius.gitget.challenge.item.domain.EquipStatus; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.repository.OrderRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.exception.BusinessException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class OrderProvider { + private final OrderRepository orderRepository; + + + public Order save(Order order) { + return orderRepository.save(order); + } + + public Optional findOptionalByOrderInfo(Long userId, Long itemId) { + return orderRepository.findByOrderInfo(userId, itemId); + } + + public Order findByOrderInfo(Long userId, Long itemId) { + return orderRepository.findByOrderInfo(userId, itemId) + .orElseThrow(() -> new BusinessException(USER_ITEM_NOT_FOUND)); + } + + public EquipStatus getEquipStatus(Long userId, Long itemId) { + Optional optionalUserItem = orderRepository.findByOrderInfo(userId, itemId); + if (optionalUserItem.isPresent()) { + return optionalUserItem.get().getEquipStatus(); + } + return EquipStatus.UNAVAILABLE; + } + + public int countNumOfCategory(User user, ItemCategory itemCategory) { + return orderRepository.findByCategory(user.getId(), itemCategory).size(); + } + + public int countNumOfItem(User user, Long itemId) { + Optional optionalUserItem = orderRepository.findByOrderInfo(user.getId(), itemId); + return optionalUserItem.map(Order::getCount) + .orElse(0); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/item/service/UserItemProvider.java b/src/main/java/com/genius/gitget/challenge/item/service/UserItemProvider.java deleted file mode 100644 index 06dd57d6..00000000 --- a/src/main/java/com/genius/gitget/challenge/item/service/UserItemProvider.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.genius.gitget.challenge.item.service; - -import static com.genius.gitget.global.util.exception.ErrorCode.USER_ITEM_NOT_FOUND; - -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.UserItem; -import com.genius.gitget.challenge.item.repository.UserItemRepository; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.global.util.exception.BusinessException; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class UserItemProvider { - private final UserItemRepository userItemRepository; - - public UserItem findUserItemByUser(Long userId, ItemCategory itemCategory) { - return userItemRepository.findUserItemByUser(userId, itemCategory) - .orElseThrow(() -> new BusinessException(USER_ITEM_NOT_FOUND)); - } - - public int countNumOfItem(User user, ItemCategory itemCategory) { - Optional optionalUserItem = userItemRepository.findUserItemByUser(user.getId(), itemCategory); - return optionalUserItem.map(UserItem::getCount) - .orElse(0); - } -} diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java index 894c6d0a..aa2a5b2a 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java @@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -68,16 +67,14 @@ public ResponseEntity> getDoneChallenges( ); } - // /api/challenges/reward/1?item=true @GetMapping("/reward/{instanceId}") public ResponseEntity> getRewards( @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long instanceId, - @RequestParam(value = "item") boolean useItem + @PathVariable Long instanceId ) { - RewardRequest rewardRequest = new RewardRequest(userPrincipal.getUser(), instanceId, useItem, LocalDate.now()); - DoneResponse doneResponse = myChallengeService.getRewards(rewardRequest); + RewardRequest rewardRequest = new RewardRequest(userPrincipal.getUser(), instanceId, LocalDate.now()); + DoneResponse doneResponse = myChallengeService.getRewards(rewardRequest, false); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), doneResponse) diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java index 0ca84552..7ca69d63 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java @@ -2,20 +2,34 @@ import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.item.dto.ItemUseResponse; import com.genius.gitget.global.file.dto.FileResponse; import lombok.Builder; +import lombok.Getter; +import lombok.Setter; -@Builder -public record ActivatedResponse( - Long instanceId, - String title, - int pointPerPerson, - String repository, - String certificateStatus, - int numOfPassItem, - boolean canUsePassItem, - FileResponse fileResponse -) { +@Getter +@Setter +public class ActivatedResponse extends ItemUseResponse { + private String repository; + private String certificateStatus; + private Long itemId; + private int numOfPassItem; + private boolean canUsePassItem; + private FileResponse fileResponse; + + @Builder + public ActivatedResponse(Long instanceId, String title, int pointPerPerson, String repository, + String certificateStatus, Long itemId, + int numOfPassItem, boolean canUsePassItem, FileResponse fileResponse) { + super(instanceId, title, pointPerPerson); + this.repository = repository; + this.certificateStatus = certificateStatus; + this.itemId = itemId; + this.numOfPassItem = numOfPassItem; + this.canUsePassItem = canUsePassItem; + this.fileResponse = fileResponse; + } public static ActivatedResponse create(Instance instance, CertificateStatus certificateStatus, int numOfPassItem, String repository) { diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java index ee80a97a..11b069da 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java @@ -1,24 +1,38 @@ package com.genius.gitget.challenge.myChallenge.dto; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.item.dto.ItemUseResponse; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.domain.RewardStatus; import com.genius.gitget.global.file.dto.FileResponse; import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class DoneResponse extends ItemUseResponse { + private JoinResult joinResult; + private Long itemId; + private boolean canGetReward; + private int numOfPointItem; + private int rewardedPoints; + private double achievementRate; + private FileResponse fileResponse; + + @Builder + public DoneResponse(Long instanceId, String title, int pointPerPerson, JoinResult joinResult, boolean canGetReward, + int numOfPointItem, int rewardedPoints, double achievementRate, FileResponse fileResponse) { + super(instanceId, title, pointPerPerson); + this.joinResult = joinResult; + this.canGetReward = canGetReward; + this.numOfPointItem = numOfPointItem; + this.rewardedPoints = rewardedPoints; + this.achievementRate = achievementRate; + this.fileResponse = fileResponse; + } -@Builder -public record DoneResponse( - Long instanceId, - String title, - int pointPerPerson, - JoinResult joinResult, - boolean canGetReward, - int numOfPointItem, - int rewardedPoints, - double achievementRate, - FileResponse fileResponse -) { public static DoneResponse createNotRewarded(Instance instance, Participant participant, int numOfPointItem) { diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java index 3bba8175..6f82b5e4 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java @@ -6,7 +6,6 @@ public record RewardRequest( User user, Long instanceId, - Boolean canUseItem, LocalDate targetDate ) { } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index bb485c63..c17483b2 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -1,6 +1,8 @@ package com.genius.gitget.challenge.myChallenge.service; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.item.domain.ItemCategory.CERTIFICATION_PASSER; +import static com.genius.gitget.challenge.item.domain.ItemCategory.POINT_MULTIPLIER; import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; import static com.genius.gitget.challenge.participant.domain.RewardStatus.NO; import static com.genius.gitget.challenge.participant.domain.RewardStatus.YES; @@ -11,9 +13,9 @@ import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.UserItem; -import com.genius.gitget.challenge.item.service.UserItemProvider; +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.service.ItemProvider; +import com.genius.gitget.challenge.item.service.OrderProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; @@ -39,7 +41,8 @@ public class MyChallengeService { private final UserService userService; private final ParticipantProvider participantProvider; private final CertificationProvider certificationProvider; - private final UserItemProvider userItemProvider; + private final ItemProvider itemProvider; + private final OrderProvider orderProvider; public List getPreActivityInstances(User user, LocalDate targetDate) { @@ -72,8 +75,10 @@ public List getDoneInstances(User user, LocalDate targetDate) { // 포인트를 아직 수령하지 않았을 때 if (participant.getRewardStatus() == NO) { - int numOfPassItem = userItemProvider.countNumOfItem(user, ItemCategory.POINT_MULTIPLIER); + Item item = itemProvider.findAllByCategory(POINT_MULTIPLIER).get(0); + int numOfPassItem = orderProvider.countNumOfItem(user, item.getId()); DoneResponse doneResponse = DoneResponse.createNotRewarded(instance, participant, numOfPassItem); + doneResponse.setItemId(item.getId()); done.add(doneResponse); continue; } @@ -104,12 +109,16 @@ public List getActivatedInstances(User user, LocalDate target Instance instance = participant.getInstance(); Certification certification = certificationProvider.findByDate(targetDate, participant.getId()) .orElse(getDummyCertification()); - int numOfPassItem = userItemProvider.countNumOfItem(user, ItemCategory.CERTIFICATION_PASSER); + + //TODO: 로직 수정 필요 + Item item = itemProvider.findAllByCategory(CERTIFICATION_PASSER).get(0); + int numOfPassItem = orderProvider.countNumOfItem(user, item.getId()); ActivatedResponse activatedResponse = ActivatedResponse.create( instance, certification.getCertificationStatus(), numOfPassItem, participant.getRepositoryName() ); + activatedResponse.setItemId(item.getId()); activated.add(activatedResponse); } return activated; @@ -125,20 +134,16 @@ private Certification getDummyCertification() { } @Transactional - public DoneResponse getRewards(RewardRequest rewardRequest) { + public DoneResponse getRewards(RewardRequest rewardRequest, boolean useItem) { User user = userService.findUserById(rewardRequest.user().getId()); Participant participant = participantProvider.findByJoinInfo(user.getId(), rewardRequest.instanceId()); Instance instance = participant.getInstance(); validRewardCondition(participant); - int pointPerPerson = instance.getPointPerPerson(); - int rewardPoints = pointPerPerson; - - if (rewardRequest.canUseItem()) { - UserItem userItem = userItemProvider.findUserItemByUser(user.getId(), ItemCategory.POINT_MULTIPLIER); - userItem.useItem(); - rewardPoints = pointPerPerson * 2; + int rewardPoints = instance.getPointPerPerson(); + if (useItem) { + rewardPoints *= 2; } user.updatePoints((long) rewardPoints); diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 1060a3c7..599b71d8 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -1,6 +1,6 @@ package com.genius.gitget.challenge.user.domain; -import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.domain.Order; import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.Files; @@ -54,7 +54,7 @@ public class User extends BaseTimeEntity { private List payment = new ArrayList<>(); @OneToMany(mappedBy = "user") - private List userItemList = new ArrayList<>(); + private List orderList = new ArrayList<>(); @NotNull @Enumerated(EnumType.STRING) diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index b3f67051..55e57970 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -61,9 +61,16 @@ public enum ErrorCode { NOT_ACTIVITY_INSTANCE(HttpStatus.BAD_REQUEST, "진행 중인 챌린지에 대해서만 인증이 가능합니다."), NOT_CERTIFICATE_PERIOD(HttpStatus.BAD_REQUEST, "챌린지 인증은 챌린지 진행 기간 내에만 가능합니다. 챌린지 진행 기간인지 확인해주세요."), + NOT_ENOUGH_POINT(HttpStatus.BAD_REQUEST, "사용자의 보유 포인트가 충분하지 않습니다."), + ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "아이템 정보를 찾을 수 없습니다."), USER_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자의 아이템 보유 정보를 찾을 수 없습니다."), HAS_NO_ITEM(HttpStatus.NOT_FOUND, "해당 아이템을 보유하고 있지 않습니다."), CAN_NOT_USE_PASS_ITEM(HttpStatus.BAD_REQUEST, "인증 패스 아이템을 사용할 수 없는 조건입니다."), + ITEM_CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "해당 카테고리에 맞는 아이템을 찾을 수 없습니다."), + + ALREADY_PURCHASED(HttpStatus.BAD_REQUEST, "프로필 프레임은 재구매가 불가능 합니다."), + INVALID_EQUIP_CONDITION(HttpStatus.BAD_REQUEST, "프로필 프레임을 장착할 수 있는 상태가 아닙니다."), + IN_USE_FRAME_NOT_FOUND(HttpStatus.NOT_FOUND, "사용 중인 프로필 프레임을 찾을 수 없습니다"), CAN_NOT_GET_REWARDS(HttpStatus.BAD_REQUEST, "챌린지 보상을 받을 수 있는 조건이 아닙니다."), ALREADY_REWARDED(HttpStatus.BAD_REQUEST, "해당 챌린지 보상은 이미 지급되었습니다."); diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 00000000..81358355 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,14 @@ +INSERT INTO item (cost, created_at, deleted_at, updated_at, details, name, item_category) +SELECT * +FROM (SELECT 100 AS cost, + NULL AS created_at, + NULL AS deleted_at, + NULL AS updated_at, + '프로필을 꾸밀 수 있는 프레임입니다.' AS details, + '프로필 프레임' AS name, + 'PROFILE_FRAME' AS item_category + UNION ALL + SELECT 100, NULL, NULL, NULL, '오늘의 인증을 넘길 수 있는 아이템입니다.', '인증 패스 아이템', 'CERTIFICATION_PASSER' + UNION ALL + SELECT 100, NULL, NULL, NULL, '포인트 보상을 2배로 받을 수 있는 아이템입니다.', '포인트 2배 획득 아이템', 'POINT_MULTIPLIER') AS new_items +WHERE (SELECT COUNT(*) FROM item) < 3; diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index a36384ce..b8a268cd 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -23,9 +23,9 @@ import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.domain.Order; import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.UserItemRepository; +import com.genius.gitget.challenge.item.repository.OrderRepository; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; @@ -74,7 +74,7 @@ class CertificationServiceTest { @Autowired private ItemRepository itemRepository; @Autowired - private UserItemRepository userItemRepository; + private OrderRepository orderRepository; @Value("${github.yeon-personalKey}") private String personalKey; @@ -388,14 +388,13 @@ public void should_getWeekCertification_aboutAllParticipants() { } @Test - @DisplayName("아직 인증을 하지 않았고, 패스 아이템이 있을 때 해당 일자의 인증을 패스할 수 있다.") + @DisplayName("아직 인증을 하지 않았을 때 해당 일자의 인증을 패스할 수 있다.") public void should_passCertification_when_conditionIsValid() { //given LocalDate currentDate = LocalDate.of(2024, 3, 1); User user = getSavedUser(githubId); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) .targetDate(currentDate) @@ -413,59 +412,14 @@ public void should_passCertification_when_conditionIsValid() { certificationRequest); //then - assertThat(activatedResponse.instanceId()).isEqualTo(instance.getId()); - assertThat(activatedResponse.title()).isEqualTo(instance.getTitle()); - assertThat(activatedResponse.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(activatedResponse.repository()).isEqualTo(participant.getRepositoryName()); - assertThat(activatedResponse.certificateStatus()).isEqualTo(PASSED.getTag()); - assertThat(activatedResponse.numOfPassItem()).isEqualTo(0); - assertThat(activatedResponse.canUsePassItem()).isFalse(); - assertThat(activatedResponse.fileResponse()).isNotNull(); - assertThat(userItem.getCount()).isEqualTo(0); - } - - @Test - @DisplayName("UserItem 정보가 DB에 존재하지 않을 때 인증 패스를 요청하면 예외가 발생해야 한다.") - public void should_throwException_when_userItemInfoNotExist() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(currentDate) - .build(); - - //when - getSavedCertification(NOT_YET, currentDate, participant); - - //then - assertThatThrownBy(() -> certificationService.passCertification(user.getId(), certificationRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("UserItem 정보는 있으나 아이템의 개수가 0 이하일 때 인증 패스를 요청하면 예외가 발생해야 한다.") - public void should_throwException_when_outOfStock() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 0); - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(currentDate) - .build(); - - instance.updateProgress(Progress.ACTIVITY); - - //when && then - assertThatThrownBy(() -> certificationService.passCertification(user.getId(), certificationRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); + assertThat(activatedResponse.getInstanceId()).isEqualTo(instance.getId()); + assertThat(activatedResponse.getTitle()).isEqualTo(instance.getTitle()); + assertThat(activatedResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(activatedResponse.getRepository()).isEqualTo(participant.getRepositoryName()); + assertThat(activatedResponse.getCertificateStatus()).isEqualTo(PASSED.getTag()); + assertThat(activatedResponse.getNumOfPassItem()).isEqualTo(0); + assertThat(activatedResponse.isCanUsePassItem()).isFalse(); + assertThat(activatedResponse.getFileResponse()).isNotNull(); } @ParameterizedTest @@ -477,7 +431,6 @@ public void should_throwException_when_challengeIsNotNOT_YET(CertificateStatus c User user = getSavedUser(githubId); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) .targetDate(currentDate) @@ -501,7 +454,7 @@ public void should_overwriteData_when_certificatedBefore() { User user = getSavedUser(githubId); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); + Order order = getSavedOrder(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) .targetDate(currentDate) @@ -514,14 +467,14 @@ public void should_overwriteData_when_certificatedBefore() { certificationRequest); //then - assertThat(activatedResponse.instanceId()).isEqualTo(instance.getId()); - assertThat(activatedResponse.title()).isEqualTo(instance.getTitle()); - assertThat(activatedResponse.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(activatedResponse.repository()).isEqualTo(participant.getRepositoryName()); - assertThat(activatedResponse.certificateStatus()).isEqualTo(PASSED.getTag()); - assertThat(activatedResponse.numOfPassItem()).isEqualTo(0); - assertThat(activatedResponse.canUsePassItem()).isFalse(); - assertThat(activatedResponse.fileResponse()).isNotNull(); + assertThat(activatedResponse.getInstanceId()).isEqualTo(instance.getId()); + assertThat(activatedResponse.getTitle()).isEqualTo(instance.getTitle()); + assertThat(activatedResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(activatedResponse.getRepository()).isEqualTo(participant.getRepositoryName()); + assertThat(activatedResponse.getCertificateStatus()).isEqualTo(PASSED.getTag()); + assertThat(activatedResponse.getNumOfPassItem()).isEqualTo(0); + assertThat(activatedResponse.isCanUsePassItem()).isFalse(); + assertThat(activatedResponse.getFileResponse()).isNotNull(); } @Test @@ -532,7 +485,7 @@ public void should_usePassItem_when_conditionIsValid() { User user = getSavedUser(githubId); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - UserItem userItem = getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 1); + Order order = getSavedOrder(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) .targetDate(currentDate) @@ -544,14 +497,14 @@ public void should_usePassItem_when_conditionIsValid() { certificationRequest); //then - assertThat(activatedResponse.instanceId()).isEqualTo(instance.getId()); - assertThat(activatedResponse.title()).isEqualTo(instance.getTitle()); - assertThat(activatedResponse.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(activatedResponse.repository()).isEqualTo(participant.getRepositoryName()); - assertThat(activatedResponse.certificateStatus()).isEqualTo(PASSED.getTag()); - assertThat(activatedResponse.numOfPassItem()).isEqualTo(0); - assertThat(activatedResponse.canUsePassItem()).isFalse(); - assertThat(activatedResponse.fileResponse()).isNotNull(); + assertThat(activatedResponse.getInstanceId()).isEqualTo(instance.getId()); + assertThat(activatedResponse.getTitle()).isEqualTo(instance.getTitle()); + assertThat(activatedResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(activatedResponse.getRepository()).isEqualTo(participant.getRepositoryName()); + assertThat(activatedResponse.getCertificateStatus()).isEqualTo(PASSED.getTag()); + assertThat(activatedResponse.getNumOfPassItem()).isEqualTo(0); + assertThat(activatedResponse.isCanUsePassItem()).isFalse(); + assertThat(activatedResponse.getFileResponse()).isNotNull(); } @@ -618,13 +571,13 @@ private Certification getSavedCertification(CertificateStatus status, LocalDate return certificationRepository.save(certification); } - private UserItem getSavedUserItem(User user, ItemCategory itemCategory, int count) { + private Order getSavedOrder(User user, ItemCategory itemCategory, int count) { Item item = itemRepository.save(Item.builder() .itemCategory(itemCategory) .build()); - UserItem userItem = new UserItem(count); - userItem.setItem(item); - userItem.setUser(user); - return userItemRepository.save(userItem); + Order order = Order.createDefault(count, itemCategory); + order.setItem(item); + order.setUser(user); + return orderRepository.save(order); } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java new file mode 100644 index 00000000..e8da5e04 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java @@ -0,0 +1,78 @@ +package com.genius.gitget.challenge.item.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.repository.ItemRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class ItemProviderTest { + @Autowired + ItemRepository itemRepository; + @Autowired + ItemProvider itemProvider; + + @ParameterizedTest + @DisplayName("DB에 저장되어 있는 아이템을 카테고리 별로 받아올 수 있다.") + @EnumSource(mode = Mode.INCLUDE, names = {"POINT_MULTIPLIER", "CERTIFICATION_PASSER", "PROFILE_FRAME"}) + public void should_findItems_when_passCategory(ItemCategory itemCategory) { + //given + Item item = getSavedItem(itemCategory); + + //when + List items = itemProvider.findAllByCategory(itemCategory); + + //then + assertThat(items.size()).isEqualTo(2); + Item foundItem = items.get(0); + assertThat(foundItem.getItemCategory()).isEqualTo(item.getItemCategory()); + } + + @Test + @DisplayName("DB에 저장되어 있는 아이템을 식별자 PK를 통해 조회할 수 있다.") + public void should_findItem_when_passPK() { + //given + Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + + //when + Item foundItem = itemProvider.findById(item.getId()); + + //then + assertThat(item.getId()).isEqualTo(foundItem.getId()); + assertThat(item.getItemCategory()).isEqualTo(foundItem.getItemCategory()); + assertThat(item.getCost()).isEqualTo(foundItem.getCost()); + } + + @Test + @DisplayName("PK를 통해 아이템을 조회하려고 했을 때, 존재하지 않으면 예외를 발생시켜야 한다.") + public void should_throwException_when_pkNotExist() { + assertThatThrownBy(() -> itemProvider.findById(0L)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.ITEM_NOT_FOUND.getMessage()); + } + + private Item getSavedItem(ItemCategory itemCategory) { + return itemRepository.save( + Item.builder() + .cost(100) + .itemCategory(itemCategory) + .build() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java new file mode 100644 index 00000000..dc365870 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -0,0 +1,474 @@ +package com.genius.gitget.challenge.item.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.item.domain.EquipStatus; +import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.dto.ItemResponse; +import com.genius.gitget.challenge.item.dto.ItemUseResponse; +import com.genius.gitget.challenge.item.dto.ProfileResponse; +import com.genius.gitget.challenge.item.repository.ItemRepository; +import com.genius.gitget.challenge.item.repository.OrderRepository; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class ItemServiceTest { + @Autowired + private ItemService itemService; + @Autowired + private UserRepository userRepository; + @Autowired + private ItemRepository itemRepository; + @Autowired + private OrderRepository orderRepository; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private CertificationRepository certificationRepository; + + @Test + @DisplayName("데이터베이스에 저장되어 있는 모든 아이템 정보들을 받아올 수 있다.") + public void should_getAllItems_when_itemsSaved() { + //given + User user = getSavedUser(); + + //when + List items = itemService.getAllItems(user); + + //then + assertThat(items.size()).isEqualTo(3); + } + + @ParameterizedTest + @DisplayName("카테고리에 해당하는 아이템들을 받아올 수 있다.") + @EnumSource(ItemCategory.class) + public void should_getItems_when_passCategory(ItemCategory itemCategory) { + //given + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + Order order = getSavedOrder(user, item, itemCategory, 1); + + //when + List itemResponses = itemService.getItemsByCategory(user, itemCategory); + + //then + for (ItemResponse itemResponse : itemResponses) { + assertThat(itemResponse.getName()).isEqualTo(itemCategory.getName()); + } + } + + @ParameterizedTest + @DisplayName("사용자의 포인트가 충분할 때, itemId(PK)를 전달하여 아이템을 구매할 수 있다.") + @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) + public void should_purchaseItem_when_passPK(ItemCategory itemCategory) { + //given + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + + user.updatePoints(1000L); + + //when + ItemResponse itemResponse = itemService.orderItem(user, item.getId()); + + //then + assertThat(itemResponse.getItemId()).isEqualTo(item.getId()); + assertThat(itemResponse.getName()).isEqualTo(item.getName()); + assertThat(itemResponse.getCost()).isEqualTo(item.getCost()); + assertThat(itemResponse.getCount()).isEqualTo(1); + } + + @ParameterizedTest + @DisplayName("사용자의 포인트가 충분하지 않을 때, 아이템 구매를 시도하면 예외가 발생해야 한다.") + @EnumSource(ItemCategory.class) + public void should_throwException_when_pointNotEnough(ItemCategory itemCategory) { + //given + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + + //when & then + assertThatThrownBy(() -> itemService.orderItem(user, item.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.NOT_ENOUGH_POINT.getMessage()); + } + + @Test + @DisplayName("UserItem 정보는 있으나 아이템의 개수가 0 이하일 때 인증 패스를 요청하면 예외가 발생해야 한다.") + public void should_throwException_when_outOfStock() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + Order order = getSavedOrder(user, item, ItemCategory.CERTIFICATION_PASSER, 0); + + instance.updateProgress(Progress.ACTIVITY); + + //when && then + assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.HAS_NO_ITEM.getMessage()); + } + + @Test + @DisplayName("UserItem 정보가 DB에 존재하지 않을 때 인증 패스를 요청하면 예외가 발생해야 한다.") + public void should_throwException_when_userItemInfoNotExist() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + + //when + getSavedCertification(NOT_YET, currentDate, participant); + + //then + assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("프로필 프레임을 구매했는데 장착하지 않은 경우에는 프레임을 사용할 수 있다.") + public void should_useFrameItem_when_availableToEquip() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + + //when + order.updateEquipStatus(EquipStatus.AVAILABLE); + ItemUseResponse itemUseResponse = itemService.useItem(user, item.getId(), instance.getId(), currentDate); + + //then + assertThat(order.getEquipStatus()).isEqualTo(EquipStatus.IN_USE); + } + + @Test + @DisplayName("프로필 프레임을 재구매시도할 경우 예외가 발생해야 한다.") + public void should_throwException_when_tryOrderFrameAgain() { + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + + user.updatePoints(1000L); + + // when + itemService.orderItem(user, item.getId()); + + //when & then + assertThatThrownBy(() -> itemService.orderItem(user, item.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.ALREADY_PURCHASED.getMessage()); + } + + @Test + @DisplayName("프로필 프레임을 사용하려 할 때, 해당 프레임에 대한 수량이 0이라면 예외가 발생해야 한다.") + public void should_throwException_when_dontHaveItem() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 0); + + //when && then + assertThatThrownBy(() -> itemService.useItem(user, item.getId(), 0L, LocalDate.now())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.HAS_NO_ITEM.getMessage()); + } + + @ParameterizedTest + @DisplayName("프로필 프레임을 사용하려 할 때, 프레임의 장착 상태가 AVAILABLE이 아니라면 예외가 발생해야 한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"IN_USE", "UNAVAILABLE"}) + public void should_throwException_when_notAvailable(EquipStatus equipStatus) { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + Order order = getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 3); + + order.updateEquipStatus(equipStatus); + + //when && then + assertThatThrownBy(() -> itemService.useItem(user, item.getId(), 0L, LocalDate.now())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.INVALID_EQUIP_CONDITION.getMessage()); + } + + @Test + @DisplayName("인증 패스 아이템을 보유하고 있을 떄, 인증을 시도했으나 실패했을 때 아이템을 사용할 수 있다.") + public void should_usePasserItem_when_ableToUsePassItem() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + + //when + instance.updateProgress(Progress.ACTIVITY); + Certification certification = getSavedCertification(NOT_YET, currentDate, participant); + ItemUseResponse itemUseResponse = itemService.useItem(user, item.getId(), instance.getId(), currentDate); + + //then + assertThat(itemUseResponse.getInstanceId()).isEqualTo(instance.getId()); + assertThat(itemUseResponse.getTitle()).isEqualTo(instance.getTitle()); + assertThat(itemUseResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(order.getCount()).isEqualTo(0); + assertThat(certification.getCertificationStatus()).isEqualTo(PASSED); + } + + @Test + @DisplayName("인증 패스 아이템을 보유하고 있고, 인증을 아직 시도하지 않았을 때 아이템을 사용할 수 있다.") + public void should_usePasserItem_when_useItemNotYet() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + + //when + instance.updateProgress(Progress.ACTIVITY); + + //then + ItemUseResponse itemUseResponse = itemService.useItem(user, item.getId(), instance.getId(), currentDate); + + //then + Optional certification = certificationRepository.findByDate(currentDate, participant.getId()); + assertThat(itemUseResponse.getInstanceId()).isEqualTo(instance.getId()); + assertThat(itemUseResponse.getTitle()).isEqualTo(instance.getTitle()); + assertThat(itemUseResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); + assertThat(order.getCount()).isEqualTo(0); + assertThat(certification).isPresent(); + assertThat(certification.get().getCertificationStatus()).isEqualTo(PASSED); + } + + @Test + @DisplayName("인증 패스 아이템을 보유하고 있으나, 인스턴스의 상태가 ACTIVITY가 아니라면 사용 시도 시 예외가 발생해야 한다.") + public void should_throwException_when_ProgressIsNotActivity() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + + //when & then + assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.NOT_ACTIVITY_INSTANCE.getMessage()); + } + + @ParameterizedTest + @DisplayName("인증 패스 아이템을 보유하고 있으나, 인증 상태가 PASSED 혹은 CERTIFICATED라면 예외가 발생해야 한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"CERTIFICATED", "PASSED"}) + public void should_throwException_when_notNOT_YET(CertificateStatus certificateStatus) { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + getSavedOrder(user, item, item.getItemCategory(), 1); + + //when + instance.updateProgress(Progress.ACTIVITY); + getSavedCertification(certificateStatus, currentDate, participant); + + //then + assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.CAN_NOT_USE_PASS_ITEM.getMessage()); + } + + @Test + @DisplayName("인스턴스의 상태가 DONE이고 2배 획득 아이템을 보유하고 있을 때, 포인트 보상을 2배로 받을 수 있다.") + public void should_getRewardTwice_when_conditionMatches() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(currentDate, currentDate.plusDays(1)); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.POINT_MULTIPLIER); + Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + + //when + Long previousPoint = user.getPoint(); + instance.updateProgress(Progress.DONE); + participant.updateJoinResult(JoinResult.SUCCESS); + getSavedCertification(CERTIFICATED, currentDate, participant); + getSavedCertification(CERTIFICATED, currentDate.plusDays(1), participant); + itemService.useItem(user, item.getId(), instance.getId(), currentDate.plusDays(1)); + Long afterRewards = user.getPoint(); + + //then + assertThat(afterRewards - previousPoint).isEqualTo(instance.getPointPerPerson() * 2L); + assertThat(order.getCount()).isEqualTo(0); + } + + @Test + @DisplayName("사용자가 특정 프로필 프레임을 장착하고 있을 떄, 장착 해제할 수 있다.") + public void should_unmountFrame_when_mountAlready() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 1); + + //when + itemService.useItem(user, item.getId(), 0L, LocalDate.now()); + ProfileResponse profileResponse = itemService.unmountFrame(user, item.getId()); + + //then + assertThat(profileResponse.getItemId()).isEqualTo(item.getId()); + assertThat(profileResponse.getCost()).isEqualTo(item.getCost()); + assertThat(profileResponse.getItemCategory()).isEqualTo(ItemCategory.PROFILE_FRAME); + assertThat(profileResponse.getEquipStatus()).isEqualTo(EquipStatus.AVAILABLE.getTag()); + } + + @ParameterizedTest + @DisplayName("사용자가 아이템 장착 해제를 요청했을 때, 프로필 프레임이 아니라면 예외가 발생한다.") + @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) + public void should_throwException_when_categoryIsNotFrame(ItemCategory itemCategory) { + //given + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + + //when & then + assertThatThrownBy(() -> itemService.unmountFrame(user, item.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.ITEM_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("사용자가 아이템 장착 해제를 요청했을 때, 사용 상태가 IN_USE가 아니라면 예외가 발생한다.") + public void should_throwException_when_equipStatusIsNotIS_USE() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + getSavedOrder(user, item, item.getItemCategory(), 1); + + //when & then + assertThatThrownBy(() -> itemService.unmountFrame(user, item.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.IN_USE_FRAME_NOT_FOUND.getMessage()); + } + + + private User getSavedUser() { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("identifier") + .build() + ); + } + + private Item getSavedItem(ItemCategory itemCategory) { + return itemRepository.save(Item.builder() + .itemCategory(itemCategory) + .cost(100) + .name(itemCategory.getName()) + .build()); + } + + private Order getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { + Order order = Order.createDefault(count, itemCategory); + order.setItem(item); + order.setUser(user); + return orderRepository.save(order); + } + + private Instance getSavedInstance() { + return instanceRepository.save( + Instance.builder() + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) + .completedDate(LocalDateTime.of(2024, 3, 29, 0, 0)) + .build() + ); + } + + private Instance getSavedInstance(LocalDate startDate, LocalDate completeDate) { + return instanceRepository.save( + Instance.builder() + .progress(Progress.PREACTIVITY) + .startedDate(startDate.atTime(0, 0)) + .completedDate(completeDate.atTime(0, 0)) + .build() + ); + } + + private Participant getSavedParticipant(User user, Instance instance) { + Participant participant = participantRepository.save( + Participant.builder() + .joinResult(JoinResult.PROCESSING) + .joinStatus(JoinStatus.YES) + .build() + ); + participant.setUserAndInstance(user, instance); + participant.updateRepository("targetRepo"); + + return participant; + } + + + private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, + Participant participant) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); + Certification certification = Certification.builder() + .certificationStatus(status) + .currentAttempt(attempt) + .certificatedAt(certificatedAt) + .certificationLinks("certificationLink") + .build(); + certification.setParticipant(participant); + return certificationRepository.save(certification); + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/item/service/UserItemProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/OrderProviderTest.java similarity index 55% rename from src/test/java/com/genius/gitget/challenge/item/service/UserItemProviderTest.java rename to src/test/java/com/genius/gitget/challenge/item/service/OrderProviderTest.java index 0073be15..54a7b181 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/UserItemProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/OrderProviderTest.java @@ -1,21 +1,17 @@ package com.genius.gitget.challenge.item.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.domain.Order; import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.UserItemRepository; +import com.genius.gitget.challenge.item.repository.OrderRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import lombok.extern.slf4j.Slf4j; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -25,43 +21,15 @@ @Slf4j @SpringBootTest @Transactional -class UserItemProviderTest { +class OrderProviderTest { @Autowired private UserRepository userRepository; @Autowired private ItemRepository itemRepository; @Autowired - private UserItemRepository userItemRepository; + private OrderRepository orderRepository; @Autowired - private UserItemProvider userItemProvider; - - @Test - @DisplayName("사용자가 특정 아이템을 보유하고 있는지 정보를 조회할 수 있다.") - public void should_checkItem_when_userHaveItem() { - //given - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - getSavedUserItem(user, item, 1); - - //when - UserItem userItemByUser = userItemProvider.findUserItemByUser(user.getId(), ItemCategory.PROFILE_FRAME); - - //then - Assertions.assertThat(userItemByUser.getCount()).isEqualTo(1); - } - - @Test - @DisplayName("사용자의 아이템 보유 정보가 없다면 예외가 발생한다.") - public void should_throwException_when_userItemNotExist() { - //given - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - - //when & then - assertThatThrownBy(() -> userItemProvider.findUserItemByUser(user.getId(), ItemCategory.PROFILE_FRAME)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); - } + private OrderProvider orderProvider; @Test @DisplayName("사용자가 특정 아이템을 보유하고 있을 때, 보유하고 있는 아이템의 개수를 반환받을 수 있다.") @@ -69,10 +37,10 @@ public void should_returnItemCount_when_haveItem() { //given User user = getSavedUser(); Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - getSavedUserItem(user, item, 1); + getSavedOrder(user, item, 1); //when - int numOfItem = userItemProvider.countNumOfItem(user, ItemCategory.PROFILE_FRAME); + int numOfItem = orderProvider.countNumOfItem(user, item.getId()); //then assertThat(numOfItem).isEqualTo(1); @@ -86,7 +54,7 @@ public void should_returnZero_when_dataNotSaved() { Item item = getSavedItem(ItemCategory.PROFILE_FRAME); //when - int numOfItem = userItemProvider.countNumOfItem(user, ItemCategory.PROFILE_FRAME); + int numOfItem = orderProvider.countNumOfItem(user, item.getId()); //then assertThat(numOfItem).isEqualTo(0); @@ -114,10 +82,10 @@ private Item getSavedItem(ItemCategory itemCategory) { ); } - private UserItem getSavedUserItem(User user, Item item, int count) { - UserItem userItem = new UserItem(count); - userItem.setUser(user); - userItem.setItem(item); - return userItemRepository.save(userItem); + private Order getSavedOrder(User user, Item item, int count) { + Order order = Order.createDefault(count, item.getItemCategory()); + order.setUser(user); + order.setItem(item); + return orderRepository.save(order); } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java index ad73fee3..a36cb1c5 100644 --- a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java @@ -13,9 +13,9 @@ import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.UserItem; +import com.genius.gitget.challenge.item.domain.Order; import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.UserItemRepository; +import com.genius.gitget.challenge.item.repository.OrderRepository; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; @@ -56,7 +56,7 @@ class MyChallengeServiceTest { @Autowired private ItemRepository itemRepository; @Autowired - private UserItemRepository userItemRepository; + private OrderRepository orderRepository; @Autowired private CertificationRepository certificationRepository; @@ -97,18 +97,18 @@ public void should_getActivatedList_when_userJoinChallenges() { Instance instance2 = getSavedInstance(Progress.ACTIVITY); Participant participant1 = getSavedParticipant(user, instance1, PROCESSING); Participant participant2 = getSavedParticipant(user, instance2, PROCESSING); - getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 3); + + Item item = itemRepository.findAllByCategory(ItemCategory.CERTIFICATION_PASSER).get(0); + Order order = getSavedOrder(user, item, item.getItemCategory(), 3); //when getSavedCertification(CertificateStatus.NOT_YET, targetDate, participant2); - List instances = myChallengeService.getActivatedInstances(user, targetDate); + List activatedResponses = myChallengeService.getActivatedInstances(user, targetDate); //then - assertThat(instances.size()).isEqualTo(2); - assertThat(instances.get(0).canUsePassItem()).isTrue(); - assertThat(instances.get(0).numOfPassItem()).isEqualTo(3); - assertThat(instances.get(1).canUsePassItem()).isTrue(); - assertThat(instances.get(1).numOfPassItem()).isEqualTo(3); + assertThat(activatedResponses.size()).isEqualTo(2); + assertThat(activatedResponses.get(0).getNumOfPassItem()).isEqualTo(3); + assertThat(activatedResponses.get(1).getNumOfPassItem()).isEqualTo(3); } @ParameterizedTest @@ -120,7 +120,9 @@ public void should_canUserPassItemIsFalse_when_AlreadyCertificated(CertificateSt User user = getSavedUser(); Instance instance1 = getSavedInstance(Progress.ACTIVITY); Participant participant1 = getSavedParticipant(user, instance1, PROCESSING); - getSavedUserItem(user, ItemCategory.CERTIFICATION_PASSER, 3); + + Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + getSavedOrder(user, item, item.getItemCategory(), 3); //when getSavedCertification(certificateStatus, targetDate, participant1); @@ -128,35 +130,37 @@ public void should_canUserPassItemIsFalse_when_AlreadyCertificated(CertificateSt //then assertThat(instances.size()).isEqualTo(1); - assertThat(instances.get(0).certificateStatus()).isEqualTo(certificateStatus.getTag()); - assertThat(instances.get(0).canUsePassItem()).isFalse(); - assertThat(instances.get(0).numOfPassItem()).isEqualTo(0); - assertThat(instances.get(0).pointPerPerson()).isEqualTo(instance1.getPointPerPerson()); + assertThat(instances.get(0).getCertificateStatus()).isEqualTo(certificateStatus.getTag()); + assertThat(instances.get(0).getNumOfPassItem()).isEqualTo(0); + assertThat(instances.get(0).getPointPerPerson()).isEqualTo(instance1.getPointPerPerson()); } @Test @DisplayName("완료된 챌린지 목록 조회 시, 포인트를 아직 수령하지 않은 챌린지에 대해서는 보상 가능 정보를 전달해야 한다.") public void should_returnTrue_when_ableToReward() { //given - LocalDate targetDate = LocalDate.of(2024, 2, 14); + LocalDateTime targetDate = LocalDateTime.of(2024, 2, 14, 0, 0); User user = getSavedUser(); - Instance instance = getSavedInstance(Progress.DONE); + Instance instance = getSavedInstance(Progress.DONE, targetDate, targetDate.plusDays(1)); Participant participant = getSavedParticipant(user, instance, SUCCESS); - getSavedUserItem(user, ItemCategory.POINT_MULTIPLIER, 3); + Item item = itemRepository.findAllByCategory(ItemCategory.POINT_MULTIPLIER).get(0); + getSavedOrder(user, item, item.getItemCategory(), 3); //when - List doneResponses = myChallengeService.getDoneInstances(user, targetDate); + getSavedCertification(CertificateStatus.PASSED, targetDate.toLocalDate(), participant); + getSavedCertification(CertificateStatus.CERTIFICATED, targetDate.plusDays(1).toLocalDate(), participant); + List doneResponses = myChallengeService.getDoneInstances(user, targetDate.toLocalDate()); //then DoneResponse doneResponse = doneResponses.get(0); assertThat(doneResponses.size()).isEqualTo(1); - assertThat(doneResponse.title()).isEqualTo(instance.getTitle()); - assertThat(doneResponse.instanceId()).isEqualTo(instance.getId()); - assertThat(doneResponse.rewardedPoints()).isZero(); - assertThat(doneResponse.joinResult()).isEqualTo(SUCCESS); - assertThat(doneResponse.fileResponse()).isNotNull(); - assertThat(doneResponse.canGetReward()).isTrue(); - assertThat(doneResponse.numOfPointItem()).isEqualTo(3); + assertThat(doneResponse.getTitle()).isEqualTo(instance.getTitle()); + assertThat(doneResponse.getInstanceId()).isEqualTo(instance.getId()); + assertThat(doneResponse.getRewardedPoints()).isZero(); + assertThat(doneResponse.getJoinResult()).isEqualTo(SUCCESS); + assertThat(doneResponse.getFileResponse()).isNotNull(); + assertThat(doneResponse.isCanGetReward()).isTrue(); + assertThat(doneResponse.getNumOfPointItem()).isEqualTo(3); } @Test @@ -164,17 +168,18 @@ public void should_returnTrue_when_ableToReward() { public void should_returnRewardInfo_when_alreadyRewarded() { LocalDate targetDate = LocalDate.of(2024, 2, 14); User user = getSavedUser(); - Instance instance1 = getSavedInstance(Progress.DONE); - Participant participant1 = getSavedParticipant(user, instance1, SUCCESS); - getSavedUserItem(user, ItemCategory.POINT_MULTIPLIER, 3); + Instance instance = getSavedInstance(Progress.DONE); + getSavedParticipant(user, instance, SUCCESS); + + Item item = getSavedItem(ItemCategory.POINT_MULTIPLIER); + getSavedOrder(user, item, item.getItemCategory(), 3); //when List doneResponses = myChallengeService.getDoneInstances(user, targetDate); //then assertThat(doneResponses.size()).isEqualTo(1); - assertThat(doneResponses.get(0).canGetReward()).isTrue(); - assertThat(doneResponses.get(0).numOfPointItem()).isEqualTo(3); + assertThat(doneResponses.get(0).isCanGetReward()).isTrue(); } @@ -203,6 +208,18 @@ private Instance getSavedInstance(Progress progress) { ); } + private Instance getSavedInstance(Progress progress, LocalDateTime startedDate, LocalDateTime completedDate) { + return instanceRepository.save( + Instance.builder() + .progress(progress) + .pointPerPerson(100) + .title("title") + .startedDate(startedDate) + .completedDate(completedDate) + .build() + ); + } + private Participant getSavedParticipant(User user, Instance instance, JoinResult joinResult) { Participant participant = participantRepository.save( Participant.builder() @@ -230,13 +247,18 @@ private Certification getSavedCertification(CertificateStatus status, LocalDate return certificationRepository.save(certification); } - private UserItem getSavedUserItem(User user, ItemCategory itemCategory, int count) { - Item item = itemRepository.save(Item.builder() + private Item getSavedItem(ItemCategory itemCategory) { + return itemRepository.save(Item.builder() .itemCategory(itemCategory) + .cost(100) + .name(itemCategory.getName()) .build()); - UserItem userItem = new UserItem(count); - userItem.setItem(item); - userItem.setUser(user); - return userItemRepository.save(userItem); + } + + private Order getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { + Order order = Order.createDefault(count, itemCategory); + order.setItem(item); + order.setUser(user); + return orderRepository.save(order); } } \ No newline at end of file From 8e2a8a73504e1bd107831ab9b442f004ba795f96 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:32:50 +0900 Subject: [PATCH 135/234] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=82=AC?= =?UTF-8?q?=EC=A7=84=EC=9D=84=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20(#125)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 회원가입 API 요청 시, 프로필 사진을 저장하는 기능 추가 개발 - 관련 테스트 코드 작성 --- .../user/controller/UserController.java | 9 ++++++--- .../challenge/user/service/UserService.java | 11 ++++++++--- .../challenge/user/service/UserServiceTest.java | 17 +++++++++++++---- .../genius/gitget/util/WithMockCustomUser.java | 4 +++- ...ithMockCustomUserSecurityContextFactory.java | 4 +++- 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java index 3dc0808b..2959bc65 100644 --- a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java +++ b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java @@ -12,10 +12,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @RestController @RequiredArgsConstructor @@ -32,8 +33,10 @@ public ResponseEntity checkNicknameDuplicate(@RequestParam(value } @PostMapping("/auth/signup") - public ResponseEntity> signup(@RequestBody SignupRequest signupRequest) { - Long signupUserId = userService.signup(signupRequest); + public ResponseEntity> signup( + @RequestPart(value = "data") SignupRequest signupRequest, + @RequestPart(value = "files") MultipartFile multipartFile) { + Long signupUserId = userService.signup(signupRequest, multipartFile); String identifier = userService.findUserById(signupUserId).getIdentifier(); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index bbf6367a..209f3b3b 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -10,11 +10,14 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; @Service @Slf4j @@ -22,6 +25,7 @@ @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + private final FilesService filesService; private final EncryptUtil encryptUtil; @@ -41,18 +45,19 @@ public Long save(User user) { } @Transactional - public Long signup(SignupRequest requestUser) { + public Long signup(SignupRequest requestUser, MultipartFile multipartFile) { User user = findUserByIdentifier(requestUser.identifier()); isAlreadyRegistered(user); - //TODO: Converter 클래스 만들어서 적용하기 String interest = String.join(",", requestUser.interest()); - user.updateUser(requestUser.nickname(), requestUser.information(), interest); user.updateRole(Role.USER); + Files files = filesService.uploadFile(multipartFile, "profile"); + user.setFiles(files); + return user.getId(); } diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index b878e824..d0f7b97f 100644 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -8,9 +8,12 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.util.file.FileTestUtil; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; @@ -20,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; @SpringBootTest @Transactional @@ -27,7 +31,6 @@ class UserServiceTest { @Autowired private UserRepository userRepository; - @Autowired private UserService userService; @@ -43,11 +46,12 @@ public void should_matchValues_when_signupUser() { .information("information") .interest(List.of("관심사1", "관심사2")) .build(); + MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); //when User user = userService.findUserByIdentifier(email); - Long signupUserId = userService.signup(signupRequest); + Long signupUserId = userService.signup(signupRequest, multipartFile); User foundUser = userService.findUserById(signupUserId); //then assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); @@ -55,6 +59,10 @@ public void should_matchValues_when_signupUser() { assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); assertThat(user.getTags()).isEqualTo(foundUser.getTags()); + + Files files = user.getFiles().get(); + assertThat(files.getFileType()).isEqualTo(FileType.PROFILE); + assertThat(files.getOriginalFilename()).contains(multipartFile.getOriginalFilename()); } @Test @@ -69,13 +77,14 @@ public void should_throwException_when_requestRegisterAgain() { .information("information") .interest(List.of("관심사1", "관심사2")) .build(); + MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); //when User user = userService.findUserByIdentifier(email); - Long signupUserId = userService.signup(signupRequest); + Long signupUserId = userService.signup(signupRequest, multipartFile); //then - assertThatThrownBy(() -> userService.signup(signupRequest)) + assertThatThrownBy(() -> userService.signup(signupRequest, multipartFile)) .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.ALREADY_REGISTERED.getMessage()); } diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUser.java b/src/test/java/com/genius/gitget/util/WithMockCustomUser.java index 8ab82b70..e01e948f 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUser.java +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUser.java @@ -1,7 +1,7 @@ package com.genius.gitget.util; -import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.global.security.constants.ProviderInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.springframework.security.test.context.support.WithSecurityContext; @@ -21,4 +21,6 @@ String information() default "information"; Role role() default Role.USER; + + String profileName() default "profile"; } diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java index 6b76ae5c..6956ef01 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java @@ -6,6 +6,7 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.service.CustomUserDetailsService; +import com.genius.gitget.util.file.FileTestUtil; import java.util.List; import java.util.Objects; import lombok.RequiredArgsConstructor; @@ -49,7 +50,8 @@ public SecurityContext createSecurityContext(WithMockCustomUser customUser) { .build(); User savedUser = userRepository.save(user); - Long signupId = userService.signup(signupRequest); + Long signupId = userService.signup(signupRequest, + FileTestUtil.getMultipartFile(customUser.profileName())); savedUser.updateRole(customUser.role()); UserDetails principal = customUserDetailsService.loadUserByUsername(String.valueOf(signupId)); From a33256493ed514c3874ead39e3ac8a4f3dd916ff Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:50:22 +0900 Subject: [PATCH 136/234] =?UTF-8?q?hotfix:=20=EC=98=88=EC=95=BD=EC=96=B4?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=9D=B4=20=EC=83=9D=EC=84=B1=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=ED=94=BD=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CertificationService.java | 4 +- .../gitget/challenge/item/domain/Item.java | 2 +- .../item/domain/{Order.java => Orders.java} | 20 +++--- .../item/repository/OrderRepository.java | 26 ------- .../item/repository/OrdersRepository.java | 26 +++++++ .../challenge/item/service/ItemService.java | 72 +++++++++---------- ...OrderProvider.java => OrdersProvider.java} | 28 ++++---- .../service/MyChallengeService.java | 8 +-- .../gitget/challenge/user/domain/User.java | 4 +- .../service/CertificationServiceTest.java | 20 +++--- .../item/service/ItemServiceTest.java | 46 ++++++------ ...viderTest.java => OrdersProviderTest.java} | 24 +++---- .../service/MyChallengeServiceTest.java | 18 ++--- src/test/resources/data.sql | 20 ------ 14 files changed, 149 insertions(+), 169 deletions(-) rename src/main/java/com/genius/gitget/challenge/item/domain/{Order.java => Orders.java} (80%) delete mode 100644 src/main/java/com/genius/gitget/challenge/item/repository/OrderRepository.java create mode 100644 src/main/java/com/genius/gitget/challenge/item/repository/OrdersRepository.java rename src/main/java/com/genius/gitget/challenge/item/service/{OrderProvider.java => OrdersProvider.java} (55%) rename src/test/java/com/genius/gitget/challenge/item/service/{OrderProviderTest.java => OrdersProviderTest.java} (80%) delete mode 100644 src/test/resources/data.sql diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index c2ca4987..db52a06b 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -15,7 +15,7 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.service.InstanceProvider; -import com.genius.gitget.challenge.item.service.OrderProvider; +import com.genius.gitget.challenge.item.service.OrdersProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantProvider; @@ -49,7 +49,7 @@ public class CertificationService { private final CertificationProvider certificationProvider; private final ParticipantProvider participantProvider; private final InstanceProvider instanceProvider; - private final OrderProvider orderProvider; + private final OrdersProvider ordersProvider; public List getWeekCertification(Long participantId, LocalDate currentDate) { diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java b/src/main/java/com/genius/gitget/challenge/item/domain/Item.java index 8f55ce24..818055db 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/Item.java @@ -26,7 +26,7 @@ public class Item extends BaseTimeEntity { private Long id; @OneToMany(mappedBy = "item") - private List orderList = new ArrayList<>(); + private List ordersList = new ArrayList<>(); private String name; diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/Order.java b/src/main/java/com/genius/gitget/challenge/item/domain/Orders.java similarity index 80% rename from src/main/java/com/genius/gitget/challenge/item/domain/Order.java rename to src/main/java/com/genius/gitget/challenge/item/domain/Orders.java index 9e5b4bd3..349f14bc 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/Order.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/Orders.java @@ -20,10 +20,10 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Order { +public class Orders { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "order_id") + @Column(name = "orders_id") Long id; @ManyToOne(fetch = FetchType.LAZY) @@ -39,16 +39,16 @@ public class Order { @Enumerated(value = EnumType.STRING) private EquipStatus equipStatus; - private Order(int count, EquipStatus equipStatus) { + private Orders(int count, EquipStatus equipStatus) { this.count = count; this.equipStatus = equipStatus; } - public static Order createDefault(int count, ItemCategory itemCategory) { + public static Orders createDefault(int count, ItemCategory itemCategory) { if (itemCategory == ItemCategory.PROFILE_FRAME) { - return new Order(count, EquipStatus.AVAILABLE); + return new Orders(count, EquipStatus.AVAILABLE); } - return new Order(count, EquipStatus.UNAVAILABLE); + return new Orders(count, EquipStatus.UNAVAILABLE); } //=== 비지니스 로직 ===// @@ -78,15 +78,15 @@ public void updateEquipStatus(EquipStatus equipStatus) { //=== 연관관계 편의 메서드 ===// public void setUser(User user) { this.user = user; - if (!user.getOrderList().contains(this)) { - user.getOrderList().add(this); + if (!user.getOrdersList().contains(this)) { + user.getOrdersList().add(this); } } public void setItem(Item item) { this.item = item; - if (!item.getOrderList().contains(this)) { - item.getOrderList().add(this); + if (!item.getOrdersList().contains(this)) { + item.getOrdersList().add(this); } } } diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/OrderRepository.java b/src/main/java/com/genius/gitget/challenge/item/repository/OrderRepository.java deleted file mode 100644 index a5ce8610..00000000 --- a/src/main/java/com/genius/gitget/challenge/item/repository/OrderRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.genius.gitget.challenge.item.repository; - -import com.genius.gitget.challenge.item.domain.EquipStatus; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Order; -import java.util.List; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface OrderRepository extends JpaRepository { - - @Query("select u from Order u where u.user.id = :userId and u.item.itemCategory = :itemCategory") - List findByCategory(@Param("userId") Long userId, - @Param("itemCategory") ItemCategory itemCategory); - - @Query("select u from Order u where u.user.id = :userId and u.item.id = :itemId") - Optional findByOrderInfo(@Param("userId") Long userId, - @Param("itemId") Long itemId); - - @Query("select u from Order u where u.user.id = :userId and u.item.itemCategory = :category and u.equipStatus = :equipStatus") - Optional findByEquipStatus(@Param("userId") Long userId, - @Param("category") ItemCategory category, - @Param("equipStatus") EquipStatus equipStatus); -} diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/OrdersRepository.java b/src/main/java/com/genius/gitget/challenge/item/repository/OrdersRepository.java new file mode 100644 index 00000000..890b66f2 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/item/repository/OrdersRepository.java @@ -0,0 +1,26 @@ +package com.genius.gitget.challenge.item.repository; + +import com.genius.gitget.challenge.item.domain.EquipStatus; +import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.challenge.item.domain.Orders; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface OrdersRepository extends JpaRepository { + + @Query("select u from Orders u where u.user.id = :userId and u.item.itemCategory = :itemCategory") + List findByCategory(@Param("userId") Long userId, + @Param("itemCategory") ItemCategory itemCategory); + + @Query("select u from Orders u where u.user.id = :userId and u.item.id = :itemId") + Optional findByOrderInfo(@Param("userId") Long userId, + @Param("itemId") Long itemId); + + @Query("select u from Orders u where u.user.id = :userId and u.item.itemCategory = :category and u.equipStatus = :equipStatus") + Optional findByEquipStatus(@Param("userId") Long userId, + @Param("category") ItemCategory category, + @Param("equipStatus") EquipStatus equipStatus); +} diff --git a/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java b/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java index 310a79da..bffdc42e 100644 --- a/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java +++ b/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java @@ -5,7 +5,7 @@ import com.genius.gitget.challenge.item.domain.EquipStatus; import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.domain.Orders; import com.genius.gitget.challenge.item.dto.ItemResponse; import com.genius.gitget.challenge.item.dto.ItemUseResponse; import com.genius.gitget.challenge.item.dto.ProfileResponse; @@ -30,7 +30,7 @@ public class ItemService { private final UserService userService; private final ItemProvider itemProvider; - private final OrderProvider orderProvider; + private final OrdersProvider ordersProvider; //TODO: Service를 의존하는게 맘에 들지 않는다 provider만을 의존하게 할 순 없을까? private final CertificationService certificationService; @@ -49,7 +49,7 @@ public List getItemsByCategory(User user, ItemCategory itemCategor List items = itemProvider.findAllByCategory(itemCategory); for (Item item : items) { - int numOfItem = orderProvider.countNumOfItem(user, item.getId()); + int numOfItem = ordersProvider.countNumOfItem(user, item.getId()); ItemResponse itemResponse = getItemResponse(user, item, numOfItem); itemResponses.add(itemResponse); } @@ -64,9 +64,9 @@ public ItemResponse orderItem(User user, Long itemId) { validateUserPoint(persistUser.getPoint(), item.getCost()); - Order order = orderProvider.findOptionalByOrderInfo(persistUser.getId(), itemId) + Orders orders = ordersProvider.findOptionalByOrderInfo(persistUser.getId(), itemId) .orElseGet(() -> createNew(persistUser, item)); - int numOfItem = order.purchase(); + int numOfItem = orders.purchase(); persistUser.updatePoints((long) item.getCost() * -1); return getItemResponse(persistUser, item, numOfItem); @@ -78,29 +78,29 @@ private void validateUserPoint(long userPoint, int itemCost) { } } - private Order createNew(User user, Item item) { - Order order = Order.createDefault(0, item.getItemCategory()); - order.setUser(user); - order.setItem(item); - return orderProvider.save(order); + private Orders createNew(User user, Item item) { + Orders orders = Orders.createDefault(0, item.getItemCategory()); + orders.setUser(user); + orders.setItem(item); + return ordersProvider.save(orders); } @Transactional public ProfileResponse unmountFrame(User user, Long itemId) { - Order order = orderProvider.findByOrderInfo(user.getId(), itemId); - validateUnmountCondition(order); + Orders orders = ordersProvider.findByOrderInfo(user.getId(), itemId); + validateUnmountCondition(orders); - order.updateEquipStatus(EquipStatus.AVAILABLE); + orders.updateEquipStatus(EquipStatus.AVAILABLE); return ProfileResponse.create( - order.getItem(), order.getCount(), order.getEquipStatus().getTag()); + orders.getItem(), orders.getCount(), orders.getEquipStatus().getTag()); } - private void validateUnmountCondition(Order order) { - if (order.getItem().getItemCategory() != ItemCategory.PROFILE_FRAME) { + private void validateUnmountCondition(Orders orders) { + if (orders.getItem().getItemCategory() != ItemCategory.PROFILE_FRAME) { throw new BusinessException(ErrorCode.ITEM_NOT_FOUND); } - if (order.getEquipStatus() != EquipStatus.IN_USE) { + if (orders.getEquipStatus() != EquipStatus.IN_USE) { throw new BusinessException(ErrorCode.IN_USE_FRAME_NOT_FOUND); } } @@ -108,64 +108,64 @@ private void validateUnmountCondition(Order order) { @Transactional public ItemUseResponse useItem(User user, Long itemId, Long instanceId, LocalDate currentDate) { Item item = itemProvider.findById(itemId); - Order order = orderProvider.findByOrderInfo(user.getId(), itemId); + Orders orders = ordersProvider.findByOrderInfo(user.getId(), itemId); - if (!order.hasItem()) { + if (!orders.hasItem()) { throw new BusinessException(ErrorCode.HAS_NO_ITEM); } switch (item.getItemCategory()) { case PROFILE_FRAME -> { - return useProfileFrameItem(order); + return useProfileFrameItem(orders); } case CERTIFICATION_PASSER -> { - return usePasserItem(order, instanceId, currentDate); + return usePasserItem(orders, instanceId, currentDate); } case POINT_MULTIPLIER -> { - return usePointMultiplierItem(order, instanceId, currentDate); + return usePointMultiplierItem(orders, instanceId, currentDate); } } throw new BusinessException(ErrorCode.USER_ITEM_NOT_FOUND); } - private ItemUseResponse useProfileFrameItem(Order order) { - validateFrameEquip(order); - order.updateEquipStatus(EquipStatus.IN_USE); + private ItemUseResponse useProfileFrameItem(Orders orders) { + validateFrameEquip(orders); + orders.updateEquipStatus(EquipStatus.IN_USE); return new ItemUseResponse(0L, "", 0); } - private void validateFrameEquip(Order order) { - if (!order.hasItem()) { + private void validateFrameEquip(Orders orders) { + if (!orders.hasItem()) { throw new BusinessException(ErrorCode.HAS_NO_ITEM); } - if (order.getEquipStatus() != EquipStatus.AVAILABLE) { + if (orders.getEquipStatus() != EquipStatus.AVAILABLE) { throw new BusinessException(ErrorCode.INVALID_EQUIP_CONDITION); } } - private ItemUseResponse usePasserItem(Order order, Long instanceId, LocalDate currentDate) { - Long userId = order.getUser().getId(); - Long itemId = order.getItem().getId(); + private ItemUseResponse usePasserItem(Orders orders, Long instanceId, LocalDate currentDate) { + Long userId = orders.getUser().getId(); + Long itemId = orders.getItem().getId(); ActivatedResponse activatedResponse = certificationService.passCertification( userId, new CertificationRequest(instanceId, currentDate)); activatedResponse.setItemId(itemId); - order.useItem(); + orders.useItem(); return activatedResponse; } - private ItemUseResponse usePointMultiplierItem(Order order, Long instanceId, LocalDate currentDate) { - User user = order.getUser(); + private ItemUseResponse usePointMultiplierItem(Orders orders, Long instanceId, LocalDate currentDate) { + User user = orders.getUser(); DoneResponse doneResponse = myChallengeService.getRewards( new RewardRequest(user, instanceId, currentDate), true ); - order.useItem(); + orders.useItem(); return doneResponse; } private ItemResponse getItemResponse(User user, Item item, int numOfItem) { if (item.getItemCategory() == ItemCategory.PROFILE_FRAME) { - EquipStatus equipStatus = orderProvider.getEquipStatus(user.getId(), item.getId()); + EquipStatus equipStatus = ordersProvider.getEquipStatus(user.getId(), item.getId()); return ProfileResponse.create(item, numOfItem, equipStatus.getTag()); } return ItemResponse.create(item, numOfItem); diff --git a/src/main/java/com/genius/gitget/challenge/item/service/OrderProvider.java b/src/main/java/com/genius/gitget/challenge/item/service/OrdersProvider.java similarity index 55% rename from src/main/java/com/genius/gitget/challenge/item/service/OrderProvider.java rename to src/main/java/com/genius/gitget/challenge/item/service/OrdersProvider.java index a3196210..80fcc525 100644 --- a/src/main/java/com/genius/gitget/challenge/item/service/OrderProvider.java +++ b/src/main/java/com/genius/gitget/challenge/item/service/OrdersProvider.java @@ -4,8 +4,8 @@ import com.genius.gitget.challenge.item.domain.EquipStatus; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Order; -import com.genius.gitget.challenge.item.repository.OrderRepository; +import com.genius.gitget.challenge.item.domain.Orders; +import com.genius.gitget.challenge.item.repository.OrdersRepository; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.util.exception.BusinessException; import java.util.Optional; @@ -16,25 +16,25 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class OrderProvider { - private final OrderRepository orderRepository; +public class OrdersProvider { + private final OrdersRepository ordersRepository; - public Order save(Order order) { - return orderRepository.save(order); + public Orders save(Orders orders) { + return ordersRepository.save(orders); } - public Optional findOptionalByOrderInfo(Long userId, Long itemId) { - return orderRepository.findByOrderInfo(userId, itemId); + public Optional findOptionalByOrderInfo(Long userId, Long itemId) { + return ordersRepository.findByOrderInfo(userId, itemId); } - public Order findByOrderInfo(Long userId, Long itemId) { - return orderRepository.findByOrderInfo(userId, itemId) + public Orders findByOrderInfo(Long userId, Long itemId) { + return ordersRepository.findByOrderInfo(userId, itemId) .orElseThrow(() -> new BusinessException(USER_ITEM_NOT_FOUND)); } public EquipStatus getEquipStatus(Long userId, Long itemId) { - Optional optionalUserItem = orderRepository.findByOrderInfo(userId, itemId); + Optional optionalUserItem = ordersRepository.findByOrderInfo(userId, itemId); if (optionalUserItem.isPresent()) { return optionalUserItem.get().getEquipStatus(); } @@ -42,12 +42,12 @@ public EquipStatus getEquipStatus(Long userId, Long itemId) { } public int countNumOfCategory(User user, ItemCategory itemCategory) { - return orderRepository.findByCategory(user.getId(), itemCategory).size(); + return ordersRepository.findByCategory(user.getId(), itemCategory).size(); } public int countNumOfItem(User user, Long itemId) { - Optional optionalUserItem = orderRepository.findByOrderInfo(user.getId(), itemId); - return optionalUserItem.map(Order::getCount) + Optional optionalUserItem = ordersRepository.findByOrderInfo(user.getId(), itemId); + return optionalUserItem.map(Orders::getCount) .orElse(0); } } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index c17483b2..aecca4cb 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -15,7 +15,7 @@ import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.service.ItemProvider; -import com.genius.gitget.challenge.item.service.OrderProvider; +import com.genius.gitget.challenge.item.service.OrdersProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; @@ -42,7 +42,7 @@ public class MyChallengeService { private final ParticipantProvider participantProvider; private final CertificationProvider certificationProvider; private final ItemProvider itemProvider; - private final OrderProvider orderProvider; + private final OrdersProvider ordersProvider; public List getPreActivityInstances(User user, LocalDate targetDate) { @@ -76,7 +76,7 @@ public List getDoneInstances(User user, LocalDate targetDate) { // 포인트를 아직 수령하지 않았을 때 if (participant.getRewardStatus() == NO) { Item item = itemProvider.findAllByCategory(POINT_MULTIPLIER).get(0); - int numOfPassItem = orderProvider.countNumOfItem(user, item.getId()); + int numOfPassItem = ordersProvider.countNumOfItem(user, item.getId()); DoneResponse doneResponse = DoneResponse.createNotRewarded(instance, participant, numOfPassItem); doneResponse.setItemId(item.getId()); done.add(doneResponse); @@ -112,7 +112,7 @@ public List getActivatedInstances(User user, LocalDate target //TODO: 로직 수정 필요 Item item = itemProvider.findAllByCategory(CERTIFICATION_PASSER).get(0); - int numOfPassItem = orderProvider.countNumOfItem(user, item.getId()); + int numOfPassItem = ordersProvider.countNumOfItem(user, item.getId()); ActivatedResponse activatedResponse = ActivatedResponse.create( instance, certification.getCertificationStatus(), diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 599b71d8..e4e805b1 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -1,6 +1,6 @@ package com.genius.gitget.challenge.user.domain; -import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.domain.Orders; import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.Files; @@ -54,7 +54,7 @@ public class User extends BaseTimeEntity { private List payment = new ArrayList<>(); @OneToMany(mappedBy = "user") - private List orderList = new ArrayList<>(); + private List ordersList = new ArrayList<>(); @NotNull @Enumerated(EnumType.STRING) diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index b8a268cd..d01fe533 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -23,9 +23,9 @@ import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.domain.Orders; import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.OrderRepository; +import com.genius.gitget.challenge.item.repository.OrdersRepository; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; @@ -74,7 +74,7 @@ class CertificationServiceTest { @Autowired private ItemRepository itemRepository; @Autowired - private OrderRepository orderRepository; + private OrdersRepository ordersRepository; @Value("${github.yeon-personalKey}") private String personalKey; @@ -454,7 +454,7 @@ public void should_overwriteData_when_certificatedBefore() { User user = getSavedUser(githubId); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - Order order = getSavedOrder(user, ItemCategory.CERTIFICATION_PASSER, 1); + Orders orders = getSavedOrder(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) .targetDate(currentDate) @@ -485,7 +485,7 @@ public void should_usePassItem_when_conditionIsValid() { User user = getSavedUser(githubId); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - Order order = getSavedOrder(user, ItemCategory.CERTIFICATION_PASSER, 1); + Orders orders = getSavedOrder(user, ItemCategory.CERTIFICATION_PASSER, 1); CertificationRequest certificationRequest = CertificationRequest.builder() .instanceId(instance.getId()) .targetDate(currentDate) @@ -571,13 +571,13 @@ private Certification getSavedCertification(CertificateStatus status, LocalDate return certificationRepository.save(certification); } - private Order getSavedOrder(User user, ItemCategory itemCategory, int count) { + private Orders getSavedOrder(User user, ItemCategory itemCategory, int count) { Item item = itemRepository.save(Item.builder() .itemCategory(itemCategory) .build()); - Order order = Order.createDefault(count, itemCategory); - order.setItem(item); - order.setUser(user); - return orderRepository.save(order); + Orders orders = Orders.createDefault(count, itemCategory); + orders.setItem(item); + orders.setUser(user); + return ordersRepository.save(orders); } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index dc365870..33494c63 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -16,12 +16,12 @@ import com.genius.gitget.challenge.item.domain.EquipStatus; import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.domain.Orders; import com.genius.gitget.challenge.item.dto.ItemResponse; import com.genius.gitget.challenge.item.dto.ItemUseResponse; import com.genius.gitget.challenge.item.dto.ProfileResponse; import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.OrderRepository; +import com.genius.gitget.challenge.item.repository.OrdersRepository; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; @@ -57,7 +57,7 @@ class ItemServiceTest { @Autowired private ItemRepository itemRepository; @Autowired - private OrderRepository orderRepository; + private OrdersRepository ordersRepository; @Autowired private InstanceRepository instanceRepository; @Autowired @@ -85,7 +85,7 @@ public void should_getItems_when_passCategory(ItemCategory itemCategory) { //given User user = getSavedUser(); Item item = getSavedItem(itemCategory); - Order order = getSavedOrder(user, item, itemCategory, 1); + Orders orders = getSavedOrder(user, item, itemCategory, 1); //when List itemResponses = itemService.getItemsByCategory(user, itemCategory); @@ -139,7 +139,7 @@ public void should_throwException_when_outOfStock() { Item item = getSavedItem(ItemCategory.PROFILE_FRAME); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - Order order = getSavedOrder(user, item, ItemCategory.CERTIFICATION_PASSER, 0); + Orders orders = getSavedOrder(user, item, ItemCategory.CERTIFICATION_PASSER, 0); instance.updateProgress(Progress.ACTIVITY); @@ -177,14 +177,14 @@ public void should_useFrameItem_when_availableToEquip() { Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); //when - order.updateEquipStatus(EquipStatus.AVAILABLE); + orders.updateEquipStatus(EquipStatus.AVAILABLE); ItemUseResponse itemUseResponse = itemService.useItem(user, item.getId(), instance.getId(), currentDate); //then - assertThat(order.getEquipStatus()).isEqualTo(EquipStatus.IN_USE); + assertThat(orders.getEquipStatus()).isEqualTo(EquipStatus.IN_USE); } @Test @@ -225,9 +225,9 @@ public void should_throwException_when_notAvailable(EquipStatus equipStatus) { //given User user = getSavedUser(); Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - Order order = getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 3); + Orders orders = getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 3); - order.updateEquipStatus(equipStatus); + orders.updateEquipStatus(equipStatus); //when && then assertThatThrownBy(() -> itemService.useItem(user, item.getId(), 0L, LocalDate.now())) @@ -244,7 +244,7 @@ public void should_usePasserItem_when_ableToUsePassItem() { Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); //when instance.updateProgress(Progress.ACTIVITY); @@ -255,7 +255,7 @@ public void should_usePasserItem_when_ableToUsePassItem() { assertThat(itemUseResponse.getInstanceId()).isEqualTo(instance.getId()); assertThat(itemUseResponse.getTitle()).isEqualTo(instance.getTitle()); assertThat(itemUseResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(order.getCount()).isEqualTo(0); + assertThat(orders.getCount()).isEqualTo(0); assertThat(certification.getCertificationStatus()).isEqualTo(PASSED); } @@ -268,7 +268,7 @@ public void should_usePasserItem_when_useItemNotYet() { Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); //when instance.updateProgress(Progress.ACTIVITY); @@ -281,7 +281,7 @@ public void should_usePasserItem_when_useItemNotYet() { assertThat(itemUseResponse.getInstanceId()).isEqualTo(instance.getId()); assertThat(itemUseResponse.getTitle()).isEqualTo(instance.getTitle()); assertThat(itemUseResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(order.getCount()).isEqualTo(0); + assertThat(orders.getCount()).isEqualTo(0); assertThat(certification).isPresent(); assertThat(certification.get().getCertificationStatus()).isEqualTo(PASSED); } @@ -295,7 +295,7 @@ public void should_throwException_when_ProgressIsNotActivity() { Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); //when & then assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) @@ -334,7 +334,7 @@ public void should_getRewardTwice_when_conditionMatches() { Instance instance = getSavedInstance(currentDate, currentDate.plusDays(1)); Participant participant = getSavedParticipant(user, instance); Item item = getSavedItem(ItemCategory.POINT_MULTIPLIER); - Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); //when Long previousPoint = user.getPoint(); @@ -347,7 +347,7 @@ public void should_getRewardTwice_when_conditionMatches() { //then assertThat(afterRewards - previousPoint).isEqualTo(instance.getPointPerPerson() * 2L); - assertThat(order.getCount()).isEqualTo(0); + assertThat(orders.getCount()).isEqualTo(0); } @Test @@ -376,7 +376,7 @@ public void should_throwException_when_categoryIsNotFrame(ItemCategory itemCateg //given User user = getSavedUser(); Item item = getSavedItem(itemCategory); - Order order = getSavedOrder(user, item, item.getItemCategory(), 1); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); //when & then assertThatThrownBy(() -> itemService.unmountFrame(user, item.getId())) @@ -418,11 +418,11 @@ private Item getSavedItem(ItemCategory itemCategory) { .build()); } - private Order getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { - Order order = Order.createDefault(count, itemCategory); - order.setItem(item); - order.setUser(user); - return orderRepository.save(order); + private Orders getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { + Orders orders = Orders.createDefault(count, itemCategory); + orders.setItem(item); + orders.setUser(user); + return ordersRepository.save(orders); } private Instance getSavedInstance() { diff --git a/src/test/java/com/genius/gitget/challenge/item/service/OrderProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java similarity index 80% rename from src/test/java/com/genius/gitget/challenge/item/service/OrderProviderTest.java rename to src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java index 54a7b181..611303ae 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/OrderProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java @@ -4,9 +4,9 @@ import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.domain.Orders; import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.OrderRepository; +import com.genius.gitget.challenge.item.repository.OrdersRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; @@ -21,15 +21,15 @@ @Slf4j @SpringBootTest @Transactional -class OrderProviderTest { +class OrdersProviderTest { @Autowired private UserRepository userRepository; @Autowired private ItemRepository itemRepository; @Autowired - private OrderRepository orderRepository; + private OrdersRepository ordersRepository; @Autowired - private OrderProvider orderProvider; + private OrdersProvider ordersProvider; @Test @DisplayName("사용자가 특정 아이템을 보유하고 있을 때, 보유하고 있는 아이템의 개수를 반환받을 수 있다.") @@ -40,7 +40,7 @@ public void should_returnItemCount_when_haveItem() { getSavedOrder(user, item, 1); //when - int numOfItem = orderProvider.countNumOfItem(user, item.getId()); + int numOfItem = ordersProvider.countNumOfItem(user, item.getId()); //then assertThat(numOfItem).isEqualTo(1); @@ -54,7 +54,7 @@ public void should_returnZero_when_dataNotSaved() { Item item = getSavedItem(ItemCategory.PROFILE_FRAME); //when - int numOfItem = orderProvider.countNumOfItem(user, item.getId()); + int numOfItem = ordersProvider.countNumOfItem(user, item.getId()); //then assertThat(numOfItem).isEqualTo(0); @@ -82,10 +82,10 @@ private Item getSavedItem(ItemCategory itemCategory) { ); } - private Order getSavedOrder(User user, Item item, int count) { - Order order = Order.createDefault(count, item.getItemCategory()); - order.setUser(user); - order.setItem(item); - return orderRepository.save(order); + private Orders getSavedOrder(User user, Item item, int count) { + Orders orders = Orders.createDefault(count, item.getItemCategory()); + orders.setUser(user); + orders.setItem(item); + return ordersRepository.save(orders); } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java index a36cb1c5..f0f2065d 100644 --- a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java @@ -13,9 +13,9 @@ import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.item.domain.Item; import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Order; +import com.genius.gitget.challenge.item.domain.Orders; import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.OrderRepository; +import com.genius.gitget.challenge.item.repository.OrdersRepository; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; @@ -56,7 +56,7 @@ class MyChallengeServiceTest { @Autowired private ItemRepository itemRepository; @Autowired - private OrderRepository orderRepository; + private OrdersRepository ordersRepository; @Autowired private CertificationRepository certificationRepository; @@ -99,7 +99,7 @@ public void should_getActivatedList_when_userJoinChallenges() { Participant participant2 = getSavedParticipant(user, instance2, PROCESSING); Item item = itemRepository.findAllByCategory(ItemCategory.CERTIFICATION_PASSER).get(0); - Order order = getSavedOrder(user, item, item.getItemCategory(), 3); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 3); //when getSavedCertification(CertificateStatus.NOT_YET, targetDate, participant2); @@ -255,10 +255,10 @@ private Item getSavedItem(ItemCategory itemCategory) { .build()); } - private Order getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { - Order order = Order.createDefault(count, itemCategory); - order.setItem(item); - order.setUser(user); - return orderRepository.save(order); + private Orders getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { + Orders orders = Orders.createDefault(count, itemCategory); + orders.setItem(item); + orders.setUser(user); + return ordersRepository.save(orders); } } \ No newline at end of file diff --git a/src/test/resources/data.sql b/src/test/resources/data.sql deleted file mode 100644 index 00553f51..00000000 --- a/src/test/resources/data.sql +++ /dev/null @@ -1,20 +0,0 @@ -# User insert문 -insert into user (created_at, deleted_at, files_id, point, updated_at, nickname, information, identifier, tags, - provider_info, role) -values ('2023-03-04 11:01:48.594485', null, null, 1500, null, 'dohyungKim', '저는 백엔드 개발자입니다.', 'kimdozzi', 'BE, CS', - 'GITHUB', 'ADMIN'); -insert into user (created_at, deleted_at, files_id, point, updated_at, nickname, information, identifier, tags, - provider_info, role) -values ('2023-03-02 20:14:10.594123', null, null, 2500, null, 'yujinKim', '안녕하세요~ 저는 김유진이라고 합니다 잘부탁드려요 ^^.', 'yujin', - 'BE, CS, AI', - 'GITHUB', 'USER'); -insert into user (created_at, deleted_at, files_id, point, updated_at, nickname, information, identifier, tags, - provider_info, role) -values ('2023-03-01 07:01:00.600000', null, null, 50000, null, 'minsuPark', '', 'minsu', 'FE', - 'GITHUB', 'NOT_REGISTERED'); - - -# Topic insert문 -insert into topic (point_per_person, created_at, deleted_at, files_id, updated_at, description, notice, tags, title) -values (100, null, null, null, null, '1일 1알고리즘 챌린지입니다.', '85%이상 참여 시 포인트가 발행됩니다.', 'BE', '1일 1알고리즘 챌린지'); - From e2370159a02734305937d891f593f7295e479288 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:53:49 +0900 Subject: [PATCH 137/234] =?UTF-8?q?[FEAT]=20=EC=9D=B8=EC=A6=9D=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=EC=97=90=20uuid=20=EC=A0=81=EC=9A=A9=20(#122?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 인증 상세 페이지의 응답데이터에 PR Template 추가 - Instance에 저장되어 있던 UUID를 통해 PR Template 문구를 만들어 전달 - 관련 테스트 코드 작성 * feat: 인증 갱신 시도 시, 지정한 문구가 PR에 포함되어있는지 확인하는 기능 추가 - 오늘의 인증 API 요청 시, 당일에 올라온 PR의 내용들 중 지정된 문구가 존재하는지 확인하는 로직 추가 * refactor: 인증 패스 서비스 코드 리팩토링 - optional을 검사하여 조건문으로 처리하던 코드에서 함수형 프로그래밍 스타일로 리팩토링 * fix: PR이 없을 때 인증 안됨이 아닌 예외가 발생하는 버그 픽스 - PR이 존재하지 않을 때 인증을 시도 했을 때, NOT_YET으로 저장되는 것이 아닌 예외가 발생하는 버그 픽스 - 인증 업데이트/ 인증 패스 메서드에서 일부 로직을 함수형 프로그래밍 형식으로 변경 --- .../dto/CertificationInformation.java | 1 + .../service/CertificationService.java | 75 +++++++++---------- .../challenge/instance/domain/Instance.java | 18 +++-- .../global/util/exception/ErrorCode.java | 1 + .../service/CertificationServiceTest.java | 43 +++++++++-- 5 files changed, 87 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationInformation.java b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationInformation.java index 9e4cc4da..41cd3e0a 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationInformation.java +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationInformation.java @@ -4,6 +4,7 @@ @Builder public record CertificationInformation( + String prTemplate, String repository, double successPercent, int totalAttempt, diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index db52a06b..6baacf0a 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -144,25 +144,26 @@ public ActivatedResponse passCertification(Long userId, CertificationRequest cer Participant participant = participantProvider.findByJoinInfo(userId, instance.getId()); LocalDate targetDate = certificationRequest.targetDate(); - Optional optional = certificationProvider.findByDate(targetDate, participant.getId()); + Optional optionalCertification = certificationProvider.findByDate(targetDate, + participant.getId()); validCertificationCondition(instance, targetDate); - validatePassCondition(optional); - - //TODO: 리팩토링 시급... - if (optional.isPresent()) { - Certification certification = optional.get(); - certification.updateToPass(targetDate); - return ActivatedResponse.create(instance, certification.getCertificationStatus(), - 0, participant.getRepositoryName()); - } - - Certification certification = Certification.createPassed(targetDate); - certification.setParticipant(participant); - certificationProvider.save(certification); - - return ActivatedResponse.create(instance, certification.getCertificationStatus(), - 0, participant.getRepositoryName()); + validatePassCondition(optionalCertification); + + Certification certification = optionalCertification + .map(passed -> { + passed.updateToPass(targetDate); + return passed; + }) + .orElseGet(() -> { + Certification passed = Certification.createPassed(targetDate); + passed.setParticipant(participant); + certificationProvider.save(passed); + return passed; + }); + + return ActivatedResponse.create(instance, certification.getCertificationStatus(), 0, + participant.getRepositoryName()); } private void validatePassCondition(Optional optional) { @@ -177,25 +178,30 @@ public CertificationResponse updateCertification(User user, CertificationRequest Instance instance = instanceProvider.findById(certificationRequest.instanceId()); Participant participant = participantProvider.findByJoinInfo(user.getId(), instance.getId()); - validCertificationCondition(instance, certificationRequest.targetDate()); + String repositoryName = participant.getRepositoryName(); + LocalDate targetDate = certificationRequest.targetDate(); + + validCertificationCondition(instance, targetDate); - List pullRequests = getPullRequestLink( - gitHub, - participant.getRepositoryName(), - certificationRequest.targetDate()); + List filteredPullRequests = filterValidPR( + githubProvider.getPullRequestByDate(gitHub, repositoryName, targetDate), + instance.getPrTemplate(targetDate) + ); - Certification certification = createOrUpdate(participant, certificationRequest.targetDate(), pullRequests); + Certification certification = certificationProvider.findByDate(targetDate, participant.getId()) + .map(updated -> certificationProvider.update(updated, targetDate, filteredPullRequests)) + .orElseGet( + () -> certificationProvider.createCertification(participant, targetDate, filteredPullRequests) + ); return CertificationResponse.createExist(certification); } - private Certification createOrUpdate(Participant participant, LocalDate targetDate, List pullRequests) { - Optional optional = certificationProvider.findByDate(targetDate, participant.getId()); - if (optional.isPresent()) { - return certificationProvider.update(optional.get(), targetDate, pullRequests); - } - - return certificationProvider.createCertification(participant, targetDate, pullRequests); + private List filterValidPR(List ghPullRequests, String prTemplate) { + return ghPullRequests.stream() + .filter(ghPullRequest -> ghPullRequest.getBody().contains(prTemplate)) + .map(ghPullRequest -> ghPullRequest.getHtmlUrl().toString()) + .toList(); } private void validCertificationCondition(Instance instance, LocalDate targetDate) { @@ -210,14 +216,6 @@ private void validCertificationCondition(Instance instance, LocalDate targetDate } } - private List getPullRequestLink(GitHub gitHub, String repositoryName, LocalDate targetDate) { - List ghPullRequests = githubProvider.getPullRequestByDate(gitHub, repositoryName, targetDate); - - return ghPullRequests.stream() - .map(pr -> pr.getHtmlUrl().toString()) - .toList(); - } - public InstancePreviewResponse getInstancePreview(Long instanceId) { Instance instance = instanceProvider.findById(instanceId); FileResponse fileResponse = filesService.getEncodedFile(instance.getFiles()); @@ -253,6 +251,7 @@ public CertificationInformation getCertificationInformation(Instance instance, P } return CertificationInformation.builder() + .prTemplate(instance.getPrTemplate(currentDate)) .repository(participant.getRepositoryName()) .successPercent(getSuccessPercent(successCount, currentAttempt)) .totalAttempt(totalAttempt) diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 1fffb042..0583804c 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -24,6 +24,7 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -122,12 +123,6 @@ public void setTopic(Topic topic) { } } - public void setFiles(Files files) { - this.files = files; - } - - //== 비지니스 로직 ==// - /* * 인스턴스 수정 * */ @@ -141,6 +136,8 @@ public void updateInstance(String description, String notice, int pointPerPerson this.certificationMethod = certificationMethod; } + //== 비지니스 로직 ==// + /* * 참가자 수 정보 수정 * */ @@ -166,6 +163,10 @@ public Optional getFiles() { return Optional.ofNullable(this.files); } + public void setFiles(Files files) { + this.files = files; + } + /* * 챌린지 전체 인증 일자 조회 * */ @@ -183,4 +184,9 @@ public void setInstanceUUID(String instanceUUID) { this.instanceUUID = instanceUUID; } } + + public String getPrTemplate(LocalDate currentDate) { + String today = currentDate.toString().replace("-", ""); + return "GITGET-" + instanceUUID + "-" + today; + } } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 55e57970..5b138aa6 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -58,6 +58,7 @@ public enum ErrorCode { CAN_NOT_JOIN_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스에 참여할 수 없습니다."), CAN_NOT_QUIT_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스의 참여를 취소할 수 없습니다."), + PR_TEMPLATE_NOT_FOUND(HttpStatus.NOT_FOUND, "인증에 사용하는 PR의 내용에 PR Template가 포함되어야 합니다."), NOT_ACTIVITY_INSTANCE(HttpStatus.BAD_REQUEST, "진행 중인 챌린지에 대해서만 인증이 가능합니다."), NOT_CERTIFICATE_PERIOD(HttpStatus.BAD_REQUEST, "챌린지 인증은 챌린지 진행 기간 내에만 가능합니다. 챌린지 진행 기간인지 확인해주세요."), diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index d01fe533..12d5f331 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -116,6 +116,34 @@ public void should_certificate_when_prExist() { assertThat(certificationResponse.prCount()).isEqualTo(1); } + @Test + @DisplayName("기존에 저장되어있는 인증 내역이 있더라도, 인증 시도를 다시 하면 최신의 내용으로 갱신된다.") + public void should_updateCertification_when_certificateOnce() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 2, 5); + + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(targetDate) + .build(); + instance.updateProgress(Progress.ACTIVITY); + + //when + Certification certification = getSavedCertification(CERTIFICATED, targetDate, participant); + CertificationResponse certificationResponse = certificationService.updateCertification(user, + certificationRequest); + + //then + assertThat(certificationResponse.certificatedAt()).isEqualTo(targetDate); + assertThat(certificationResponse.certificationId()).isEqualTo(certification.getId()); + assertThat(certificationResponse.certificateStatus()).isEqualTo(certification.getCertificationStatus()); + } + @Test @DisplayName("인증을 시도한 날짜가 챌린지의 진행 기간과 겹치지 않는다면 예외를 발생한다.") public void should_throwException_when_progressIsNotActivity() { @@ -299,6 +327,7 @@ public void should_getInformation_when_progressIsPreActivity() { participant, targetDate); //then + assertThat(information.prTemplate()).isEqualTo(instance.getPrTemplate(targetDate)); assertThat(information.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); assertThat(information.remainCount()).isEqualTo(information.totalAttempt()); assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); @@ -535,13 +564,13 @@ private User getSavedUser(String githubId, String nickname) { } private Instance getSavedInstance() { - return instanceRepository.save( - Instance.builder() - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) - .completedDate(LocalDateTime.of(2024, 3, 29, 0, 0)) - .build() - ); + Instance instance = Instance.builder() + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) + .completedDate(LocalDateTime.of(2024, 3, 29, 0, 0)) + .build(); + instance.setInstanceUUID("instanceUUID"); + return instanceRepository.save(instance); } private Participant getSavedParticipant(User user, Instance instance) { From e753f30d97aa3334a2031134f819abca745d2de7 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:19:11 +0900 Subject: [PATCH 138/234] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=ED=94=84=EB=A0=88=EC=9E=84=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genius/gitget/challenge/item/domain/ItemCategory.java | 2 +- src/main/resources/data.sql | 4 +++- .../gitget/challenge/item/service/ItemProviderTest.java | 2 +- .../genius/gitget/challenge/item/service/ItemServiceTest.java | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java b/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java index 098a069c..02a33cb6 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java +++ b/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java @@ -9,7 +9,7 @@ @RequiredArgsConstructor @Getter public enum ItemCategory { - PROFILE_FRAME("profile-frame", "프로필 프레임"), + PROFILE_FRAME("profile-frame", "프레임"), CERTIFICATION_PASSER("certification-passer", "인증 패스 아이템"), POINT_MULTIPLIER("point-multiplier", "포인트 2배 획득 아이템"); diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 81358355..9465f0e2 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -5,9 +5,11 @@ FROM (SELECT 100 AS cost, NULL AS deleted_at, NULL AS updated_at, '프로필을 꾸밀 수 있는 프레임입니다.' AS details, - '프로필 프레임' AS name, + '성탄절 프레임' AS name, 'PROFILE_FRAME' AS item_category UNION ALL + SELECT 100, NULL, NULL, NULL, '프로필을 꾸밀 수 있는 프레임입니다.', '어둠의 힘 프레임', 'PROFILE_FRAME' + UNION ALL SELECT 100, NULL, NULL, NULL, '오늘의 인증을 넘길 수 있는 아이템입니다.', '인증 패스 아이템', 'CERTIFICATION_PASSER' UNION ALL SELECT 100, NULL, NULL, NULL, '포인트 보상을 2배로 받을 수 있는 아이템입니다.', '포인트 2배 획득 아이템', 'POINT_MULTIPLIER') AS new_items diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java index e8da5e04..61476fda 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java @@ -30,7 +30,7 @@ class ItemProviderTest { @ParameterizedTest @DisplayName("DB에 저장되어 있는 아이템을 카테고리 별로 받아올 수 있다.") - @EnumSource(mode = Mode.INCLUDE, names = {"POINT_MULTIPLIER", "CERTIFICATION_PASSER", "PROFILE_FRAME"}) + @EnumSource(mode = Mode.INCLUDE, names = {"POINT_MULTIPLIER", "CERTIFICATION_PASSER"}) public void should_findItems_when_passCategory(ItemCategory itemCategory) { //given Item item = getSavedItem(itemCategory); diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index 33494c63..a56fc112 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -75,7 +75,7 @@ public void should_getAllItems_when_itemsSaved() { List items = itemService.getAllItems(user); //then - assertThat(items.size()).isEqualTo(3); + assertThat(items.size()).isEqualTo(4); } @ParameterizedTest @@ -92,7 +92,7 @@ public void should_getItems_when_passCategory(ItemCategory itemCategory) { //then for (ItemResponse itemResponse : itemResponses) { - assertThat(itemResponse.getName()).isEqualTo(itemCategory.getName()); + assertThat(itemResponse.getName()).contains(itemCategory.getName()); } } From 786b695bbdda2e407a2290803c6f7adbb31cf458 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Fri, 15 Mar 2024 20:50:28 +0900 Subject: [PATCH 139/234] =?UTF-8?q?chore:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CertificationService.java | 2 +- .../myChallenge/dto/ActivatedResponse.java | 2 +- .../myChallenge/dto/DoneResponse.java | 2 +- .../service/MyChallengeService.java | 10 +++--- .../gitget/challenge/user/domain/User.java | 4 +-- .../item/controller/ItemController.java | 12 +++---- .../item/domain/EquipStatus.java | 2 +- .../item/domain/Item.java | 2 +- .../item/domain/ItemCategory.java | 2 +- .../item/domain/Orders.java | 2 +- .../store/item/domain/PurchaseType.java | 8 +++++ .../gitget/store/item/domain/history.java | 35 +++++++++++++++++++ .../item/dto/ItemResponse.java | 6 ++-- .../item/dto/ItemUseResponse.java | 2 +- .../item/dto/ProfileResponse.java | 4 +-- .../item/repository/ItemRepository.java | 6 ++-- .../item/repository/OrdersRepository.java | 8 ++--- .../item/service/ItemProvider.java | 8 ++--- .../item/service/ItemService.java | 16 ++++----- .../item/service/OrdersProvider.java | 10 +++--- .../payment/config/TossPaymentConfig.java | 2 +- .../payment/controller/PaymentController.java | 14 ++++---- .../{ => store}/payment/domain/Payment.java | 2 +- .../payment/dto/PaymentFailRequest.java | 2 +- .../payment/dto/PaymentRequest.java | 4 +-- .../payment/dto/PaymentResponse.java | 4 +-- .../payment/dto/PaymentSuccessRequest.java | 2 +- .../payment/dto/PaymentSuccessResponse.java | 4 +-- .../payment/repository/PaymentRepository.java | 4 +-- .../payment/service/PaymentService.java | 18 +++++----- .../service/CertificationServiceTest.java | 10 +++--- .../InstanceSearchRepositoryTest.java | 2 +- .../item/service/ItemProviderTest.java | 7 ++-- .../item/service/ItemServiceTest.java | 19 +++++----- .../item/service/OrdersProviderTest.java | 11 +++--- .../service/MyChallengeServiceTest.java | 10 +++--- .../gitget/payment/PaymentRepositoryTest.java | 2 +- 37 files changed, 153 insertions(+), 107 deletions(-) rename src/main/java/com/genius/gitget/{challenge => store}/item/controller/ItemController.java (90%) rename src/main/java/com/genius/gitget/{challenge => store}/item/domain/EquipStatus.java (83%) rename src/main/java/com/genius/gitget/{challenge => store}/item/domain/Item.java (96%) rename src/main/java/com/genius/gitget/{challenge => store}/item/domain/ItemCategory.java (94%) rename src/main/java/com/genius/gitget/{challenge => store}/item/domain/Orders.java (98%) create mode 100644 src/main/java/com/genius/gitget/store/item/domain/PurchaseType.java create mode 100644 src/main/java/com/genius/gitget/store/item/domain/history.java rename src/main/java/com/genius/gitget/{challenge => store}/item/dto/ItemResponse.java (77%) rename src/main/java/com/genius/gitget/{challenge => store}/item/dto/ItemUseResponse.java (90%) rename src/main/java/com/genius/gitget/{challenge => store}/item/dto/ProfileResponse.java (82%) rename src/main/java/com/genius/gitget/{challenge => store}/item/repository/ItemRepository.java (71%) rename src/main/java/com/genius/gitget/{challenge => store}/item/repository/OrdersRepository.java (83%) rename src/main/java/com/genius/gitget/{challenge => store}/item/service/ItemProvider.java (76%) rename src/main/java/com/genius/gitget/{challenge => store}/item/service/ItemService.java (93%) rename src/main/java/com/genius/gitget/{challenge => store}/item/service/OrdersProvider.java (85%) rename src/main/java/com/genius/gitget/{ => store}/payment/config/TossPaymentConfig.java (92%) rename src/main/java/com/genius/gitget/{ => store}/payment/controller/PaymentController.java (83%) rename src/main/java/com/genius/gitget/{ => store}/payment/domain/Payment.java (97%) rename src/main/java/com/genius/gitget/{ => store}/payment/dto/PaymentFailRequest.java (72%) rename src/main/java/com/genius/gitget/{ => store}/payment/dto/PaymentRequest.java (90%) rename src/main/java/com/genius/gitget/{ => store}/payment/dto/PaymentResponse.java (90%) rename src/main/java/com/genius/gitget/{ => store}/payment/dto/PaymentSuccessRequest.java (77%) rename src/main/java/com/genius/gitget/{ => store}/payment/dto/PaymentSuccessResponse.java (92%) rename src/main/java/com/genius/gitget/{ => store}/payment/repository/PaymentRepository.java (67%) rename src/main/java/com/genius/gitget/{ => store}/payment/service/PaymentService.java (91%) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 6baacf0a..90df1840 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -15,7 +15,7 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.service.InstanceProvider; -import com.genius.gitget.challenge.item.service.OrdersProvider; +import com.genius.gitget.store.item.service.OrdersProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantProvider; diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java index 7ca69d63..e5f5082e 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java @@ -2,7 +2,7 @@ import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; import com.genius.gitget.global.file.dto.FileResponse; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java index 11b069da..7126ec38 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java @@ -1,7 +1,7 @@ package com.genius.gitget.challenge.myChallenge.dto; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.domain.RewardStatus; diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index aecca4cb..c7d6ff10 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -1,8 +1,8 @@ package com.genius.gitget.challenge.myChallenge.service; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; -import static com.genius.gitget.challenge.item.domain.ItemCategory.CERTIFICATION_PASSER; -import static com.genius.gitget.challenge.item.domain.ItemCategory.POINT_MULTIPLIER; +import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; +import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; import static com.genius.gitget.challenge.participant.domain.RewardStatus.NO; import static com.genius.gitget.challenge.participant.domain.RewardStatus.YES; @@ -13,9 +13,9 @@ import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.service.ItemProvider; -import com.genius.gitget.challenge.item.service.OrdersProvider; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.service.ItemProvider; +import com.genius.gitget.store.item.service.OrdersProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index e4e805b1..f7ea81bb 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -1,12 +1,12 @@ package com.genius.gitget.challenge.user.domain; -import com.genius.gitget.challenge.item.domain.Orders; +import com.genius.gitget.store.item.domain.Orders; import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.domain.BaseTimeEntity; -import com.genius.gitget.payment.domain.Payment; +import com.genius.gitget.store.payment.domain.Payment; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/genius/gitget/challenge/item/controller/ItemController.java b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java similarity index 90% rename from src/main/java/com/genius/gitget/challenge/item/controller/ItemController.java rename to src/main/java/com/genius/gitget/store/item/controller/ItemController.java index 1bba8cc0..90f92938 100644 --- a/src/main/java/com/genius/gitget/challenge/item/controller/ItemController.java +++ b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java @@ -1,12 +1,12 @@ -package com.genius.gitget.challenge.item.controller; +package com.genius.gitget.store.item.controller; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.dto.ItemResponse; -import com.genius.gitget.challenge.item.dto.ItemUseResponse; -import com.genius.gitget.challenge.item.dto.ProfileResponse; -import com.genius.gitget.challenge.item.service.ItemService; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.ProfileResponse; +import com.genius.gitget.store.item.service.ItemService; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.ListResponse; diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/EquipStatus.java b/src/main/java/com/genius/gitget/store/item/domain/EquipStatus.java similarity index 83% rename from src/main/java/com/genius/gitget/challenge/item/domain/EquipStatus.java rename to src/main/java/com/genius/gitget/store/item/domain/EquipStatus.java index 91726105..5e3a368a 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/EquipStatus.java +++ b/src/main/java/com/genius/gitget/store/item/domain/EquipStatus.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.item.domain; +package com.genius.gitget.store.item.domain; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java b/src/main/java/com/genius/gitget/store/item/domain/Item.java similarity index 96% rename from src/main/java/com/genius/gitget/challenge/item/domain/Item.java rename to src/main/java/com/genius/gitget/store/item/domain/Item.java index 818055db..a0ae350f 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/Item.java +++ b/src/main/java/com/genius/gitget/store/item/domain/Item.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.item.domain; +package com.genius.gitget.store.item.domain; import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.Column; diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java b/src/main/java/com/genius/gitget/store/item/domain/ItemCategory.java similarity index 94% rename from src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java rename to src/main/java/com/genius/gitget/store/item/domain/ItemCategory.java index 02a33cb6..a523287e 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/ItemCategory.java +++ b/src/main/java/com/genius/gitget/store/item/domain/ItemCategory.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.item.domain; +package com.genius.gitget.store.item.domain; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; diff --git a/src/main/java/com/genius/gitget/challenge/item/domain/Orders.java b/src/main/java/com/genius/gitget/store/item/domain/Orders.java similarity index 98% rename from src/main/java/com/genius/gitget/challenge/item/domain/Orders.java rename to src/main/java/com/genius/gitget/store/item/domain/Orders.java index 349f14bc..0ad49f74 100644 --- a/src/main/java/com/genius/gitget/challenge/item/domain/Orders.java +++ b/src/main/java/com/genius/gitget/store/item/domain/Orders.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.item.domain; +package com.genius.gitget.store.item.domain; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.util.exception.BusinessException; diff --git a/src/main/java/com/genius/gitget/store/item/domain/PurchaseType.java b/src/main/java/com/genius/gitget/store/item/domain/PurchaseType.java new file mode 100644 index 00000000..4935d2ae --- /dev/null +++ b/src/main/java/com/genius/gitget/store/item/domain/PurchaseType.java @@ -0,0 +1,8 @@ +package com.genius.gitget.store.item.domain; + +import lombok.Getter; + +@Getter +public enum PurchaseType { + POINTS, ITEM +} diff --git a/src/main/java/com/genius/gitget/store/item/domain/history.java b/src/main/java/com/genius/gitget/store/item/domain/history.java new file mode 100644 index 00000000..55ed7362 --- /dev/null +++ b/src/main/java/com/genius/gitget/store/item/domain/history.java @@ -0,0 +1,35 @@ +package com.genius.gitget.store.item.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Null; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "history") +public class history { + @Id + @GeneratedValue + @Column(name = "history_id") + private Long id; + + @Enumerated(EnumType.STRING) + private PurchaseType purchaseType; + + @Null + private String PurchaseItem; + + private LocalDateTime PurchaseDate; + + +} diff --git a/src/main/java/com/genius/gitget/challenge/item/dto/ItemResponse.java b/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java similarity index 77% rename from src/main/java/com/genius/gitget/challenge/item/dto/ItemResponse.java rename to src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java index ff8555b1..aa5677e4 100644 --- a/src/main/java/com/genius/gitget/challenge/item/dto/ItemResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java @@ -1,7 +1,7 @@ -package com.genius.gitget.challenge.item.dto; +package com.genius.gitget.store.item.dto; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; import lombok.Data; @Data diff --git a/src/main/java/com/genius/gitget/challenge/item/dto/ItemUseResponse.java b/src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java similarity index 90% rename from src/main/java/com/genius/gitget/challenge/item/dto/ItemUseResponse.java rename to src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java index 6428f337..c213fbdb 100644 --- a/src/main/java/com/genius/gitget/challenge/item/dto/ItemUseResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.item.dto; +package com.genius.gitget.store.item.dto; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/genius/gitget/challenge/item/dto/ProfileResponse.java b/src/main/java/com/genius/gitget/store/item/dto/ProfileResponse.java similarity index 82% rename from src/main/java/com/genius/gitget/challenge/item/dto/ProfileResponse.java rename to src/main/java/com/genius/gitget/store/item/dto/ProfileResponse.java index 31005902..05b1c480 100644 --- a/src/main/java/com/genius/gitget/challenge/item/dto/ProfileResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/ProfileResponse.java @@ -1,6 +1,6 @@ -package com.genius.gitget.challenge.item.dto; +package com.genius.gitget.store.item.dto; -import com.genius.gitget.challenge.item.domain.Item; +import com.genius.gitget.store.item.domain.Item; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java b/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java similarity index 71% rename from src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java rename to src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java index 1e04a4a8..788eca12 100644 --- a/src/main/java/com/genius/gitget/challenge/item/repository/ItemRepository.java +++ b/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java @@ -1,7 +1,7 @@ -package com.genius.gitget.challenge.item.repository; +package com.genius.gitget.store.item.repository; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/genius/gitget/challenge/item/repository/OrdersRepository.java b/src/main/java/com/genius/gitget/store/item/repository/OrdersRepository.java similarity index 83% rename from src/main/java/com/genius/gitget/challenge/item/repository/OrdersRepository.java rename to src/main/java/com/genius/gitget/store/item/repository/OrdersRepository.java index 890b66f2..66ce1c96 100644 --- a/src/main/java/com/genius/gitget/challenge/item/repository/OrdersRepository.java +++ b/src/main/java/com/genius/gitget/store/item/repository/OrdersRepository.java @@ -1,8 +1,8 @@ -package com.genius.gitget.challenge.item.repository; +package com.genius.gitget.store.item.repository; -import com.genius.gitget.challenge.item.domain.EquipStatus; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Orders; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java b/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java similarity index 76% rename from src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java rename to src/main/java/com/genius/gitget/store/item/service/ItemProvider.java index bba8bd9b..e38204ed 100644 --- a/src/main/java/com/genius/gitget/challenge/item/service/ItemProvider.java +++ b/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java @@ -1,8 +1,8 @@ -package com.genius.gitget.challenge.item.service; +package com.genius.gitget.store.item.service; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.repository.ItemRepository; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.repository.ItemRepository; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import java.util.List; diff --git a/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java b/src/main/java/com/genius/gitget/store/item/service/ItemService.java similarity index 93% rename from src/main/java/com/genius/gitget/challenge/item/service/ItemService.java rename to src/main/java/com/genius/gitget/store/item/service/ItemService.java index bffdc42e..62da3fbb 100644 --- a/src/main/java/com/genius/gitget/challenge/item/service/ItemService.java +++ b/src/main/java/com/genius/gitget/store/item/service/ItemService.java @@ -1,14 +1,14 @@ -package com.genius.gitget.challenge.item.service; +package com.genius.gitget.store.item.service; import com.genius.gitget.challenge.certification.dto.CertificationRequest; import com.genius.gitget.challenge.certification.service.CertificationService; -import com.genius.gitget.challenge.item.domain.EquipStatus; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Orders; -import com.genius.gitget.challenge.item.dto.ItemResponse; -import com.genius.gitget.challenge.item.dto.ItemUseResponse; -import com.genius.gitget.challenge.item.dto.ProfileResponse; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.ProfileResponse; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; diff --git a/src/main/java/com/genius/gitget/challenge/item/service/OrdersProvider.java b/src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java similarity index 85% rename from src/main/java/com/genius/gitget/challenge/item/service/OrdersProvider.java rename to src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java index 80fcc525..fc9ac83b 100644 --- a/src/main/java/com/genius/gitget/challenge/item/service/OrdersProvider.java +++ b/src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java @@ -1,11 +1,11 @@ -package com.genius.gitget.challenge.item.service; +package com.genius.gitget.store.item.service; import static com.genius.gitget.global.util.exception.ErrorCode.USER_ITEM_NOT_FOUND; -import com.genius.gitget.challenge.item.domain.EquipStatus; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Orders; -import com.genius.gitget.challenge.item.repository.OrdersRepository; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.util.exception.BusinessException; import java.util.Optional; diff --git a/src/main/java/com/genius/gitget/payment/config/TossPaymentConfig.java b/src/main/java/com/genius/gitget/store/payment/config/TossPaymentConfig.java similarity index 92% rename from src/main/java/com/genius/gitget/payment/config/TossPaymentConfig.java rename to src/main/java/com/genius/gitget/store/payment/config/TossPaymentConfig.java index 821c721d..32c0c75d 100644 --- a/src/main/java/com/genius/gitget/payment/config/TossPaymentConfig.java +++ b/src/main/java/com/genius/gitget/store/payment/config/TossPaymentConfig.java @@ -1,4 +1,4 @@ -package com.genius.gitget.payment.config; +package com.genius.gitget.store.payment.config; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; diff --git a/src/main/java/com/genius/gitget/payment/controller/PaymentController.java b/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java similarity index 83% rename from src/main/java/com/genius/gitget/payment/controller/PaymentController.java rename to src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java index adf9ea88..e78cbbac 100644 --- a/src/main/java/com/genius/gitget/payment/controller/PaymentController.java +++ b/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java @@ -1,15 +1,15 @@ -package com.genius.gitget.payment.controller; +package com.genius.gitget.store.payment.controller; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; -import com.genius.gitget.payment.dto.PaymentFailRequest; -import com.genius.gitget.payment.dto.PaymentRequest; -import com.genius.gitget.payment.dto.PaymentResponse; -import com.genius.gitget.payment.dto.PaymentSuccessRequest; -import com.genius.gitget.payment.dto.PaymentSuccessResponse; -import com.genius.gitget.payment.service.PaymentService; +import com.genius.gitget.store.payment.dto.PaymentFailRequest; +import com.genius.gitget.store.payment.dto.PaymentRequest; +import com.genius.gitget.store.payment.dto.PaymentResponse; +import com.genius.gitget.store.payment.dto.PaymentSuccessRequest; +import com.genius.gitget.store.payment.dto.PaymentSuccessResponse; +import com.genius.gitget.store.payment.service.PaymentService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/genius/gitget/payment/domain/Payment.java b/src/main/java/com/genius/gitget/store/payment/domain/Payment.java similarity index 97% rename from src/main/java/com/genius/gitget/payment/domain/Payment.java rename to src/main/java/com/genius/gitget/store/payment/domain/Payment.java index 2247e011..1444f33b 100644 --- a/src/main/java/com/genius/gitget/payment/domain/Payment.java +++ b/src/main/java/com/genius/gitget/store/payment/domain/Payment.java @@ -1,4 +1,4 @@ -package com.genius.gitget.payment.domain; +package com.genius.gitget.store.payment.domain; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.util.domain.BaseTimeEntity; diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentFailRequest.java b/src/main/java/com/genius/gitget/store/payment/dto/PaymentFailRequest.java similarity index 72% rename from src/main/java/com/genius/gitget/payment/dto/PaymentFailRequest.java rename to src/main/java/com/genius/gitget/store/payment/dto/PaymentFailRequest.java index 6c80f787..e2d05b87 100644 --- a/src/main/java/com/genius/gitget/payment/dto/PaymentFailRequest.java +++ b/src/main/java/com/genius/gitget/store/payment/dto/PaymentFailRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.payment.dto; +package com.genius.gitget.store.payment.dto; import lombok.Data; diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java b/src/main/java/com/genius/gitget/store/payment/dto/PaymentRequest.java similarity index 90% rename from src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java rename to src/main/java/com/genius/gitget/store/payment/dto/PaymentRequest.java index 895807eb..93e813a1 100644 --- a/src/main/java/com/genius/gitget/payment/dto/PaymentRequest.java +++ b/src/main/java/com/genius/gitget/store/payment/dto/PaymentRequest.java @@ -1,7 +1,7 @@ -package com.genius.gitget.payment.dto; +package com.genius.gitget.store.payment.dto; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.payment.domain.Payment; +import com.genius.gitget.store.payment.domain.Payment; import java.util.UUID; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java b/src/main/java/com/genius/gitget/store/payment/dto/PaymentResponse.java similarity index 90% rename from src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java rename to src/main/java/com/genius/gitget/store/payment/dto/PaymentResponse.java index 8f6c4b27..74e17fa4 100644 --- a/src/main/java/com/genius/gitget/payment/dto/PaymentResponse.java +++ b/src/main/java/com/genius/gitget/store/payment/dto/PaymentResponse.java @@ -1,6 +1,6 @@ -package com.genius.gitget.payment.dto; +package com.genius.gitget.store.payment.dto; -import com.genius.gitget.payment.domain.Payment; +import com.genius.gitget.store.payment.domain.Payment; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessRequest.java b/src/main/java/com/genius/gitget/store/payment/dto/PaymentSuccessRequest.java similarity index 77% rename from src/main/java/com/genius/gitget/payment/dto/PaymentSuccessRequest.java rename to src/main/java/com/genius/gitget/store/payment/dto/PaymentSuccessRequest.java index 7723de21..2ff49410 100644 --- a/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessRequest.java +++ b/src/main/java/com/genius/gitget/store/payment/dto/PaymentSuccessRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.payment.dto; +package com.genius.gitget.store.payment.dto; import lombok.Data; diff --git a/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java b/src/main/java/com/genius/gitget/store/payment/dto/PaymentSuccessResponse.java similarity index 92% rename from src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java rename to src/main/java/com/genius/gitget/store/payment/dto/PaymentSuccessResponse.java index 0204b5c9..f4c09ddd 100644 --- a/src/main/java/com/genius/gitget/payment/dto/PaymentSuccessResponse.java +++ b/src/main/java/com/genius/gitget/store/payment/dto/PaymentSuccessResponse.java @@ -1,7 +1,7 @@ -package com.genius.gitget.payment.dto; +package com.genius.gitget.store.payment.dto; -import com.genius.gitget.payment.domain.Payment; +import com.genius.gitget.store.payment.domain.Payment; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/genius/gitget/payment/repository/PaymentRepository.java b/src/main/java/com/genius/gitget/store/payment/repository/PaymentRepository.java similarity index 67% rename from src/main/java/com/genius/gitget/payment/repository/PaymentRepository.java rename to src/main/java/com/genius/gitget/store/payment/repository/PaymentRepository.java index 79498d42..66377d45 100644 --- a/src/main/java/com/genius/gitget/payment/repository/PaymentRepository.java +++ b/src/main/java/com/genius/gitget/store/payment/repository/PaymentRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.payment.repository; +package com.genius.gitget.store.payment.repository; -import com.genius.gitget.payment.domain.Payment; +import com.genius.gitget.store.payment.domain.Payment; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/genius/gitget/payment/service/PaymentService.java b/src/main/java/com/genius/gitget/store/payment/service/PaymentService.java similarity index 91% rename from src/main/java/com/genius/gitget/payment/service/PaymentService.java rename to src/main/java/com/genius/gitget/store/payment/service/PaymentService.java index 13c44276..546e2def 100644 --- a/src/main/java/com/genius/gitget/payment/service/PaymentService.java +++ b/src/main/java/com/genius/gitget/store/payment/service/PaymentService.java @@ -1,4 +1,4 @@ -package com.genius.gitget.payment.service; +package com.genius.gitget.store.payment.service; import static java.lang.Long.valueOf; @@ -6,14 +6,14 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; -import com.genius.gitget.payment.config.TossPaymentConfig; -import com.genius.gitget.payment.domain.Payment; -import com.genius.gitget.payment.dto.PaymentFailRequest; -import com.genius.gitget.payment.dto.PaymentRequest; -import com.genius.gitget.payment.dto.PaymentResponse; -import com.genius.gitget.payment.dto.PaymentSuccessRequest; -import com.genius.gitget.payment.dto.PaymentSuccessResponse; -import com.genius.gitget.payment.repository.PaymentRepository; +import com.genius.gitget.store.payment.config.TossPaymentConfig; +import com.genius.gitget.store.payment.domain.Payment; +import com.genius.gitget.store.payment.dto.PaymentFailRequest; +import com.genius.gitget.store.payment.dto.PaymentRequest; +import com.genius.gitget.store.payment.dto.PaymentResponse; +import com.genius.gitget.store.payment.dto.PaymentSuccessRequest; +import com.genius.gitget.store.payment.dto.PaymentSuccessResponse; +import com.genius.gitget.store.payment.repository.PaymentRepository; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index 12d5f331..5fd5275a 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -21,11 +21,11 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Orders; -import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.OrdersRepository; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index 89272f0f..a940dc98 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -154,7 +154,7 @@ public void setup() throws IOException { cnt++; } } - Assertions.assertThat(cnt).isEqualTo(1); + Assertions.assertThat(cnt).isEqualTo(0); } @Test diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java index 61476fda..2e78d383 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java @@ -3,11 +3,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.repository.ItemRepository; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.repository.ItemRepository; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.service.ItemProvider; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index a56fc112..6154ca6e 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -13,15 +13,15 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.item.domain.EquipStatus; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Orders; -import com.genius.gitget.challenge.item.dto.ItemResponse; -import com.genius.gitget.challenge.item.dto.ItemUseResponse; -import com.genius.gitget.challenge.item.dto.ProfileResponse; -import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.OrdersRepository; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.ProfileResponse; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; @@ -32,6 +32,7 @@ import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.service.ItemService; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; diff --git a/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java index 611303ae..78e9b52c 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java @@ -2,15 +2,16 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Orders; -import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.OrdersRepository; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.store.item.service.OrdersProvider; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java index f0f2065d..3ec0df6a 100644 --- a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java @@ -11,11 +11,11 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.item.domain.Item; -import com.genius.gitget.challenge.item.domain.ItemCategory; -import com.genius.gitget.challenge.item.domain.Orders; -import com.genius.gitget.challenge.item.repository.ItemRepository; -import com.genius.gitget.challenge.item.repository.OrdersRepository; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; diff --git a/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java b/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java index 37f3687c..3a759b69 100644 --- a/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java +++ b/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java @@ -1,7 +1,7 @@ package com.genius.gitget.payment; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.payment.repository.PaymentRepository; +import com.genius.gitget.store.payment.repository.PaymentRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; From b0cad1bb06d6959363949b1e1af38bfc68b3d47b Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 17 Mar 2024 19:08:37 +0900 Subject: [PATCH 140/234] =?UTF-8?q?[FIX]=20=EC=9D=B8=EC=A6=9D=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=EA=B4=80=EB=A0=A8=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=ED=94=BD=EC=8A=A4=20(#128)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 패스한 인증에 대해 인증 갱신 시 예외를 던지도록 변경 - 이미 패스한 인증에 대해 인증 갱신 시, 패스한 인증에 대해서는 갱신이 불가능하다는 예외를 반환하도록 처리 - 관련 테스트 코드 추가 * fix: API 요청 시 영속화 문제로 인해 Files를 불러오지 못하던 버그 픽스 * fix: 챌린지 시작 일자에 따라 잘못된 날짜를 전달하는 버그 픽스 - 챌린지의 시작일자가 월요일이 아니고 첫째 주일 때, 잘못된 날짜를 반환하는 버그 픽스 --- .../controller/CertificationController.java | 2 +- .../service/CertificationService.java | 9 +++- .../certification/util/DateUtil.java | 19 ++++---- .../global/util/exception/ErrorCode.java | 2 + .../service/CertificationServiceTest.java | 37 +++++++++++++--- .../certification/util/DateUtilTest.java | 43 +++++++++++++++++++ 6 files changed, 95 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index cefd6003..a63e73c0 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -93,7 +93,7 @@ public ResponseEntity> getWeekCertification( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long instanceId ) { - User user = userPrincipal.getUser(); + User user = userService.findUserById(userPrincipal.getUser().getId()); Participant participant = participantProvider.findByJoinInfo(user.getId(), instanceId); List weekCertification = certificationService.getWeekCertification( participant.getId(), LocalDate.now()); diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 90df1840..6338f72b 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -15,7 +15,6 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.service.InstanceProvider; -import com.genius.gitget.store.item.service.OrdersProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantProvider; @@ -24,6 +23,7 @@ import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.service.OrdersProvider; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; @@ -189,7 +189,12 @@ public CertificationResponse updateCertification(User user, CertificationRequest ); Certification certification = certificationProvider.findByDate(targetDate, participant.getId()) - .map(updated -> certificationProvider.update(updated, targetDate, filteredPullRequests)) + .map(updated -> { + if (updated.getCertificationStatus() == PASSED) { + throw new BusinessException(ErrorCode.ALREADY_PASSED_CERTIFICATION); + } + return certificationProvider.update(updated, targetDate, filteredPullRequests); + }) .orElseGet( () -> certificationProvider.createCertification(participant, targetDate, filteredPullRequests) ); diff --git a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java index ed109dba..2130c390 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java +++ b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java @@ -1,6 +1,5 @@ package com.genius.gitget.challenge.certification.util; -import java.time.DayOfWeek; import java.time.LocalDate; import java.time.ZoneId; import java.time.temporal.ChronoUnit; @@ -23,7 +22,7 @@ public static int getWeekAttempt(LocalDate challengeStartDate, LocalDate targetD int weekAttempt = targetDate.getDayOfWeek().ordinal() + 1; int totalAttempt = getAttemptCount(challengeStartDate, targetDate); - if (isNotStartWithMonday(challengeStartDate, targetDate)) { + if (isFirstWeek(challengeStartDate, targetDate)) { return totalAttempt; } @@ -31,11 +30,11 @@ public static int getWeekAttempt(LocalDate challengeStartDate, LocalDate targetD } public static LocalDate getWeekStartDate(LocalDate challengeStartDate, LocalDate currentDate) { - if (isNotStartWithMonday(challengeStartDate, currentDate)) { + if (isFirstWeek(challengeStartDate, currentDate)) { return challengeStartDate; } - - return currentDate.minusDays(currentDate.getDayOfWeek().ordinal()); + LocalDate mondayOfWeek = currentDate.minusDays(currentDate.getDayOfWeek().ordinal()); + return mondayOfWeek; } public static LocalDate convertToLocalDate(Date date) { @@ -45,10 +44,12 @@ public static LocalDate convertToLocalDate(Date date) { ); } - private static boolean isNotStartWithMonday(LocalDate challengeStartDate, LocalDate currentDate) { - int totalAttempt = getAttemptCount(challengeStartDate, currentDate); - // 첫째주이고 && 시작일이 월요일이 아닐 때 - if ((challengeStartDate.getDayOfWeek() != DayOfWeek.MONDAY) && (totalAttempt < 8)) { + private static boolean isFirstWeek(LocalDate challengeStartDate, LocalDate currentDate) { + LocalDate mondayOfWeek = challengeStartDate.minusDays(challengeStartDate.getDayOfWeek().ordinal()); + LocalDate sundayOfWeek = mondayOfWeek.plusDays(6); + + if (currentDate.isAfter(mondayOfWeek.minusDays(1)) + && currentDate.isBefore(sundayOfWeek.plusDays(1))) { return true; } return false; diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 5b138aa6..cbb318aa 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -25,6 +25,8 @@ public enum ErrorCode { INVALID_INSTANCE_DATE(HttpStatus.BAD_REQUEST, "인스턴스 생성/종료 일자는 현재 일자 이후여야 합니다."), INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), PARTICIPANT_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 참여 정보를 찾을 수 없습니다."), + + ALREADY_PASSED_CERTIFICATION(HttpStatus.BAD_REQUEST, "패스한 인증에 대해서는 인증 갱신할 수 없습니다."), CERTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인증 정보를 찾을 수 없습니다."), ALREADY_REGISTERED(HttpStatus.BAD_REQUEST, "이미 회원가입이 완료된 사용자입니다."), diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index 5fd5275a..16c34a9d 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -3,6 +3,7 @@ import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; +import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_PASSED_CERTIFICATION; import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_CERTIFICATE_PERIOD; import static org.assertj.core.api.Assertions.assertThat; @@ -21,11 +22,6 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; @@ -37,6 +33,11 @@ import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -167,6 +168,32 @@ public void should_throwException_when_progressIsNotActivity() { .hasMessageContaining(NOT_CERTIFICATE_PERIOD.getMessage()); } + @Test + @DisplayName("패스를 완료했을 때, 인증 갱신을 요청한다면 예외가 발생한다.") + public void should_throwException_when_passedAlready() { + //given + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(user, instance); + githubService.registerGithubPersonalToken(user, personalKey); + + LocalDate targetDate = LocalDate.of(2024, 2, 6); + + CertificationRequest certificationRequest = CertificationRequest.builder() + .instanceId(instance.getId()) + .targetDate(targetDate) + .build(); + instance.updateProgress(Progress.ACTIVITY); + + //when + getSavedCertification(PASSED, targetDate, participant); + + //then + assertThatThrownBy(() -> certificationService.updateCertification(user, certificationRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ALREADY_PASSED_CERTIFICATION.getMessage()); + } + @Test @DisplayName("사용자가 연결한 레포지토리에 특정 날짜의 PR이 존재하지 않으면 인증이 아직 안된 것으로 간주한다.") public void should_notCertificate_when_prNotExist() { diff --git a/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java b/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java index db8675d3..3f535c66 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.time.DayOfWeek; import java.time.LocalDate; import java.util.Date; import lombok.extern.slf4j.Slf4j; @@ -93,4 +94,46 @@ public void should_returnMinus_when_startDateBeforeThenTargetDate() { //then assertThat(remainDays).isEqualTo(0); } + + @Test + @DisplayName("챌린지 시작 일자가 월요일이 아니고 시작한 그 주일 때, 시작일자를 반환해야 한다.") + public void should_returnStartDate_when_StartDateNotMonDayAndSecondWeek() { + LocalDate challengeStartDate = LocalDate.of(2024, 3, 13); + LocalDate targetDate = LocalDate.of(2024, 3, 15); + + //when + LocalDate weekStartDate = DateUtil.getWeekStartDate(challengeStartDate, targetDate); + + //then + assertThat(weekStartDate).isEqualTo(challengeStartDate); + } + + @Test + @DisplayName("챌린지 시작일자가 월요일이 아니고 그 다움주일 때, 해당 주의 월요일을 반환해야 한다.") + public void should_returnMonday_when_startDateIsNotMonday() { + LocalDate challengeStartDate = LocalDate.of(2024, 3, 10); + LocalDate targetDate = LocalDate.of(2024, 3, 15); + + //when + LocalDate weekStartDate = DateUtil.getWeekStartDate(challengeStartDate, targetDate); + + //then + + assertThat(weekStartDate.getDayOfWeek()).isEqualTo(DayOfWeek.MONDAY); + } + + @Test + @DisplayName("챌린지 시작일자에 상관없이 시작한지 두 번째 주 일 때, 해당 주의 월요일을 전달해야한다") + public void should_returnMonday_when_secondWeek() { + //given + LocalDate challengeStartDate = LocalDate.of(2024, 3, 10); + LocalDate targetDate = LocalDate.of(2024, 3, 20); + + //when + LocalDate weekStartDate = DateUtil.getWeekStartDate(challengeStartDate, targetDate); + + //then + assertThat(weekStartDate).isEqualTo(LocalDate.of(2024, 3, 18)); + assertThat(weekStartDate.getDayOfWeek()).isEqualTo(DayOfWeek.MONDAY); + } } \ No newline at end of file From 27bc71581ab3edbf48785e63f361903190d4e305 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 17 Mar 2024 19:08:51 +0900 Subject: [PATCH 141/234] =?UTF-8?q?[FEAT]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C,=20=EA=B0=9C=EB=B0=9C=EC=9E=90=EC=97=90?= =?UTF-8?q?=20=ED=95=B4=EB=8B=B9=ED=95=98=EB=8A=94=20=EA=B9=83=ED=97=88?= =?UTF-8?q?=EB=B8=8C=20=EA=B3=84=EC=A0=95=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=9A=B4=EC=98=81=EC=A7=84=EC=9C=BC=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=20(#130)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 회원가입 시도 시, 개발자라면 Role을 자동으로 ADMIN으로 설정 - 사용자가 회원가입 시도 시, Admin 리스트에 포함되는 사용자라면 자동으로 ADMIN으로 설정되는 로직 추가 개발 - 관련 테스트 코드 작성 * test: UserControllerTest 수정 중 * chore: yml 경로 값 변경 --- .../gitget/challenge/user/domain/User.java | 2 +- .../challenge/user/service/UserService.java | 15 +++++- .../user/controller/UserControllerTest.java | 53 +++++++++++++++---- .../user/service/UserServiceTest.java | 44 +++++++++++---- 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index f7ea81bb..51c0da90 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -1,11 +1,11 @@ package com.genius.gitget.challenge.user.domain; -import com.genius.gitget.store.item.domain.Orders; import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.domain.BaseTimeEntity; +import com.genius.gitget.store.item.domain.Orders; import com.genius.gitget.store.payment.domain.Payment; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 209f3b3b..975b8425 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -13,8 +13,10 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -28,6 +30,9 @@ public class UserService { private final FilesService filesService; private final EncryptUtil encryptUtil; + @Value("${admin.githubId}") + private List adminIds; + public User findUserById(Long id) { return userRepository.findById(id) @@ -53,7 +58,7 @@ public Long signup(SignupRequest requestUser, MultipartFile multipartFile) { user.updateUser(requestUser.nickname(), requestUser.information(), interest); - user.updateRole(Role.USER); + updateRole(user); Files files = filesService.uploadFile(multipartFile, "profile"); user.setFiles(files); @@ -61,6 +66,14 @@ public Long signup(SignupRequest requestUser, MultipartFile multipartFile) { return user.getId(); } + private void updateRole(User user) { + if (adminIds.contains(user.getIdentifier())) { + user.updateRole(Role.ADMIN); + return; + } + user.updateRole(Role.USER); + } + public void isNicknameDuplicate(String nickname) { if (userRepository.findByNickname(nickname).isPresent()) { throw new BusinessException(DUPLICATED_NICKNAME); diff --git a/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java b/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java index 53b6c067..bc7682f0 100644 --- a/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java @@ -4,21 +4,29 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.fasterxml.jackson.databind.ObjectMapper; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import java.nio.charset.StandardCharsets; +import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; @@ -27,12 +35,17 @@ @SpringBootTest @Transactional @Slf4j +@ActiveProfiles({"file"}) class UserControllerTest { MockMvc mockMvc; @Autowired WebApplicationContext context; @Autowired UserRepository userRepository; + @Autowired + ObjectMapper objectMapper; + @Value("${file.upload.path}") + private String testUploadPath; @BeforeEach public void setup() { @@ -70,23 +83,31 @@ public void should_return2XX_when_nicknameNotDuplicated() throws Exception { @DisplayName("사용자의 회원가입이 완료되었다면 사용자의 identifier를 전달한다.") public void should_passIdentifier_when_signupCompleted() throws Exception { //given - saveUnsignedUser(); - - String requestBody = "{\"identifier\" : \"kimdozzi\",\"nickname\" : \"nickname\",\"interest\" : [\"Backend\", \"Frontend\"],\"information\" : \"hello\"}"; + String identifier = "identifier"; + saveUnsignedUser(identifier); + SignupRequest signupRequest = SignupRequest.builder() + .identifier(identifier) + .nickname("nickname") + .information("information") + .interest(List.of("관심사1", "관심사2")) + .build(); + String signupAsString = objectMapper.writeValueAsString(signupRequest); + MockMultipartFile profileFile = setMockMultipartFile("testImage", "png"); //when & then - mockMvc.perform(post("/api/auth/signup") - .contentType(MediaType.APPLICATION_JSON) - .content(requestBody)) - .andExpect(status().is2xxSuccessful()); + mockMvc.perform(multipart("/api/auth/signup") + .file(profileFile) + .file(new MockMultipartFile("data", "", "application/json", + signupAsString.getBytes(StandardCharsets.UTF_8)))) + .andExpect(status().isOk()); } - private void saveUnsignedUser() { + private void saveUnsignedUser(String identifier) { userRepository.save(User.builder() .role(Role.NOT_REGISTERED) .providerInfo(ProviderInfo.GITHUB) - .identifier("kimdozzi") + .identifier(identifier) .build()); } @@ -100,4 +121,16 @@ private User getSavedUser() { .providerInfo(ProviderInfo.GITHUB) .build()); } + + private MockMultipartFile setMockMultipartFile(String fileName, String contentType) { + try { + return new MockMultipartFile("files", + fileName + "." + contentType, +// contentType, + MediaType.IMAGE_PNG_VALUE, + fileName.getBytes()); + } catch (Exception e) { + throw new BusinessException(e); + } + } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index d0f7b97f..b3ee287a 100644 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -38,10 +38,10 @@ class UserServiceTest { @DisplayName("특정 사용자를 가입한 이후, 사용자를 찾았을 때 가입했을 때 입력한 정보와 일치해야 한다.") public void should_matchValues_when_signupUser() { //given - String email = "test@naver.com"; - saveUnsignedUser(); + String identifier = "identifier"; + saveUnsignedUser(identifier); SignupRequest signupRequest = SignupRequest.builder() - .identifier(email) + .identifier(identifier) .nickname("nickname") .information("information") .interest(List.of("관심사1", "관심사2")) @@ -49,30 +49,54 @@ public void should_matchValues_when_signupUser() { MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); //when - User user = userService.findUserByIdentifier(email); + User user = userService.findUserByIdentifier(identifier); Long signupUserId = userService.signup(signupRequest, multipartFile); User foundUser = userService.findUserById(signupUserId); + //then assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); assertThat(user.getTags()).isEqualTo(foundUser.getTags()); + assertThat(user.getRole()).isEqualTo(Role.USER); Files files = user.getFiles().get(); assertThat(files.getFileType()).isEqualTo(FileType.PROFILE); assertThat(files.getOriginalFilename()).contains(multipartFile.getOriginalFilename()); } + @Test + @DisplayName("어드민 깃허브 계정에 해당하는 사용자가 가입을 요청하는 경우, ROLE이 자동으로 ADMIN으로 설정된다.") + public void should_setRoleAdmin_when_identifierMatchesWithAdmin() { + //given + String identifier = "SSung023"; + saveUnsignedUser(identifier); + SignupRequest signupRequest = SignupRequest.builder() + .identifier(identifier) + .nickname("nickname") + .information("information") + .interest(List.of("관심사1", "관심사2")) + .build(); + MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); + + //when + Long signupUserId = userService.signup(signupRequest, multipartFile); + User signupUser = userService.findUserById(signupUserId); + + //then + assertThat(signupUser.getRole()).isEqualTo(Role.ADMIN); + } + @Test @DisplayName("사용자가 한 차례 회원가입을 진행한 후, 한 번 더 회원가입을 요청하면 예외가 발생해야 한다.") public void should_throwException_when_requestRegisterAgain() { //given - String email = "test@naver.com"; - saveUnsignedUser(); + String identifier = "identifier"; + saveUnsignedUser(identifier); SignupRequest signupRequest = SignupRequest.builder() - .identifier(email) + .identifier(identifier) .nickname("nickname") .information("information") .interest(List.of("관심사1", "관심사2")) @@ -80,7 +104,7 @@ public void should_throwException_when_requestRegisterAgain() { MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); //when - User user = userService.findUserByIdentifier(email); + User user = userService.findUserByIdentifier(identifier); Long signupUserId = userService.signup(signupRequest, multipartFile); //then @@ -171,11 +195,11 @@ public void should_throwException_when_githubTokenNull() { } - private void saveUnsignedUser() { + private void saveUnsignedUser(String identifier) { userRepository.save(User.builder() .role(Role.NOT_REGISTERED) .providerInfo(ProviderInfo.NAVER) - .identifier("test@naver.com") + .identifier(identifier) .build()); } From 970d607a711dc394dc2a520f564805afac894ccd Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 17 Mar 2024 23:32:19 +0900 Subject: [PATCH 142/234] =?UTF-8?q?[FEAT]=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4(=EC=B1=8C=EB=A6=B0=EC=A7=80=EC=9D=98)=20Progress=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=EC=97=90=20=EC=8A=A4=EC=BC=80=EC=A4=84?= =?UTF-8?q?=EB=A7=81=20=EC=A0=81=EC=9A=A9=20(#132)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 인스턴스 업데이트 기능에 스케쥴러 적용 - @Scheduled 어노테이션을 통해 정해진 시간에 Instance의 상태를 업데이트하는 기능 추가 - yml 파일을 통해 값 관리 * chore: 패키지명 변경 --- .../com/genius/gitget/GitgetApplication.java | 4 ++- .../controller/ProgressController.java} | 12 ++++----- .../service/ProgressService.java} | 7 +++-- .../schedule/service/ScheduleService.java | 27 +++++++++++++++++++ ...aterTest.java => ProgressServiceTest.java} | 13 ++++----- 5 files changed, 48 insertions(+), 15 deletions(-) rename src/main/java/com/genius/gitget/{challenge/instance/controller/ProgressUpdateController.java => schedule/controller/ProgressController.java} (71%) rename src/main/java/com/genius/gitget/{challenge/instance/service/ProgressUpdater.java => schedule/service/ProgressService.java} (95%) create mode 100644 src/main/java/com/genius/gitget/schedule/service/ScheduleService.java rename src/test/java/com/genius/gitget/challenge/instance/service/{ProgressUpdaterTest.java => ProgressServiceTest.java} (96%) diff --git a/src/main/java/com/genius/gitget/GitgetApplication.java b/src/main/java/com/genius/gitget/GitgetApplication.java index 0cfe27cd..20dc5e46 100644 --- a/src/main/java/com/genius/gitget/GitgetApplication.java +++ b/src/main/java/com/genius/gitget/GitgetApplication.java @@ -4,8 +4,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; +import org.springframework.scheduling.annotation.EnableScheduling; -@SpringBootApplication // (exclude = {SecurityAutoConfiguration.class}) +@SpringBootApplication +@EnableScheduling @EnableJpaAuditing @EnableMongoRepositories public class GitgetApplication { diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/ProgressUpdateController.java b/src/main/java/com/genius/gitget/schedule/controller/ProgressController.java similarity index 71% rename from src/main/java/com/genius/gitget/challenge/instance/controller/ProgressUpdateController.java rename to src/main/java/com/genius/gitget/schedule/controller/ProgressController.java index ebe34d65..747fabb4 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/ProgressUpdateController.java +++ b/src/main/java/com/genius/gitget/schedule/controller/ProgressController.java @@ -1,9 +1,9 @@ -package com.genius.gitget.challenge.instance.controller; +package com.genius.gitget.schedule.controller; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.challenge.instance.service.ProgressUpdater; import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.schedule.service.ProgressService; import java.time.LocalDate; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -14,14 +14,14 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api") -public class ProgressUpdateController { - private final ProgressUpdater progressUpdater; +public class ProgressController { + private final ProgressService scheduleService; @GetMapping("/challenges/update") public ResponseEntity updateProgress() { LocalDate currentDate = LocalDate.now(); - progressUpdater.updateToActivity(currentDate); - progressUpdater.updateToDone(currentDate); + scheduleService.updateToActivity(currentDate); + scheduleService.updateToDone(currentDate); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java b/src/main/java/com/genius/gitget/schedule/service/ProgressService.java similarity index 95% rename from src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java rename to src/main/java/com/genius/gitget/schedule/service/ProgressService.java index fd45395b..758e2c3e 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/ProgressUpdater.java +++ b/src/main/java/com/genius/gitget/schedule/service/ProgressService.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.instance.service; +package com.genius.gitget.schedule.service; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; @@ -6,19 +6,22 @@ import com.genius.gitget.challenge.certification.service.CertificationProvider; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.service.InstanceProvider; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class ProgressUpdater { +public class ProgressService { private final InstanceProvider instanceProvider; private final CertificationProvider certificationProvider; private final double SUCCESS_THRESHOLD = 85; diff --git a/src/main/java/com/genius/gitget/schedule/service/ScheduleService.java b/src/main/java/com/genius/gitget/schedule/service/ScheduleService.java new file mode 100644 index 00000000..dc5a5c44 --- /dev/null +++ b/src/main/java/com/genius/gitget/schedule/service/ScheduleService.java @@ -0,0 +1,27 @@ +package com.genius.gitget.schedule.service; + +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ScheduleService { + private final ProgressService scheduleService; + + @Transactional + @Scheduled(cron = "${schedule.cron}") + public void run() { + LocalDate now = LocalDate.now(); + + log.info(now + ": Schedule 설정에 따라 instance의 Progress 업데이트 진행"); + + scheduleService.updateToActivity(now); + scheduleService.updateToDone(now); + } +} diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java similarity index 96% rename from src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java rename to src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java index 5077727c..894aaf36 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressUpdaterTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java @@ -21,6 +21,7 @@ import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.schedule.service.ProgressService; import java.time.LocalDate; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -36,11 +37,11 @@ @SpringBootTest @Transactional @ActiveProfiles({"github"}) -class ProgressUpdaterTest { +class ProgressServiceTest { @Autowired private InstanceDetailService instanceDetailService; @Autowired - private ProgressUpdater progressUpdater; + private ProgressService scheduleService; @Autowired private GithubService githubService; @Autowired @@ -89,7 +90,7 @@ public void should_updateToActivity_when_conditionMatches() { //when List preActivities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.READY); - progressUpdater.updateToActivity(currentDate); + scheduleService.updateToActivity(currentDate); List activities = instanceProvider.findAllByProgress(Progress.ACTIVITY); //then @@ -112,7 +113,7 @@ public void should_updateToDone_when_conditionMatches() { //when List activities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); - progressUpdater.updateToDone(currentDate); + scheduleService.updateToDone(currentDate); List done = instanceProvider.findAllByProgress(Progress.DONE); //then @@ -138,7 +139,7 @@ public void should_updateToSuccess_then_rateOverThreshold() { getSavedCertification(CertificateStatus.PASSED, completedDate, participant1); //when - progressUpdater.updateToDone(currentDate); + scheduleService.updateToDone(currentDate); //then List done = instanceProvider.findAllByProgress(Progress.DONE); @@ -164,7 +165,7 @@ public void should_updateToFail_when_rateUnderThreshold() { getSavedCertification(CertificateStatus.PASSED, completedDate, participant1); //when - progressUpdater.updateToDone(currentDate); + scheduleService.updateToDone(currentDate); //then List done = instanceProvider.findAllByProgress(Progress.DONE); From 4378591558713db809f9bd857c92c89d54c35cff Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Thu, 21 Mar 2024 11:13:22 +0900 Subject: [PATCH 143/234] =?UTF-8?q?[FEAT]=20=EA=B2=B0=EC=A0=9C=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20=20(#135)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 결제 내역 조회 - itemService 수정 - PaymentTossController와 PaymentController 분리 - OrderType에 따라 다른 DTO 반환 - PaymentRepository 쿼리 추가 * feat: 결제 내역 조회 개발 및 테스트 - 테스트 코드 작성 - 코드 리펙토링 --- .../challenge/instance/domain/Instance.java | 1 + .../global/util/exception/ErrorCode.java | 6 +- .../gitget/store/item/domain/EquipStatus.java | 1 + .../gitget/store/item/domain/history.java | 35 ---- .../store/item/service/ItemService.java | 31 ++- .../payment/controller/PaymentController.java | 45 ++--- .../controller/PaymentTossController.java | 55 ++++++ .../store/payment/domain/OrderType.java | 14 ++ .../gitget/store/payment/domain/Payment.java | 9 +- .../payment/dto/PaymentDetailsResponse.java | 47 +++++ .../payment/repository/PaymentRepository.java | 5 + .../store/payment/service/PaymentService.java | 47 ++++- .../item/service/ItemServiceTest.java | 55 +++++- .../challenge/user/domain/UserTest.java | 2 - .../gitget/payment/PaymentServiceTest.java | 185 ++++++++++++++++++ 15 files changed, 445 insertions(+), 93 deletions(-) delete mode 100644 src/main/java/com/genius/gitget/store/item/domain/history.java create mode 100644 src/main/java/com/genius/gitget/store/payment/controller/PaymentTossController.java create mode 100644 src/main/java/com/genius/gitget/store/payment/domain/OrderType.java create mode 100644 src/main/java/com/genius/gitget/store/payment/dto/PaymentDetailsResponse.java create mode 100644 src/test/java/com/genius/gitget/payment/PaymentServiceTest.java diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 0583804c..21a836f0 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -84,6 +84,7 @@ public class Instance { @Column(name = "completed_at") private LocalDateTime completedDate; + @Column(name = "instance_uuid") private String instanceUUID; @Builder diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index cbb318aa..fad56654 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -12,11 +12,11 @@ public enum ErrorCode { MEMBER_NOT_UPDATED(HttpStatus.BAD_REQUEST, "유저 정보가 업데이트되지 않았습니다."), - LIKES_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 좋아요 목록을 찾을 수 없습니다"), + LIKES_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 좋아요 목록을 찾을 수 없습니다."), - FAILED_POINT_PAYMENT(HttpStatus.BAD_REQUEST, "최소 충전 금액은 100원 이상입니다."), + FAILED_POINT_PAYMENT(HttpStatus.BAD_REQUEST, "결제 조건이 충족하지 않습니다."), INVALID_PAYMENT_AMOUNT(HttpStatus.BAD_REQUEST, "최초 결제 요청 금액과 일치하지 않습니다."), - FAILED_FINAL_PAYMENT(HttpStatus.BAD_REQUEST, "최종 결제가 승인되지 않았습니다"), + FAILED_FINAL_PAYMENT(HttpStatus.BAD_REQUEST, "최종 결제가 승인되지 않았습니다."), INVALID_ORDERID(HttpStatus.NOT_FOUND, "해당 주문번호가 존재하지 않습니다."), TOPIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 토픽을 찾을 수 없습니다."), diff --git a/src/main/java/com/genius/gitget/store/item/domain/EquipStatus.java b/src/main/java/com/genius/gitget/store/item/domain/EquipStatus.java index 5e3a368a..c0a13bd2 100644 --- a/src/main/java/com/genius/gitget/store/item/domain/EquipStatus.java +++ b/src/main/java/com/genius/gitget/store/item/domain/EquipStatus.java @@ -12,3 +12,4 @@ public enum EquipStatus { private final String tag; } + diff --git a/src/main/java/com/genius/gitget/store/item/domain/history.java b/src/main/java/com/genius/gitget/store/item/domain/history.java deleted file mode 100644 index 55ed7362..00000000 --- a/src/main/java/com/genius/gitget/store/item/domain/history.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.genius.gitget.store.item.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.validation.constraints.Null; -import java.time.LocalDateTime; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "history") -public class history { - @Id - @GeneratedValue - @Column(name = "history_id") - private Long id; - - @Enumerated(EnumType.STRING) - private PurchaseType purchaseType; - - @Null - private String PurchaseItem; - - private LocalDateTime PurchaseDate; - - -} diff --git a/src/main/java/com/genius/gitget/store/item/service/ItemService.java b/src/main/java/com/genius/gitget/store/item/service/ItemService.java index 62da3fbb..eccbc021 100644 --- a/src/main/java/com/genius/gitget/store/item/service/ItemService.java +++ b/src/main/java/com/genius/gitget/store/item/service/ItemService.java @@ -2,13 +2,6 @@ import com.genius.gitget.challenge.certification.dto.CertificationRequest; import com.genius.gitget.challenge.certification.service.CertificationService; -import com.genius.gitget.store.item.domain.EquipStatus; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.dto.ItemResponse; -import com.genius.gitget.store.item.dto.ItemUseResponse; -import com.genius.gitget.store.item.dto.ProfileResponse; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; @@ -17,6 +10,16 @@ import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.ProfileResponse; +import com.genius.gitget.store.payment.domain.OrderType; +import com.genius.gitget.store.payment.domain.Payment; +import com.genius.gitget.store.payment.repository.PaymentRepository; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -36,6 +39,8 @@ public class ItemService { private final CertificationService certificationService; private final MyChallengeService myChallengeService; + private final PaymentRepository paymentRepository; + public List getAllItems(User user) { List itemResponses = new ArrayList<>(); for (ItemCategory itemCategory : ItemCategory.values()) { @@ -64,6 +69,8 @@ public ItemResponse orderItem(User user, Long itemId) { validateUserPoint(persistUser.getPoint(), item.getCost()); + paymentRepository.save(getPayment(user, item)); + Orders orders = ordersProvider.findOptionalByOrderInfo(persistUser.getId(), itemId) .orElseGet(() -> createNew(persistUser, item)); int numOfItem = orders.purchase(); @@ -72,6 +79,16 @@ public ItemResponse orderItem(User user, Long itemId) { return getItemResponse(persistUser, item, numOfItem); } + private static Payment getPayment(User user, Item item) { + return Payment.builder() + .user(user) + .orderType(OrderType.ITEM) + .isSuccess(true) + .pointAmount(Long.parseLong(String.valueOf(item.getCost()))) + .orderName(item.getName()) + .build(); + } + private void validateUserPoint(long userPoint, int itemCost) { if (userPoint < itemCost) { throw new BusinessException(ErrorCode.NOT_ENOUGH_POINT); diff --git a/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java b/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java index e78cbbac..99017a14 100644 --- a/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java +++ b/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java @@ -2,20 +2,17 @@ import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.exception.SuccessCode; -import com.genius.gitget.global.util.response.dto.CommonResponse; -import com.genius.gitget.global.util.response.dto.SingleResponse; -import com.genius.gitget.store.payment.dto.PaymentFailRequest; -import com.genius.gitget.store.payment.dto.PaymentRequest; -import com.genius.gitget.store.payment.dto.PaymentResponse; -import com.genius.gitget.store.payment.dto.PaymentSuccessRequest; -import com.genius.gitget.store.payment.dto.PaymentSuccessResponse; +import com.genius.gitget.global.util.response.dto.PagingResponse; +import com.genius.gitget.store.payment.dto.PaymentDetailsResponse; import com.genius.gitget.store.payment.service.PaymentService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -27,29 +24,17 @@ public class PaymentController { private final PaymentService paymentService; - @PostMapping("/toss") - public ResponseEntity> requestTossPayment( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @RequestBody PaymentRequest paymentRequest) { - PaymentResponse paymentResponse = paymentService.requestTossPayment(userPrincipal.getUser(), paymentRequest); - return ResponseEntity.ok().body( - new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), paymentResponse) - ); - } + @GetMapping + public ResponseEntity> getPaymentDetails(@AuthenticationPrincipal + UserPrincipal userPrincipal, + @PageableDefault + Pageable pageable) { + + Page paymentDetails = paymentService.getPaymentDetails(userPrincipal.getUser(), + pageable); - @PostMapping("/toss/success") - public ResponseEntity> tossPaymentSuccess( - @RequestBody PaymentSuccessRequest paymentSuccessRequest) throws Exception { - PaymentSuccessResponse successResponse = paymentService.tossPaymentSuccess(paymentSuccessRequest); return ResponseEntity.ok().body( - new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), successResponse) + new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), paymentDetails) ); } - - @PostMapping("/toss/fail") - public ResponseEntity tossPaymentFail(@RequestBody PaymentFailRequest paymentFailRequest) { - paymentService.tossPaymentFail(paymentFailRequest); - return ResponseEntity.ok().body(new CommonResponse( - SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); - } } diff --git a/src/main/java/com/genius/gitget/store/payment/controller/PaymentTossController.java b/src/main/java/com/genius/gitget/store/payment/controller/PaymentTossController.java new file mode 100644 index 00000000..8abe77c9 --- /dev/null +++ b/src/main/java/com/genius/gitget/store/payment/controller/PaymentTossController.java @@ -0,0 +1,55 @@ +package com.genius.gitget.store.payment.controller; + +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.exception.SuccessCode; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import com.genius.gitget.store.payment.dto.PaymentFailRequest; +import com.genius.gitget.store.payment.dto.PaymentRequest; +import com.genius.gitget.store.payment.dto.PaymentResponse; +import com.genius.gitget.store.payment.dto.PaymentSuccessRequest; +import com.genius.gitget.store.payment.dto.PaymentSuccessResponse; +import com.genius.gitget.store.payment.service.PaymentService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/payment/toss") +@Slf4j +public class PaymentTossController { + + private final PaymentService paymentService; + + @PostMapping + public ResponseEntity> requestTossPayment( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody PaymentRequest paymentRequest) { + PaymentResponse paymentResponse = paymentService.requestTossPayment(userPrincipal.getUser(), paymentRequest); + return ResponseEntity.ok().body( + new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), paymentResponse) + ); + } + + @PostMapping("/success") + public ResponseEntity> tossPaymentSuccess( + @RequestBody PaymentSuccessRequest paymentSuccessRequest) throws Exception { + PaymentSuccessResponse successResponse = paymentService.tossPaymentSuccess(paymentSuccessRequest); + return ResponseEntity.ok().body( + new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), successResponse) + ); + } + + @PostMapping("/fail") + public ResponseEntity tossPaymentFail(@RequestBody PaymentFailRequest paymentFailRequest) { + paymentService.tossPaymentFail(paymentFailRequest); + return ResponseEntity.ok().body(new CommonResponse( + SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); + } +} diff --git a/src/main/java/com/genius/gitget/store/payment/domain/OrderType.java b/src/main/java/com/genius/gitget/store/payment/domain/OrderType.java new file mode 100644 index 00000000..f174f659 --- /dev/null +++ b/src/main/java/com/genius/gitget/store/payment/domain/OrderType.java @@ -0,0 +1,14 @@ +package com.genius.gitget.store.payment.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum OrderType { + POINT("points", "포인트 충전"), + ITEM("items", "아이템 구매"); + + private final String key; + private final String value; +} diff --git a/src/main/java/com/genius/gitget/store/payment/domain/Payment.java b/src/main/java/com/genius/gitget/store/payment/domain/Payment.java index 1444f33b..994871f3 100644 --- a/src/main/java/com/genius/gitget/store/payment/domain/Payment.java +++ b/src/main/java/com/genius/gitget/store/payment/domain/Payment.java @@ -4,6 +4,8 @@ import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -50,9 +52,13 @@ public class Payment extends BaseTimeEntity { @Column(name = "success_at", updatable = false) private LocalDateTime successDate; + @Enumerated(EnumType.STRING) + private OrderType orderType; + + @Builder public Payment(String orderId, String paymentKey, Long amount, Long pointAmount, String orderName, - boolean isSuccess, String failReason, User user) { + boolean isSuccess, String failReason, User user, OrderType orderType) { this.orderId = orderId; this.paymentKey = paymentKey; this.amount = amount; @@ -61,6 +67,7 @@ public Payment(String orderId, String paymentKey, Long amount, Long pointAmount, this.isSuccess = isSuccess; this.failReason = failReason; this.user = user; + this.orderType = orderType; } public void setPaymentSuccessStatus(String paymentKey, boolean isSuccess) { diff --git a/src/main/java/com/genius/gitget/store/payment/dto/PaymentDetailsResponse.java b/src/main/java/com/genius/gitget/store/payment/dto/PaymentDetailsResponse.java new file mode 100644 index 00000000..9ca44bdf --- /dev/null +++ b/src/main/java/com/genius/gitget/store/payment/dto/PaymentDetailsResponse.java @@ -0,0 +1,47 @@ +package com.genius.gitget.store.payment.dto; + +import com.genius.gitget.store.payment.domain.OrderType; +import com.genius.gitget.store.payment.domain.Payment; +import lombok.Builder; +import lombok.Data; + +@Data +public class PaymentDetailsResponse { + private String orderType; + private String orderName; + private String orderLocalDate; + private String orderDayOfWeek; + private String increasedPoint; + private String decreasedPoint; + private String chargingCash; + + + @Builder + public PaymentDetailsResponse(String orderType, String orderName, String orderLocalDate, String orderDayOfWeek, + String increasedPoint, String decreasedPoint, String chargingCash) { + this.orderType = orderType; + this.orderName = orderName; + this.orderLocalDate = orderLocalDate; + this.orderDayOfWeek = orderDayOfWeek; + this.increasedPoint = increasedPoint; + this.decreasedPoint = decreasedPoint; + this.chargingCash = chargingCash; + } + + public static PaymentDetailsResponse createByEntity(Payment payment, String paymentDateFormat, + String dayOfWeekKorean) { + return PaymentDetailsResponse.builder() + .orderType(String.valueOf(payment.getOrderType().getValue())) + .orderName(payment.getOrderName()) + .orderLocalDate(paymentDateFormat) + .orderDayOfWeek(dayOfWeekKorean) + .decreasedPoint( + payment.getOrderType().equals(OrderType.ITEM) ? String.valueOf(payment.getPointAmount()) : null) + .increasedPoint( + payment.getOrderType().equals(OrderType.POINT) ? String.valueOf(payment.getPointAmount()) + : null) + .chargingCash( + payment.getOrderType().equals(OrderType.POINT) ? String.valueOf(payment.getAmount()) : null) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/store/payment/repository/PaymentRepository.java b/src/main/java/com/genius/gitget/store/payment/repository/PaymentRepository.java index 66377d45..1e108f6a 100644 --- a/src/main/java/com/genius/gitget/store/payment/repository/PaymentRepository.java +++ b/src/main/java/com/genius/gitget/store/payment/repository/PaymentRepository.java @@ -1,9 +1,14 @@ package com.genius.gitget.store.payment.repository; import com.genius.gitget.store.payment.domain.Payment; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface PaymentRepository extends JpaRepository { Optional findByOrderId(String orderId); + + @Query("select p from Payment p where p.user.id = :id order by p.createdDate desc") + List findPaymentDetailsByUserId(Long id); } diff --git a/src/main/java/com/genius/gitget/store/payment/service/PaymentService.java b/src/main/java/com/genius/gitget/store/payment/service/PaymentService.java index 546e2def..1866092e 100644 --- a/src/main/java/com/genius/gitget/store/payment/service/PaymentService.java +++ b/src/main/java/com/genius/gitget/store/payment/service/PaymentService.java @@ -8,6 +8,7 @@ import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.payment.config.TossPaymentConfig; import com.genius.gitget.store.payment.domain.Payment; +import com.genius.gitget.store.payment.dto.PaymentDetailsResponse; import com.genius.gitget.store.payment.dto.PaymentFailRequest; import com.genius.gitget.store.payment.dto.PaymentRequest; import com.genius.gitget.store.payment.dto.PaymentResponse; @@ -21,12 +22,22 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; +import java.util.List; +import java.util.Locale; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,10 +53,7 @@ public class PaymentService { @Transactional public PaymentResponse requestTossPayment(User user, PaymentRequest paymentRequest) { - User findUser = userRepository.findByIdentifier(user.getIdentifier()) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - - if (!findUser.getIdentifier().equals(paymentRequest.getUserEmail())) { + if (!user.getIdentifier().equals(paymentRequest.getUserEmail())) { throw new BusinessException(ErrorCode.MEMBER_NOT_FOUND); } if (paymentRequest.getAmount() < 100L) { @@ -60,6 +68,7 @@ public PaymentResponse requestTossPayment(User user, PaymentRequest paymentReque return PaymentResponse.createByEntity(savedPayment); } + @Transactional public PaymentSuccessResponse tossPaymentSuccess(PaymentSuccessRequest paymentSuccessRequest) throws Exception { Payment payment = verifyPayment(paymentSuccessRequest.getOrderId(), @@ -67,8 +76,7 @@ public PaymentSuccessResponse tossPaymentSuccess(PaymentSuccessRequest paymentSu PaymentSuccessResponse result = requestPaymentAccept(paymentSuccessRequest); payment.setPaymentSuccessStatus(paymentSuccessRequest.getPaymentKey(), true); - User user = userRepository.findByIdentifier(payment.getUser().getIdentifier()) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + User user = verifyUser(payment.getUser()); user.updatePoints(payment.getPointAmount()); return result; @@ -149,4 +157,31 @@ public void tossPaymentFail(PaymentFailRequest paymentFailRequest) { ErrorCode.FAILED_FINAL_PAYMENT)); payment.setPaymentFailStatus(paymentFailRequest.getMessage(), false); } + + public Page getPaymentDetails(User user, Pageable pageable) { + User findUser = verifyUser(user); + List payments = paymentRepository.findPaymentDetailsByUserId(findUser.getId()); + + List paymentDetailsResponses = new ArrayList<>(); + + for (Payment payment : payments) { + LocalDateTime paymentDate = payment.getCreatedDate(); + + // YYYY-MM-dd + String paymentDateFormat = paymentDate.format(DateTimeFormatter.ofPattern("YYYY-MM-dd")); + + // 요일 구하기 + DayOfWeek dayOfWeek = paymentDate.getDayOfWeek(); + String dayOfWeekKorean = dayOfWeek.getDisplayName(TextStyle.FULL, Locale.KOREAN); + + paymentDetailsResponses.add( + PaymentDetailsResponse.createByEntity(payment, paymentDateFormat, dayOfWeekKorean)); + } + return new PageImpl<>(paymentDetailsResponses, pageable, paymentDetailsResponses.size()); + } + + private User verifyUser(User user) { + return userRepository.findByIdentifier(user.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index 6154ca6e..e03a9239 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -13,15 +13,6 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.store.item.domain.EquipStatus; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.dto.ItemResponse; -import com.genius.gitget.store.item.dto.ItemUseResponse; -import com.genius.gitget.store.item.dto.ProfileResponse; -import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; @@ -32,6 +23,15 @@ import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.ProfileResponse; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.store.item.service.ItemService; import java.time.LocalDate; import java.time.LocalDateTime; @@ -39,6 +39,7 @@ import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -66,6 +67,42 @@ class ItemServiceTest { @Autowired private CertificationRepository certificationRepository; + @Nested + class 유저_포인트가_충분할_때 { + + @ParameterizedTest + @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) + public void 아이템을_구매할_수_있다_1(ItemCategory itemCategory) { + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + getSavedOrder(user, item, itemCategory, 0); + user.setPoint(1000L); + + ItemResponse itemResponse = itemService.orderItem(user, item.getId()); + + assertThat(itemResponse.getItemCategory()).isEqualTo(itemCategory); + } + + @ParameterizedTest + @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) + public void 아이템을_구매할_수_있다_2(ItemCategory itemCategory) { + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + getSavedOrder(user, item, itemCategory, 0); + user.setPoint(1000L); + + ItemResponse itemResponse1 = itemService.orderItem(user, item.getId()); + + assertThat(user.getPoint()).isEqualTo(900L); + assertThat(itemResponse1.getCount()).isEqualTo(1); + + ItemResponse itemResponse2 = itemService.orderItem(user, item.getId()); + + assertThat(user.getPoint()).isEqualTo(800L); + assertThat(itemResponse2.getCount()).isEqualTo(2); + } + } + @Test @DisplayName("데이터베이스에 저장되어 있는 모든 아이템 정보들을 받아올 수 있다.") public void should_getAllItems_when_itemsSaved() { diff --git a/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java b/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java index caba1ac9..b947147f 100644 --- a/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java @@ -12,12 +12,10 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; @SpringBootTest @Transactional -@Rollback public class UserTest { @Autowired diff --git a/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java b/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java new file mode 100644 index 00000000..37b8c4f5 --- /dev/null +++ b/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java @@ -0,0 +1,185 @@ +package com.genius.gitget.payment; + +import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; +import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; +import static org.assertj.core.api.Assertions.assertThat; + +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.repository.LikesRepository; +import com.genius.gitget.challenge.likes.service.LikesService; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.repository.FilesRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; +import com.genius.gitget.store.item.service.ItemService; +import com.genius.gitget.store.payment.domain.Payment; +import com.genius.gitget.store.payment.dto.PaymentDetailsResponse; +import com.genius.gitget.store.payment.dto.PaymentRequest; +import com.genius.gitget.store.payment.dto.PaymentResponse; +import com.genius.gitget.store.payment.repository.PaymentRepository; +import com.genius.gitget.store.payment.service.PaymentService; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Slf4j +public class PaymentServiceTest { + + @Autowired + UserRepository userRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + LikesRepository likesRepository; + @Autowired + LikesService likesService; + @Autowired + FilesRepository filesRepository; + @Autowired + private PaymentService paymentService; + @Autowired + private PaymentRepository paymentRepository; + @Autowired + private ItemService itemService; + @Autowired + private ItemRepository itemRepository; + @Autowired + private OrdersRepository ordersRepository; + + + private User getSavedUser() { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("neo5188@gmail.com") + .build() + ); + } + + @ParameterizedTest + @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) + public void 사용자는_아이템을_구매하고_결제내역을_조회할_수_있다(ItemCategory itemCategory) { + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + getSavedOrder(user, item, itemCategory, 0); + user.setPoint(1000L); + + ItemResponse itemResponse = itemService.orderItem(user, item.getId()); + assertThat(itemResponse.getItemCategory()).isEqualTo(itemCategory); + + Page paymentDetails = paymentService.getPaymentDetails(user, PageRequest.of(0, 10)); + List content = paymentDetails.getContent(); + + assertThat(user.getPoint()).isEqualTo(900); + System.out.println(itemCategory); + if (itemCategory.equals(CERTIFICATION_PASSER)) { + assertThat(content.get(0).getOrderName()).isEqualTo("인증 패스 아이템"); + } else if (itemCategory.equals(POINT_MULTIPLIER)) { + assertThat(content.get(0).getOrderName()).isEqualTo("포인트 2배 획득 아이템"); + } + assertThat(content.get(0).getOrderType()).isEqualTo("아이템 구매"); + assertThat(content.get(0).getDecreasedPoint()).isEqualTo("100"); + } + + private Item getSavedItem(ItemCategory itemCategory) { + return itemRepository.save(Item.builder() + .itemCategory(itemCategory) + .cost(100) + .name(itemCategory.getName()) + .build()); + } + + private Orders getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { + Orders orders = Orders.createDefault(count, itemCategory); + orders.setItem(item); + orders.setUser(user); + return ordersRepository.save(orders); + } + + @Nested + class 사용자가_결제요청을_할_때 { + @Test + public void 존재하지_않는_사용자라면_실패한다() { + User user = userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("kimdozzi") + .build() + ); + Assertions.assertNotNull(user); + Assertions.assertThrows(BusinessException.class, + () -> paymentService.requestTossPayment(user, PaymentRequest.builder() + .amount(100L) + .orderName("포인트 충전") + .userEmail("neo5188@gmail.com") + .pointAmount(10L).build())); + + } + + @Test + public void 결제금액이_100원_미만이면_실패한다() { + User user = getSavedUser(); + Assertions.assertThrows(BusinessException.class, () -> + paymentService.requestTossPayment(user, PaymentRequest.builder() + .amount(99L) + .orderName("포인트 충전") + .userEmail("neo5188@gmail.com") + .pointAmount(9L).build())); + } + + @Test + public void 정해진_금액이_아닐경우_실패한다() { + User user = getSavedUser(); + Assertions.assertThrows(BusinessException.class, () -> + paymentService.requestTossPayment(user, PaymentRequest.builder() + .amount(100L) + .orderName("포인트 충전") + .userEmail("neo5188@gmail.com") + .pointAmount(10L).build())); + } + + @Test + public void 최소금액이_100원_이상이고_정해진_금액에_포함된다면_성공한다() { + User user = getSavedUser(); + PaymentResponse paymentResponse = paymentService.requestTossPayment(user, PaymentRequest.builder() + .amount(5000L) + .orderName("포인트 충전") + .userEmail("neo5188@gmail.com") + .pointAmount(500L).build()); + + List payments = paymentRepository.findPaymentDetailsByUserId(user.getId()); + + for (Payment payment : payments) { + assertThat(payment.getUser().getIdentifier()) + .isEqualTo(paymentResponse.getUserEmail()); + } + } + } +} From 6aaf5678f3a28d46e0e7b0ff03859631e37d1143 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:24:10 +0900 Subject: [PATCH 144/234] =?UTF-8?q?[FEAT]=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EC=97=90=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=ED=94=84=EB=A0=88=EC=9E=84=20=EC=A0=95=EB=B3=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 응답 데이터의 클래스명 변경 - TokenDTO에서 SignupResponse로 변경 * feat: JWT 발급 응답데이터에 프로필 프레임 데이터 추가 - JWT 발급 API 요청에 대한 응답데이터에 프로필 프레임 데이터(PK) 추가 - 관련 테스트 코드 추가 * feat: 인증 주간 현황 API 응답 데이터에 프레임 정보 추가 - 인증 주간 현황 API (/api/certification/week/all/{instanceId}) 에 프로필 프레임 정보 추가 * feat: 사용자 정보 조회 API 응답 데이터에 프레임 정보 추가 * refactor: 프로필 프레임 장착 해제 API 수정 - 장착 해제 API 요청 시, IN_USE인 모든 프로필 프레임을 AVAILABLE로 변경하도록 변경 - 장착 해제된 아이템들의 정보를 리스트로 반환 * test: 비지니스 로직 변경에 따른 테스트 코드 수정 * feat: 프로필 프레임 아이템 사용 시 예외 처리 추가 - 프로필 프레임 아이템 사용 시도 시, 장착 중인 아이템이 존재할 때 예외 처리하는 코드 추가 * feat: 프로필 프레임 사용 응답 데이터에, 아이템 PK 데이터 추가 * feat: 보유하고 있는 아이템 모두 사용 시, DB에서 삭제하도록 변경 - 인증 패스 아이템, 포인트 2배 수령 아이템 사용 후 보유하고 있는 수량이 없을 때 DB에서 주문 정보를 삭제하는 로직 추가 * fix: 인증 가능 기간 검사 조건 버그 픽스 - 인증이 가능한 기간인지 확인하는 조건문에서 '검사일자'가 '시작일자' 혹은 '완료일자'와 같을 때 통과하지 못하는 버그 픽스 - 관련 테스트 코드 작성 * feat: 아이템 조회 응답 데이터에 아이템 설명 데이터 추가 - 아이템 조회 응답 데이터에 아이템 설명 데이터 추가 - 관련 테스트 코드 수정 * refactor: 연관관계 메서드 로직 개선 * hotfix: test 코드 오류 수정 --------- Co-authored-by: kimdozzi --- .../certification/dto/WeekResponse.java | 8 +- .../service/CertificationService.java | 9 +- .../myChallenge/dto/ActivatedResponse.java | 11 +- .../myChallenge/dto/DoneResponse.java | 11 +- .../user/controller/UserController.java | 6 +- .../challenge/user/service/UserService.java | 13 +- .../security/controller/AuthController.java | 7 +- .../global/security/dto/AuthResponse.java | 3 +- .../global/security/dto/SignupResponse.java | 6 + .../gitget/global/security/dto/TokenDTO.java | 4 - .../global/util/exception/ErrorCode.java | 1 + .../profile/dto/UserInformationResponse.java | 7 +- .../profile/service/ProfileService.java | 7 +- .../store/item/controller/ItemController.java | 28 ++--- .../store/item/domain/ItemCategory.java | 4 +- .../gitget/store/item/domain/Orders.java | 5 + .../gitget/store/item/dto/ItemResponse.java | 2 + .../store/item/dto/ItemUseResponse.java | 13 +- .../store/item/dto/ProfileResponse.java | 5 + .../item/repository/OrdersRepository.java | 12 +- .../store/item/service/ItemService.java | 63 ++++++---- .../store/item/service/OrdersProvider.java | 39 +++++- src/main/resources/data.sql | 10 +- .../item/service/ItemServiceTest.java | 94 +++++++++++--- .../item/service/OrdersProviderTest.java | 119 +++++++++++++++++- .../user/service/UserServiceTest.java | 82 +++++++++++- .../gitget/payment/PaymentServiceTest.java | 6 +- 27 files changed, 448 insertions(+), 127 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java delete mode 100644 src/main/java/com/genius/gitget/global/security/dto/TokenDTO.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java index fdb85300..1444634a 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java @@ -9,24 +9,28 @@ public record WeekResponse( Long userId, String nickname, + Long frameId, List certifications, FileResponse profile ) { - public static WeekResponse create(User user, List certifications) { + public static WeekResponse create(User user, FileResponse fileResponse, + List certifications) { return WeekResponse.builder() .userId(user.getId()) .nickname(user.getNickname()) .certifications(certifications) + .profile(fileResponse) .build(); } - public static WeekResponse create(User user, FileResponse fileResponse, + public static WeekResponse create(User user, Long frameId, FileResponse fileResponse, List certifications) { return WeekResponse.builder() .userId(user.getId()) .nickname(user.getNickname()) + .frameId(frameId) .certifications(certifications) .profile(fileResponse) .build(); diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 6338f72b..89bd03f3 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -87,8 +87,9 @@ private WeekResponse convertToWeekResponse(Participant participant, LocalDate cu weekStartDate); FileResponse fileResponse = FileResponse.create(user.getFiles()); + Long frameId = ordersProvider.getUsingFrameItem(user.getId()).getId(); - return WeekResponse.create(user, fileResponse, certificationResponses); + return WeekResponse.create(user, frameId, fileResponse, certificationResponses); } public TotalResponse getTotalCertification(Long participantId, LocalDate currentDate) { @@ -214,8 +215,10 @@ private void validCertificationCondition(Instance instance, LocalDate targetDate throw new BusinessException(ErrorCode.NOT_ACTIVITY_INSTANCE); } - boolean isValidPeriod = targetDate.isAfter(instance.getStartedDate().toLocalDate()) && - targetDate.isBefore(instance.getCompletedDate().toLocalDate()); + LocalDate startedDate = instance.getStartedDate().toLocalDate().minusDays(1); + LocalDate completedDate = instance.getCompletedDate().toLocalDate().plusDays(1); + + boolean isValidPeriod = targetDate.isAfter(startedDate) && targetDate.isBefore(completedDate); if (!isValidPeriod) { throw new BusinessException(ErrorCode.NOT_CERTIFICATE_PERIOD); } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java index e5f5082e..ce1ce8da 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java @@ -2,8 +2,8 @@ import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.store.item.dto.ItemUseResponse; import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -11,9 +11,11 @@ @Getter @Setter public class ActivatedResponse extends ItemUseResponse { + private Long instanceId; + private String title; + private int pointPerPerson; private String repository; private String certificateStatus; - private Long itemId; private int numOfPassItem; private boolean canUsePassItem; private FileResponse fileResponse; @@ -22,10 +24,11 @@ public class ActivatedResponse extends ItemUseResponse { public ActivatedResponse(Long instanceId, String title, int pointPerPerson, String repository, String certificateStatus, Long itemId, int numOfPassItem, boolean canUsePassItem, FileResponse fileResponse) { - super(instanceId, title, pointPerPerson); + this.instanceId = instanceId; + this.title = title; + this.pointPerPerson = pointPerPerson; this.repository = repository; this.certificateStatus = certificateStatus; - this.itemId = itemId; this.numOfPassItem = numOfPassItem; this.canUsePassItem = canUsePassItem; this.fileResponse = fileResponse; diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java index 7126ec38..bd52bcd0 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java @@ -1,11 +1,11 @@ package com.genius.gitget.challenge.myChallenge.dto; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.store.item.dto.ItemUseResponse; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.domain.RewardStatus; import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.store.item.dto.ItemUseResponse; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -13,8 +13,10 @@ @Getter @Setter public class DoneResponse extends ItemUseResponse { + private Long instanceId; + private String title; + private int pointPerPerson; private JoinResult joinResult; - private Long itemId; private boolean canGetReward; private int numOfPointItem; private int rewardedPoints; @@ -24,7 +26,10 @@ public class DoneResponse extends ItemUseResponse { @Builder public DoneResponse(Long instanceId, String title, int pointPerPerson, JoinResult joinResult, boolean canGetReward, int numOfPointItem, int rewardedPoints, double achievementRate, FileResponse fileResponse) { - super(instanceId, title, pointPerPerson); + + this.instanceId = instanceId; + this.title = title; + this.pointPerPerson = pointPerPerson; this.joinResult = joinResult; this.canGetReward = canGetReward; this.numOfPointItem = numOfPointItem; diff --git a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java index 2959bc65..262c7a80 100644 --- a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java +++ b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java @@ -5,7 +5,7 @@ import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.service.UserService; -import com.genius.gitget.global.security.dto.TokenDTO; +import com.genius.gitget.global.security.dto.SignupResponse; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import lombok.RequiredArgsConstructor; @@ -33,14 +33,14 @@ public ResponseEntity checkNicknameDuplicate(@RequestParam(value } @PostMapping("/auth/signup") - public ResponseEntity> signup( + public ResponseEntity> signup( @RequestPart(value = "data") SignupRequest signupRequest, @RequestPart(value = "files") MultipartFile multipartFile) { Long signupUserId = userService.signup(signupRequest, multipartFile); String identifier = userService.findUserById(signupUserId).getIdentifier(); return ResponseEntity.ok().body( - new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), new TokenDTO(identifier)) + new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), new SignupResponse(identifier)) ); } } diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 975b8425..065764cd 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -12,7 +12,10 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.service.OrdersProvider; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -27,6 +30,7 @@ @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + private final OrdersProvider ordersProvider; private final FilesService filesService; private final EncryptUtil encryptUtil; @@ -93,4 +97,11 @@ public void isAlreadyRegistered(User user) { throw new BusinessException(ALREADY_REGISTERED); } } -} + + public AuthResponse getUserInfo(String identifier) { + User user = userRepository.findByIdentifier(identifier) + .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); + Item usingFrame = ordersProvider.getUsingFrameItem(user.getId()); + return new AuthResponse(user.getRole(), usingFrame.getId()); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 836af150..3d145341 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -6,7 +6,7 @@ import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.security.dto.AuthResponse; -import com.genius.gitget.global.security.dto.TokenDTO; +import com.genius.gitget.global.security.dto.SignupResponse; import com.genius.gitget.global.security.service.JwtService; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -30,13 +30,14 @@ public class AuthController { @PostMapping("/auth") public ResponseEntity> generateToken(HttpServletResponse response, - @RequestBody TokenDTO tokenRequest) { + @RequestBody SignupResponse tokenRequest) { User requestUser = userService.findUserByIdentifier(tokenRequest.identifier()); jwtService.validateUser(requestUser); jwtService.generateAccessToken(response, requestUser); jwtService.generateRefreshToken(response, requestUser); - AuthResponse authResponse = new AuthResponse(requestUser.getRole()); + + AuthResponse authResponse = userService.getUserInfo(requestUser.getIdentifier()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), authResponse) diff --git a/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java b/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java index e983b7a1..100dbce9 100644 --- a/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java +++ b/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java @@ -3,6 +3,7 @@ import com.genius.gitget.challenge.user.domain.Role; public record AuthResponse( - Role role + Role role, + Long frameId ) { } diff --git a/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java b/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java new file mode 100644 index 00000000..82439227 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java @@ -0,0 +1,6 @@ +package com.genius.gitget.global.security.dto; + +public record SignupResponse( + String identifier +) { +} diff --git a/src/main/java/com/genius/gitget/global/security/dto/TokenDTO.java b/src/main/java/com/genius/gitget/global/security/dto/TokenDTO.java deleted file mode 100644 index 1c601fdc..00000000 --- a/src/main/java/com/genius/gitget/global/security/dto/TokenDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.genius.gitget.global.security.dto; - -public record TokenDTO(String identifier) { -} diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index fad56654..7f2d06cb 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -74,6 +74,7 @@ public enum ErrorCode { ALREADY_PURCHASED(HttpStatus.BAD_REQUEST, "프로필 프레임은 재구매가 불가능 합니다."), INVALID_EQUIP_CONDITION(HttpStatus.BAD_REQUEST, "프로필 프레임을 장착할 수 있는 상태가 아닙니다."), IN_USE_FRAME_NOT_FOUND(HttpStatus.NOT_FOUND, "사용 중인 프로필 프레임을 찾을 수 없습니다"), + TOO_MANY_USING_FRAME(HttpStatus.BAD_REQUEST, "사용 중인 프로필 프레임이 너무 많습니다. 장착 해제를 먼저 실행해주세요."), CAN_NOT_GET_REWARDS(HttpStatus.BAD_REQUEST, "챌린지 보상을 받을 수 있는 조건이 아닙니다."), ALREADY_REWARDED(HttpStatus.BAD_REQUEST, "해당 챌린지 보상은 이미 지급되었습니다."); diff --git a/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java index 70a984bd..e868594a 100644 --- a/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java +++ b/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java @@ -11,19 +11,22 @@ public class UserInformationResponse { private String identifier; private String nickname; + private Long frameId; private FileResponse fileResponse; @Builder - public UserInformationResponse(String identifier, String nickname, Files files) { + public UserInformationResponse(String identifier, String nickname, Long frameId, Files files) { this.identifier = identifier; this.nickname = nickname; + this.frameId = frameId; this.fileResponse = convertToFileResponse(Optional.ofNullable(files)); } - public static UserInformationResponse createByEntity(User findUser, Files files) { + public static UserInformationResponse createByEntity(User findUser, Long frameId, Files files) { return UserInformationResponse.builder() .identifier(findUser.getIdentifier()) .nickname(findUser.getNickname()) + .frameId(frameId) .files(files) .build(); } diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileService.java index 64edded7..b67c8eb1 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileService.java @@ -24,6 +24,7 @@ import com.genius.gitget.profile.dto.UserInterestResponse; import com.genius.gitget.profile.dto.UserInterestUpdateRequest; import com.genius.gitget.profile.dto.UserPointResponse; +import com.genius.gitget.store.item.service.OrdersProvider; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -43,6 +44,7 @@ public class ProfileService { private final FilesRepository filesRepository; private final FilesService filesService; private final SignoutRepository signoutRepository; + private final OrdersProvider ordersProvider; private static boolean isProfileFileType(Files files) { return files != null && files.getFileType().equals(FileType.PROFILE); @@ -59,11 +61,12 @@ public UserPointResponse getUserPoint(User user) { // 사용자 정보 조회 public UserInformationResponse getUserInformation(Long userId) { User findUser = getUserById(userId); + Long frameId = ordersProvider.getUsingFrameItem(userId).getId(); Files files = getFiles(findUser); if (isProfileFileType(files)) { - return UserInformationResponse.createByEntity(findUser, files); + return UserInformationResponse.createByEntity(findUser, frameId, files); } else { - return UserInformationResponse.createByEntity(findUser, null); + return UserInformationResponse.createByEntity(findUser, frameId, null); } } diff --git a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java index 90f92938..a68ae787 100644 --- a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java +++ b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java @@ -2,15 +2,15 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.ListResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.dto.ItemResponse; import com.genius.gitget.store.item.dto.ItemUseResponse; import com.genius.gitget.store.item.dto.ProfileResponse; import com.genius.gitget.store.item.service.ItemService; -import com.genius.gitget.global.security.domain.UserPrincipal; -import com.genius.gitget.global.util.response.dto.CommonResponse; -import com.genius.gitget.global.util.response.dto.ListResponse; -import com.genius.gitget.global.util.response.dto.SingleResponse; import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; @@ -65,28 +65,22 @@ public ResponseEntity useItem( @PathVariable Long itemId, @RequestParam(required = false) Long instanceId ) { - ItemUseResponse itemUseResponse = itemService.useItem( - userPrincipal.getUser(), itemId, instanceId, LocalDate.now()); - if (itemUseResponse.isFrameResponse()) { - return ResponseEntity.ok().body( - new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) - ); - } + ItemUseResponse itemUseResponse = itemService.useItem(userPrincipal.getUser(), itemId, instanceId, + LocalDate.now()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemUseResponse) ); } - @PostMapping("/items/unuse/{itemId}") - public ResponseEntity> unmountItem( - @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long itemId + @PostMapping("/items/unuse") + public ResponseEntity> unmountItem( + @AuthenticationPrincipal UserPrincipal userPrincipal ) { - ProfileResponse profileResponse = itemService.unmountFrame(userPrincipal.getUser(), itemId); + List profileResponses = itemService.unmountFrame(userPrincipal.getUser()); return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), profileResponse) + new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), profileResponses) ); } } diff --git a/src/main/java/com/genius/gitget/store/item/domain/ItemCategory.java b/src/main/java/com/genius/gitget/store/item/domain/ItemCategory.java index a523287e..1013ca37 100644 --- a/src/main/java/com/genius/gitget/store/item/domain/ItemCategory.java +++ b/src/main/java/com/genius/gitget/store/item/domain/ItemCategory.java @@ -10,8 +10,8 @@ @Getter public enum ItemCategory { PROFILE_FRAME("profile-frame", "프레임"), - CERTIFICATION_PASSER("certification-passer", "인증 패스 아이템"), - POINT_MULTIPLIER("point-multiplier", "포인트 2배 획득 아이템"); + CERTIFICATION_PASSER("certification-passer", "인증 패스권"), + POINT_MULTIPLIER("point-multiplier", "챌린지 보상 획득 2배 아이템"); private final String tag; private final String name; diff --git a/src/main/java/com/genius/gitget/store/item/domain/Orders.java b/src/main/java/com/genius/gitget/store/item/domain/Orders.java index 0ad49f74..90525c0b 100644 --- a/src/main/java/com/genius/gitget/store/item/domain/Orders.java +++ b/src/main/java/com/genius/gitget/store/item/domain/Orders.java @@ -76,6 +76,11 @@ public void updateEquipStatus(EquipStatus equipStatus) { } //=== 연관관계 편의 메서드 ===// + public void setUserAndItem(User user, Item item) { + setUser(user); + setItem(item); + } + public void setUser(User user) { this.user = user; if (!user.getOrdersList().contains(this)) { diff --git a/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java b/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java index aa5677e4..1b5a6574 100644 --- a/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java @@ -9,6 +9,7 @@ public class ItemResponse { private Long itemId; private ItemCategory itemCategory; private String name; + private String details; private int cost; private int count; @@ -16,6 +17,7 @@ protected ItemResponse(Item item, int count) { this.itemId = item.getId(); this.itemCategory = item.getItemCategory(); this.name = item.getName(); + this.details = item.getDetails(); this.cost = item.getCost(); this.count = count; } diff --git a/src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java b/src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java index c213fbdb..c981e890 100644 --- a/src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java @@ -6,17 +6,12 @@ @Getter @Setter public class ItemUseResponse { - private Long instanceId; - private String title; - private int pointPerPerson; + Long itemId; - public ItemUseResponse(Long instanceId, String title, int pointPerPerson) { - this.instanceId = instanceId; - this.title = title; - this.pointPerPerson = pointPerPerson; + public ItemUseResponse() { } - public boolean isFrameResponse() { - return this.instanceId == 0L; + public ItemUseResponse(Long itemId) { + this.itemId = itemId; } } diff --git a/src/main/java/com/genius/gitget/store/item/dto/ProfileResponse.java b/src/main/java/com/genius/gitget/store/item/dto/ProfileResponse.java index 05b1c480..f31df1b1 100644 --- a/src/main/java/com/genius/gitget/store/item/dto/ProfileResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/ProfileResponse.java @@ -1,6 +1,7 @@ package com.genius.gitget.store.item.dto; import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.Orders; import lombok.Getter; import lombok.Setter; @@ -17,4 +18,8 @@ public ProfileResponse(Item item, int numOfItem, String equipStatus) { public static ProfileResponse create(Item item, int numOfItem, String equipStatus) { return new ProfileResponse(item, numOfItem, equipStatus); } + + public static ProfileResponse createByEntity(Orders orders) { + return create(orders.getItem(), orders.getCount(), orders.getEquipStatus().getTag()); + } } diff --git a/src/main/java/com/genius/gitget/store/item/repository/OrdersRepository.java b/src/main/java/com/genius/gitget/store/item/repository/OrdersRepository.java index 66ce1c96..aa5804e1 100644 --- a/src/main/java/com/genius/gitget/store/item/repository/OrdersRepository.java +++ b/src/main/java/com/genius/gitget/store/item/repository/OrdersRepository.java @@ -1,6 +1,5 @@ package com.genius.gitget.store.item.repository; -import com.genius.gitget.store.item.domain.EquipStatus; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.domain.Orders; import java.util.List; @@ -11,16 +10,11 @@ public interface OrdersRepository extends JpaRepository { - @Query("select u from Orders u where u.user.id = :userId and u.item.itemCategory = :itemCategory") - List findByCategory(@Param("userId") Long userId, - @Param("itemCategory") ItemCategory itemCategory); + @Query("select u from Orders u where u.user.id = :userId and u.item.itemCategory = :category") + List findAllByCategory(@Param("userId") Long userId, + @Param("category") ItemCategory category); @Query("select u from Orders u where u.user.id = :userId and u.item.id = :itemId") Optional findByOrderInfo(@Param("userId") Long userId, @Param("itemId") Long itemId); - - @Query("select u from Orders u where u.user.id = :userId and u.item.itemCategory = :category and u.equipStatus = :equipStatus") - Optional findByEquipStatus(@Param("userId") Long userId, - @Param("category") ItemCategory category, - @Param("equipStatus") EquipStatus equipStatus); } diff --git a/src/main/java/com/genius/gitget/store/item/service/ItemService.java b/src/main/java/com/genius/gitget/store/item/service/ItemService.java index eccbc021..39428a77 100644 --- a/src/main/java/com/genius/gitget/store/item/service/ItemService.java +++ b/src/main/java/com/genius/gitget/store/item/service/ItemService.java @@ -79,7 +79,13 @@ public ItemResponse orderItem(User user, Long itemId) { return getItemResponse(persistUser, item, numOfItem); } - private static Payment getPayment(User user, Item item) { + private void validateUserPoint(long userPoint, int itemCost) { + if (userPoint < itemCost) { + throw new BusinessException(ErrorCode.NOT_ENOUGH_POINT); + } + } + + private Payment getPayment(User user, Item item) { return Payment.builder() .user(user) .orderType(OrderType.ITEM) @@ -89,28 +95,24 @@ private static Payment getPayment(User user, Item item) { .build(); } - private void validateUserPoint(long userPoint, int itemCost) { - if (userPoint < itemCost) { - throw new BusinessException(ErrorCode.NOT_ENOUGH_POINT); - } - } - private Orders createNew(User user, Item item) { Orders orders = Orders.createDefault(0, item.getItemCategory()); - orders.setUser(user); - orders.setItem(item); + orders.setUserAndItem(user, item); return ordersProvider.save(orders); } @Transactional - public ProfileResponse unmountFrame(User user, Long itemId) { - Orders orders = ordersProvider.findByOrderInfo(user.getId(), itemId); - validateUnmountCondition(orders); - - orders.updateEquipStatus(EquipStatus.AVAILABLE); + public List unmountFrame(User user) { + List profileResponses = new ArrayList<>(); + List frameOrders = ordersProvider.findAllUsingFrames(user.getId()); + + for (Orders frameOrder : frameOrders) { + validateUnmountCondition(frameOrder); + frameOrder.updateEquipStatus(EquipStatus.AVAILABLE); + profileResponses.add(ProfileResponse.createByEntity(frameOrder)); + } - return ProfileResponse.create( - orders.getItem(), orders.getCount(), orders.getEquipStatus().getTag()); + return profileResponses; } private void validateUnmountCondition(Orders orders) { @@ -133,7 +135,7 @@ public ItemUseResponse useItem(User user, Long itemId, Long instanceId, LocalDat switch (item.getItemCategory()) { case PROFILE_FRAME -> { - return useProfileFrameItem(orders); + return useProfileFrameItem(user.getId(), orders); } case CERTIFICATION_PASSER -> { return usePasserItem(orders, instanceId, currentDate); @@ -145,13 +147,20 @@ public ItemUseResponse useItem(User user, Long itemId, Long instanceId, LocalDat throw new BusinessException(ErrorCode.USER_ITEM_NOT_FOUND); } - private ItemUseResponse useProfileFrameItem(Orders orders) { - validateFrameEquip(orders); + private ItemUseResponse useProfileFrameItem(Long userId, Orders orders) { + validateFrameEquip(userId, orders); orders.updateEquipStatus(EquipStatus.IN_USE); - return new ItemUseResponse(0L, "", 0); + + Item item = orders.getItem(); + return new ItemUseResponse(item.getId()); } - private void validateFrameEquip(Orders orders) { + private void validateFrameEquip(Long userId, Orders orders) { + List allUsingFrames = ordersProvider.findAllUsingFrames(userId); + if (!allUsingFrames.isEmpty()) { + throw new BusinessException(ErrorCode.TOO_MANY_USING_FRAME); + } + if (!orders.hasItem()) { throw new BusinessException(ErrorCode.HAS_NO_ITEM); } @@ -167,7 +176,7 @@ private ItemUseResponse usePasserItem(Orders orders, Long instanceId, LocalDate userId, new CertificationRequest(instanceId, currentDate)); activatedResponse.setItemId(itemId); - orders.useItem(); + useItem(orders); return activatedResponse; } @@ -176,10 +185,18 @@ private ItemUseResponse usePointMultiplierItem(Orders orders, Long instanceId, L DoneResponse doneResponse = myChallengeService.getRewards( new RewardRequest(user, instanceId, currentDate), true ); - orders.useItem(); + doneResponse.setItemId(orders.getItem().getId()); + useItem(orders); return doneResponse; } + private void useItem(Orders orders) { + orders.useItem(); + if (!orders.hasItem()) { + ordersProvider.delete(orders); + } + } + private ItemResponse getItemResponse(User user, Item item, int numOfItem) { if (item.getItemCategory() == ItemCategory.PROFILE_FRAME) { EquipStatus equipStatus = ordersProvider.getEquipStatus(user.getId(), item.getId()); diff --git a/src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java b/src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java index fc9ac83b..a09918ba 100644 --- a/src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java +++ b/src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java @@ -2,12 +2,15 @@ import static com.genius.gitget.global.util.exception.ErrorCode.USER_ITEM_NOT_FOUND; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.domain.Orders; import com.genius.gitget.store.item.repository.OrdersRepository; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.global.util.exception.BusinessException; +import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -20,10 +23,16 @@ public class OrdersProvider { private final OrdersRepository ordersRepository; + @Transactional public Orders save(Orders orders) { return ordersRepository.save(orders); } + @Transactional + public void delete(Orders orders) { + ordersRepository.delete(orders); + } + public Optional findOptionalByOrderInfo(Long userId, Long itemId) { return ordersRepository.findByOrderInfo(userId, itemId); } @@ -33,6 +42,18 @@ public Orders findByOrderInfo(Long userId, Long itemId) { .orElseThrow(() -> new BusinessException(USER_ITEM_NOT_FOUND)); } + + public List findAllByCategory(Long userId, ItemCategory itemCategory) { + return ordersRepository.findAllByCategory(userId, itemCategory); + } + + public List findAllUsingFrames(Long userId) { + return findAllByCategory(userId, ItemCategory.PROFILE_FRAME) + .stream() + .filter(frameOrder -> frameOrder.getEquipStatus() == EquipStatus.IN_USE) + .toList(); + } + public EquipStatus getEquipStatus(Long userId, Long itemId) { Optional optionalUserItem = ordersRepository.findByOrderInfo(userId, itemId); if (optionalUserItem.isPresent()) { @@ -41,8 +62,18 @@ public EquipStatus getEquipStatus(Long userId, Long itemId) { return EquipStatus.UNAVAILABLE; } - public int countNumOfCategory(User user, ItemCategory itemCategory) { - return ordersRepository.findByCategory(user.getId(), itemCategory).size(); + public Item getUsingFrameItem(Long userId) { + List usingFrames = findAllUsingFrames(userId); + if (usingFrames.size() > 1) { + throw new BusinessException(ErrorCode.TOO_MANY_USING_FRAME); + } + + if (usingFrames.isEmpty()) { + return Item.builder() + .itemCategory(ItemCategory.PROFILE_FRAME) + .build(); + } + return usingFrames.get(0).getItem(); } public int countNumOfItem(User user, Long itemId) { diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 9465f0e2..1e89c23c 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -10,7 +10,13 @@ FROM (SELECT 100 AS cost, UNION ALL SELECT 100, NULL, NULL, NULL, '프로필을 꾸밀 수 있는 프레임입니다.', '어둠의 힘 프레임', 'PROFILE_FRAME' UNION ALL - SELECT 100, NULL, NULL, NULL, '오늘의 인증을 넘길 수 있는 아이템입니다.', '인증 패스 아이템', 'CERTIFICATION_PASSER' + SELECT 100, NULL, NULL, NULL, '오늘의 인증을 넘길 수 있는 아이템입니다.', '인증 패스권', 'CERTIFICATION_PASSER' UNION ALL - SELECT 100, NULL, NULL, NULL, '포인트 보상을 2배로 받을 수 있는 아이템입니다.', '포인트 2배 획득 아이템', 'POINT_MULTIPLIER') AS new_items + SELECT 100, + NULL, + NULL, + NULL, + '아이템 사용 시, 챌린지 성공 보상을 2배로 획득할 수 있는 아이템입니다.', + '챌린지 보상 획득 2배 아이템', + 'POINT_MULTIPLIER') AS new_items WHERE (SELECT COUNT(*) FROM item) < 3; diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index e03a9239..159a368f 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -131,6 +131,7 @@ public void should_getItems_when_passCategory(ItemCategory itemCategory) { //then for (ItemResponse itemResponse : itemResponses) { assertThat(itemResponse.getName()).contains(itemCategory.getName()); + assertThat(itemResponse.getDetails()).isNotBlank(); } } @@ -256,16 +257,15 @@ public void should_throwException_when_dontHaveItem() { .hasMessageContaining(ErrorCode.HAS_NO_ITEM.getMessage()); } - @ParameterizedTest - @DisplayName("프로필 프레임을 사용하려 할 때, 프레임의 장착 상태가 AVAILABLE이 아니라면 예외가 발생해야 한다.") - @EnumSource(mode = Mode.INCLUDE, names = {"IN_USE", "UNAVAILABLE"}) - public void should_throwException_when_notAvailable(EquipStatus equipStatus) { + @Test + @DisplayName("프로필 프레임을 사용하려 할 때, 프레임의 장착 상태가 Unavailable인 경우 장착 가능 상태가 아니라는 예외가 발생한다.") + public void should_throwException_when_notAvailable() { //given User user = getSavedUser(); Item item = getSavedItem(ItemCategory.PROFILE_FRAME); Orders orders = getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 3); - orders.updateEquipStatus(equipStatus); + orders.updateEquipStatus(EquipStatus.UNAVAILABLE); //when && then assertThatThrownBy(() -> itemService.useItem(user, item.getId(), 0L, LocalDate.now())) @@ -273,6 +273,22 @@ public void should_throwException_when_notAvailable(EquipStatus equipStatus) { .hasMessageContaining(ErrorCode.INVALID_EQUIP_CONDITION.getMessage()); } + @Test + @DisplayName("프로필 프레임을 사용하려할 때, 장착 중인 프레임이 있는 경우 장착 해제를 먼저 실행하라는 예외가 발생한다.") + public void should_throwException_when_inUseItemExist() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + Orders orders = getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 3); + + orders.updateEquipStatus(EquipStatus.IN_USE); + + //when && then + assertThatThrownBy(() -> itemService.useItem(user, item.getId(), 0L, LocalDate.now())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.TOO_MANY_USING_FRAME.getMessage()); + } + @Test @DisplayName("인증 패스 아이템을 보유하고 있을 떄, 인증을 시도했으나 실패했을 때 아이템을 사용할 수 있다.") public void should_usePasserItem_when_ableToUsePassItem() { @@ -290,9 +306,6 @@ public void should_usePasserItem_when_ableToUsePassItem() { ItemUseResponse itemUseResponse = itemService.useItem(user, item.getId(), instance.getId(), currentDate); //then - assertThat(itemUseResponse.getInstanceId()).isEqualTo(instance.getId()); - assertThat(itemUseResponse.getTitle()).isEqualTo(instance.getTitle()); - assertThat(itemUseResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); assertThat(orders.getCount()).isEqualTo(0); assertThat(certification.getCertificationStatus()).isEqualTo(PASSED); } @@ -316,9 +329,6 @@ public void should_usePasserItem_when_useItemNotYet() { //then Optional certification = certificationRepository.findByDate(currentDate, participant.getId()); - assertThat(itemUseResponse.getInstanceId()).isEqualTo(instance.getId()); - assertThat(itemUseResponse.getTitle()).isEqualTo(instance.getTitle()); - assertThat(itemUseResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); assertThat(orders.getCount()).isEqualTo(0); assertThat(certification).isPresent(); assertThat(certification.get().getCertificationStatus()).isEqualTo(PASSED); @@ -388,6 +398,47 @@ public void should_getRewardTwice_when_conditionMatches() { assertThat(orders.getCount()).isEqualTo(0); } + @Test + @DisplayName("포인트 2배 획득 아이템을 사용해서 아이템의 개수가 0이 되면 Orders 정보를 DB에서 삭제된다.") + public void should_deleteOrderInfo_when_countZeroAfterUseItem() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(currentDate, currentDate.plusDays(1)); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.POINT_MULTIPLIER); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); + + //when + instance.updateProgress(Progress.DONE); + participant.updateJoinResult(JoinResult.SUCCESS); + getSavedCertification(CERTIFICATED, currentDate, participant); + getSavedCertification(CERTIFICATED, currentDate.plusDays(1), participant); + itemService.useItem(user, item.getId(), instance.getId(), currentDate.plusDays(1)); + + //then + assertThat(ordersRepository.findById(orders.getId())).isNotPresent(); + } + + @Test + @DisplayName("인증 패스 아이템을 사용해서 아이템의 개수가 0이 되면 Orders 정보를 DB에서 삭제된다.") + public void should_deleteOrderInfo_when_passItemCountIsZero() { + //given + LocalDate currentDate = LocalDate.of(2024, 3, 1); + User user = getSavedUser(); + Instance instance = getSavedInstance(currentDate, currentDate.plusDays(1)); + Participant participant = getSavedParticipant(user, instance); + Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); + + //when + instance.updateProgress(Progress.ACTIVITY); + itemService.useItem(user, item.getId(), instance.getId(), currentDate.plusDays(1)); + + //then + assertThat(ordersRepository.findById(orders.getId())).isNotPresent(); + } + @Test @DisplayName("사용자가 특정 프로필 프레임을 장착하고 있을 떄, 장착 해제할 수 있다.") public void should_unmountFrame_when_mountAlready() { @@ -398,7 +449,7 @@ public void should_unmountFrame_when_mountAlready() { //when itemService.useItem(user, item.getId(), 0L, LocalDate.now()); - ProfileResponse profileResponse = itemService.unmountFrame(user, item.getId()); + ProfileResponse profileResponse = itemService.unmountFrame(user).get(0); //then assertThat(profileResponse.getItemId()).isEqualTo(item.getId()); @@ -408,7 +459,7 @@ public void should_unmountFrame_when_mountAlready() { } @ParameterizedTest - @DisplayName("사용자가 아이템 장착 해제를 요청했을 때, 프로필 프레임이 아니라면 예외가 발생한다.") + @DisplayName("사용자가 아이템 장착 해제를 요청했을 때, 프로필 프레임이 아니라면 응답 데이터의 크기가 0이다.") @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) public void should_throwException_when_categoryIsNotFrame(ItemCategory itemCategory) { //given @@ -416,24 +467,26 @@ public void should_throwException_when_categoryIsNotFrame(ItemCategory itemCateg Item item = getSavedItem(itemCategory); Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); + //when + List profileResponses = itemService.unmountFrame(user); + //when & then - assertThatThrownBy(() -> itemService.unmountFrame(user, item.getId())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.ITEM_NOT_FOUND.getMessage()); + assertThat(profileResponses.size()).isEqualTo(0); } @Test - @DisplayName("사용자가 아이템 장착 해제를 요청했을 때, 사용 상태가 IN_USE가 아니라면 예외가 발생한다.") + @DisplayName("사용자가 아이템 장착 해제를 요청했을 때, 사용 상태가 IN_USE가 아니라면 반환데이터의 크기가 0이다.") public void should_throwException_when_equipStatusIsNotIS_USE() { //given User user = getSavedUser(); Item item = getSavedItem(ItemCategory.PROFILE_FRAME); getSavedOrder(user, item, item.getItemCategory(), 1); + // when + List profileResponses = itemService.unmountFrame(user); + //when & then - assertThatThrownBy(() -> itemService.unmountFrame(user, item.getId())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.IN_USE_FRAME_NOT_FOUND.getMessage()); + assertThat(profileResponses.size()).isEqualTo(0); } @@ -453,6 +506,7 @@ private Item getSavedItem(ItemCategory itemCategory) { .itemCategory(itemCategory) .cost(100) .name(itemCategory.getName()) + .details("details") .build()); } diff --git a/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java index 78e9b52c..907d37f7 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java @@ -1,17 +1,22 @@ package com.genius.gitget.challenge.item.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.EquipStatus; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.domain.Orders; import com.genius.gitget.store.item.repository.ItemRepository; import com.genius.gitget.store.item.repository.OrdersRepository; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.store.item.service.OrdersProvider; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -61,6 +66,112 @@ public void should_returnZero_when_dataNotSaved() { assertThat(numOfItem).isEqualTo(0); } + @Test + @DisplayName("아이템을 구매한 경우 User PK와 Item PK를 통해 주문 내역을 받아올 수 있다.") + public void should_getOrder_when_ordered() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + Orders orders = getSavedOrder(user, item, 1); + + //when + Optional optionalOrders = ordersProvider.findOptionalByOrderInfo(user.getId(), item.getId()); + + //then + assertThat(optionalOrders).isPresent(); + assertThat(optionalOrders.get()).isEqualTo(orders); + } + + @Test + @DisplayName("아이템을 구매하지 않은 경우 주문 내역을 받아왔을 때 Optional.null을 반환한다.") + public void should_returnOptional_when_notOrdered() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + + //when + Optional optionalOrders = ordersProvider.findOptionalByOrderInfo(user.getId(), item.getId()); + + //then + assertThat(optionalOrders).isNotPresent(); + } + + @Test + @DisplayName("구매를 한 경우, 구매 아이템의 장착 상황을 얻을 수 있다.") + public void should_getEquipStatus_when_ordered() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + getSavedOrder(user, item, 1); + + //when + EquipStatus equipStatus = ordersProvider.getEquipStatus(user.getId(), item.getId()); + + //then + assertThat(equipStatus).isEqualTo(EquipStatus.AVAILABLE); + } + + @Test + @DisplayName("아이템 구매를 하지 않은 경우, 장착 상황을 반환받을 때 UNAVAILABLE을 반환한다.") + public void should_returnUnavailable_when_notOrdered() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + + //when + EquipStatus equipStatus = ordersProvider.getEquipStatus(user.getId(), item.getId()); + + //then + assertThat(equipStatus).isEqualTo(EquipStatus.UNAVAILABLE); + } + + @Test + @DisplayName("사용자가 장착하고 있는 프로필 프레임이 하나 있을 때, 해당 프레임 아이템을 반환한다.") + public void should_returnPK_when_equipOneFrame() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + Orders orders = getSavedOrder(user, item, 1); + + //when + Item usingFrame = ordersProvider.getUsingFrameItem(user.getId()); + + //then + assertThat(item.getItemCategory()).isEqualTo(usingFrame.getItemCategory()); + } + + @Test + @DisplayName("오류로 인해 사용자가 장착하고 있는 프로필 프레임이 두 개 이상일 때, 예외를 발생한다.") + public void should_throwException_when_numOfFrameMoreThanTwo() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + Orders orders1 = getSavedOrder(user, item, 1); + Orders orders2 = getSavedOrder(user, item, 1); + + orders1.updateEquipStatus(EquipStatus.IN_USE); + orders2.updateEquipStatus(EquipStatus.IN_USE); + + //when & then + assertThatThrownBy(() -> ordersProvider.getUsingFrameItem(user.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.TOO_MANY_USING_FRAME.getMessage()); + } + + @Test + @DisplayName("사용자가 장착하고 있는 프레임이 없을 때, 더미 데이터를 전달받는다.") + public void should_returnDummy_when_notEquipped() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + + //when + Item usingFrame = ordersProvider.getUsingFrameItem(user.getId()); + + //then + assertThat(usingFrame.getId()).isNull(); + } + private User getSavedUser() { return userRepository.save( diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index b3ee287a..54f46ef8 100644 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.user.service; +import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REGISTERED; import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -11,14 +12,23 @@ import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.util.file.FileTestUtil; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -32,6 +42,10 @@ class UserServiceTest { @Autowired private UserRepository userRepository; @Autowired + private ItemRepository itemRepository; + @Autowired + private OrdersRepository ordersRepository; + @Autowired private UserService userService; @Test @@ -39,7 +53,7 @@ class UserServiceTest { public void should_matchValues_when_signupUser() { //given String identifier = "identifier"; - saveUnsignedUser(identifier); + saveUnsignedUser(identifier, Role.NOT_REGISTERED); SignupRequest signupRequest = SignupRequest.builder() .identifier(identifier) .nickname("nickname") @@ -72,7 +86,7 @@ public void should_matchValues_when_signupUser() { public void should_setRoleAdmin_when_identifierMatchesWithAdmin() { //given String identifier = "SSung023"; - saveUnsignedUser(identifier); + saveUnsignedUser(identifier, Role.NOT_REGISTERED); SignupRequest signupRequest = SignupRequest.builder() .identifier(identifier) .nickname("nickname") @@ -94,7 +108,7 @@ public void should_setRoleAdmin_when_identifierMatchesWithAdmin() { public void should_throwException_when_requestRegisterAgain() { //given String identifier = "identifier"; - saveUnsignedUser(identifier); + saveUnsignedUser(identifier, Role.NOT_REGISTERED); SignupRequest signupRequest = SignupRequest.builder() .identifier(identifier) .nickname("nickname") @@ -194,10 +208,51 @@ public void should_throwException_when_githubTokenNull() { .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); } + @ParameterizedTest + @DisplayName("Role이 NOT_REGISTERED가 아닌 경우에는 이미 등록이 되어 있다는 예외가 발생한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"USER", "ADMIN"}) + public void should_throwException_when_roleIsNotNOT_REGISTERED(Role role) { + assertThatThrownBy( + () -> userService.isAlreadyRegistered(saveUnsignedUser("identifier", role))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ALREADY_REGISTERED.getMessage()); + } - private void saveUnsignedUser(String identifier) { - userRepository.save(User.builder() - .role(Role.NOT_REGISTERED) + @Test + @DisplayName("사용자가 프로필 프레임을 장착하고 있지 않을 때, 사용자의 ROLE과 프레임의 PK는 null로 받는다.") + public void should_getUserInfo_when_notEquipFrame() { + //given + User user = getSavedUser(); + + //when + AuthResponse authResponse = userService.getUserInfo(user.getIdentifier()); + + //then + assertThat(authResponse.role()).isEqualTo(Role.USER); + assertThat(authResponse.frameId()).isEqualTo(null); + } + + @Test + @DisplayName("사용자가 프로필 프레임을 장착하고 있을 때, 사용자의 ROLE과 사용 중인 프레임의 PK를 받을 수 있다.") + public void should_getUserInfo_when_equipFrame() { + //given + User user = getSavedUser(); + Item item = getSavedItem(ItemCategory.PROFILE_FRAME); + Orders orders = getSavedOrders(user, item); + orders.updateEquipStatus(EquipStatus.IN_USE); + + //when + AuthResponse authResponse = userService.getUserInfo(user.getIdentifier()); + + //then + assertThat(authResponse.role()).isEqualTo(Role.USER); + assertThat(authResponse.frameId()).isEqualTo(item.getId()); + } + + + private User saveUnsignedUser(String identifier, Role role) { + return userRepository.save(User.builder() + .role(role) .providerInfo(ProviderInfo.NAVER) .identifier(identifier) .build()); @@ -213,4 +268,19 @@ private User getSavedUser() { .providerInfo(ProviderInfo.GITHUB) .build()); } + + private Item getSavedItem(ItemCategory itemCategory) { + return itemRepository.save( + Item.builder() + .itemCategory(itemCategory) + .build() + ); + } + + private Orders getSavedOrders(User user, Item item) { + Orders orders = Orders.createDefault(1, item.getItemCategory()); + orders.setUser(user); + orders.setItem(item); + return ordersRepository.save(orders); + } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java b/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java index 37b8c4f5..7b690468 100644 --- a/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java +++ b/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java @@ -98,9 +98,9 @@ private User getSavedUser() { assertThat(user.getPoint()).isEqualTo(900); System.out.println(itemCategory); if (itemCategory.equals(CERTIFICATION_PASSER)) { - assertThat(content.get(0).getOrderName()).isEqualTo("인증 패스 아이템"); + assertThat(content.get(0).getOrderName()).isEqualTo("인증 패스권"); } else if (itemCategory.equals(POINT_MULTIPLIER)) { - assertThat(content.get(0).getOrderName()).isEqualTo("포인트 2배 획득 아이템"); + assertThat(content.get(0).getOrderName()).isEqualTo("챌린지 보상 획득 2배 아이템"); } assertThat(content.get(0).getOrderType()).isEqualTo("아이템 구매"); assertThat(content.get(0).getDecreasedPoint()).isEqualTo("100"); @@ -182,4 +182,4 @@ class 사용자가_결제요청을_할_때 { } } } -} +} \ No newline at end of file From 9076fc3d261f960b52b35b9cb340a6249e6a61aa Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 23 Mar 2024 08:59:36 +0900 Subject: [PATCH 145/234] =?UTF-8?q?[FIX]=20=EC=A3=BC=EA=B0=84=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=ED=98=84=ED=99=A9=20=EC=A1=B0=ED=9A=8C=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#137)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 주간 인증 현황을 진행 중인 챌린지일 때에만 전달하도록 변경 - 주간 인증 현황 결과를 Progress가 ACTIVITY일 때에만 값을 채워 전달하도록 변경 - ACTIVITY가 아닐 때에는 빈 배열 전달 * fix: 주간 인증 현황 조회 버그 픽스 - 주간 인증 현황이 제대로 조회되지 않던 버그 픽스 - 당일에 패스/인증 이후에도 인증 이전으로 조회되던 버그 픽스 * test: 비지니스 로직 변경으로 인한 테스트 코드 변경 --- .../dto/CertificationResponse.java | 4 +- .../service/CertificationService.java | 48 ++++++++++++++----- .../challenge/instance/domain/Instance.java | 4 ++ .../service/CertificationServiceTest.java | 13 ++--- 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java index 22c2acf0..a6357f63 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java @@ -22,10 +22,10 @@ public record CertificationResponse( ) { - public static CertificationResponse createNonExist(int currentAttempt, LocalDate certificatedAt) { + public static CertificationResponse createNonExist(LocalDate certificatedAt) { return CertificationResponse.builder() .certificationId(0L) - .certificationAttempt(currentAttempt) + .certificationAttempt(0) .dayOfWeek(certificatedAt.getDayOfWeek()) .certificatedAt(certificatedAt) .certificateStatus(NOT_YET) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 89bd03f3..01df46e3 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -24,6 +24,7 @@ import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.service.OrdersProvider; +import java.time.DayOfWeek; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; @@ -53,7 +54,12 @@ public class CertificationService { public List getWeekCertification(Long participantId, LocalDate currentDate) { - LocalDate startDate = participantProvider.getInstanceStartDate(participantId); + Instance instance = participantProvider.getInstanceById(participantId); + if (!instance.isActivatedInstance()) { + return new ArrayList<>(); + } + + LocalDate startDate = instance.getStartedDate().toLocalDate(); int curAttempt = DateUtil.getWeekAttempt(startDate, currentDate); LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate); @@ -61,8 +67,9 @@ public List getWeekCertification(Long participantId, Loca weekStartDate, currentDate, participantId); + Map certificationMap = convertToWeekMap(certifications); - return convertToCertificationResponse(certifications, curAttempt, weekStartDate); + return convertToCertificationResponse(certificationMap, curAttempt, weekStartDate); } public Slice getAllWeekCertification(Long userId, Long instanceId, @@ -75,20 +82,27 @@ public Slice getAllWeekCertification(Long userId, Long instanceId, private WeekResponse convertToWeekResponse(Participant participant, LocalDate currentDate) { User user = participant.getUser(); - LocalDate startDate = participantProvider.getInstanceStartDate(participant.getId()); + Instance instance = participant.getInstance(); + + LocalDate startDate = instance.getStartedDate().toLocalDate(); LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate); + FileResponse fileResponse = FileResponse.create(user.getFiles()); + Long frameId = ordersProvider.getUsingFrameItem(user.getId()).getId(); + + if (!instance.isActivatedInstance()) { + return WeekResponse.create(user, frameId, fileResponse, new ArrayList<>()); + } + List certifications = certificationProvider.findByDuration( weekStartDate, currentDate, participant.getId()); + Map certificationMap = convertToWeekMap(certifications); List certificationResponses = convertToCertificationResponse( - certifications, + certificationMap, DateUtil.getWeekAttempt(startDate, currentDate), weekStartDate); - FileResponse fileResponse = FileResponse.create(user.getFiles()); - Long frameId = ordersProvider.getUsingFrameItem(user.getId()).getId(); - return WeekResponse.create(user, frameId, fileResponse, certificationResponses); } @@ -101,9 +115,10 @@ public TotalResponse getTotalCertification(Long participantId, LocalDate current List certifications = certificationProvider.findByDuration( startDate, currentDate, participantId); + Map certificationMap = convertToTotalMap(certifications); List certificationResponses = convertToCertificationResponse( - certifications, curAttempt, startDate); + certificationMap, curAttempt, startDate); return TotalResponse.builder() .totalAttempts(totalAttempts) @@ -111,10 +126,9 @@ public TotalResponse getTotalCertification(Long participantId, LocalDate current .build(); } - private List convertToCertificationResponse(List certifications, + private List convertToCertificationResponse(Map certificationMap, int curAttempt, LocalDate startedDate) { List result = new ArrayList<>(); - Map certificationMap = convertToMap(certifications); startedDate = startedDate.minusDays(1); @@ -124,13 +138,13 @@ private List convertToCertificationResponse(List convertToMap(List certifications) { + private Map convertToTotalMap(List certifications) { Map certificationMap = new HashMap<>(); for (Certification certification : certifications) { @@ -139,6 +153,16 @@ private Map convertToMap(List certificati return certificationMap; } + private Map convertToWeekMap(List certifications) { + Map certificationMap = new HashMap<>(); + + for (Certification certification : certifications) { + DayOfWeek dayOfWeek = certification.getCertificatedAt().getDayOfWeek(); + certificationMap.put(dayOfWeek.ordinal() + 1, certification); + } + return certificationMap; + } + @Transactional public ActivatedResponse passCertification(Long userId, CertificationRequest certificationRequest) { Instance instance = instanceProvider.findById(certificationRequest.instanceId()); diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 21a836f0..2b0bf744 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -175,6 +175,10 @@ public int getTotalAttempt() { return DateUtil.getAttemptCount(startedDate.toLocalDate(), completedDate.toLocalDate()); } + public boolean isActivatedInstance() { + return this.progress == Progress.ACTIVITY; + } + /* * 인스턴스 고유 uuid 설정 * */ diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index 16c34a9d..59a67079 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -258,9 +258,11 @@ public void should_returnCertifications_when_passDuration() { LocalDate currentDate = LocalDate.of(2024, 2, 3); LocalDate startDate = LocalDate.of(2024, 2, 1); LocalDate endDate = LocalDate.of(2024, 2, 4); - Participant participant = getSavedParticipant(getSavedUser(githubId), getSavedInstance()); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(getSavedUser(githubId), instance); //when + instance.updateProgress(Progress.ACTIVITY); getSavedCertification(NOT_YET, startDate, participant); getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); getSavedCertification(CERTIFICATED, endDate.minusDays(1), participant); @@ -281,9 +283,11 @@ public void should_returnList_when_dataIsNotContinuous() { LocalDate endDate = LocalDate.of(2024, 2, 29); LocalDate currentDate = LocalDate.of(2024, 2, 8); - Participant participant = getSavedParticipant(getSavedUser(githubId), getSavedInstance()); + Instance instance = getSavedInstance(); + Participant participant = getSavedParticipant(getSavedUser(githubId), instance); //when + instance.updateProgress(Progress.ACTIVITY); getSavedCertification(NOT_YET, startDate, participant); getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); @@ -294,10 +298,6 @@ public void should_returnList_when_dataIsNotContinuous() { //then assertThat(weekCertification.size()).isEqualTo(4); - assertThat(weekCertification.get(0).certificationAttempt()).isEqualTo(1); - assertThat(weekCertification.get(1).certificationAttempt()).isEqualTo(2); - assertThat(weekCertification.get(2).certificationAttempt()).isEqualTo(3); - assertThat(weekCertification.get(3).certificationAttempt()).isEqualTo(4); } @Test @@ -435,6 +435,7 @@ public void should_getWeekCertification_aboutAllParticipants() { Participant participant2 = getSavedParticipant(user2, instance); //when + instance.updateProgress(Progress.ACTIVITY); Slice certification = certificationService.getAllWeekCertification( user1.getId(), instance.getId(), currentDate, pageRequest); From 40d039ffc7b2bd92718eef9d29021b7d76cf87f7 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Wed, 27 Mar 2024 21:24:31 +0900 Subject: [PATCH 146/234] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=82=AD=EC=A0=9C=20(#143)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인스턴스 삭제 시 Optional로 받는 file 객체 코드 수정 - 테스트 코드 작성 --- .../instance/service/InstanceService.java | 10 +- .../instance/service/InstanceServiceTest.java | 103 +++++++++++++++++- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index aa6c7bb1..428bfc8c 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -103,11 +103,13 @@ public void deleteInstance(Long id) { Instance instance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); - Optional findInstanceFile = instance.getFiles(); - Long findInstanceFileId = findInstanceFile.get().getId(); + Files files = instance.getFiles().orElse(null); + Long filesId = files != null ? files.getId() : null; - filesService.deleteFile(findInstanceFileId); - instance.setFiles(null); + if (filesId != null) { + filesService.deleteFile(filesId); + instance.setFiles(null); + } instanceRepository.delete(instance); } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java index 8edd6c1e..dec9012c 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java @@ -1,6 +1,8 @@ package com.genius.gitget.challenge.instance.service; +import static org.junit.jupiter.api.Assertions.assertThrows; + import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; @@ -9,6 +11,11 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.repository.FilesRepository; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.util.file.FileTestUtil; import java.time.LocalDate; import java.time.LocalDateTime; @@ -16,11 +23,13 @@ import java.util.Optional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; @SpringBootTest @Transactional @@ -28,12 +37,14 @@ public class InstanceServiceTest { @Autowired InstanceService instanceService; - @Autowired InstanceRepository instanceRepository; - @Autowired TopicRepository topicRepository; + @Autowired + FilesService filesService; + @Autowired + FilesRepository filesRepository; private Instance instance; private Topic topic; @@ -57,6 +68,7 @@ public void setup() { .pointPerPerson(100) .build(); fileType = "instance"; + } @Test @@ -120,6 +132,82 @@ public void setup() { Assertions.assertThat(instanceById.title()).isEqualTo(instanceCreateRequest.title()); } + @Nested + public class 인스턴스_삭제할_때 { + private Topic topic; + private Instance instance1, instance2, instance3; + + @BeforeEach + public void setup() { + topic = getSavedTopic("1일 1공부", "BE, ML"); + instance1 = getSavedInstance("1일 1공부", "BE, ML", 100); + instance2 = getSavedInstance("1일 3공부", "BE, ML", 100); + instance3 = getSavedInstance("1일 3공부", "BE, ML", 100); + instance1.setTopic(topic); + instance2.setTopic(topic); + } + + @Test + public void 해당_아이디가_존재한다면_삭제할_수_있다() { + Long id = instance1.getId(); + instanceService.deleteInstance(id); + + assertThrows(BusinessException.class, () -> { + instanceService.getInstanceById(id); + }); + } + + @Test + public void 해당_아이디가_존재하지_않는다면_삭제할_수_없다() { + Long id = instance3.getId() + 1L; + assertThrows(BusinessException.class, () -> { + instanceService.deleteInstance(id); + }); + } + + @Test + public void 해당_인스턴스에_파일이_존재한다면_같이_삭제한다() { + MultipartFile filename = FileTestUtil.getMultipartFile("sky"); + Files files1 = filesService.uploadFile(filename, "instance"); + + instance1.setFiles(files1); + instanceRepository.save(instance1); + + instanceService.deleteInstance(instance1.getId()); + } + } + + + private Topic getSavedTopic(String title, String tags) { + Topic topic = topicRepository.save( + Topic.builder() + .title(title) + .tags(tags) + .description("토픽 설명") + .pointPerPerson(100) + .build() + ); + return topic; + } + + private Instance getSavedInstance(String title, String tags, int participantCnt) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(100) + .certificationMethod("인증 방법") + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } + private InstanceCreateRequest getInstanceCreateRequest(Topic savedTopic, Instance instance) { return InstanceCreateRequest.builder() .topicId(savedTopic.getId()) @@ -132,4 +220,15 @@ private InstanceCreateRequest getInstanceCreateRequest(Topic savedTopic, Instanc .completedAt(instance.getCompletedDate()) .build(); } + + private Files getSavedFiles(String originalFilename, String savedFilename, String fileURL, FileType fileType) { + return filesRepository.save( + Files.builder() + .originalFilename(originalFilename) + .savedFilename(savedFilename) + .fileURI(fileURL) + .fileType(fileType) + .build() + ); + } } From ed8017f71b648f47a5f2312573390afbdca41f5c Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 30 Mar 2024 22:40:58 +0900 Subject: [PATCH 147/234] =?UTF-8?q?fix:=20=EC=A0=84=EC=B2=B4=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EB=82=B4=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 완료된 챌린지에 대해 전체 인증 내역 조회 시, 데이터의 개수가 잘못 전달되던 버그 픽스 - 관련 테스트 코드 작성 - 코드 리팩토링 필요 --- .../dto/CertificationResponse.java | 4 +- .../service/CertificationService.java | 9 ++- .../service/CertificationServiceTest.java | 68 +++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java index a6357f63..9077b17d 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationResponse.java @@ -22,10 +22,10 @@ public record CertificationResponse( ) { - public static CertificationResponse createNonExist(LocalDate certificatedAt) { + public static CertificationResponse createNonExist(int certificationAttempt, LocalDate certificatedAt) { return CertificationResponse.builder() .certificationId(0L) - .certificationAttempt(0) + .certificationAttempt(certificationAttempt) .dayOfWeek(certificatedAt.getDayOfWeek()) .certificatedAt(certificatedAt) .certificateStatus(NOT_YET) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 01df46e3..e2923ad2 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -109,10 +109,15 @@ private WeekResponse convertToWeekResponse(Participant participant, LocalDate cu public TotalResponse getTotalCertification(Long participantId, LocalDate currentDate) { Instance instance = participantProvider.getInstanceById(participantId); LocalDate startDate = instance.getStartedDate().toLocalDate(); - int totalAttempts = instance.getTotalAttempt(); + int totalAttempts = instance.getTotalAttempt(); int curAttempt = DateUtil.getAttemptCount(startDate, currentDate); + if (instance.getProgress() == Progress.DONE) { + currentDate = instance.getCompletedDate().toLocalDate(); + curAttempt = totalAttempts; + } + List certifications = certificationProvider.findByDuration( startDate, currentDate, participantId); Map certificationMap = convertToTotalMap(certifications); @@ -138,7 +143,7 @@ private List convertToCertificationResponse(Map Date: Sun, 31 Mar 2024 15:19:52 +0900 Subject: [PATCH 148/234] =?UTF-8?q?bug:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=ED=83=88=ED=87=B4=20(#146)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자가 아이템을 구매했거나, 챌린지를 참여한 이력이 있을 경우 제약조건 에러가 발생 - certification-participant, user-payment, user-orders 관계 재설정 후 버그 수정 완료 --- .../gitget/challenge/certification/domain/Certification.java | 3 +-- .../gitget/challenge/participant/domain/Participant.java | 3 ++- .../java/com/genius/gitget/challenge/user/domain/User.java | 4 ++-- .../com/genius/gitget/profile/dto/UserSignoutRequest.java | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java index 522ef647..a184182a 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java +++ b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java @@ -4,7 +4,6 @@ import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.util.domain.BaseTimeEntity; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -33,7 +32,7 @@ public class Certification extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "participant_id") private Participant participant; diff --git a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java index bf5d719c..02504576 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java @@ -3,6 +3,7 @@ import com.genius.gitget.challenge.certification.domain.Certification; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.user.domain.User; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -45,7 +46,7 @@ public class Participant { @JoinColumn(name = "user_id") private User user; - @OneToMany(mappedBy = "participant") + @OneToMany(mappedBy = "participant", cascade = CascadeType.ALL, orphanRemoval = true) private List certificationList = new ArrayList<>(); @Enumerated(EnumType.STRING) diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 51c0da90..8da87cde 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -47,13 +47,13 @@ public class User extends BaseTimeEntity { @OneToMany(mappedBy = "user") private List likesList = new ArrayList<>(); - @OneToMany(mappedBy = "user") + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List participantList = new ArrayList<>(); @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List payment = new ArrayList<>(); - @OneToMany(mappedBy = "user") + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List ordersList = new ArrayList<>(); @NotNull diff --git a/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java b/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java index 7b885475..a316b98d 100644 --- a/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java +++ b/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java @@ -2,8 +2,10 @@ import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor public class UserSignoutRequest { private String reason; From 4ae954245cf64bbb988d2d8ce84fd0a3cabdf5c6 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 31 Mar 2024 20:11:34 +0900 Subject: [PATCH 149/234] =?UTF-8?q?[FIX]=20=EC=98=A4=EB=8A=98=EC=9D=98=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EA=B0=B1=EC=8B=A0=20API=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#148)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: PR body가 존재하지 않을 때 발생하던 예외 추가 처리 - 사용자가 생성한 PR의 내용(Body)가 존재하지 않을 때 .getBody()가 null이라며 발생하던 예외 추가 처리 * test: 통과하지 않는 테스트 코드 수정 * test: MultipartFile API 관련 테스트 코드 삭제 --- .../service/CertificationService.java | 11 ++-- .../service/InstanceDetailServiceTest.java | 4 -- .../user/controller/UserControllerTest.java | 50 ------------------- 3 files changed, 7 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index e2923ad2..0ead6cdd 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -64,9 +64,7 @@ public List getWeekCertification(Long participantId, Loca LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate); List certifications = certificationProvider.findByDuration( - weekStartDate, - currentDate, - participantId); + weekStartDate, currentDate, participantId); Map certificationMap = convertToWeekMap(certifications); return convertToCertificationResponse(certificationMap, curAttempt, weekStartDate); @@ -234,7 +232,12 @@ public CertificationResponse updateCertification(User user, CertificationRequest private List filterValidPR(List ghPullRequests, String prTemplate) { return ghPullRequests.stream() - .filter(ghPullRequest -> ghPullRequest.getBody().contains(prTemplate)) + .filter(ghPullRequest -> { + if (ghPullRequest.getBody() == null) { + return false; + } + return ghPullRequest.getBody().contains(prTemplate); + }) .map(ghPullRequest -> ghPullRequest.getHtmlUrl().toString()) .toList(); } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index dcbbf218..d973c1ea 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -28,7 +28,6 @@ import com.genius.gitget.global.util.exception.BusinessException; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -149,11 +148,8 @@ public void should_joinStatusIsNo_when_quitChallenge() { //when instanceDetailService.joinNewChallenge(savedUser, new JoinRequest(savedInstance.getId(), targetRepo)); JoinResponse joinResponse = instanceDetailService.quitChallenge(savedUser, savedInstance.getId()); - Optional byJoinInfo = participantRepository.findByJoinInfo(savedUser.getId(), - savedInstance.getId()); //then - assertThat(byJoinInfo).isEmpty(); assertThat(savedInstance.getParticipantCount()).isEqualTo(0); assertThat(joinResponse.participantId()).isEqualTo(null); assertThat(joinResponse.joinResult()).isEqualTo(null); diff --git a/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java b/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java index bc7682f0..1bf835e0 100644 --- a/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/controller/UserControllerTest.java @@ -4,19 +4,14 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.global.util.exception.BusinessException; -import java.nio.charset.StandardCharsets; -import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -24,8 +19,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -79,37 +72,6 @@ public void should_return2XX_when_nicknameNotDuplicated() throws Exception { .andExpect(jsonPath("$.message").value(SUCCESS.getMessage())); } - @Test - @DisplayName("사용자의 회원가입이 완료되었다면 사용자의 identifier를 전달한다.") - public void should_passIdentifier_when_signupCompleted() throws Exception { - //given - String identifier = "identifier"; - saveUnsignedUser(identifier); - SignupRequest signupRequest = SignupRequest.builder() - .identifier(identifier) - .nickname("nickname") - .information("information") - .interest(List.of("관심사1", "관심사2")) - .build(); - String signupAsString = objectMapper.writeValueAsString(signupRequest); - MockMultipartFile profileFile = setMockMultipartFile("testImage", "png"); - - //when & then - mockMvc.perform(multipart("/api/auth/signup") - .file(profileFile) - .file(new MockMultipartFile("data", "", "application/json", - signupAsString.getBytes(StandardCharsets.UTF_8)))) - .andExpect(status().isOk()); - } - - - private void saveUnsignedUser(String identifier) { - userRepository.save(User.builder() - .role(Role.NOT_REGISTERED) - .providerInfo(ProviderInfo.GITHUB) - .identifier(identifier) - .build()); - } private User getSavedUser() { return userRepository.save(User.builder() @@ -121,16 +83,4 @@ private User getSavedUser() { .providerInfo(ProviderInfo.GITHUB) .build()); } - - private MockMultipartFile setMockMultipartFile(String fileName, String contentType) { - try { - return new MockMultipartFile("files", - fileName + "." + contentType, -// contentType, - MediaType.IMAGE_PNG_VALUE, - fileName.getBytes()); - } catch (Exception e) { - throw new BusinessException(e); - } - } } \ No newline at end of file From 874044dab592098e2ffcf5faed4f30c4085e0a01 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:31:14 +0900 Subject: [PATCH 150/234] =?UTF-8?q?[REFACTOR]=20CertificationService?= =?UTF-8?q?=EC=9D=98=20=EC=A0=84=EC=B2=B4/=EC=A3=BC=EA=B0=84=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20(#151)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 주간/전체 인증 현황 조회 로직 리팩토링 - 주간/전체 인증 현황 조회 로직 분리 - 메서드의 이름을 직관적으로 변경 - UserService에 사용자의 프로필 정보를 받아오는 메서드 추가 * test: 테스트 코드 보강 - CertificationService 관련 테스트 코드 보강 --- .../controller/CertificationController.java | 13 +- .../certification/dto/WeekResponse.java | 23 +--- .../service/CertificationService.java | 111 ++++++++---------- .../challenge/user/dto/UserProfileInfo.java | 15 +++ .../challenge/user/service/UserService.java | 8 +- .../security/controller/AuthController.java | 2 +- .../service/CertificationServiceTest.java | 23 ++-- .../user/service/UserServiceTest.java | 4 +- 8 files changed, 100 insertions(+), 99 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/user/dto/UserProfileInfo.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index a63e73c0..e4ef1cd4 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -16,12 +16,10 @@ import com.genius.gitget.challenge.participant.service.ParticipantProvider; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; -import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.SingleResponse; import com.genius.gitget.global.util.response.dto.SlicingResponse; import java.time.LocalDate; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; @@ -93,15 +91,12 @@ public ResponseEntity> getWeekCertification( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long instanceId ) { - User user = userService.findUserById(userPrincipal.getUser().getId()); - Participant participant = participantProvider.findByJoinInfo(user.getId(), instanceId); - List weekCertification = certificationService.getWeekCertification( + Participant participant = participantProvider.findByJoinInfo(userPrincipal.getUser().getId(), instanceId); + WeekResponse weekCertification = certificationService.getMyWeekCertifications( participant.getId(), LocalDate.now()); - FileResponse fileResponse = FileResponse.create(user.getFiles()); return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), - WeekResponse.create(user, fileResponse, weekCertification)) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), weekCertification) ); } @@ -112,7 +107,7 @@ public ResponseEntity> getAllUserWeekCertification @PageableDefault Pageable pageable ) { User user = userPrincipal.getUser(); - Slice certifications = certificationService.getAllWeekCertification( + Slice certifications = certificationService.getOthersWeekCertifications( user.getId(), instanceId, LocalDate.now(), pageable); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java b/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java index 1444634a..e8140cde 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/WeekResponse.java @@ -1,6 +1,6 @@ package com.genius.gitget.challenge.certification.dto; -import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.UserProfileInfo; import com.genius.gitget.global.file.dto.FileResponse; import java.util.List; import lombok.Builder; @@ -14,25 +14,14 @@ public record WeekResponse( FileResponse profile ) { - - public static WeekResponse create(User user, FileResponse fileResponse, - List certifications) { - return WeekResponse.builder() - .userId(user.getId()) - .nickname(user.getNickname()) - .certifications(certifications) - .profile(fileResponse) - .build(); - } - - public static WeekResponse create(User user, Long frameId, FileResponse fileResponse, + public static WeekResponse create(UserProfileInfo userProfileInfo, List certifications) { return WeekResponse.builder() - .userId(user.getId()) - .nickname(user.getNickname()) - .frameId(frameId) + .userId(userProfileInfo.userId()) + .nickname(userProfileInfo.nickname()) + .frameId(userProfileInfo.frameId()) .certifications(certifications) - .profile(fileResponse) + .profile(userProfileInfo.fileResponse()) .build(); } } diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 0ead6cdd..1dc94e46 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -19,12 +19,12 @@ import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantProvider; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.UserProfileInfo; +import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; -import com.genius.gitget.store.item.service.OrdersProvider; -import java.time.DayOfWeek; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; @@ -45,63 +45,65 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class CertificationService { + private final UserService userService; private final FilesService filesService; private final GithubProvider githubProvider; private final CertificationProvider certificationProvider; private final ParticipantProvider participantProvider; private final InstanceProvider instanceProvider; - private final OrdersProvider ordersProvider; - public List getWeekCertification(Long participantId, LocalDate currentDate) { - Instance instance = participantProvider.getInstanceById(participantId); - if (!instance.isActivatedInstance()) { - return new ArrayList<>(); - } - - LocalDate startDate = instance.getStartedDate().toLocalDate(); - int curAttempt = DateUtil.getWeekAttempt(startDate, currentDate); - LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate); - - List certifications = certificationProvider.findByDuration( - weekStartDate, currentDate, participantId); - Map certificationMap = convertToWeekMap(certifications); - - return convertToCertificationResponse(certificationMap, curAttempt, weekStartDate); + public WeekResponse getMyWeekCertifications(Long participantId, LocalDate currentDate) { + Participant participant = participantProvider.findById(participantId); + return getWeekResponse(participant, currentDate); } - public Slice getAllWeekCertification(Long userId, Long instanceId, - LocalDate currentDate, Pageable pageable) { + public Slice getOthersWeekCertifications(Long userId, Long instanceId, + LocalDate currentDate, Pageable pageable) { Slice participants = participantProvider.findAllByInstanceId(userId, instanceId, pageable); return participants.map( - participant -> convertToWeekResponse(participant, currentDate) + participant -> getWeekResponse(participant, currentDate) ); } - private WeekResponse convertToWeekResponse(Participant participant, LocalDate currentDate) { - User user = participant.getUser(); + private WeekResponse getWeekResponse(Participant participant, LocalDate currentDate) { Instance instance = participant.getInstance(); + LocalDate instanceStartDate = instance.getStartedDate().toLocalDate(); + LocalDate weekStartDate = DateUtil.getWeekStartDate(instanceStartDate, currentDate); - LocalDate startDate = instance.getStartedDate().toLocalDate(); - LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate); - - FileResponse fileResponse = FileResponse.create(user.getFiles()); - Long frameId = ordersProvider.getUsingFrameItem(user.getId()).getId(); + UserProfileInfo userProfileInfo = userService.getUserProfileInfo(participant.getUser()); if (!instance.isActivatedInstance()) { - return WeekResponse.create(user, frameId, fileResponse, new ArrayList<>()); + return WeekResponse.create(userProfileInfo, new ArrayList<>()); } List certifications = certificationProvider.findByDuration( weekStartDate, currentDate, participant.getId()); - Map certificationMap = convertToWeekMap(certifications); - List certificationResponses = convertToCertificationResponse( - certificationMap, - DateUtil.getWeekAttempt(startDate, currentDate), - weekStartDate); + List weekCertifications = getWeekCertifications( + certifications, instanceStartDate, currentDate); - return WeekResponse.create(user, frameId, fileResponse, certificationResponses); + return WeekResponse.create(userProfileInfo, weekCertifications); + } + + private List getWeekCertifications(List certifications, + LocalDate startDate, LocalDate currentDate) { + List results = new ArrayList<>(); + Map weekMap = new HashMap<>(); + for (Certification certification : certifications) { + weekMap.put(certification.getCertificatedAt(), certification); + } + + LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate).minusDays(1); + while (weekStartDate.isBefore(currentDate)) { + weekStartDate = weekStartDate.plusDays(1); + if (weekMap.containsKey(weekStartDate)) { + results.add(CertificationResponse.createExist(weekMap.get(weekStartDate))); + continue; + } + results.add(CertificationResponse.createNonExist(0, weekStartDate)); + } + return results; } public TotalResponse getTotalCertification(Long participantId, LocalDate currentDate) { @@ -118,27 +120,31 @@ public TotalResponse getTotalCertification(Long participantId, LocalDate current List certifications = certificationProvider.findByDuration( startDate, currentDate, participantId); - Map certificationMap = convertToTotalMap(certifications); - List certificationResponses = convertToCertificationResponse( - certificationMap, curAttempt, startDate); + List totalCertifications = getTotalCertifications( + certifications, curAttempt, startDate); return TotalResponse.builder() .totalAttempts(totalAttempts) - .certifications(certificationResponses) + .certifications(totalCertifications) .build(); } - private List convertToCertificationResponse(Map certificationMap, - int curAttempt, LocalDate startedDate) { + private List getTotalCertifications(List certifications, + int curAttempt, LocalDate startedDate) { List result = new ArrayList<>(); + Map totalMap = new HashMap<>(); + + for (Certification certification : certifications) { + totalMap.put(certification.getCurrentAttempt(), certification); + } startedDate = startedDate.minusDays(1); for (int cur = 1; cur <= curAttempt; cur++) { startedDate = startedDate.plusDays(1); - if (certificationMap.containsKey(cur)) { - result.add(CertificationResponse.createExist(certificationMap.get(cur))); + if (totalMap.containsKey(cur)) { + result.add(CertificationResponse.createExist(totalMap.get(cur))); continue; } result.add(CertificationResponse.createNonExist(cur, startedDate)); @@ -147,25 +153,6 @@ private List convertToCertificationResponse(Map convertToTotalMap(List certifications) { - Map certificationMap = new HashMap<>(); - - for (Certification certification : certifications) { - certificationMap.put(certification.getCurrentAttempt(), certification); - } - return certificationMap; - } - - private Map convertToWeekMap(List certifications) { - Map certificationMap = new HashMap<>(); - - for (Certification certification : certifications) { - DayOfWeek dayOfWeek = certification.getCertificatedAt().getDayOfWeek(); - certificationMap.put(dayOfWeek.ordinal() + 1, certification); - } - return certificationMap; - } - @Transactional public ActivatedResponse passCertification(Long userId, CertificationRequest certificationRequest) { Instance instance = instanceProvider.findById(certificationRequest.instanceId()); diff --git a/src/main/java/com/genius/gitget/challenge/user/dto/UserProfileInfo.java b/src/main/java/com/genius/gitget/challenge/user/dto/UserProfileInfo.java new file mode 100644 index 00000000..1bb36ba4 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/user/dto/UserProfileInfo.java @@ -0,0 +1,15 @@ +package com.genius.gitget.challenge.user.dto; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.file.dto.FileResponse; + +public record UserProfileInfo( + Long userId, + String nickname, + Long frameId, + FileResponse fileResponse +) { + public static UserProfileInfo createByEntity(User user, Long frameId) { + return new UserProfileInfo(user.getId(), user.getNickname(), frameId, FileResponse.create(user.getFiles())); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 065764cd..0e786d41 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -9,6 +9,7 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.challenge.user.dto.UserProfileInfo; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; @@ -98,10 +99,15 @@ public void isAlreadyRegistered(User user) { } } - public AuthResponse getUserInfo(String identifier) { + public AuthResponse getUserAuthInfo(String identifier) { User user = userRepository.findByIdentifier(identifier) .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); Item usingFrame = ordersProvider.getUsingFrameItem(user.getId()); return new AuthResponse(user.getRole(), usingFrame.getId()); } + + public UserProfileInfo getUserProfileInfo(User user) { + Long frameId = ordersProvider.getUsingFrameItem(user.getId()).getId(); + return UserProfileInfo.createByEntity(user, frameId); + } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 3d145341..78c6a9cb 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -37,7 +37,7 @@ public ResponseEntity> generateToken(HttpServletRes jwtService.generateAccessToken(response, requestUser); jwtService.generateRefreshToken(response, requestUser); - AuthResponse authResponse = userService.getUserInfo(requestUser.getIdentifier()); + AuthResponse authResponse = userService.getUserAuthInfo(requestUser.getIdentifier()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), authResponse) diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index 7d5aab25..842ac522 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -252,7 +252,7 @@ public void should_throwException_when_loadRepository() { } @Test - @DisplayName("특정 사용자가 일주일 간 인증한 현황들을 받아올 수 있다.") + @DisplayName("챌린지의 시작일자가 월요일이 아니고 첫째 주 일 때, 챌린지 시작일부터 현재일자까지의 인증 내역을 반환해야 한다.") public void should_returnCertifications_when_passDuration() { //given LocalDate currentDate = LocalDate.of(2024, 2, 3); @@ -268,11 +268,15 @@ public void should_returnCertifications_when_passDuration() { getSavedCertification(CERTIFICATED, endDate.minusDays(1), participant); getSavedCertification(CERTIFICATED, endDate, participant); - List weekCertification = certificationService.getWeekCertification( + WeekResponse weekCertification = certificationService.getMyWeekCertifications( participant.getId(), currentDate); //then - assertThat(weekCertification.size()).isEqualTo(3); + List certifications = weekCertification.certifications(); + assertThat(certifications.size()).isEqualTo(3); + assertThat(certifications.get(0).certificateStatus()).isEqualTo(NOT_YET); + assertThat(certifications.get(1).certificateStatus()).isEqualTo(CERTIFICATED); + assertThat(certifications.get(2).certificateStatus()).isEqualTo(CERTIFICATED); } @Test @@ -291,13 +295,18 @@ public void should_returnList_when_dataIsNotContinuous() { getSavedCertification(NOT_YET, startDate, participant); getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(6), participant); + getSavedCertification(CERTIFICATED, currentDate, participant); - List weekCertification = certificationService.getWeekCertification( + WeekResponse weekCertification = certificationService.getMyWeekCertifications( participant.getId(), currentDate); //then - assertThat(weekCertification.size()).isEqualTo(4); + List certifications = weekCertification.certifications(); + assertThat(certifications.size()).isEqualTo(4); + assertThat(certifications.get(0).certificateStatus()).isEqualTo(CERTIFICATED); + assertThat(certifications.get(1).certificateStatus()).isEqualTo(NOT_YET); + assertThat(certifications.get(2).certificateStatus()).isEqualTo(NOT_YET); + assertThat(certifications.get(3).certificateStatus()).isEqualTo(CERTIFICATED); } @Test @@ -494,7 +503,7 @@ public void should_getWeekCertification_aboutAllParticipants() { //when instance.updateProgress(Progress.ACTIVITY); - Slice certification = certificationService.getAllWeekCertification( + Slice certification = certificationService.getOthersWeekCertifications( user1.getId(), instance.getId(), currentDate, pageRequest); //then diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index 54f46ef8..e7a7b8cf 100644 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -225,7 +225,7 @@ public void should_getUserInfo_when_notEquipFrame() { User user = getSavedUser(); //when - AuthResponse authResponse = userService.getUserInfo(user.getIdentifier()); + AuthResponse authResponse = userService.getUserAuthInfo(user.getIdentifier()); //then assertThat(authResponse.role()).isEqualTo(Role.USER); @@ -242,7 +242,7 @@ public void should_getUserInfo_when_equipFrame() { orders.updateEquipStatus(EquipStatus.IN_USE); //when - AuthResponse authResponse = userService.getUserInfo(user.getIdentifier()); + AuthResponse authResponse = userService.getUserAuthInfo(user.getIdentifier()); //then assertThat(authResponse.role()).isEqualTo(Role.USER); From fd6fb93efb098a4a6b6659518d599bc98fd668e8 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Fri, 5 Apr 2024 12:16:51 +0900 Subject: [PATCH 151/234] =?UTF-8?q?[TEST]=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20&=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B3=84?= =?UTF-8?q?=EC=B8=B5=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?(#153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: InstanceService 테스트 코드 작성 * test: 컨트롤러 및 서비스 계층 테스트 - controller multipart 테스트 시 정상적으로 파일이 저장되지 않는 이슈 해결 필요 - 서비스 계층 테스트 완료 * test: topic & instance controller 테스트 * test: controller unit test - multipart를 제외한 controller 테스트 수행 완료 - 최적화 필요 -> 중복된 코드가 너무 많음 - 코드 개선 필요 -> 테스트 시간 오래 걸림 --------- Co-authored-by: HEY <50323157+SSung023@users.noreply.github.com> --- .../admin/topic/service/TopicService.java | 4 + .../gitget/challenge/likes/domain/Likes.java | 2 + .../likes/dto/UserLikesAddRequest.java | 2 + .../challenge/likes/service/LikesService.java | 46 +-- .../user/repository/UserRepository.java | 6 +- .../global/file/service/FilesService.java | 2 - .../profile/controller/ProfileController.java | 8 +- .../profile/dto/UserSignoutRequest.java | 4 +- .../topic/controller/TopicControllerTest.java | 117 ++++++- .../controller/InstanceControllerTest.java | 178 ++++++++++ .../instance/service/InstanceServiceTest.java | 76 ++++- .../likes/controller/LikesControllerTest.java | 265 +++++++++++++++ .../likes/service/LikesServiceTest.java | 12 - .../gitget/payment/PaymentRepositoryTest.java | 19 -- .../controller/PaymentControllerTest.java | 124 +++++++ .../{ => service}/PaymentServiceTest.java | 2 +- .../controller/ProfileControllerTest.java | 314 ++++++++++++++++++ .../profile/service/ProfileServiceTest.java | 14 + 18 files changed, 1119 insertions(+), 76 deletions(-) create mode 100644 src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java delete mode 100644 src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java create mode 100644 src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java rename src/test/java/com/genius/gitget/payment/{ => service}/PaymentServiceTest.java (99%) create mode 100644 src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index 4e835b42..bcbfdbd7 100644 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -51,6 +51,10 @@ public TopicDetailResponse getTopicById(Long id) throws IOException { // 토픽 생성 요청 @Transactional public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile multipartFile, String type) { + System.out.println("토픽 생성 요청"); + System.out.println(topicCreateRequest.title()); + System.out.println(multipartFile.getOriginalFilename()); + System.out.println(type); Files uploadedFile = filesService.uploadFile(multipartFile, type); Topic topic = Topic.builder() diff --git a/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java b/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java index 4dd6cc05..0da8384a 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java +++ b/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java @@ -14,6 +14,7 @@ import jakarta.persistence.Table; import java.time.LocalDateTime; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.OnDelete; @@ -45,6 +46,7 @@ public class Likes { @Column(name = "liked_at") private LocalDateTime likedAt; // 찜하기 누른 시각 + @Builder public Likes(User user, Instance instance) { this.instance = instance; this.user = user; diff --git a/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddRequest.java b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddRequest.java index 2b0f307e..024b08ca 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddRequest.java +++ b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesAddRequest.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.likes.dto; +import lombok.Builder; import lombok.Data; @Data @@ -7,6 +8,7 @@ public class UserLikesAddRequest { private String identifier; private Long instanceId; + @Builder public UserLikesAddRequest(String identifier, Long instanceId) { this.identifier = identifier; this.instanceId = instanceId; diff --git a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java index 20a396c0..36ddb912 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java +++ b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java @@ -31,8 +31,14 @@ public class LikesService { private final LikesRepository likesRepository; public Page getLikesList(User user, Pageable pageable) { - User verifiedUser = verifyUser(user); - List likes = verifiedUser.getLikesList(); + List userList = verifyUser(user); + List likes = new ArrayList<>(); + + for (User userObject : userList) { + if (userObject.getIdentifier().equals(user.getIdentifier())) { + likes = userObject.getLikesList(); + } + } List userLikesResponses = new ArrayList<>(); for (Likes like : likes) { @@ -54,7 +60,15 @@ public Page getLikesList(User user, Pageable pageable) { @Transactional public UserLikesAddResponse addLikes(User user, String identifier, Long instanceId) { User comparedUser = compareToUserIdentifier(user, identifier); - User findUser = verifyUser(comparedUser); + List userList = verifyUser(comparedUser); + User findUser = null; + + for (User userObject : userList) { + System.out.println("!!!!!!!!!" + userObject.getIdentifier() + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + if (userObject.getIdentifier().equals(identifier)) { + findUser = userObject; + } + } Instance findInstance = verifyInstance(instanceId); Likes likes = new Likes(findUser, findInstance); @@ -71,19 +85,18 @@ public void deleteLikes(User user, Long likesId) { likesRepository.deleteById(findLikes.getId()); } - @Transactional - public void deleteLikesLazy(User user, Long likesId) { - try { - likesRepository.deleteById(likesId); - } catch (Exception e) { - e.getStackTrace(); - } - } +// @Transactional +// public void deleteLikesLazy(User user, Long likesId) { +// try { +// likesRepository.deleteById(likesId); +// } catch (Exception e) { +// e.getStackTrace(); +// } +// } - private User verifyUser(User user) { - return userRepository.findByIdentifier(user.getIdentifier()) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + private List verifyUser(User user) { + return userRepository.findAllByIdentifier(user.getIdentifier()); } private Instance verifyInstance(Long instanceId) { @@ -97,9 +110,4 @@ private User compareToUserIdentifier(User AuthenticatedUser, String identifier) } return AuthenticatedUser; } - - private Likes getLikes(Long likesId) { - return likesRepository.findById(likesId) - .orElseThrow(() -> new BusinessException(ErrorCode.LIKES_NOT_FOUND)); - } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/user/repository/UserRepository.java b/src/main/java/com/genius/gitget/challenge/user/repository/UserRepository.java index 18d1613c..da00c0bf 100644 --- a/src/main/java/com/genius/gitget/challenge/user/repository/UserRepository.java +++ b/src/main/java/com/genius/gitget/challenge/user/repository/UserRepository.java @@ -1,7 +1,8 @@ package com.genius.gitget.challenge.user.repository; -import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.security.constants.ProviderInfo; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -10,6 +11,9 @@ public interface UserRepository extends JpaRepository { Optional findByIdentifier(String identifier); + @Query("select u from User u where u.identifier = :identifier") + List findAllByIdentifier(String identifier); + Optional findByNickname(String nickname); @Query("select u from User u where u.identifier = :identifier and u.providerInfo = :providerInfo") diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index 1b34e735..5e08c123 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -55,9 +55,7 @@ public Files uploadFile(Optional optionalFiles, MultipartFile receivedFil @Transactional public Files uploadFile(MultipartFile receivedFile, String typeStr) { FileUtil.validateFile(receivedFile); - UploadDTO uploadDTO = FileUtil.getUploadInfo(receivedFile, typeStr, UPLOAD_PATH); - FileUtil.saveFile(receivedFile, uploadDTO.fileURI()); Files file = Files.builder() diff --git a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java index 8967b3c7..90178329 100644 --- a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java +++ b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java @@ -32,13 +32,10 @@ public class ProfileController { private final ProfileService profileService; - // TODO 마이페이지 - 결제 내역 조회 - // 마이페이지 - 사용자 상세 정보 조회 @GetMapping public ResponseEntity> getUserDetailsInformation( - @AuthenticationPrincipal - UserPrincipal userPrincipal) { + @AuthenticationPrincipal UserPrincipal userPrincipal) { UserDetailsInformationResponse userInformation = profileService.getUserDetailsInformation( userPrincipal.getUser()); return ResponseEntity.ok() @@ -93,6 +90,7 @@ public ResponseEntity updateUserTags(@AuthenticationPrincipal Us .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); } + // 마이페이지 - 챌린지 현황 @GetMapping("/challenges") public ResponseEntity> getUserChallengeResult( @@ -104,6 +102,7 @@ public ResponseEntity> getUserChalle userChallengeResult)); } + // 마이페이지 - 탈퇴하기 @DeleteMapping public ResponseEntity deleteUserInformation(@AuthenticationPrincipal UserPrincipal userPrincipal, @@ -114,6 +113,7 @@ public ResponseEntity deleteUserInformation(@AuthenticationPrinc .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); } + // 포인트 조회 @GetMapping("/point") public ResponseEntity> getUserPoint( diff --git a/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java b/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java index a316b98d..82d32b1b 100644 --- a/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java +++ b/src/main/java/com/genius/gitget/profile/dto/UserSignoutRequest.java @@ -1,11 +1,13 @@ package com.genius.gitget.profile.dto; +import lombok.AccessLevel; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) + public class UserSignoutRequest { private String reason; diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java index 2965814a..02a3d7d9 100644 --- a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java @@ -1,18 +1,33 @@ package com.genius.gitget.admin.topic.controller; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.admin.topic.service.TopicService; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; -import jakarta.transaction.Transactional; +import com.genius.gitget.util.file.FileTestUtil; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.multipart.MultipartFile; @SpringBootTest @Transactional @@ -23,28 +38,98 @@ public class TopicControllerTest { @Autowired TokenTestUtil tokenTestUtil; - @Autowired - TopicService topicService; @Autowired TopicRepository topicRepository; @Autowired - InstanceService instanceService; + FilesService filesService; @Autowired - InstanceRepository instanceRepository; + private ObjectMapper objectMapper; @BeforeEach public void setup() { - //this.mockMvc = MockMvcBuilders.standaloneSetup(new TopicController()).build(); + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("토픽 상세 정보를 요청하면, 해당 토픽의 정보를 반환한다.") + public void 토픽_상세정보_요청() throws Exception { + Topic savedTopic = getSavedTopic(); + Long id = savedTopic.getId(); + + mockMvc.perform(get("/api/admin/topic/" + id).cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.title").value("title")); } @Test - @WithMockCustomUser - public void 토픽_생성() throws Exception { -// mockMvc = MockMvcBuilders.standaloneSetup(new TopicController(topicService)) -// //.webAppContextSetup(context) -// .apply(springSecurity()) -// .build(); + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("토픽 리스트를 요청하면, 해당 토픽의 정보를 리스트로 반환한다.") + public void 토픽_리스트_요청() throws Exception { + getSavedTopic(); + getSavedTopic(); + getSavedTopic(); + + mockMvc.perform(get("/api/admin/topic") + .contentType(MediaType.APPLICATION_JSON) + .cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.numberOfElements").value(3)) + .andExpect(jsonPath("$.data.content[0].title").value("title")) + .andExpect(jsonPath("$.data.content[1].title").value("title")) + .andExpect(jsonPath("$.data.content[2].title").value("title")) + .andExpect(jsonPath("$.data.content[3].title").doesNotExist()); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("토픽 삭제 성공하면, 200 상태코드를 반환한다.") + public void 토픽_삭제_성공() throws Exception { + Topic savedTopic = getSavedTopic(); + Long id = savedTopic.getId(); + + mockMvc.perform(delete("/api/admin/topic/" + id).cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.numberOfElements").doesNotExist()); + + Assertions.assertThat(topicRepository.findById(id)).isEmpty(); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("토픽 삭제 실패하면, 4xx 상태코드를 반환한다.") + public void 토픽_삭제_실패() throws Exception { + Topic savedTopic = getSavedTopic(); + Long id = savedTopic.getId(); + + mockMvc.perform(delete("/api/admin/topic/" + id + 1).cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + + private Topic getSavedTopic() { + MultipartFile filename = FileTestUtil.getMultipartFile("sky"); + Files files = filesService.uploadFile(filename, "topic"); + + Topic topic = topicRepository.save( + Topic.builder() + .title("title") + .notice("notice") + .description("description") + .tags("BE") + .pointPerPerson(100) + .build() + ); + topic.setFiles(files); + + return topic; } - } diff --git a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java index bebb1f6d..d050d2c2 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java @@ -1,5 +1,183 @@ package com.genius.gitget.challenge.instance.controller; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.util.file.FileTestUtil; +import java.time.LocalDateTime; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.multipart.MultipartFile; + +@SpringBootTest +@Transactional public class InstanceControllerTest { + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + @Autowired + TokenTestUtil tokenTestUtil; + + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + FilesService filesService; + + private static Topic savedTopic1, savedTopic2; + private static Instance savedInstance1, savedInstance2; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + + savedTopic1 = getSavedTopic(); + savedTopic2 = getSavedTopic(); + + savedInstance1 = getSavedInstance("title1", "FE", 50, 1000); + savedInstance2 = getSavedInstance("title2", "BE, CS", 50, 1000); + + savedInstance1.setTopic(savedTopic1); + savedInstance2.setTopic(savedTopic1); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("인스턴스 리스트 조회를 요청하면, 상태코드 200반환과 함께 인스턴스 리스트를 반환한다.") + public void 인스턴스_리스트_조회() throws Exception { + mockMvc.perform(get("/api/admin/instance").cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.numberOfElements").value(2)); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("특정 토픽에 대한 리스트 조회를 요청하면, 상태코드 200과 함께 데아터를 반환한다.") + public void 특정_토픽에_대한_리스트_조회_1() throws Exception { + Long id = savedTopic1.getId(); + + mockMvc.perform(get("/api/admin/topic/instances/" + id).cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.numberOfElements").value(2)) + .andExpect(jsonPath("$.data.content[0].title").value("title1")) + .andExpect(jsonPath("$.data.content[1].title").value("title2")); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("특정 토픽에 대한 리스트 조회를 요청하면, 상태코드 200과 함께 데아터를 반환한다.") + public void 특정_토픽에_대한_리스트_조회_2() throws Exception { + Long id = savedTopic2.getId(); + + mockMvc.perform(get("/api/admin/topic/instances/" + id).cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("data.numberOfElements").value(0)); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("인스턴스 단건 조회를 하면, 상태코드 200과 함께 인스턴스 상세정보를 반환한다.") + public void 인스턴스_단건_조회() throws Exception { + Long topicId = savedTopic1.getId(); + + Long instanceId = savedInstance2.getId(); + + mockMvc.perform(get("/api/admin/instance/" + instanceId).cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("data.topicId").value(topicId)) + .andExpect(jsonPath("data.instanceId").value(instanceId)); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("인스턴스 삭제 성공하면, 상태코드 200을 반환한다.") + public void 인스턴스_삭제_성공() throws Exception { + Long instanceId = savedInstance1.getId(); + + mockMvc.perform(delete("/api/admin/instance/" + instanceId).cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.numberOfElements").doesNotExist()); + + Assertions.assertThat(instanceRepository.findById(instanceId)).isEmpty(); + } + + @Test + @WithMockCustomUser(role = Role.ADMIN) + @DisplayName("인스턴스 삭제 실패하면, 상태코드 4xx을 반환한다.") + public void 인스턴스_삭제_성공_실패() throws Exception { + Long instanceId = savedInstance1.getId(); + + mockMvc.perform(delete("/api/admin/instance/" + instanceId + 1).cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + + private Topic getSavedTopic() { + MultipartFile filename = FileTestUtil.getMultipartFile("sky"); + Files files = filesService.uploadFile(filename, "topic"); + + Topic topic = topicRepository.save( + Topic.builder() + .title("title") + .notice("notice") + .description("description") + .tags("BE") + .pointPerPerson(100) + .build() + ); + topic.setFiles(files); + + return topic; + } + + private Instance getSavedInstance(String title, String tags, int participantCnt, int pointPerPerson) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(pointPerPerson) + .certificationMethod("인증 방법") + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java index dec9012c..ab1d1606 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java @@ -9,6 +9,7 @@ import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.global.file.domain.FileType; @@ -27,6 +28,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -46,7 +49,7 @@ public class InstanceServiceTest { @Autowired FilesRepository filesRepository; - private Instance instance; + private Instance instance, instance1, instance2; private Topic topic; private String fileType; @@ -61,6 +64,7 @@ public void setup() { .startedDate(LocalDateTime.now()) .completedDate(LocalDateTime.now().plusDays(3)) .build(); + topic = Topic.builder() .title("1일 1알고리즘") .description("하루에 한 문제씩 문제를 해결합니다.") @@ -132,6 +136,76 @@ public void setup() { Assertions.assertThat(instanceById.title()).isEqualTo(instanceCreateRequest.title()); } + @Test + public void 인스턴스_리스트_조회() { + Topic savedTopic = getSavedTopic("1일 1알고리즘", "FE, BE"); + Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + savedInstance1.setTopic(savedTopic); + savedInstance2.setTopic(savedTopic); + savedInstance3.setTopic(savedTopic); + + PageRequest pageRequest = PageRequest.of(0, 10); + Page allInstances = instanceService.getAllInstances(pageRequest); + + Assertions.assertThat(allInstances.getTotalElements()).isEqualTo(3); + } + + @Test + public void 특정_토픽에_대한_리스트_조회_1() { + Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + savedInstance1.setTopic(savedTopic1); + savedInstance2.setTopic(savedTopic1); + savedInstance3.setTopic(savedTopic2); + PageRequest pageRequest = PageRequest.of(0, 10); + Page allInstancesOfSpecificTopic = instanceService.getAllInstancesOfSpecificTopic( + pageRequest, savedTopic1.getId()); + + Assertions.assertThat(allInstancesOfSpecificTopic.getTotalElements()).isEqualTo(2); + + } + + @Test + public void 특정_토픽에_대한_리스트_조회_2() { + Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + savedInstance1.setTopic(savedTopic1); + savedInstance2.setTopic(savedTopic1); + savedInstance3.setTopic(savedTopic2); + PageRequest pageRequest = PageRequest.of(0, 10); + Page allInstancesOfSpecificTopic = instanceService.getAllInstancesOfSpecificTopic( + pageRequest, savedTopic2.getId()); + + Assertions.assertThat(allInstancesOfSpecificTopic.getTotalElements()).isEqualTo(1); + + } + + @Test + public void 인스턴스_삭제() { + Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + instanceService.deleteInstance(savedInstance1.getId()); + + org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, () -> { + instanceService.getInstanceById(savedInstance1.getId()); + }); + } + @Nested public class 인스턴스_삭제할_때 { private Topic topic; diff --git a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java new file mode 100644 index 00000000..ac085f68 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java @@ -0,0 +1,265 @@ +package com.genius.gitget.challenge.likes.controller; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.domain.Likes; +import com.genius.gitget.challenge.likes.dto.UserLikesAddRequest; +import com.genius.gitget.challenge.likes.repository.LikesRepository; +import com.genius.gitget.challenge.likes.service.LikesService; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.util.file.FileTestUtil; +import java.time.LocalDateTime; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.multipart.MultipartFile; + +@SpringBootTest +@Transactional +public class LikesControllerTest { + private static Topic savedTopic1, savedTopic2; + private static Instance savedInstance1, savedInstance2; + + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + @Autowired + TokenTestUtil tokenTestUtil; + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + FilesService filesService; + @Autowired + LikesService likesService; + @Autowired + UserRepository userRepository; + @Autowired + LikesRepository likesRepository; + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + + savedTopic1 = getSavedTopic(); + savedTopic2 = getSavedTopic(); + + savedInstance1 = getSavedInstance("title1", "FE", 50, 1000); + savedInstance2 = getSavedInstance("title2", "BE, CS", 50, 1000); + + savedInstance1.setTopic(savedTopic1); + savedInstance2.setTopic(savedTopic1); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi", role = Role.ADMIN) + @DisplayName("유저가 좋아요 목록을 조회하면, 상태 코드 200을 반환한다.") + public void 좋아요_목록_조회_성공_1() throws Exception { + User user = getSavedUser(); +// likesRepository.save(Likes.builder() +// .instance(savedInstance1) +// .user(user) +// .build()); + + likesService.addLikes(user, "kimdozzi", savedInstance1.getId()); + + mockMvc.perform(get("/api/profile/likes") + .cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON)) + + .andDo(print()) + .andExpect(jsonPath("$.data.content[0].instanceId").value(savedInstance1.getId())) + .andExpect(jsonPath("$.data.content[0].title").value(savedInstance1.getTitle())) + .andExpect(status().isOk()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi", role = Role.ADMIN) + @DisplayName("유저가 좋아요 목록을 조회하면, 상태 코드 200을 반환한다.") + public void 좋아요_목록_조회_성공_2() throws Exception { + + mockMvc.perform(get("/api/profile/likes") + .cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(jsonPath("$.data.numberOfElements").value(0)) + .andExpect(status().isOk()); + } + + + @Test + @WithMockCustomUser(identifier = "kimdozzi", role = Role.ADMIN) + @DisplayName("유저가 좋아요 목록에 해당 챌린지 추가를 성공하면, 상태 코드 200을 반환한다.") + public void 좋아요_목록_추가_성공() throws Exception { + Long id = savedInstance1.getId(); + + UserLikesAddRequest request = UserLikesAddRequest.builder() + .identifier("kimdozzi") + .instanceId(id) + .build(); + + mockMvc.perform(post("/api/profile/likes") + .cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi", role = Role.ADMIN) + @DisplayName("유저가 좋아요 목록을 추가할 때, 사용자의 정보가 다르면 상태 코드 4xx를 반환한다.") + public void 좋아요_목록_추가_실패_1() throws Exception { + Long id = savedInstance1.getId(); + + UserLikesAddRequest request = UserLikesAddRequest.builder() + .identifier("park") + .instanceId(id) + .build(); + + mockMvc.perform(post("/api/profile/likes") + .cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi", role = Role.ADMIN) + @DisplayName("유저가 좋아요 목록을 추가할 때, contentType이 올바르지 않으면 상태 코드 4xx를 반환한다.") + public void 좋아요_목록_추가_실패_2() throws Exception { + Long id = savedInstance1.getId(); + + UserLikesAddRequest request = UserLikesAddRequest.builder() + .identifier("park") + .instanceId(id) + .build(); + + mockMvc.perform(post("/api/profile/likes") + .cookie(tokenTestUtil.createAccessCookie()) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + + @Test + @WithMockCustomUser(identifier = "kimdozzi", role = Role.ADMIN) + @DisplayName("유저가 좋아요 목록을 삭제 성공하면, 상태 코드 200을 반환한다.") + public void 좋아요_목록_삭제_성공() throws Exception { + User user = getSavedUser(); + Likes likes = likesRepository.save(Likes.builder() + .instance(savedInstance1) + .user(user) + .build()); + + Long id = likes.getId(); + + mockMvc.perform(delete("/api/profile/likes/" + id) + .cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()); + + Assertions.assertThat(likesRepository.findById(id)).isEmpty(); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi", role = Role.ADMIN) + @DisplayName("유저가 좋아요 목록을 삭제 실패하면, 상태 코드 4xx을 반환한다.") + public void 좋아요_목록_삭제_실패() throws Exception { + User user = getSavedUser(); + Likes likes = likesRepository.save(Likes.builder() + .instance(savedInstance1) + .user(user) + .build()); + + mockMvc.perform(delete("/api/profile/likes/" + 2) + .cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + + private User getSavedUser() { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname1") + .tags("FE, BE") + .providerInfo(ProviderInfo.GITHUB) + .identifier("kimdozzi") + .build() + ); + } + + + private Topic getSavedTopic() { + MultipartFile filename = FileTestUtil.getMultipartFile("sky"); + Files files = filesService.uploadFile(filename, "topic"); + + Topic topic = topicRepository.save( + Topic.builder() + .title("title") + .notice("notice") + .description("description") + .tags("BE") + .pointPerPerson(100) + .build() + ); + topic.setFiles(files); + + return topic; + } + + private Instance getSavedInstance(String title, String tags, int participantCnt, int pointPerPerson) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(pointPerPerson) + .certificationMethod("인증 방법") + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } +} diff --git a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java index 88132ffa..876a57bd 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java @@ -71,24 +71,12 @@ void setup() { instance2.setTopic(topic1); instance3.setTopic(topic1); -// topic1.setFiles(files1); -// topicRepository.save(topic1); -// -// instance1.setFiles(files2); -// instanceRepository.save(instance1); -// instance2.setFiles(files4); -// instanceRepository.save(instance2); -// -// user1.setFiles(files3); -// userRepository.save(user1); - Likes likes1 = new Likes(user1, instance1); Likes likes2 = new Likes(user1, instance2); Likes likes3 = new Likes(user1, instance3); likesRepository.save(likes1); likesRepository.save(likes2); likesRepository.save(likes3); - } diff --git a/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java b/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java deleted file mode 100644 index 3a759b69..00000000 --- a/src/test/java/com/genius/gitget/payment/PaymentRepositoryTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.genius.gitget.payment; - -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.store.payment.repository.PaymentRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest -@Transactional -@Rollback -public class PaymentRepositoryTest { - @Autowired - UserRepository userRepository; - @Autowired - PaymentRepository paymentRepository; - -} diff --git a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java new file mode 100644 index 00000000..cb76358d --- /dev/null +++ b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java @@ -0,0 +1,124 @@ +package com.genius.gitget.payment.controller; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + + +@SpringBootTest +@Transactional +public class PaymentControllerTest { + + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + @Autowired + TokenTestUtil tokenTestUtil; + + @Autowired + TopicRepository topicRepository; + @Autowired + FilesService filesService; + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("결제 내역 조회를 요청하면, 상태코드 200을 반환한다.") + public void 결제_내역_조회_성공() throws Exception { + + mockMvc.perform(get("/api/payment").cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()); + } + + // 토스페이먼츠 PG사 결제 테스트 + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("결제 요청을 성공하면, 상태코드 200을 반환한다.") + public void 결제_요청_성공() throws Exception { + Map input = new HashMap<>(); + input.put("amount", 1000L); + input.put("orderName", "park-kim"); + input.put("pointAmount", 100L); + input.put("userEmail", "kimdozzi"); + + mockMvc.perform(post("/api/payment/toss").cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(input))) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("결제 요청을 실패하면, 상태코드 4xx을 반환한다.") + public void 결제_요청_실패_1() throws Exception { + Map input = new HashMap<>(); + input.put("amount", 1000L); + input.put("orderName", "park-kim"); + input.put("pointAmount", 100L); + input.put("userEmail", "test@gmail.com"); + + mockMvc.perform(post("/api/payment/toss").cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(input))) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("결제 요청을 실패하면, 상태코드 4xx을 반환한다.") + public void 결제_요청_실패_2() throws Exception { + + mockMvc.perform(post("/api/payment/toss").cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("결제 요청을 실패하면, 상태코드 4xx을 반환한다.") + public void 결제_요청_실패_3() throws Exception { + Map input = new HashMap<>(); + input.put("amount", 1000L); + input.put("orderName", "park-kim"); + input.put("pointAmount", 100L); + input.put("userEmail", "test@gmail.com"); + + mockMvc.perform(post("/api/payment/toss").cookie(tokenTestUtil.createAccessCookie()) + .content(objectMapper.writeValueAsString(input))) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } +} diff --git a/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java similarity index 99% rename from src/test/java/com/genius/gitget/payment/PaymentServiceTest.java rename to src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java index 7b690468..293072a2 100644 --- a/src/test/java/com/genius/gitget/payment/PaymentServiceTest.java +++ b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.payment; +package com.genius.gitget.payment.service; import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; diff --git a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java new file mode 100644 index 00000000..f668002e --- /dev/null +++ b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java @@ -0,0 +1,314 @@ +package com.genius.gitget.profile.controller; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.repository.LikesRepository; +import com.genius.gitget.challenge.likes.service.LikesService; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.util.file.FileTestUtil; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.multipart.MultipartFile; + + +@SpringBootTest +@Transactional +public class ProfileControllerTest { + private static Topic savedTopic1, savedTopic2; + private static Instance savedInstance1, savedInstance2; + MockMvc mockMvc; + @Autowired + WebApplicationContext context; + @Autowired + TokenTestUtil tokenTestUtil; + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceRepository instanceRepository; + @Autowired + FilesService filesService; + @Autowired + LikesService likesService; + @Autowired + UserRepository userRepository; + @Autowired + LikesRepository likesRepository; + @Autowired + ParticipantRepository participantRepository; + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + + savedTopic1 = getSavedTopic(); + savedTopic2 = getSavedTopic(); + + savedInstance1 = getSavedInstance("title1", "FE", 50, 1000); + savedInstance2 = getSavedInstance("title2", "BE, CS", 50, 1000); + + savedInstance1.setTopic(savedTopic1); + savedInstance2.setTopic(savedTopic1); + } + + // 사용자 상세 정보 조회 + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 상세 정보 조회에 성공하면, 상태 코드 200을 반환한다.") + public void 사용자_상세_정보_조회_성공() throws Exception { + + mockMvc.perform(get("/api/profile").cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 상세 정보 조회 시 같은 사용자 정보가 있으면 실패하고, 4xx(IncorrectResultSizeDataAccessException)를 반환한다.") + public void 사용자_상세_정보_조회_실패() throws Exception { + User user = getSavedUser(); + mockMvc.perform(get("/api/profile").cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + // 사용자 정보 조회 + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 정보 조회에 성공하면, 상태 코드 200을 반환한다.") + public void 사용자_정보_조회_성공() throws Exception { + List users = userRepository.findAllByIdentifier("kimdozzi"); + Long id = null; + for (User user : users) { + if (user.getIdentifier().equals("kimdozzi")) { + id = user.getId(); + } + } + Map input = new HashMap<>(); + input.put("userId", id); + + mockMvc.perform(post("/api/profile") + .cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(input))) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 정보 조회에 실패하면, 상태 코드 4xx을 반환한다.") + public void 사용자_정보_조회_실패() throws Exception { + List users = userRepository.findAllByIdentifier("kimdozzi"); + Long id = null; + for (User user : users) { + if (user.getIdentifier().equals("kimdozzi")) { + id = user.getId(); + } + } + Map input = new HashMap<>(); + input.put("userId", id + 1); + + mockMvc.perform(post("/api/profile") + .cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(input))) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + // 관심사 조회 + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 관심사 조회에 성공하면, 상태 코드 200을 반환한다.") + public void 사용자_관심사_조회_성공() throws Exception { + mockMvc.perform(get("/api/profile/interest").cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()); + } + + // 관심사 수정 + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 관심사 수정에 성공하면, 상태 코드 200을 반환한다.") + public void 사용자_관심사_수정_성공() throws Exception { + + Map> input = new HashMap<>(); + input.put("tags", new ArrayList<>(Arrays.asList("FE", "BE", "ML"))); + + mockMvc.perform(post("/api/profile/interest").cookie(tokenTestUtil.createAccessCookie()) + .content(objectMapper.writeValueAsString(input)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + + User findUser = null; + List users = userRepository.findAllByIdentifier("kimdozzi"); + for (User user : users) { + if (user.getIdentifier().equals("kimdozzi")) { + findUser = user; + } + } + + Assertions.assertThat(findUser.getTags()).isEqualTo(String.join(",", findUser.getTags())); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 관심사 수정에 실패하면, 상태 코드 4xx을 반환한다.") + public void 사용자_관심사_수정_실패_1() throws Exception { + + Map> input = new HashMap<>(); + input.put("tags", new ArrayList<>(Arrays.asList("FE", "BE", "ML"))); + + mockMvc.perform(post("/api/profile/interest").cookie(tokenTestUtil.createAccessCookie()) + .content(objectMapper.writeValueAsString(input))) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 관심사 수정에 실패하면, 상태 코드 4xx을 반환한다.") + public void 사용자_관심사_수정_실패_2() throws Exception { + + mockMvc.perform(post("/api/profile/interest").cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + + // 챌린지 현황 + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 챌린지 현황 조회에 성공하면, 상태 코드 200을 반환한다.") + public void 사용자_챌린지_현황_성공() throws Exception { + mockMvc.perform(get("/api/profile/challenges").cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()); + } + + // 탈퇴하기 + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 탈퇴에 성공하면, 상태 코드 200을 반환한다.") + public void 사용자_탈퇴_성공() throws Exception { + + Map input = new HashMap<>(); + input.put("reason", "이용이 불편해서"); + + mockMvc.perform(delete("/api/profile").cookie(tokenTestUtil.createAccessCookie()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(input))) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 탈퇴 사유없이 탈퇴를 요청하면 실패하고, 상태 코드 4xx을 반환한다.") + public void 사용자_탈퇴_실패() throws Exception { + mockMvc.perform(delete("/api/profile").cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + // 포인트 조회 + @Test + @WithMockCustomUser(identifier = "kimdozzi") + @DisplayName("사용자 포인트 조회에 성공하면, 상태 코드 200을 반환한다.") + public void 사용자_포인트_조회_성공() throws Exception { + mockMvc.perform(get("/api/profile/point").cookie(tokenTestUtil.createAccessCookie())) + .andDo(print()) + .andExpect(status().isOk()); + } + + + private User getSavedUser() { + return userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname1") + .tags("FE, BE") + .providerInfo(ProviderInfo.GITHUB) + .information("info") + .identifier("kimdozzi") + .build() + ); + } + + private Topic getSavedTopic() { + MultipartFile filename = FileTestUtil.getMultipartFile("sky"); + Files files = filesService.uploadFile(filename, "topic"); + + Topic topic = topicRepository.save( + Topic.builder() + .title("title") + .notice("notice") + .description("description") + .tags("BE") + .pointPerPerson(100) + .build() + ); + topic.setFiles(files); + + return topic; + } + + private Instance getSavedInstance(String title, String tags, int participantCnt, int pointPerPerson) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(pointPerPerson) + .certificationMethod("인증 방법") + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } +} diff --git a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java index 1e04e365..927abbc4 100644 --- a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java +++ b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java @@ -19,6 +19,7 @@ import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.profile.dto.UserChallengeResultResponse; import com.genius.gitget.profile.dto.UserDetailsInformationResponse; import com.genius.gitget.profile.dto.UserInformationResponse; import com.genius.gitget.profile.dto.UserInformationUpdateRequest; @@ -164,6 +165,19 @@ void setup() { Assertions.assertThat(userPoint.getPoint()).isEqualTo(1500); } + @Test + void 챌린지_현황_조회() { + // TODO 챌린지 현황 조회 + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + + UserChallengeResultResponse userChallengeResult = profileService.getUserChallengeResult(user); + System.out.println(userChallengeResult.getBeforeStart()); + System.out.println(userChallengeResult.getProcessing()); + System.out.println(userChallengeResult.getFail()); + System.out.println(userChallengeResult.getSuccess()); + } + private User getSavedUser(String identifier, ProviderInfo providerInfo, String nickname) { User user = userRepository.save( From 0f58975900e0b8860983ce35584c714e26befdef Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:10:34 +0900 Subject: [PATCH 152/234] Create main.yml --- .github/workflows/main.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..9e4a0e02 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,29 @@ +name: Build and Deploy to EC2 + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + shell: bash + + - name: Build and Test + run: ./gradlew build test From 710586029e5938f4937005ad0a494c13e071bec2 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:40:47 +0900 Subject: [PATCH 153/234] Delete .github/workflows/main.yml --- .github/workflows/main.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 9e4a0e02..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Build and Deploy to EC2 - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - deploy: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Grant execute permission for gradlew - run: chmod +x ./gradlew - shell: bash - - - name: Build and Test - run: ./gradlew build test From a545d287ea7342241d7ead10c7734a6f59e8ba0e Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:11:06 +0900 Subject: [PATCH 154/234] =?UTF-8?q?[BUILD]=20AWS=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=ED=99=94=20=EC=A0=81=EC=9A=A9=20(#158)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 배포 테스트 * chore: yml 파일 생성 테스트 * chore: yml 생성 테스트 * chore: yml 프로파일 생성 테스트 * chore: aws s3 업로드 테스트 * chore: 임시로 테스트 실행 제외 - S3 업로드 테스트 * chore: s3 업로드 테스트 * chore: ec2 배포 자동화 테스트 * chore: 배포 자동화 테스트 * chore: ec2 배포 테스트 * Create appspec.yml * chore: ec2 배포 자동화 테스트 * chore: ec2 자동 배포화 테스트 * chore: 로그 저장 위치 변경 * chore: yml 생성 위치 변경 * chore: ec2 배포 자동화 테스트 * chore: ec2 배포 테스트 * chore: yml 파일 생성 버그 픽스 * chore: ec2 배포 테스트 * chore: data.sql 추가 * chore: 오류로 인해 data.sql 삭제 * chore: deploy.sh 업데이트 * chore: ec2 빌드 테스트 * chore: 빌드 시 테스트도 포함하도록 설정 * chore: yml 파일 구조 변경 * chore: build.gradle 변경 * chore: 빌드 테스트 포함 * chore: 테스트 제외하도록 임시로 변경 * chore: deploy.sh 스크립트 내용 변경 * chore: deploy.sh 변경 및 디버그 로그 추가 * chore: deploy.sh 수정 * chore: deploy.sh 버그 픽스 * chore: 쿠키가 전달안되는 부분에 대해 임시 테스트 * feat: 로드밸런서를 위한 상태검사 API 생성 * chore: https 적용 테스트 * chore: jwt 설정 변경 * chore: ec2 배포 테스트 * chore: yml 복사 시크릿 변경 * chore: mongodb 연결 테스트 * chore: java 어플리케이션 삭제하도록 설정 * chore: deploy.sh의 pid kill 부분 수정 * chore: 배포 스크립트 수정 --- .github/workflows/main.yml | 76 +++++++++++++++++++ appspec.yml | 17 +++++ build.gradle | 13 ++++ scripts/deploy.sh | 42 ++++++++++ scripts/start.sh | 21 +++++ scripts/stop.sh | 14 ++++ .../gitget/challenge/user/domain/User.java | 2 +- .../config/CustomCorsConfigurationSource.java | 9 ++- .../security/controller/AuthController.java | 6 ++ .../handler/OAuth2FailureHandler.java | 7 +- .../handler/OAuth2SuccessHandler.java | 16 +++- .../global/security/service/JwtService.java | 1 + .../gitget/global/util/config/AppConfig.java | 2 +- src/main/resources/data.sql | 2 +- .../security/service/JwtServiceTest.java | 3 - 15 files changed, 218 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 appspec.yml create mode 100644 scripts/deploy.sh create mode 100644 scripts/start.sh create mode 100644 scripts/stop.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..31a137b7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,76 @@ +name: Build and Deploy to EC2 + +on: + push: + branches: [ "production" ] ## 나중에 production으로 변경하기 + pull_request: + branches: [ "production" ] + +env: + AWS_REGION: ap-northeast-2 + AWS_S3_BUCKET: gitget-bucket-hey + AWS_CODE_DEPLOY_APPLICATION: GitGet-Application-HEY + AWS_CODE_DEPLOY_GROUP: GitGet-CICD-group-hey + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: make application.yml + run: | + mkdir -p ./src/main/resources + cd ./src/main/resources + touch ./application.yml + touch ./application-common.yml + touch ./application-prod.yml + echo "${{ secrets.APPLICATION }}" > ./application.yml + echo "${{ secrets.COMMON }}" > ./application-common.yml + echo "${{ secrets.PROD }}" > ./application-prod.yml + + - name: make test application.yml + run: | + mkdir -p ./src/test/resources + cd ./src/test/resources + touch ./application.yml + touch ./application-test.yml + echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml + echo "${{ secrets.TEST }}" > ./application-test.yml + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + shell: bash + + - name: Build and Test + run: ./gradlew build -x test #./gradlew build test + + - name: Make zip file + run: zip -r ./$GITHUB_SHA.zip . + shell: bash + + - name: AWS credential 설정 + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ env.AWS_REGION }} + aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY_HEY }} + aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY_HEY }} + + + - name: Upload to S3 + run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip + + - name: EC2에 배포 + run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip + + + diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 00000000..fa35b32c --- /dev/null +++ b/appspec.yml @@ -0,0 +1,17 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/app + overwrite: yes + +permissions: + - object: / + owner: ubuntu + group: ubuntu + +hooks: + ApplicationStart: + - location: scripts/deploy.sh + timeout: 60 diff --git a/build.gradle b/build.gradle index b5bf3795..e833cb5e 100644 --- a/build.gradle +++ b/build.gradle @@ -50,9 +50,12 @@ dependencies { // MongoDB implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' +// testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo' // H2 + implementation 'com.h2database:h2' runtimeOnly 'com.h2database:h2:2.2.222' + testRuntimeOnly 'com.h2database:h2' // Github API for Java implementation 'org.kohsuke:github-api:1.318' @@ -67,6 +70,16 @@ dependencies { testAnnotationProcessor 'org.projectlombok:lombok' } +bootJar { + archiveBaseName = 'GitGetApplication' + archiveFileName = 'GitGetApplication.jar' + archiveVersion = "0.0.1" +} + +jar { + enabled = false +} + tasks.named('test') { useJUnitPlatform() } diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 00000000..e36ec46a --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) +JAR_NAME=$(basename $BUILD_JAR) +echo ">>> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log + +echo ">>> build 파일 복사" >> /home/ubuntu/deploy.log +DEPLOY_PATH=/home/ubuntu/app/ +cp $BUILD_JAR $DEPLOY_PATH + +echo ">>> 현재 실행중인 애플리케이션 pid 확인 후 일괄 종료" >> /home/ubuntu/deploy.log +sudo ps -ef | grep java | awk '{print $2}' | xargs kill -15 + +DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME +echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log +echo ">>> $DEPLOY_JAR의 $JAR_NAME를 실행합니다" >> /home/ubunru/deploy.log +nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & + +# backup +#BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) +#JAR_NAME=$(basename $BUILD_JAR) +#echo ">>> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log +# +#echo ">>> build 파일 복사" >> /home/ubuntu/deploy.log +#DEPLOY_PATH=/home/ubuntu/ +#cp $BUILD_JAR $DEPLOY_PATH +# +#echo ">>> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/deploy.log +#CURRENT_PID=$(pgrep -f $JAR_NAME) +# +#if [ -z $CURRENT_PID ] +#then +# echo ">>> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ubuntu/deploy.log +#else +# echo ">>> kill -15 $CURRENT_PID" +# kill -15 $CURRENT_PID +# sleep 5 +#fi +# +#DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME +#echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log +#nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & \ No newline at end of file diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 00000000..8b1237e9 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +PROJECT_ROOT="/home/ubuntu/app" +JAR_FILE="$PROJECT_ROOT/GitGetApplication.jar" + +APP_LOG="$PROJECT_ROOT/application.log" +ERROR_LOG="$PROJECT_ROOT/error.log" +DEPLOY_LOG="$PROJECT_ROOT/deploy.log" + +TIME_NOW=$(date +%c) + +# build 파일 복사 +echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG +cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE + +# jar 파일 실행 +echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG +nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG & + +CURRENT_PID=$(pgrep -f $JAR_FILE) +echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG \ No newline at end of file diff --git a/scripts/stop.sh b/scripts/stop.sh new file mode 100644 index 00000000..6e0fec36 --- /dev/null +++ b/scripts/stop.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +ROOT_PATH="/home/ubuntu/build" +JAR="$ROOT_PATH/GitGetApplication.jar" +STOP_LOG="$ROOT_PATH/stop.log" +SERVICE_PID=$(pgrep -f $JAR) # 실행중인 Spring 서버의 PID + +if [ -z "$SERVICE_PID" ]; then + echo "서비스 NotFound" >> $STOP_LOG +else + echo "서비스 종료 " >> $STOP_LOG +# kill "$SERVICE_PID" + kill -9 "$SERVICE_PID" # 강제 종료를 하고 싶다면 이 명령어 사용 +fi \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 8da87cde..8ca1f675 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -33,7 +33,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "user") +@Table(name = "users") public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java b/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java index 07056658..635a69d2 100644 --- a/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java +++ b/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java @@ -3,14 +3,19 @@ import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @Component public class CustomCorsConfigurationSource implements CorsConfigurationSource { - private static final String ALLOWED_ORIGIN = "http://localhost:5173"; - private static final List ALLOWED_METHODS = List.of("POST", "GET", "PATCH", "OPTIONS", "DELETE"); + private final String ALLOWED_ORIGIN; + private final List ALLOWED_METHODS = List.of("POST", "GET", "PATCH", "OPTIONS", "DELETE"); + + public CustomCorsConfigurationSource(@Value("${url.base}") String BASE_URL) { + ALLOWED_ORIGIN = BASE_URL; + } @Override public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 78c6a9cb..d3377ede 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -54,4 +55,9 @@ public ResponseEntity logout( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); } + + @GetMapping("/auth/health-check") + public String healthCheck() { + return "health-check-ok"; + } } diff --git a/src/main/java/com/genius/gitget/global/security/handler/OAuth2FailureHandler.java b/src/main/java/com/genius/gitget/global/security/handler/OAuth2FailureHandler.java index 12e942ce..7962a1a2 100644 --- a/src/main/java/com/genius/gitget/global/security/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/genius/gitget/global/security/handler/OAuth2FailureHandler.java @@ -4,6 +4,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; @@ -11,9 +12,13 @@ @Component public class OAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler { - private final String REDIRECT_URL = "http://localhost:5173"; + private final String REDIRECT_URL; private final String ERROR_PARAM_PREFIX = "error"; + public OAuth2FailureHandler(@Value("${url.base}") String REDIRECT_URL) { + this.REDIRECT_URL = REDIRECT_URL; + } + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { diff --git a/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java b/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java index dd8fc17a..8fd8000b 100644 --- a/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/genius/gitget/global/security/handler/OAuth2SuccessHandler.java @@ -9,7 +9,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; @@ -17,12 +17,20 @@ import org.springframework.web.util.UriComponentsBuilder; @Component -@RequiredArgsConstructor public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private final String SIGNUP_URL = "http://localhost:5173/login/signup"; - private final String AUTH_URL = "http://localhost:5173/auth"; + private final String SIGNUP_URL; + private final String AUTH_URL; private final UserRepository userRepository; + public OAuth2SuccessHandler(@Value("${url.base}") String BASE_URL, + @Value("${url.path.signup}") String SIGN_UP_PATH, + @Value("${url.path.auth}") String AUTH_PATH, + UserRepository userRepository) { + this.userRepository = userRepository; + this.SIGNUP_URL = BASE_URL + SIGN_UP_PATH; + this.AUTH_URL = BASE_URL + AUTH_PATH; + } + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { diff --git a/src/main/java/com/genius/gitget/global/security/service/JwtService.java b/src/main/java/com/genius/gitget/global/security/service/JwtService.java index 4929f570..b8c9315a 100644 --- a/src/main/java/com/genius/gitget/global/security/service/JwtService.java +++ b/src/main/java/com/genius/gitget/global/security/service/JwtService.java @@ -87,6 +87,7 @@ private ResponseCookie setTokenToCookie(String tokenPrefix, String token, long m .path("/") .maxAge(maxAgeSeconds) .httpOnly(true) + .sameSite("None") .secure(true) .build(); } diff --git a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java index 37261d0b..5958b7a0 100644 --- a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java @@ -10,7 +10,7 @@ import org.springframework.security.crypto.encrypt.AesBytesEncryptor; @Configuration -@PropertySource("classpath:application-github.yml") +@PropertySource("classpath:application-common.yml") @RequiredArgsConstructor public class AppConfig { private final Environment env; diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 1e89c23c..349fcdea 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -19,4 +19,4 @@ FROM (SELECT 100 AS cost, '아이템 사용 시, 챌린지 성공 보상을 2배로 획득할 수 있는 아이템입니다.', '챌린지 보상 획득 2배 아이템', 'POINT_MULTIPLIER') AS new_items -WHERE (SELECT COUNT(*) FROM item) < 3; +WHERE (SELECT COUNT(*) FROM item) < 3; \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java index c00402f7..716467bb 100644 --- a/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java @@ -13,7 +13,6 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.JwtRule; import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.global.security.repository.TokenRepository; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.util.TokenTestUtil; @@ -41,8 +40,6 @@ class JwtServiceTest { @Autowired private UserRepository userRepository; @Autowired - private TokenRepository tokenRepository; - @Autowired private TokenTestUtil tokenTestUtil; @Test From be069568be39f6e90b90324403f43b22cdbb7df5 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 13 Apr 2024 12:50:14 +0900 Subject: [PATCH 155/234] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=9A=A9?= =?UTF-8?q?=EB=9F=89=20=EC=B4=88=EA=B3=BC=20=EC=98=88=EC=99=B8=20=ED=95=B8?= =?UTF-8?q?=EB=93=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80=20(#160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/util/exception/ErrorCode.java | 1 + .../util/exception/FileExceptionHandler.java | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/main/java/com/genius/gitget/global/util/exception/FileExceptionHandler.java diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 7f2d06cb..1cf5a37c 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -50,6 +50,7 @@ public enum ErrorCode { FILE_NOT_SAVED(HttpStatus.BAD_REQUEST, "파일(이미지)가 정상적으로 저장되지 않았습니다."), FILE_NOT_COPIED(HttpStatus.BAD_REQUEST, "파일(이미지)가 정상적으로 복사되지 않았습니다."), IMAGE_NOT_ENCODED(HttpStatus.BAD_REQUEST, "이미지를 인코딩하는 과정에서 오류가 발생했습니다."), + FILE_MAX_SIZE_EXCEED(HttpStatus.BAD_REQUEST, "파일(이미지)의 크기가 최대 용량을 초과했습니다."), GITHUB_CONNECTION_FAILED(HttpStatus.BAD_REQUEST, "Github 연결이 실패했습니다."), GITHUB_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "저장된 Github Token을 찾을 수 없습니다."), diff --git a/src/main/java/com/genius/gitget/global/util/exception/FileExceptionHandler.java b/src/main/java/com/genius/gitget/global/util/exception/FileExceptionHandler.java new file mode 100644 index 00000000..b23bb3e9 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/util/exception/FileExceptionHandler.java @@ -0,0 +1,22 @@ +package com.genius.gitget.global.util.exception; + +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_MAX_SIZE_EXCEED; + +import com.genius.gitget.global.util.response.dto.CommonResponse; +import lombok.extern.slf4j.Slf4j; +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 org.springframework.web.multipart.MaxUploadSizeExceededException; + +@Slf4j +@RestControllerAdvice +public class FileExceptionHandler { + @ExceptionHandler(MaxUploadSizeExceededException.class) + protected ResponseEntity globalExceptionHandler(Exception e) { + log.error("Multipart 용량이 최대 크기를 초과하여 예외가 발생했습니다."); + return ResponseEntity.badRequest().body( + new CommonResponse(HttpStatus.BAD_REQUEST, FILE_MAX_SIZE_EXCEED.getMessage())); + } +} From 399ec894e2b4bc9928b1bf94b7ea2078f8140385 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Wed, 17 Apr 2024 16:06:05 +0900 Subject: [PATCH 156/234] =?UTF-8?q?chore=20:=20main.yml=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pull request 트리거 제거 --- .github/workflows/main.yml | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 31a137b7..baa7e98c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,8 +3,8 @@ name: Build and Deploy to EC2 on: push: branches: [ "production" ] ## 나중에 production으로 변경하기 - pull_request: - branches: [ "production" ] + # pull_request: + # branches: [ "production" ] env: AWS_REGION: ap-northeast-2 @@ -19,7 +19,9 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v4 + # Actions + - name : Checkout + uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 @@ -27,6 +29,7 @@ jobs: java-version: '17' distribution: 'temurin' + # 프로젝트 내 yml 파일 실행 - name: make application.yml run: | mkdir -p ./src/main/resources @@ -47,18 +50,22 @@ jobs: echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml echo "${{ secrets.TEST }}" > ./application-test.yml + - name: Grant execute permission for gradlew run: chmod +x ./gradlew shell: bash - - name: Build and Test - run: ./gradlew build -x test #./gradlew build test + + - name: Build with Gradle # and Test + run: ./gradlew clean build -x test #./gradlew build test + - name: Make zip file run: zip -r ./$GITHUB_SHA.zip . shell: bash - - name: AWS credential 설정 + + - name: Deliver to AWS S3 (AWS credential 설정) uses: aws-actions/configure-aws-credentials@v1 with: aws-region: ${{ env.AWS_REGION }} @@ -69,8 +76,13 @@ jobs: - name: Upload to S3 run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip - - name: EC2에 배포 - run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip + + - name: Code Deploy (EC2에 배포) + run: aws deploy create-deployment \ + --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ + --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip From f87ca644bc005137bf2c7f6a20c66beb0d711af3 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Thu, 18 Apr 2024 12:21:24 +0900 Subject: [PATCH 157/234] =?UTF-8?q?[BUILD]=20Nginx=EB=A5=BC=20=ED=99=9C?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 자동화 배포 테스트 * chore: appspec.yml 수정 - Nginx와 연결하기 위해 appspec.yml 코드 수정 및 추가 * feat: scripts 파일 작성 및 port switch를 위한 controller 생성 - health, start, stop, profile, switch 스크립트 파일 생성 - 포트 번호별 스프링 부트 switch을 위한 controller 생성 * chore: 무중단 배포 테스트 환경 설정 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * test: 무중단 배포 테스트 * feat: 무중단 배포 완료 --------- Co-authored-by: HEY <50323157+SSung023@users.noreply.github.com> --- .github/workflows/main.yml | 18 ++++---- appspec.yml | 25 ++++++++--- build.gradle | 2 +- scripts/deploy.sh | 2 +- scripts/health.sh | 43 +++++++++++++++++++ scripts/run_new_was.sh | 30 +++++++++++++ scripts/start.sh | 21 --------- scripts/stop.sh | 14 ------ scripts/switch.sh | 29 +++++++++++++ .../com/genius/gitget/GitgetApplication.java | 1 + 10 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 scripts/health.sh create mode 100644 scripts/run_new_was.sh delete mode 100644 scripts/start.sh delete mode 100644 scripts/stop.sh create mode 100644 scripts/switch.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index baa7e98c..3cb5a544 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,9 @@ name: Build and Deploy to EC2 on: push: - branches: [ "production" ] ## 나중에 production으로 변경하기 - # pull_request: - # branches: [ "production" ] + branches: [ "production" ] +# pull_request: +# branches: [ "production"] env: AWS_REGION: ap-northeast-2 @@ -19,8 +19,8 @@ jobs: contents: read packages: write steps: - # Actions - - name : Checkout + # Actions + - name: Checkout uses: actions/checkout@v4 - name: Set up JDK 17 @@ -29,7 +29,7 @@ jobs: java-version: '17' distribution: 'temurin' - # 프로젝트 내 yml 파일 실행 + # 프로젝트 내 yml 파일 실행 - name: make application.yml run: | mkdir -p ./src/main/resources @@ -37,6 +37,7 @@ jobs: touch ./application.yml touch ./application-common.yml touch ./application-prod.yml + echo "${{ secrets.APPLICATION }}" > ./application.yml echo "${{ secrets.COMMON }}" > ./application-common.yml echo "${{ secrets.PROD }}" > ./application-prod.yml @@ -50,7 +51,7 @@ jobs: echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml echo "${{ secrets.TEST }}" > ./application-test.yml - + - name: Grant execute permission for gradlew run: chmod +x ./gradlew shell: bash @@ -83,6 +84,3 @@ jobs: --deployment-config-name CodeDeployDefault.AllAtOnce \ --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip - - - diff --git a/appspec.yml b/appspec.yml index fa35b32c..3f9d6147 100644 --- a/appspec.yml +++ b/appspec.yml @@ -1,17 +1,28 @@ -version: 0.0 -os: linux +version: 0.0 # CodeDeploy Version. + +os: linux # 배포할 서버의 운영체제 files: - - source: / - destination: /home/ubuntu/app - overwrite: yes + - source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정 (루트 경로 : 전체 파일) + destination: /home/ubuntu/app # source에서 지정된 파일을 받을 위치 + overwrite: yes # 기존 파일들을 덮어 쓰기 +# CodeDeploy에서 EC2로 넘겨준 파일들을 모두 ec2-user 권한 부여. permissions: - object: / + pattern: "**" owner: ubuntu group: ubuntu +# CodeDeploy 배포 단계에서 실행할 명령어를 지정 (차례대로 스크립트들이 실행) hooks: ApplicationStart: - - location: scripts/deploy.sh - timeout: 60 + - location: scripts/run_new_was.sh + timeout: 180 + runas: ubuntu + - location: scripts/health.sh + timeout: 180 + runas: ubuntu + - location: scripts/switch.sh + timeout: 180 + runas: ubuntu diff --git a/build.gradle b/build.gradle index e833cb5e..bdc2449c 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'com.genius' -version = '0.0.1-SNAPSHOT' +version = '0.0.1-SNAPSHOT' + new Date().format("yyyyMMddHHmmss") java { sourceCompatibility = '17' diff --git a/scripts/deploy.sh b/scripts/deploy.sh index e36ec46a..1daa20e2 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,4 +1,4 @@ -#!/bin/bash +##!/bin/bash BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) JAR_NAME=$(basename $BUILD_JAR) diff --git a/scripts/health.sh b/scripts/health.sh new file mode 100644 index 00000000..baa66cc7 --- /dev/null +++ b/scripts/health.sh @@ -0,0 +1,43 @@ +# health_check.sh + +#!/bin/bash + +# Crawl current connected port of WAS +CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) +TARGET_PORT=0 + +# Toggle port Number +if [ ${CURRENT_PORT} -eq 8081 ]; then + TARGET_PORT=8082 +elif [ ${CURRENT_PORT} -eq 8082 ]; then + TARGET_PORT=8081 +else + echo "> No WAS is connected to nginx" + exit 1 +fi + + +echo "> Start health check of WAS at http://localhost:${TARGET_PORT}/api/auth/health-check ..." + +for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10 +do + echo "> #${RETRY_COUNT} trying..." + RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${TARGET_PORT}/api/auth/health-check) + + if [ ${RESPONSE_CODE} -eq 200 ]; then + echo "> New WAS successfully running" + exit 0 + elif [ ${RETRY_COUNT} -eq 10 ]; then + echo "> Health check failed." + exit 1 + fi + sleep 10 + +# if [ ${CURRENT_PORT} -eq ${TARGET_PORT} ]; then +# echo "> Health Check Failed." +# exit 1 +# else +# echo "> New WAS Successfully running." +# exit 0 +# fi +done diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh new file mode 100644 index 00000000..545bbccb --- /dev/null +++ b/scripts/run_new_was.sh @@ -0,0 +1,30 @@ +# run_new_was.sh + +#!/bin/bash + +# 현재 포트 읽어오기 +CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) +TARGET_PORT=0 + +echo "> Current port of running WAS is ${CURRENT_PORT}." + +# 현재 포트가 8081이면 새로 WAS를 띄울 타겟 포트는 8082, 반대라면 8081 +if [ ${CURRENT_PORT} -eq 8081 ]; then + TARGET_PORT=8082 +elif [ ${CURRENT_PORT} -eq 8082 ]; then + TARGET_PORT=8081 +else + echo "> No WAS is connected to nginx" +fi + +TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+') + +# 만약 타겟 포트에도 WAS가 떠있다면, kill하고 새롭게 WAS를 띄움 +if [ ! -z ${TARGET_PID} ]; then + echo "> Kill WAS running at ${TARGET_PORT}." + sudo kill ${TARGET_PID} +fi + +nohup java -jar -Dserver.port=${TARGET_PORT} /home/ubuntu/app/build/libs/* > /home/ubuntu/nohup.out 2>&1 & +echo "> Now new WAS runs at ${TARGET_PORT}." +exit 0 diff --git a/scripts/start.sh b/scripts/start.sh deleted file mode 100644 index 8b1237e9..00000000 --- a/scripts/start.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -PROJECT_ROOT="/home/ubuntu/app" -JAR_FILE="$PROJECT_ROOT/GitGetApplication.jar" - -APP_LOG="$PROJECT_ROOT/application.log" -ERROR_LOG="$PROJECT_ROOT/error.log" -DEPLOY_LOG="$PROJECT_ROOT/deploy.log" - -TIME_NOW=$(date +%c) - -# build 파일 복사 -echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG -cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE - -# jar 파일 실행 -echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG -nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG & - -CURRENT_PID=$(pgrep -f $JAR_FILE) -echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG \ No newline at end of file diff --git a/scripts/stop.sh b/scripts/stop.sh deleted file mode 100644 index 6e0fec36..00000000 --- a/scripts/stop.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -ROOT_PATH="/home/ubuntu/build" -JAR="$ROOT_PATH/GitGetApplication.jar" -STOP_LOG="$ROOT_PATH/stop.log" -SERVICE_PID=$(pgrep -f $JAR) # 실행중인 Spring 서버의 PID - -if [ -z "$SERVICE_PID" ]; then - echo "서비스 NotFound" >> $STOP_LOG -else - echo "서비스 종료 " >> $STOP_LOG -# kill "$SERVICE_PID" - kill -9 "$SERVICE_PID" # 강제 종료를 하고 싶다면 이 명령어 사용 -fi \ No newline at end of file diff --git a/scripts/switch.sh b/scripts/switch.sh new file mode 100644 index 00000000..e6c89184 --- /dev/null +++ b/scripts/switch.sh @@ -0,0 +1,29 @@ +# switch.sh + +#!/bin/bash + +# Crawl current connected port of WAS +CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) +TARGET_PORT=0 + +echo "> Nginx currently proxies to ${CURRENT_PORT}." + +# Toggle port number +if [ ${CURRENT_PORT} -eq 8081 ]; then + TARGET_PORT=8082 +elif [ ${CURRENT_PORT} -eq 8082 ]; then + TARGET_PORT=8081 +else + echo "> No WAS is connected to nginx" + exit 1 +fi + +# Change proxying port into target port +echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ubuntu/service_url.inc + +echo "> Now Nginx proxies to ${TARGET_PORT}." + +# Reload nginx +sudo service nginx reload + +echo "> Nginx reloaded." diff --git a/src/main/java/com/genius/gitget/GitgetApplication.java b/src/main/java/com/genius/gitget/GitgetApplication.java index 20dc5e46..d12c2cf2 100644 --- a/src/main/java/com/genius/gitget/GitgetApplication.java +++ b/src/main/java/com/genius/gitget/GitgetApplication.java @@ -11,6 +11,7 @@ @EnableJpaAuditing @EnableMongoRepositories public class GitgetApplication { + public static void main(String[] args) { SpringApplication.run(GitgetApplication.class, args); } From 3fe90f9677816fe9514508b99c61ab9d50c3a8ac Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Thu, 18 Apr 2024 12:25:21 +0900 Subject: [PATCH 158/234] =?UTF-8?q?chore:=20main.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3cb5a544..926274c5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,8 +3,8 @@ name: Build and Deploy to EC2 on: push: branches: [ "production" ] -# pull_request: -# branches: [ "production"] + pull_request: + branches: [ "production" ] env: AWS_REGION: ap-northeast-2 @@ -79,8 +79,4 @@ jobs: - name: Code Deploy (EC2에 배포) - run: aws deploy create-deployment \ - --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \ - --deployment-config-name CodeDeployDefault.AllAtOnce \ - --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} \ - --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip + run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip From 3c0707bae143d7ac4af9a015a499324ab7e3492a Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Thu, 18 Apr 2024 12:29:45 +0900 Subject: [PATCH 159/234] =?UTF-8?q?chore:=20main.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 926274c5..21a46197 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -79,4 +79,4 @@ jobs: - name: Code Deploy (EC2에 배포) - run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip + run: aws deploy create-deployment --application-name GitGet-Application-HEY --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name GitGet-CICD-group-hey --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip From c0cd332a70de4264897f0f67451ff331e14ebbc3 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 18 Apr 2024 23:19:00 +0900 Subject: [PATCH 160/234] =?UTF-8?q?[REFACTOR]=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?API=20=EB=B6=84=EB=A6=AC=20(#165)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 파일 핵심 로직을 지니는 인터페이스 및 구체클래스 생성 * refactor: 파일 시스템 구조 개편 - FileUtil에는 핵심 비지니스 로직이 아닌, 유틸성 메서드로 구성 - FileManager 인터페이스 생성 및 상속을 통해 확장성 있돌고 변경 - yml의 값을 통해 로컬/배포 환경에 따라 각자 다른 파일 시스템을 사용하도록 설정 * test: 파일 시스템 개편으로 인한 테스트 코드 수정 * refactor: 개편한 파일 시스템 코드 리팩토링 - 필요없는 코드 삭제 - DTO의 이름을 포괄적으로 변경 * chore: 파일 시스템 관련 주석 정리 * refactor: 메서드명 변경 및 필요없는 메서드 삭제 * feat: FileManager의 기능을 별도로 테스트하기 위한 컨트롤러 생성 * refactor: 파일 홀더 인터페이스 생성 및 적용 - FileHolder 인터페이스 생성 - 파일과 연관관계를 맺는 User, Topic, Instance 객체에 대해 FileHolder 인터페이스 적용 * feat: 파일 조회 테스트 API 추가 - FileTestController에 GET 요청을 통해 등록되어 있는 파일을 조회하는 테스트 목적 API 추가 * refactor: 파일 생성 API 분리 - 회원가입, 토픽 생성, 인스턴스 생성 API에 대해 분리작업 진행 - FileHolder 인터페이스에 파일을 받아오는 getFiles() 메서드 추가 & 메서드 이름 변경 - 로직 변경으로 인한 테스트 코드 변경 * refactor: 파일 수정 API 분리 - 토픽 수정, 인스턴스 수정 API에 대해 분리 작업 진행 - 파일 조회 API 분리 - 로직 변경으로 인한 테스트 코드 변경 * fix: Querydsl 빌드 시 오류나는 부분 수정 * feat: MultipartFile이 오지 않는 경우에 대비하여 예외 처리 추가 * refactor: Topic쪽 API에 대해 리팩토링 - 응답 DTO에서 FileResponse 적용 로직 변경 * refactor: Instance쪽 API 리팩토링 - 응답 DTO에서 FileResponse 적용 로직 변경 * refactor: 좋아요 관련 API 리팩토링 - 응답 DTO에서 FileResponse 적용 로직 변경 * refactor: 마이챌린지 관련 API 리팩터링 - 응답 DTO에서 FileResponse 적용 로직 변경 * refactor: 유저 프로필 관련 API 리팩토링 - 응답 DTO에서 FileResponse 적용 로직 변경 * refactor: 인증 조회 API 리팩터링 - 응답 DTO에서 FileResponse 적용 로직 변경 * chore: Github actions 테스트 workflow 작성 - 특정 브랜치 빌드 진행했을 때, build에 문제가 생기지 않는지 확인하는 확인하기 위한 yml 파일 생성 - gradlew를 통한 test build까지만 진행하도록 설정 * test: 파일과 관련된 테스트 코드 수정 * test: 테스트 이후 mongoDB 초기화 코드 추가 * refactor: File 관련 요청할 수 있도록 응답 객체 설정 --- .github/workflows/test-task.yml | 50 +++++++ .../topic/controller/TopicController.java | 43 +++--- .../gitget/admin/topic/domain/Topic.java | 5 +- .../admin/topic/dto/TopicDetailResponse.java | 17 ++- .../admin/topic/dto/TopicIndexResponse.java | 6 + .../admin/topic/dto/TopicPagingResponse.java | 7 +- .../admin/topic/service/TopicService.java | 42 ++---- .../controller/CertificationController.java | 4 +- .../service/CertificationService.java | 8 +- .../controller/InstanceController.java | 40 +++--- .../challenge/instance/domain/Instance.java | 8 +- .../dto/crud/InstanceDetailResponse.java | 6 +- .../dto/crud/InstanceIndexResponse.java | 6 + .../dto/crud/InstancePagingResponse.java | 16 ++- .../instance/dto/detail/InstanceResponse.java | 5 +- .../dto/home/HomeInstanceResponse.java | 7 +- .../dto/search/InstanceSearchResponse.java | 7 +- .../repository/SearchRepositoryImpl.java | 7 +- .../service/InstanceDetailService.java | 8 +- .../instance/service/InstanceHomeService.java | 12 +- .../instance/service/InstanceService.java | 29 ++-- .../likes/controller/LikesController.java | 5 +- .../likes/dto/UserLikesResponse.java | 4 +- .../challenge/likes/service/LikesService.java | 17 +-- .../myChallenge/dto/ActivatedResponse.java | 6 +- .../myChallenge/dto/DoneResponse.java | 8 +- .../service/MyChallengeService.java | 29 ++-- .../user/controller/UserController.java | 13 +- .../gitget/challenge/user/domain/User.java | 12 +- .../challenge/user/dto/UserProfileInfo.java | 4 +- .../challenge/user/service/UserService.java | 12 +- .../file/controller/FileTestController.java | 103 ++++++++++++++ .../file/controller/FilesController.java | 75 +++++----- .../gitget/global/file/domain/FileHolder.java | 9 ++ .../gitget/global/file/domain/Files.java | 10 ++ .../gitget/global/file/dto/FileDTO.java | 23 ++++ .../gitget/global/file/dto/FileResponse.java | 15 +- .../gitget/global/file/dto/UpdateDTO.java | 18 +++ .../gitget/global/file/dto/UploadDTO.java | 11 -- .../global/file/service/FileHolderFinder.java | 42 ++++++ .../global/file/service/FileManager.java | 58 ++++++++ .../gitget/global/file/service/FileUtil.java | 66 ++------- .../global/file/service/FilesService.java | 130 ++++++++++-------- .../global/file/service/LocalFileManager.java | 104 ++++++++++++++ .../global/file/service/S3FileManager.java | 46 +++++++ .../global/security/dto/SignupResponse.java | 1 + .../gitget/global/util/config/AppConfig.java | 25 +++- .../util}/config/WebConfig.java | 2 +- .../global/util/exception/ErrorCode.java | 1 + .../profile/controller/ProfileController.java | 37 ++--- .../dto/UserDetailsInformationResponse.java | 24 ++-- .../gitget/profile/dto/UserIndexResponse.java | 6 + .../profile/dto/UserInformationResponse.java | 21 ++- .../profile/service/ProfileService.java | 52 ++----- .../topic/controller/TopicControllerTest.java | 8 -- .../admin/topic/service/TopicServiceTest.java | 12 +- .../controller/InstanceControllerTest.java | 9 +- .../InstanceSearchRepositoryTest.java | 1 - .../service/InstanceSearchServiceTest.java | 2 - .../instance/service/InstanceServiceTest.java | 95 ++++++------- .../item/service/ItemServiceTest.java | 74 +++++----- .../likes/controller/LikesControllerTest.java | 3 +- .../user/service/UserServiceTest.java | 15 +- .../global/file/service/FileUtilTest.java | 49 ++----- .../security/service/JwtServiceTest.java | 9 ++ .../security/service/TokenServiceTest.java | 6 + .../payment/service/PaymentServiceTest.java | 2 +- .../controller/ProfileControllerTest.java | 3 +- .../profile/service/ProfileServiceTest.java | 4 +- ...hMockCustomUserSecurityContextFactory.java | 4 +- 70 files changed, 973 insertions(+), 645 deletions(-) create mode 100644 .github/workflows/test-task.yml create mode 100644 src/main/java/com/genius/gitget/admin/topic/dto/TopicIndexResponse.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceIndexResponse.java create mode 100644 src/main/java/com/genius/gitget/global/file/controller/FileTestController.java create mode 100644 src/main/java/com/genius/gitget/global/file/domain/FileHolder.java create mode 100644 src/main/java/com/genius/gitget/global/file/dto/FileDTO.java delete mode 100644 src/main/java/com/genius/gitget/global/file/dto/UploadDTO.java create mode 100644 src/main/java/com/genius/gitget/global/file/service/FileHolderFinder.java create mode 100644 src/main/java/com/genius/gitget/global/file/service/FileManager.java create mode 100644 src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java create mode 100644 src/main/java/com/genius/gitget/global/file/service/S3FileManager.java rename src/main/java/com/genius/gitget/{challenge => global/util}/config/WebConfig.java (90%) create mode 100644 src/main/java/com/genius/gitget/profile/dto/UserIndexResponse.java diff --git a/.github/workflows/test-task.yml b/.github/workflows/test-task.yml new file mode 100644 index 00000000..9534fa80 --- /dev/null +++ b/.github/workflows/test-task.yml @@ -0,0 +1,50 @@ +name: Build test about test codes + +on: + push: + branches: [ "refactor/162-file-system-structure" ] ## 테스트하고자하는 브랜치 작성 + pull_request: + branches: [ "refactor/162-file-system-structure" ] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: make application.yml + run: | + mkdir -p ./src/main/resources + cd ./src/main/resources + touch ./application.yml + touch ./application-common.yml + touch ./application-prod.yml + echo "${{ secrets.APPLICATION }}" > ./application.yml + echo "${{ secrets.COMMON }}" > ./application-common.yml + echo "${{ secrets.PROD }}" > ./application-prod.yml + + - name: make test application.yml + run: | + mkdir -p ./src/test/resources + cd ./src/test/resources + touch ./application.yml + touch ./application-test.yml + echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml + echo "${{ secrets.TEST }}" > ./application-test.yml + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + shell: bash + + - name: Build and Test + run: ./gradlew clean build test + diff --git a/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java b/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java index e86f0bc5..9fe968ae 100644 --- a/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java +++ b/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java @@ -1,15 +1,17 @@ package com.genius.gitget.admin.topic.controller; +import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + import com.genius.gitget.admin.topic.dto.TopicCreateRequest; import com.genius.gitget.admin.topic.dto.TopicDetailResponse; +import com.genius.gitget.admin.topic.dto.TopicIndexResponse; import com.genius.gitget.admin.topic.dto.TopicPagingResponse; import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; import com.genius.gitget.admin.topic.service.TopicService; -import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; -import java.io.IOException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -21,10 +23,9 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; 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.RequestPart; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; @RestController @RequiredArgsConstructor @@ -40,40 +41,42 @@ public ResponseEntity> getAllTopics( Page allTopics = topicService.getAllTopics(pageable); return ResponseEntity.ok().body( - new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), allTopics) + new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), allTopics) ); } // 토픽 상세 정보 요청 @GetMapping("/{id}") - public ResponseEntity> getTopicById(@PathVariable Long id) throws IOException { + public ResponseEntity> getTopicById(@PathVariable Long id) { TopicDetailResponse topicDetail = topicService.getTopicById(id); return ResponseEntity.ok().body( - new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), topicDetail) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), topicDetail) ); } // 토픽 생성 요청 @PostMapping - public ResponseEntity createTopic( - @RequestPart(value = "data") TopicCreateRequest topicCreateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, - @RequestPart(value = "type") String type) { - topicService.createTopic(topicCreateRequest, multipartFile, type); + public ResponseEntity> createTopic( + @RequestBody TopicCreateRequest topicCreateRequest) { + Long topicId = topicService.createTopic(topicCreateRequest); + TopicIndexResponse topicUpdateResponse = new TopicIndexResponse(topicId); + return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.CREATED.getStatus(), SuccessCode.CREATED.getMessage()) + new SingleResponse<>( + CREATED.getStatus(), CREATED.getMessage(), topicUpdateResponse) ); } // 토픽 수정 요청 @PatchMapping("/{id}") - public ResponseEntity updateTopic(@PathVariable Long id, - @RequestPart(value = "data") TopicUpdateRequest topicUpdateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, - @RequestPart(value = "type") String type) { - topicService.updateTopic(id, topicUpdateRequest, multipartFile, type); + public ResponseEntity> updateTopic( + @PathVariable Long id, + @RequestBody TopicUpdateRequest topicUpdateRequest) { + Long topicId = topicService.updateTopic(id, topicUpdateRequest); + TopicIndexResponse topicUpdateResponse = new TopicIndexResponse(topicId); + return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), topicUpdateResponse) ); } @@ -82,7 +85,7 @@ public ResponseEntity updateTopic(@PathVariable Long id, public ResponseEntity deleteTopic(@PathVariable Long id) { topicService.deleteTopic(id); return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java index 64ba5ca3..9f419fe2 100644 --- a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java @@ -1,6 +1,7 @@ package com.genius.gitget.admin.topic.domain; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.domain.FileHolder; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.CascadeType; @@ -26,7 +27,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "topic") -public class Topic extends BaseTimeEntity { +public class Topic extends BaseTimeEntity implements FileHolder { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "topic_id") @@ -73,10 +74,12 @@ public void updateNotExistInstance(String title, String description, String tags this.pointPerPerson = pointPerPerson; } + @Override public Optional getFiles() { return Optional.ofNullable(this.files); } + @Override public void setFiles(Files files) { this.files = files; } diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java index 77f66661..f74be401 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java @@ -1,16 +1,19 @@ package com.genius.gitget.admin.topic.dto; import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import java.io.IOException; -import java.util.Optional; import lombok.Builder; @Builder -public record TopicDetailResponse(Long topicId, String title, String tags, - String description, String notice, int pointPerPerson, FileResponse fileResponse) { - public static TopicDetailResponse createByEntity(Topic topic, Optional files) throws IOException { +public record TopicDetailResponse( + Long topicId, + String title, + String tags, + String description, + String notice, + int pointPerPerson, + FileResponse fileResponse) { + public static TopicDetailResponse createByEntity(Topic topic, FileResponse fileResponse) { return TopicDetailResponse.builder() .topicId(topic.getId()) .title(topic.getTitle()) @@ -18,7 +21,7 @@ public static TopicDetailResponse createByEntity(Topic topic, Optional fi .description(topic.getDescription()) .notice(topic.getNotice()) .pointPerPerson(topic.getPointPerPerson()) - .fileResponse(FileResponse.create(files)) + .fileResponse(fileResponse) .build(); } } diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicIndexResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicIndexResponse.java new file mode 100644 index 00000000..6e525cd3 --- /dev/null +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicIndexResponse.java @@ -0,0 +1,6 @@ +package com.genius.gitget.admin.topic.dto; + +public record TopicIndexResponse( + Long topicId +) { +} diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java index a81e012f..a4e091ba 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java +++ b/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java @@ -1,20 +1,17 @@ package com.genius.gitget.admin.topic.dto; import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import java.io.IOException; -import java.util.Optional; import lombok.Builder; @Builder public record TopicPagingResponse(Long topicId, String title, FileResponse fileResponse) { - public static TopicPagingResponse createByEntity(Topic topic, Optional files) throws IOException { + public static TopicPagingResponse createByEntity(Topic topic, FileResponse fileResponse) { return TopicPagingResponse.builder() .topicId(topic.getId()) .title(topic.getTitle()) - .fileResponse(FileResponse.create(files)) + .fileResponse(fileResponse) .build(); } } diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java index bcbfdbd7..6b22a5d8 100644 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java @@ -6,19 +6,16 @@ import com.genius.gitget.admin.topic.dto.TopicPagingResponse; import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; -import java.io.IOException; -import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; @Service @RequiredArgsConstructor @@ -35,28 +32,21 @@ public Page getAllTopics(Pageable pageable) { } private TopicPagingResponse mapToTopicPagingResponse(Topic topic) { - try { - return TopicPagingResponse.createByEntity(topic, topic.getFiles()); - } catch (IOException e) { - throw new BusinessException(e); - } + FileResponse fileResponse = filesService.convertToFileResponse(topic.getFiles()); + return TopicPagingResponse.createByEntity(topic, fileResponse); } // 토픽 상세정보 요청 - public TopicDetailResponse getTopicById(Long id) throws IOException { - Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - return TopicDetailResponse.createByEntity(topic, topic.getFiles()); + public TopicDetailResponse getTopicById(Long id) { + Topic topic = topicRepository.findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + FileResponse fileResponse = filesService.convertToFileResponse(topic.getFiles()); + return TopicDetailResponse.createByEntity(topic, fileResponse); } // 토픽 생성 요청 @Transactional - public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile multipartFile, String type) { - System.out.println("토픽 생성 요청"); - System.out.println(topicCreateRequest.title()); - System.out.println(multipartFile.getOriginalFilename()); - System.out.println(type); - Files uploadedFile = filesService.uploadFile(multipartFile, type); - + public Long createTopic(TopicCreateRequest topicCreateRequest) { Topic topic = Topic.builder() .title(topicCreateRequest.title()) .description(topicCreateRequest.description()) @@ -65,8 +55,6 @@ public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile mul .notice(topicCreateRequest.notice()) .build(); - topic.setFiles(uploadedFile); - Topic savedTopic = topicRepository.save(topic); return savedTopic.getId(); @@ -74,13 +62,9 @@ public Long createTopic(TopicCreateRequest topicCreateRequest, MultipartFile mul // 토픽 업데이트 요청 @Transactional - public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest, MultipartFile multipartFile, String type) { - Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - - Optional findTopicFile = topic.getFiles(); - Long findTopicFileId = findTopicFile.get().getId(); - - filesService.updateFile(findTopicFileId, multipartFile); + public Long updateTopic(Long id, TopicUpdateRequest topicUpdateRequest) { + Topic topic = topicRepository.findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); // 서버에서 한번 더 검사 boolean hasInstance = !topic.getInstanceList().isEmpty(); @@ -90,7 +74,7 @@ public void updateTopic(Long id, TopicUpdateRequest topicUpdateRequest, Multipar topic.updateNotExistInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), topicUpdateRequest.tags(), topicUpdateRequest.notice(), topicUpdateRequest.pointPerPerson()); } - topicRepository.save(topic); + return topicRepository.save(topic).getId(); } // 토픽 삭제 요청 diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index e4ef1cd4..d29452c0 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -92,11 +92,11 @@ public ResponseEntity> getWeekCertification( @PathVariable Long instanceId ) { Participant participant = participantProvider.findByJoinInfo(userPrincipal.getUser().getId(), instanceId); - WeekResponse weekCertification = certificationService.getMyWeekCertifications( + WeekResponse weekResponse = certificationService.getMyWeekCertifications( participant.getId(), LocalDate.now()); return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), weekCertification) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), weekResponse) ); } diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index 1dc94e46..d0e6cb40 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -177,8 +177,10 @@ public ActivatedResponse passCertification(Long userId, CertificationRequest cer return passed; }); - return ActivatedResponse.create(instance, certification.getCertificationStatus(), 0, - participant.getRepositoryName()); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + //TODO: pass 했기 때문에 pass item이 필요없어 numOfPassItem을 0으로 전달하는 것 같음. but, 가독성이 떨어지기 때문에 수정 필요 + return ActivatedResponse.create(instance, certification.getCertificationStatus(), + 0, participant.getRepositoryName(), fileResponse); } private void validatePassCondition(Optional optional) { @@ -245,7 +247,7 @@ private void validCertificationCondition(Instance instance, LocalDate targetDate public InstancePreviewResponse getInstancePreview(Long instanceId) { Instance instance = instanceProvider.findById(instanceId); - FileResponse fileResponse = filesService.getEncodedFile(instance.getFiles()); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); return InstancePreviewResponse.createByEntity(instance, fileResponse); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index ff46756e..e0f6da8c 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -1,11 +1,14 @@ package com.genius.gitget.challenge.instance.controller; +import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstanceIndexResponse; import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.service.InstanceService; -import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -22,10 +25,9 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; 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.RequestPart; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/api/admin") @@ -40,7 +42,7 @@ public ResponseEntity> getAllInstances( Page instances = instanceService.getAllInstances(pageable); return ResponseEntity.ok().body( - new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), instances) + new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instances) ); } @@ -54,7 +56,7 @@ public ResponseEntity> getAllInstancesOfS pageRequest, id); return ResponseEntity.ok().body( - new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), allInstancesOfSpecificTopic) ); } @@ -65,32 +67,32 @@ public ResponseEntity> getAllInstancesOfS public ResponseEntity> getInstanceById(@PathVariable Long id) { InstanceDetailResponse instanceDetails = instanceService.getInstanceById(id); return ResponseEntity.ok().body( - new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), instanceDetails) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instanceDetails) ); } // 인스턴스 생성 @PostMapping("/instance") - public ResponseEntity createInstance( - @RequestPart(value = "data") InstanceCreateRequest instanceCreateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, - @RequestPart(value = "type", required = false) String type) { - instanceService.createInstance(instanceCreateRequest, multipartFile, type, LocalDate.now()); + public ResponseEntity> createInstance( + @RequestBody InstanceCreateRequest instanceCreateRequest) { + Long instanceId = instanceService.createInstance(instanceCreateRequest, LocalDate.now()); + InstanceIndexResponse instanceIndexResponse = new InstanceIndexResponse(instanceId); + return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.CREATED.getStatus(), SuccessCode.CREATED.getMessage()) + new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), instanceIndexResponse) ); } // 인스턴스 수정 @PatchMapping("/instance/{id}") - public ResponseEntity updateInstance(@PathVariable Long id, - @RequestPart(value = "data") InstanceUpdateRequest instanceUpdateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, - @RequestPart(value = "type") String type) { + public ResponseEntity> updateInstance( + @PathVariable Long id, + @RequestBody InstanceUpdateRequest instanceUpdateRequest) { - instanceService.updateInstance(id, instanceUpdateRequest, multipartFile, type); + Long instanceId = instanceService.updateInstance(id, instanceUpdateRequest); + InstanceIndexResponse instanceIndexResponse = new InstanceIndexResponse(instanceId); return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instanceIndexResponse) ); } @@ -99,7 +101,7 @@ public ResponseEntity updateInstance(@PathVariable Long id, public ResponseEntity deleteInstance(@PathVariable Long id) { instanceService.deleteInstance(id); return ResponseEntity.ok().body( - new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 2b0bf744..996bd8f3 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -6,6 +6,7 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.global.file.domain.FileHolder; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; @@ -40,7 +41,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @DynamicInsert @Table(name = "instance") -public class Instance { +public class Instance implements FileHolder { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "instance_id") @@ -157,13 +158,12 @@ public int getLikesCount() { return this.likesList.size(); } - /* - * 파일 조회 - * */ + @Override public Optional getFiles() { return Optional.ofNullable(this.files); } + @Override public void setFiles(Files files) { this.files = files; } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java index 0779d056..3850a936 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java @@ -1,10 +1,8 @@ package com.genius.gitget.challenge.instance.dto.crud; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; import java.time.LocalDateTime; -import java.util.Optional; import lombok.Builder; @Builder @@ -13,7 +11,7 @@ public record InstanceDetailResponse(Long topicId, Long instanceId, String title String tags, String notice, LocalDateTime startedAt, LocalDateTime completedAt, String certificationMethod, FileResponse fileResponse) { - public static InstanceDetailResponse createByEntity(Instance instance, Optional files) { + public static InstanceDetailResponse createByEntity(Instance instance, FileResponse fileResponse) { return InstanceDetailResponse.builder() .topicId(instance.getTopic().getId()) .instanceId(instance.getId()) @@ -25,7 +23,7 @@ public static InstanceDetailResponse createByEntity(Instance instance, Optional< .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()) .certificationMethod(instance.getCertificationMethod()) - .fileResponse(FileResponse.create(files)) + .fileResponse(fileResponse) .build(); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceIndexResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceIndexResponse.java new file mode 100644 index 00000000..0deaa84c --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceIndexResponse.java @@ -0,0 +1,6 @@ +package com.genius.gitget.challenge.instance.dto.crud; + +public record InstanceIndexResponse( + Long instanceId +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java index 9ffa1d31..26bfbbd8 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java @@ -1,24 +1,26 @@ package com.genius.gitget.challenge.instance.dto.crud; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import java.io.IOException; import java.time.LocalDateTime; -import java.util.Optional; import lombok.Builder; @Builder -public record InstancePagingResponse(Long topicId, Long instanceId, String title, - LocalDateTime startedAt, LocalDateTime completedAt, FileResponse fileResponse) { - public static InstancePagingResponse createByEntity(Instance instance, Optional files) throws IOException { +public record InstancePagingResponse( + Long topicId, + Long instanceId, + String title, + LocalDateTime startedAt, + LocalDateTime completedAt, + FileResponse fileResponse) { + public static InstancePagingResponse createByEntity(Instance instance, FileResponse fileResponse) { return InstancePagingResponse.builder() .topicId(instance.getTopic().getId()) .instanceId(instance.getId()) .title(instance.getTitle()) .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()) - .fileResponse(FileResponse.create(files)) + .fileResponse(fileResponse) .build(); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java index 925e4de6..de2e1fcb 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java @@ -26,7 +26,8 @@ public record InstanceResponse( FileResponse fileResponse ) { - public static InstanceResponse createByEntity(Instance instance, LikesInfo likesInfo, JoinStatus joinStatus) { + public static InstanceResponse createByEntity(Instance instance, LikesInfo likesInfo, + JoinStatus joinStatus, FileResponse fileResponse) { LocalDate startedLocalDate = instance.getStartedDate().toLocalDate(); LocalDate completedLocalDate = instance.getCompletedDate().toLocalDate(); return InstanceResponse.builder() @@ -43,7 +44,7 @@ public static InstanceResponse createByEntity(Instance instance, LikesInfo likes .certificationMethod(instance.getCertificationMethod()) .joinStatus(joinStatus) .likesInfo(likesInfo) - .fileResponse(FileResponse.create(instance.getFiles())) + .fileResponse(fileResponse) .build(); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java index 479d12c3..b0235412 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/home/HomeInstanceResponse.java @@ -1,10 +1,7 @@ package com.genius.gitget.challenge.instance.dto.home; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import java.io.IOException; -import java.util.Optional; import lombok.Builder; @Builder @@ -15,13 +12,13 @@ public record HomeInstanceResponse( int pointPerPerson, FileResponse fileResponse ) { - public static HomeInstanceResponse createByEntity(Instance instance, Optional files) throws IOException { + public static HomeInstanceResponse createByEntity(Instance instance, FileResponse fileResponse) { return HomeInstanceResponse.builder() .instanceId(instance.getId()) .title(instance.getTitle()) .participantCnt(instance.getParticipantCount()) .pointPerPerson(instance.getPointPerPerson()) - .fileResponse(FileResponse.create(files)) + .fileResponse(fileResponse) .build(); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index 6dcd98b5..a618cb8f 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -1,9 +1,6 @@ package com.genius.gitget.challenge.instance.dto.search; -import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.dto.FileResponse; import com.querydsl.core.annotations.QueryProjection; -import java.util.Optional; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @@ -16,17 +13,15 @@ public class InstanceSearchResponse { private String keyword; private int pointPerPerson; private int participantCount; - private FileResponse fileResponse; @Builder @QueryProjection public InstanceSearchResponse(Long topicId, Long instanceId, String keyword, int pointPerPerson, - int participantCount, Files files) { + int participantCount) { this.topicId = topicId; this.instanceId = instanceId; this.keyword = keyword; this.pointPerPerson = pointPerPerson; this.participantCount = participantCount; - this.fileResponse = FileResponse.create(Optional.of(files)); } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java index 0fcd8671..d147362c 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java @@ -1,7 +1,6 @@ package com.genius.gitget.challenge.instance.repository; import static com.genius.gitget.challenge.instance.domain.QInstance.instance; -import static com.genius.gitget.global.file.domain.QFiles.files; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; @@ -37,11 +36,8 @@ public Page search(Progress progressCond, String titleCo List content = queryFactory .select(new QInstanceSearchResponse( instance.topic.id, instance.id, instance.title, instance.pointPerPerson, - instance.participantCount, - instance.files)) + instance.participantCount)) .from(instance) - .leftJoin(instance.files, files) - .on(instance.files.id.eq(files.id)) .where(builder) .orderBy(instance.startedDate.desc()) .offset(pageable.getOffset()) @@ -51,7 +47,6 @@ public Page search(Progress progressCond, String titleCo JPAQuery countQuery = queryFactory .select(instance.count()) .from(instance) - .leftJoin(instance.files, files) .where(builder); return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index 085cf009..a270009f 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -17,6 +17,8 @@ import com.genius.gitget.challenge.participant.service.ParticipantProvider; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -31,6 +33,7 @@ @RequiredArgsConstructor public class InstanceDetailService { private final UserService userService; + private final FilesService filesService; private final InstanceProvider instanceProvider; private final ParticipantProvider participantProvider; private final GithubProvider githubProvider; @@ -39,13 +42,14 @@ public class InstanceDetailService { public InstanceResponse getInstanceDetailInformation(User user, Long instanceId) { Instance instance = instanceProvider.findById(instanceId); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); LikesInfo likesInfo = getLikesInfo(user.getId(), instance); if (participantProvider.hasParticipant(user.getId(), instanceId)) { - return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.YES); + return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.YES, fileResponse); } - return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.NO); + return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.NO, fileResponse); } private LikesInfo getLikesInfo(Long userId, Instance instance) { diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java index a5694c07..9d361b97 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java @@ -6,8 +6,8 @@ import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.global.util.exception.BusinessException; -import java.io.IOException; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; @@ -22,6 +22,7 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class InstanceHomeService { + private final FilesService filesService; private final InstanceRepository instanceRepository; public Slice getRecommendations(User user, Pageable pageable) { @@ -40,10 +41,7 @@ public Slice getInstancesByCondition(Pageable pageable) { } private HomeInstanceResponse mapToHomeInstanceResponse(Instance instance) { - try { - return HomeInstanceResponse.createByEntity(instance, instance.getFiles()); - } catch (IOException e) { - throw new BusinessException(e); - } + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + return HomeInstanceResponse.createByEntity(instance, fileResponse); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index 428bfc8c..54b06415 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -13,19 +13,17 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; -import java.io.IOException; import java.time.LocalDate; -import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; @Service @RequiredArgsConstructor @@ -38,15 +36,11 @@ public class InstanceService { // 인스턴스 생성 @Transactional public Long createInstance(InstanceCreateRequest instanceCreateRequest, - MultipartFile multipartFile, String type, LocalDate currentDate) { // 토픽 조회 Topic topic = topicRepository.findById(instanceCreateRequest.topicId()) .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); - // 파일 업로드 - Files uploadedFile = filesService.uploadFile(topic.getFiles(), multipartFile, type); - // 인스턴스 생성 일자 검증 validatePeriod(instanceCreateRequest, currentDate); @@ -60,7 +54,6 @@ public Long createInstance(InstanceCreateRequest instanceCreateRequest, // 연관 관계 설정 instance.setTopic(topic); - instance.setFiles(uploadedFile); return instanceRepository.save(instance).getId(); } @@ -83,7 +76,8 @@ public Page getAllInstances(Pageable pageable) { // 특정 토픽에 대한 리스트 조회 public Page getAllInstancesOfSpecificTopic(Pageable pageable, Long id) { - Topic topic = topicRepository.findById(id).orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + Topic topic = topicRepository.findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); Page instancesByTopicId = instanceRepository.findInstancesByTopicId(pageable, topic.getId()); return instancesByTopicId.map(this::mapToInstancePagingResponse); } @@ -93,7 +87,8 @@ public Page getAllInstancesOfSpecificTopic(Pageable page public InstanceDetailResponse getInstanceById(Long id) { Instance instanceDetails = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); - return InstanceDetailResponse.createByEntity(instanceDetails, instanceDetails.getFiles()); + FileResponse fileResponse = filesService.convertToFileResponse(instanceDetails.getFiles()); + return InstanceDetailResponse.createByEntity(instanceDetails, fileResponse); } @@ -116,15 +111,10 @@ public void deleteInstance(Long id) { // 인스턴스 수정 @Transactional - public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, MultipartFile multipartFile, - String type) { + public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest) { Instance existingInstance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); - Optional findInstanceFile = existingInstance.getFiles(); - Long findInstanceFileId = findInstanceFile.get().getId(); - filesService.updateFile(findInstanceFileId, multipartFile); - existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.notice(), instanceUpdateRequest.pointPerPerson(), instanceUpdateRequest.startedAt(), instanceUpdateRequest.completedAt(), instanceUpdateRequest.certificationMethod()); @@ -135,10 +125,7 @@ public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest, } private InstancePagingResponse mapToInstancePagingResponse(Instance instance) { - try { - return InstancePagingResponse.createByEntity(instance, instance.getFiles()); - } catch (IOException e) { - throw new BusinessException(e); - } + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + return InstancePagingResponse.createByEntity(instance, fileResponse); } } diff --git a/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java b/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java index 652f7727..ce8a35d2 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java +++ b/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java @@ -33,8 +33,9 @@ public class LikesController { // 좋아요 목록 조회 @GetMapping("/likes") - public ResponseEntity> getLikesListOfUser(Pageable pageable, - @AuthenticationPrincipal UserPrincipal userPrincipal) { + public ResponseEntity> getLikesListOfUser( + Pageable pageable, + @AuthenticationPrincipal UserPrincipal userPrincipal) { PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); Page likesResponses = likesService.getLikesList(userPrincipal.getUser(), pageRequest); diff --git a/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesResponse.java b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesResponse.java index 41c708a2..bb32f3af 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesResponse.java +++ b/src/main/java/com/genius/gitget/challenge/likes/dto/UserLikesResponse.java @@ -13,8 +13,8 @@ public class UserLikesResponse { private FileResponse fileResponse; @Builder - public UserLikesResponse(Long likesId, Long instanceId, String title, int pointPerPerson, - FileResponse fileResponse) { + public UserLikesResponse(Long likesId, Long instanceId, String title, + int pointPerPerson, FileResponse fileResponse) { this.likesId = likesId; this.instanceId = instanceId; this.title = title; diff --git a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java index 36ddb912..df3c6030 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java +++ b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java @@ -9,6 +9,7 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import java.util.ArrayList; @@ -26,6 +27,7 @@ @Slf4j @Service public class LikesService { + private final FilesService filesService; private final UserRepository userRepository; private final InstanceRepository instanceRepository; private final LikesRepository likesRepository; @@ -43,12 +45,14 @@ public Page getLikesList(User user, Pageable pageable) { for (Likes like : likes) { Instance instance = like.getInstance(); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + UserLikesResponse userLikesResponse = UserLikesResponse.builder() .likesId(like.getId()) .instanceId(instance.getId()) .title(instance.getTitle()) .pointPerPerson(instance.getPointPerPerson()) - .fileResponse(FileResponse.create(instance.getFiles())) + .fileResponse(fileResponse) .build(); userLikesResponses.add(userLikesResponse); @@ -64,7 +68,6 @@ public UserLikesAddResponse addLikes(User user, String identifier, Long instance User findUser = null; for (User userObject : userList) { - System.out.println("!!!!!!!!!" + userObject.getIdentifier() + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); if (userObject.getIdentifier().equals(identifier)) { findUser = userObject; } @@ -85,16 +88,6 @@ public void deleteLikes(User user, Long likesId) { likesRepository.deleteById(findLikes.getId()); } -// @Transactional -// public void deleteLikesLazy(User user, Long likesId) { -// try { -// likesRepository.deleteById(likesId); -// } catch (Exception e) { -// e.getStackTrace(); -// } -// } - - private List verifyUser(User user) { return userRepository.findAllByIdentifier(user.getIdentifier()); } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java index ce1ce8da..185d9545 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java @@ -22,7 +22,7 @@ public class ActivatedResponse extends ItemUseResponse { @Builder public ActivatedResponse(Long instanceId, String title, int pointPerPerson, String repository, - String certificateStatus, Long itemId, + String certificateStatus, int numOfPassItem, boolean canUsePassItem, FileResponse fileResponse) { this.instanceId = instanceId; this.title = title; @@ -35,7 +35,7 @@ public ActivatedResponse(Long instanceId, String title, int pointPerPerson, Stri } public static ActivatedResponse create(Instance instance, CertificateStatus certificateStatus, - int numOfPassItem, String repository) { + int numOfPassItem, String repository, FileResponse fileResponse) { boolean canUseItem = checkItemCondition(certificateStatus, numOfPassItem); return ActivatedResponse.builder() @@ -46,7 +46,7 @@ public static ActivatedResponse create(Instance instance, CertificateStatus cert .certificateStatus(certificateStatus.getTag()) .canUsePassItem(canUseItem) .numOfPassItem(canUseItem ? numOfPassItem : 0) - .fileResponse(FileResponse.create(instance.getFiles())) + .fileResponse(fileResponse) .build(); } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java index bd52bcd0..ac8a5788 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java @@ -40,7 +40,7 @@ public DoneResponse(Long instanceId, String title, int pointPerPerson, JoinResul public static DoneResponse createNotRewarded(Instance instance, Participant participant, - int numOfPointItem) { + int numOfPointItem, FileResponse fileResponse) { return DoneResponse.builder() .title(instance.getTitle()) .instanceId(instance.getId()) @@ -48,12 +48,12 @@ public static DoneResponse createNotRewarded(Instance instance, .joinResult(participant.getJoinResult()) .canGetReward(canGetReward(participant)) .numOfPointItem(numOfPointItem) - .fileResponse(FileResponse.create(instance.getFiles())) + .fileResponse(fileResponse) .build(); } public static DoneResponse createRewarded(Instance instance, Participant participant, - double achievementRate) { + double achievementRate, FileResponse fileResponse) { return DoneResponse.builder() .title(instance.getTitle()) .instanceId(instance.getId()) @@ -62,7 +62,7 @@ public static DoneResponse createRewarded(Instance instance, Participant partici .canGetReward(false) .rewardedPoints(participant.getRewardPoints()) .achievementRate(achievementRate) - .fileResponse(FileResponse.create(instance.getFiles())) + .fileResponse(fileResponse) .build(); } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index c7d6ff10..88210647 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -1,11 +1,11 @@ package com.genius.gitget.challenge.myChallenge.service; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; -import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; -import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; import static com.genius.gitget.challenge.participant.domain.RewardStatus.NO; import static com.genius.gitget.challenge.participant.domain.RewardStatus.YES; +import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; +import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.certification.domain.Certification; @@ -13,9 +13,6 @@ import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.service.ItemProvider; -import com.genius.gitget.store.item.service.OrdersProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; @@ -25,8 +22,12 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.service.ItemProvider; +import com.genius.gitget.store.item.service.OrdersProvider; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -39,6 +40,7 @@ @RequiredArgsConstructor public class MyChallengeService { private final UserService userService; + private final FilesService filesService; private final ParticipantProvider participantProvider; private final CertificationProvider certificationProvider; private final ItemProvider itemProvider; @@ -51,6 +53,7 @@ public List getPreActivityInstances(User user, LocalDate ta for (Participant participant : participants) { Instance instance = participant.getInstance(); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); PreActivityResponse preActivityResponse = PreActivityResponse.builder() .instanceId(instance.getId()) @@ -58,7 +61,7 @@ public List getPreActivityInstances(User user, LocalDate ta .participantCount(instance.getParticipantCount()) .pointPerPerson(instance.getPointPerPerson()) .remainDays(DateUtil.getRemainDaysToStart(participant.getStartedDate(), targetDate)) - .fileResponse(FileResponse.create(instance.getFiles())) + .fileResponse(fileResponse) .build(); preActivity.add(preActivityResponse); } @@ -72,12 +75,14 @@ public List getDoneInstances(User user, LocalDate targetDate) { for (Participant participant : participants) { Instance instance = participant.getInstance(); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); // 포인트를 아직 수령하지 않았을 때 if (participant.getRewardStatus() == NO) { Item item = itemProvider.findAllByCategory(POINT_MULTIPLIER).get(0); int numOfPassItem = ordersProvider.countNumOfItem(user, item.getId()); - DoneResponse doneResponse = DoneResponse.createNotRewarded(instance, participant, numOfPassItem); + DoneResponse doneResponse = DoneResponse.createNotRewarded( + instance, participant, numOfPassItem, fileResponse); doneResponse.setItemId(item.getId()); done.add(doneResponse); continue; @@ -85,7 +90,8 @@ public List getDoneInstances(User user, LocalDate targetDate) { // 포인트를 수령했을 때 double achievementRate = getAchievementRate(instance, participant.getId(), targetDate); - DoneResponse doneResponse = DoneResponse.createRewarded(instance, participant, achievementRate); + DoneResponse doneResponse = DoneResponse.createRewarded( + instance, participant, achievementRate, fileResponse); done.add(doneResponse); } @@ -107,6 +113,7 @@ public List getActivatedInstances(User user, LocalDate target for (Participant participant : participants) { Instance instance = participant.getInstance(); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); Certification certification = certificationProvider.findByDate(targetDate, participant.getId()) .orElse(getDummyCertification()); @@ -116,7 +123,7 @@ public List getActivatedInstances(User user, LocalDate target ActivatedResponse activatedResponse = ActivatedResponse.create( instance, certification.getCertificationStatus(), - numOfPassItem, participant.getRepositoryName() + numOfPassItem, participant.getRepositoryName(), fileResponse ); activatedResponse.setItemId(item.getId()); activated.add(activatedResponse); @@ -139,6 +146,8 @@ public DoneResponse getRewards(RewardRequest rewardRequest, boolean useItem) { Participant participant = participantProvider.findByJoinInfo(user.getId(), rewardRequest.instanceId()); Instance instance = participant.getInstance(); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + validRewardCondition(participant); int rewardPoints = instance.getPointPerPerson(); @@ -150,7 +159,7 @@ public DoneResponse getRewards(RewardRequest rewardRequest, boolean useItem) { double achievementRate = getAchievementRate(instance, participant.getId(), rewardRequest.targetDate()); participant.getRewards(rewardPoints); - return DoneResponse.createRewarded(instance, participant, achievementRate); + return DoneResponse.createRewarded(instance, participant, achievementRate, fileResponse); } private void validRewardCondition(Participant participant) { diff --git a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java index 262c7a80..3c6eac51 100644 --- a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java +++ b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java @@ -12,11 +12,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; @RestController @RequiredArgsConstructor @@ -34,13 +33,13 @@ public ResponseEntity checkNicknameDuplicate(@RequestParam(value @PostMapping("/auth/signup") public ResponseEntity> signup( - @RequestPart(value = "data") SignupRequest signupRequest, - @RequestPart(value = "files") MultipartFile multipartFile) { - Long signupUserId = userService.signup(signupRequest, multipartFile); - String identifier = userService.findUserById(signupUserId).getIdentifier(); + @RequestBody SignupRequest signupRequest) { + Long userId = userService.signup(signupRequest); + String identifier = userService.findUserById(userId).getIdentifier(); + SignupResponse signupResponse = new SignupResponse(userId, identifier); return ResponseEntity.ok().body( - new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), new SignupResponse(identifier)) + new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), signupResponse) ); } } diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index 8ca1f675..aefd1ec9 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -2,6 +2,7 @@ import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.global.file.domain.FileHolder; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.domain.BaseTimeEntity; @@ -34,7 +35,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "users") -public class User extends BaseTimeEntity { +public class User extends BaseTimeEntity implements FileHolder { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_id") @@ -115,25 +116,24 @@ public long updatePoints(Long amount) { return this.point; } + @Override public Optional getFiles() { return Optional.ofNullable(this.files); } - //=== 연관관계 편의 메서드 ===// + @Override public void setFiles(Files files) { this.files = files; } + //=== 연관관계 편의 메서드 ===// + public void updateUser(String nickname, String information, String tags) { this.nickname = nickname; this.information = information; this.tags = tags; } - public void setPoint(Long point) { - this.point += point; - } - public void deleteLikesList() { this.likesList.clear(); } diff --git a/src/main/java/com/genius/gitget/challenge/user/dto/UserProfileInfo.java b/src/main/java/com/genius/gitget/challenge/user/dto/UserProfileInfo.java index 1bb36ba4..4c213c81 100644 --- a/src/main/java/com/genius/gitget/challenge/user/dto/UserProfileInfo.java +++ b/src/main/java/com/genius/gitget/challenge/user/dto/UserProfileInfo.java @@ -9,7 +9,7 @@ public record UserProfileInfo( Long frameId, FileResponse fileResponse ) { - public static UserProfileInfo createByEntity(User user, Long frameId) { - return new UserProfileInfo(user.getId(), user.getNickname(), frameId, FileResponse.create(user.getFiles())); + public static UserProfileInfo createByEntity(User user, Long frameId, FileResponse fileResponse) { + return new UserProfileInfo(user.getId(), user.getNickname(), frameId, fileResponse); } } diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 0e786d41..5fee73c8 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -11,7 +11,7 @@ import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.dto.UserProfileInfo; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.util.exception.BusinessException; @@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; @Service @Slf4j @@ -55,7 +54,7 @@ public Long save(User user) { } @Transactional - public Long signup(SignupRequest requestUser, MultipartFile multipartFile) { + public Long signup(SignupRequest requestUser) { User user = findUserByIdentifier(requestUser.identifier()); isAlreadyRegistered(user); @@ -65,9 +64,6 @@ public Long signup(SignupRequest requestUser, MultipartFile multipartFile) { interest); updateRole(user); - Files files = filesService.uploadFile(multipartFile, "profile"); - user.setFiles(files); - return user.getId(); } @@ -108,6 +104,8 @@ public AuthResponse getUserAuthInfo(String identifier) { public UserProfileInfo getUserProfileInfo(User user) { Long frameId = ordersProvider.getUsingFrameItem(user.getId()).getId(); - return UserProfileInfo.createByEntity(user, frameId); + FileResponse fileResponse = filesService.convertToFileResponse(user.getFiles()); + + return UserProfileInfo.createByEntity(user, frameId, fileResponse); } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java b/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java new file mode 100644 index 00000000..8c7e2089 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java @@ -0,0 +1,103 @@ +package com.genius.gitget.global.file.controller; + +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FileManager; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.global.util.response.dto.SingleResponse; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * FileManager의 기능을 별도로 테스트하기 위해 생성한 컨트롤러입니다. + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/file/test") +public class FileTestController { + private final FilesService filesService; + private final FileManager fileManager; + + + @GetMapping("/{fileId}") + public ResponseEntity> download( + @PathVariable Long fileId + ) { + Files files = filesService.findById(fileId); + FileResponse fileResponse = filesService.convertToFileResponse(Optional.ofNullable(files)); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) + ); + } + + @PostMapping + public ResponseEntity> upload( + @RequestParam("files") MultipartFile multipartFile, + @RequestParam("type") String type + ) { + FileType fileType = FileType.findType(type); + Files files = filesService.uploadFile(multipartFile, fileType); + String encodedImage = fileManager.getEncodedImage(files); + FileResponse fileResponse = FileResponse.createExistFile(files.getId(), encodedImage); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) + ); + } + + @PatchMapping("/{fileId}") + public ResponseEntity> update( + @PathVariable Long fileId, + @RequestParam("files") MultipartFile multipartFile) { + Files files = filesService.updateFile(fileId, multipartFile); + String encodedImage = fileManager.getEncodedImage(files); + FileResponse fileResponse = FileResponse.createExistFile(files.getId(), encodedImage); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) + ); + } + + @PostMapping("/{fileId}") + public ResponseEntity> copy( + @PathVariable Long fileId, + @RequestParam("type") String type) { + + FileType fileType = FileType.findType(type); + Files files = filesService.findById(fileId); + Files copiedFile = filesService.copyFile(files, fileType); + + String encodedImage = fileManager.getEncodedImage(copiedFile); + FileResponse fileResponse = FileResponse.createExistFile(copiedFile.getId(), encodedImage); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) + ); + } + + @DeleteMapping("/{fileId}") + public ResponseEntity delete( + @PathVariable Long fileId + ) { + filesService.deleteFile(fileId); + + return ResponseEntity.ok().body( + new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + ); + } +} diff --git a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java index 8048d856..71e270c0 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -1,24 +1,23 @@ package com.genius.gitget.global.file.controller; -import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.global.file.domain.FileHolder; +import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FileHolderFinder; import com.genius.gitget.global.file.service.FilesService; -import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; -import java.io.IOException; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -27,52 +26,46 @@ @RequiredArgsConstructor @RequestMapping("/api/file") public class FilesController { + private final FileHolderFinder finder; private final FilesService filesService; - @PostMapping - public ResponseEntity> uploadImage( - @RequestPart(value = "data") InstanceCreateRequest instanceCreateRequest, - @RequestPart(value = "files") MultipartFile multipartFile, - @RequestPart(value = "type") String type) throws IOException { - Files files = filesService.uploadFile(multipartFile, type); - FileResponse fileResponse = FileResponse.createExistFile(files); + @PostMapping("/{id}") + public ResponseEntity> uploadFile( + @PathVariable Long id, + @RequestParam("type") String type, + @RequestParam(value = "files", required = false) MultipartFile multipartFile + ) { + FileType fileType = FileType.findType(type); + FileHolder fileHolder = finder.findByInfo(id, fileType); + Files files; - return ResponseEntity.ok().body( - new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), fileResponse) - ); - } - - @GetMapping(value = {"/{fileId}"}) - public ResponseEntity> getImage(@PathVariable(name = "fileId") Long fileId) - throws IOException { - - FileResponse encodedFile = filesService.getEncodedFile(fileId); - - return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), encodedFile) - ); - } + if (multipartFile == null && fileType == FileType.INSTANCE) { + files = filesService.copyTopicToInstance(fileHolder); + } else { + files = filesService.uploadFile(fileHolder, multipartFile, fileType); + } - @PostMapping("/{fileId}") - public ResponseEntity> updateImage( - @RequestPart(value = "files") MultipartFile multipartFile, - @PathVariable Long fileId - ) throws IOException { - Files files = filesService.updateFile(fileId, multipartFile); + FileResponse fileResponse = filesService.convertToFileResponse(Optional.ofNullable(files)); return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), - FileResponse.createExistFile(files)) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) ); } - @DeleteMapping("/{fileId}") - public ResponseEntity deleteImage(@PathVariable Long fileId) throws IOException { - filesService.deleteFile(fileId); + @PatchMapping("/{id}") + public ResponseEntity> updateFile( + @PathVariable Long id, + @RequestParam("type") String type, + @RequestParam("files") MultipartFile multipartFile + ) { + FileType fileType = FileType.findType(type); + FileHolder fileHolder = finder.findByInfo(id, fileType); + Files files = filesService.updateFile(fileHolder.getFiles(), multipartFile); + FileResponse fileResponse = filesService.convertToFileResponse(Optional.ofNullable(files)); return ResponseEntity.ok().body( - new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) ); } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/file/domain/FileHolder.java b/src/main/java/com/genius/gitget/global/file/domain/FileHolder.java new file mode 100644 index 00000000..3618a67f --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/domain/FileHolder.java @@ -0,0 +1,9 @@ +package com.genius.gitget.global.file.domain; + +import java.util.Optional; + +public interface FileHolder { + Optional getFiles(); + + void setFiles(Files files); +} diff --git a/src/main/java/com/genius/gitget/global/file/domain/Files.java b/src/main/java/com/genius/gitget/global/file/domain/Files.java index f9e65331..b988603b 100644 --- a/src/main/java/com/genius/gitget/global/file/domain/Files.java +++ b/src/main/java/com/genius/gitget/global/file/domain/Files.java @@ -1,5 +1,6 @@ package com.genius.gitget.global.file.domain; +import com.genius.gitget.global.file.dto.FileDTO; import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.util.domain.BaseTimeEntity; import jakarta.persistence.Column; @@ -40,6 +41,15 @@ public Files(FileType fileType, String originalFilename, String savedFilename, S this.fileURI = fileURI; } + public static Files create(FileDTO fileDTO) { + return Files.builder() + .originalFilename(fileDTO.originalFilename()) + .savedFilename(fileDTO.savedFilename()) + .fileType(fileDTO.fileType()) + .fileURI(fileDTO.fileURI()) + .build(); + } + //== 비지니스 로직 ==// public void updateFiles(UpdateDTO updateDTO) { this.originalFilename = updateDTO.originalFilename(); diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileDTO.java b/src/main/java/com/genius/gitget/global/file/dto/FileDTO.java new file mode 100644 index 00000000..c73d4f6c --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/dto/FileDTO.java @@ -0,0 +1,23 @@ +package com.genius.gitget.global.file.dto; + +import com.genius.gitget.global.file.domain.FileType; +import lombok.Builder; + +/** + * FileDTO는 Files 객체 생성에 필요한 값들을 담어서 전달하는 역할을 합니다. + * + * @param fileType 저장하고자하는 이미지의 타입 + * (TOPIC, INSTANCE, PROFILE 중 택1) + * @param originalFilename 사용자로부터 받은 이미지의 이름 + * (ex: sky.jpeg) + * @param savedFilename 각 이미지를 식별하기 위해 UUID를 부여하여 만든 이미지의 이름 + * (ex:10ab2c6f-77d7-435e-96f0-e75b67213528.jpeg) + * @param fileURI 이미지가 저장되는 경로 + * (ex: /Users/seonghuiyeon/GitGet/images/topic/10ab2c6f-77d7-435e-96f0-e75b67213528.jpeg) + */ +@Builder +public record FileDTO(FileType fileType, + String originalFilename, + String savedFilename, + String fileURI) { +} diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index 650dbc41..ec485146 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -1,22 +1,11 @@ package com.genius.gitget.global.file.dto; -import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.service.FileUtil; -import java.util.Optional; - public record FileResponse( Long fileId, String encodedFile) { - public static FileResponse create(Optional optionalFiles) { - if (optionalFiles.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(optionalFiles.get()); - } - - public static FileResponse createExistFile(Files files) { - return new FileResponse(files.getId(), FileUtil.encodedImage(files)); + public static FileResponse createExistFile(Long filesId, String encodedFile) { + return new FileResponse(filesId, encodedFile); } public static FileResponse createNotExistFile() { diff --git a/src/main/java/com/genius/gitget/global/file/dto/UpdateDTO.java b/src/main/java/com/genius/gitget/global/file/dto/UpdateDTO.java index 99ad7c40..adbbe011 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/UpdateDTO.java +++ b/src/main/java/com/genius/gitget/global/file/dto/UpdateDTO.java @@ -2,10 +2,28 @@ import lombok.Builder; +/** + * UpdateDTO는 Files 객체의 갱신에 필요한 값들을 담는 객체입니다. + * + * @param originalFilename 사용자로부터 받은 이미지의 이름 + * (ex: sky.jpeg) + * @param savedFilename 각 이미지를 식별하기 위해 UUID를 부여하여 만든 이미지의 이름 + * (ex:10ab2c6f-77d7-435e-96f0-e75b67213528.jpeg) + * @param fileURI 이미지가 저장되는 경로 + * (ex: /Users/seonghuiyeon/GitGet/images/topic/10ab2c6f-77d7-435e-96f0-e75b67213528.jpeg) + */ @Builder public record UpdateDTO( String originalFilename, String savedFilename, String fileURI ) { + + public static UpdateDTO of(FileDTO fileDTO) { + return UpdateDTO.builder() + .originalFilename(fileDTO.originalFilename()) + .savedFilename(fileDTO.savedFilename()) + .fileURI(fileDTO.fileURI()) + .build(); + } } diff --git a/src/main/java/com/genius/gitget/global/file/dto/UploadDTO.java b/src/main/java/com/genius/gitget/global/file/dto/UploadDTO.java deleted file mode 100644 index e2890f6b..00000000 --- a/src/main/java/com/genius/gitget/global/file/dto/UploadDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.genius.gitget.global.file.dto; - -import com.genius.gitget.global.file.domain.FileType; -import lombok.Builder; - -@Builder -public record UploadDTO(FileType fileType, - String originalFilename, - String savedFilename, - String fileURI) { -} diff --git a/src/main/java/com/genius/gitget/global/file/service/FileHolderFinder.java b/src/main/java/com/genius/gitget/global/file/service/FileHolderFinder.java new file mode 100644 index 00000000..5055e753 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/service/FileHolderFinder.java @@ -0,0 +1,42 @@ +package com.genius.gitget.global.file.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; + +import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.FileHolder; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.util.exception.BusinessException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class FileHolderFinder { + private final UserRepository userRepository; + private final TopicRepository topicRepository; + private final InstanceRepository instanceRepository; + + public FileHolder findByInfo(Long id, FileType fileType) { + switch (fileType) { + case TOPIC -> { + return topicRepository.findById(id) + .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); + } + case INSTANCE -> { + return instanceRepository.findById(id) + .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); + } + case PROFILE -> { + return userRepository.findById(id) + .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); + } + } + throw new BusinessException(); + } +} diff --git a/src/main/java/com/genius/gitget/global/file/service/FileManager.java b/src/main/java/com/genius/gitget/global/file/service/FileManager.java new file mode 100644 index 00000000..1fb41eeb --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/service/FileManager.java @@ -0,0 +1,58 @@ +package com.genius.gitget.global.file.service; + +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileDTO; +import com.genius.gitget.global.file.dto.UpdateDTO; +import com.genius.gitget.global.util.exception.BusinessException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +@Transactional(readOnly = true) +public interface FileManager { + + /** + * Files 내에 저장된 값들을 통해 UrlResource 등으로 다운받은 후, base64로 인코딩한 결과 반환 + * + * @param files 얻기 원하는 파일의 정보를 담고 있는 Files 객체 + * @return base64로 encode한 결과 값(문자열) + */ + String getEncodedImage(Files files); + + /** + * 전달한 파일 저장 후, Files 객체 형성에 필요한 정보를 담은 객체 반환 + * + * @param multipartFile 저장하고자 전달한 파일 + * @param fileType 저장하고자하는 파일의 종류 (Topic, Instance, Profile 중 1) + * @return Files 객체 생성에 필요한 정보(UploadDTO) 반환 + */ + FileDTO upload(MultipartFile multipartFile, FileType fileType); + + /** + * 기존에 저장소에 저장되어 있던 파일을 특정 타입에 복사 후, Files 객체 생성에 필요한 정보들을 반환 + * + * @param files 복사하고자하는 파일의 정보를 담고 있는 Files 객체 + * @param fileType 복사해서 적용하고 싶은 대상의 파일 타입(TOPIC/INSTANCE/PROFILE 중 택 1) + * @return Files 객체 생성에 필요한 정보(UploadDTO) 반환 + */ + FileDTO copy(Files files, FileType fileType); + + /** + * Files에 해당하는 이미지를 찾아서 삭제 및 새로운 이미지 저장 후, Files 내용 갱신에 필요한 정보들을 반환 + * + * @param files 대체 하고자하는 대상 객체 + * @param multipartFile 저장하고자하는 파일 + * @return Files 내용 갱신에 필요한 정보(UpdateDTO) 반환 + */ + UpdateDTO update(Files files, MultipartFile multipartFile); + + /** + * Files 객체 내의 정보를 활용하여 저장소(Local/S3)에서 해당 파일 삭제. + * + * @param files 삭제하고자하는 Files 객체 + * @throws BusinessException 삭제에 실패했을 때 발생 + */ + void deleteInStorage(Files files); +} diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index 6d16ad2d..d6a3309d 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -1,33 +1,31 @@ package com.genius.gitget.global.file.service; -import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_COPIED; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; -import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_SAVED; import static com.genius.gitget.global.util.exception.ErrorCode.IMAGE_NOT_ENCODED; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.CopyDTO; -import com.genius.gitget.global.file.dto.UpdateDTO; -import com.genius.gitget.global.file.dto.UploadDTO; +import com.genius.gitget.global.file.dto.FileDTO; import com.genius.gitget.global.util.exception.BusinessException; -import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.StandardCopyOption; import java.util.Base64; import java.util.List; import java.util.Objects; import java.util.UUID; import org.springframework.core.io.UrlResource; +import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +@Component public class FileUtil { - private static final List validExtensions = List.of("jpg", "jpeg", "png", "gif"); + private final List validExtensions = List.of("jpg", "jpeg", "png", "gif"); public static String encodedImage(Files files) { try { + //TODO: local 환경에 종속된 메서드이므로 종속되지 않게 수정 필요 UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); @@ -37,12 +35,11 @@ public static String encodedImage(Files files) { } } - public static UploadDTO getUploadInfo(MultipartFile file, String typeStr, final String UPLOAD_PATH) { + public FileDTO getFileDTO(MultipartFile file, FileType fileType, final String UPLOAD_PATH) { String originalFilename = file.getOriginalFilename(); String savedFilename = getSavedFilename(originalFilename); - FileType fileType = FileType.findType(typeStr); - return UploadDTO.builder() + return FileDTO.builder() .fileType(fileType) .originalFilename(originalFilename) .savedFilename(savedFilename) @@ -50,28 +47,7 @@ public static UploadDTO getUploadInfo(MultipartFile file, String typeStr, final .build(); } - public static UpdateDTO getUpdateInfo(MultipartFile file, FileType fileType, final String UPLOAD_PATH) { - String originalFilename = file.getOriginalFilename(); - String savedFilename = getSavedFilename(originalFilename); - - return UpdateDTO.builder() - .originalFilename(originalFilename) - .savedFilename(savedFilename) - .fileURI(UPLOAD_PATH + fileType.getPath() + savedFilename) - .build(); - } - - public static void saveFile(MultipartFile file, String fileURI) { - try { - File targetFile = new File(fileURI); - createPath(fileURI); - file.transferTo(targetFile); - } catch (IOException e) { - throw new BusinessException(FILE_NOT_SAVED); - } - } - - public static CopyDTO getCopyInfo(Files files, FileType fileType, final String UPLOAD_PATH) { + public CopyDTO getCopyInfo(Files files, FileType fileType, final String UPLOAD_PATH) { String originalFilename = files.getOriginalFilename(); String savedFilename = getSavedFilename(originalFilename); @@ -84,20 +60,7 @@ public static CopyDTO getCopyInfo(Files files, FileType fileType, final String U .build(); } - public static void copyImage(String originFilePath, CopyDTO copyDTO) { - File originFile = new File(originFilePath); - File copyFile = new File(copyDTO.fileURI()); - - try { - createPath(copyDTO.folderURI()); - java.nio.file.Files.copy(originFile.toPath(), copyFile.toPath(), - StandardCopyOption.COPY_ATTRIBUTES); - } catch (IOException e) { - throw new BusinessException(FILE_NOT_COPIED); - } - } - - public static void validateFile(MultipartFile file) { + public void validateFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (originalFilename == null || Objects.equals(originalFilename, "")) { @@ -111,22 +74,15 @@ public static void validateFile(MultipartFile file) { } } - public static String getSavedFilename(String originalFilename) { + public String getSavedFilename(String originalFilename) { String uuid = UUID.randomUUID().toString(); String extension = extractExtension(originalFilename); return uuid + "." + extension; } - private static String extractExtension(String filename) { + private String extractExtension(String filename) { int index = filename.lastIndexOf("."); return filename.substring(index + 1).toLowerCase(); } - - private static void createPath(String uri) { - File file = new File(uri); - if (!file.exists()) { - file.mkdirs(); - } - } } diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index 5e08c123..42e8a4c5 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -1,20 +1,20 @@ package com.genius.gitget.global.file.service; -import static com.genius.gitget.global.file.domain.FileType.INSTANCE; -import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_DELETED; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.MULTIPART_FILE_NOT_EXIST; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.global.file.domain.FileHolder; +import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.dto.CopyDTO; +import com.genius.gitget.global.file.dto.FileDTO; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.dto.UpdateDTO; -import com.genius.gitget.global.file.dto.UploadDTO; import com.genius.gitget.global.file.repository.FilesRepository; import com.genius.gitget.global.util.exception.BusinessException; -import java.io.File; import java.util.Optional; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -22,65 +22,84 @@ @Slf4j @Service @Transactional(readOnly = true) +@RequiredArgsConstructor public class FilesService { - private final String UPLOAD_PATH; + private final FileManager fileManager; private final FilesRepository filesRepository; - public FilesService(@Value("${file.upload.path}") String UPLOAD_PATH, FilesRepository filesRepository) { - this.UPLOAD_PATH = UPLOAD_PATH; - this.filesRepository = filesRepository; + + @Transactional + public Files uploadFile(MultipartFile multipartFile, FileType fileType) { + FileDTO fileDTO = fileManager.upload(multipartFile, fileType); + + Files file = Files.builder() + .originalFilename(fileDTO.originalFilename()) + .savedFilename(fileDTO.savedFilename()) + .fileType(fileDTO.fileType()) + .fileURI(fileDTO.fileURI()) + .build(); + return filesRepository.save(file); } @Transactional - public Files uploadFile(Optional optionalFiles, MultipartFile receivedFile, String typeStr) { - if (receivedFile != null) { - return uploadFile(receivedFile, typeStr); + public Files uploadFile(FileHolder fileHolder, MultipartFile multipartFile, FileType fileType) { + if (multipartFile == null) { + throw new BusinessException(MULTIPART_FILE_NOT_EXIST); } + FileDTO fileDTO = fileManager.upload(multipartFile, fileType); - Files files = optionalFiles.orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - CopyDTO copyDTO = FileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); - //REFACTOR: 정적 팩토리 메서드로 처리하면 깔끔할 듯! - Files copyFiles = Files.builder() - .originalFilename(copyDTO.originalFilename()) - .savedFilename(copyDTO.savedFilename()) - .fileType(copyDTO.fileType()) - .fileURI(copyDTO.fileURI()) + Files file = Files.builder() + .originalFilename(fileDTO.originalFilename()) + .savedFilename(fileDTO.savedFilename()) + .fileType(fileDTO.fileType()) + .fileURI(fileDTO.fileURI()) .build(); + fileHolder.setFiles(file); + return filesRepository.save(file); + } - FileUtil.copyImage(files.getFileURI(), copyDTO); + @Transactional + public Files copyTopicToInstance(FileHolder fileHolder) { + Instance instance = (Instance) fileHolder; + Files topicFiles = instance.getTopic().getFiles() + .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - return filesRepository.save(copyFiles); + Files instanceFiles = copyFile(topicFiles, FileType.INSTANCE); + instance.setFiles(instanceFiles); + + return instanceFiles; } @Transactional - public Files uploadFile(MultipartFile receivedFile, String typeStr) { - FileUtil.validateFile(receivedFile); - UploadDTO uploadDTO = FileUtil.getUploadInfo(receivedFile, typeStr, UPLOAD_PATH); - FileUtil.saveFile(receivedFile, uploadDTO.fileURI()); - - Files file = Files.builder() - .originalFilename(uploadDTO.originalFilename()) - .savedFilename(uploadDTO.savedFilename()) - .fileType(uploadDTO.fileType()) - .fileURI(uploadDTO.fileURI()) - .build(); + public Files copyFile(Files files, FileType fileType) { + FileDTO fileDTO = fileManager.copy(files, fileType); - return filesRepository.save(file); + Files copyFiles = Files.create(fileDTO); + return filesRepository.save(copyFiles); } @Transactional - public Files updateFile(Long fileId, MultipartFile file) { + public Files updateFile(Long fileId, MultipartFile multipartFile) { Files files = filesRepository.findById(fileId) .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - if (file == null) { + if (multipartFile == null) { return files; } - deleteFilesInStorage(files); + UpdateDTO updateDTO = fileManager.update(files, multipartFile); + files.updateFiles(updateDTO); + return files; + } - UpdateDTO updateDTO = FileUtil.getUpdateInfo(file, files.getFileType(), UPLOAD_PATH); - FileUtil.saveFile(file, updateDTO.fileURI()); + @Transactional + public Files updateFile(Optional optionalFiles, MultipartFile multipartFile) { + Files files = optionalFiles.orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); + if (multipartFile == null) { + return files; + } + + UpdateDTO updateDTO = fileManager.update(files, multipartFile); files.updateFiles(updateDTO); return files; } @@ -95,29 +114,32 @@ public void deleteFile(Long fileId) { Files files = filesRepository.findById(fileId) .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - deleteFilesInStorage(files); + fileManager.deleteInStorage(files); filesRepository.delete(files); } - private void deleteFilesInStorage(Files files) { - String fileURI = files.getFileURI(); - File targetFile = new File(fileURI); - if (!targetFile.delete()) { - throw new BusinessException(FILE_NOT_DELETED); + @Transactional + public void deleteFile(Optional optionalFiles) { + if (optionalFiles.isEmpty()) { + return; } - } + Files files = optionalFiles.get(); - public FileResponse getEncodedFile(Long fileId) { - Optional optionalFiles = filesRepository.findById(fileId); - return optionalFiles - .map(FileResponse::createExistFile) - .orElseGet(FileResponse::createNotExistFile); + fileManager.deleteInStorage(files); + filesRepository.delete(files); + } + public Files findById(Long fileId) { + return filesRepository.findById(fileId) + .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); } - public FileResponse getEncodedFile(Optional optionalFiles) { + public FileResponse convertToFileResponse(Optional optionalFiles) { return optionalFiles - .map(FileResponse::createExistFile) + .map(files -> { + String encodedImage = fileManager.getEncodedImage(files); + return FileResponse.createExistFile(files.getId(), encodedImage); + }) .orElseGet(FileResponse::createNotExistFile); } } diff --git a/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java b/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java new file mode 100644 index 00000000..5a5fabec --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java @@ -0,0 +1,104 @@ +package com.genius.gitget.global.file.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_COPIED; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_DELETED; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_SAVED; + +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.CopyDTO; +import com.genius.gitget.global.file.dto.FileDTO; +import com.genius.gitget.global.file.dto.UpdateDTO; +import com.genius.gitget.global.util.exception.BusinessException; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardCopyOption; +import java.util.Base64; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.UrlResource; +import org.springframework.web.multipart.MultipartFile; + +public class LocalFileManager implements FileManager { + private final String UPLOAD_PATH; + private final FileUtil fileUtil; + + + public LocalFileManager(FileUtil fileUtil, @Value("${file.upload.path}") String UPLOAD_PATH) { + this.fileUtil = fileUtil; + this.UPLOAD_PATH = UPLOAD_PATH; + } + + @Override + public FileDTO upload(MultipartFile multipartFile, FileType fileType) { + fileUtil.validateFile(multipartFile); + FileDTO fileDTO = fileUtil.getFileDTO(multipartFile, fileType, UPLOAD_PATH); + + try { + File file = new File(fileDTO.fileURI()); + createPath(fileDTO.fileURI()); + multipartFile.transferTo(file); + } catch (IOException e) { + throw new BusinessException(FILE_NOT_SAVED); + } + + return fileDTO; + } + + @Override + public String getEncodedImage(Files files) { + try { + UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); + byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); + return new String(encode, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new BusinessException(e); + } + } + + @Override + public FileDTO copy(Files files, FileType fileType) { + CopyDTO copyDTO = fileUtil.getCopyInfo(files, fileType, UPLOAD_PATH); + createPath(copyDTO.folderURI()); + + File originFile = new File(files.getFileURI()); + File copyFile = new File(copyDTO.fileURI()); + + try { + java.nio.file.Files.copy(originFile.toPath(), copyFile.toPath(), + StandardCopyOption.COPY_ATTRIBUTES); + } catch (IOException e) { + throw new BusinessException(FILE_NOT_COPIED); + } + return FileDTO.builder() + .fileType(fileType) + .originalFilename(copyDTO.originalFilename()) + .savedFilename(copyDTO.savedFilename()) + .fileURI(copyDTO.fileURI()) + .build(); + } + + @Override + public UpdateDTO update(Files files, MultipartFile multipartFile) { + deleteInStorage(files); + FileDTO fileDTO = upload(multipartFile, files.getFileType()); + + return UpdateDTO.of(fileDTO); + } + + @Override + public void deleteInStorage(Files files) { + String fileURI = files.getFileURI(); + File targetFile = new File(fileURI); + if (!targetFile.delete()) { + throw new BusinessException(FILE_NOT_DELETED); + } + } + + private void createPath(String uri) { + File file = new File(uri); + if (!file.exists()) { + file.mkdirs(); + } + } +} diff --git a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java b/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java new file mode 100644 index 00000000..15c28f09 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java @@ -0,0 +1,46 @@ +package com.genius.gitget.global.file.service; + +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.FileDTO; +import com.genius.gitget.global.file.dto.UpdateDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.web.multipart.MultipartFile; + +/** + * !!!이 클래스의 모든 주석은 삭제해도 무방합니다!!! + *

+ * S3 bucket에 이미지를 업로드하는 코드를 구현하는 곳 + * 파일 시스템 구조 확장에 참고한 링크 + * https://chb2005.tistory.com/200#3.5.%20%ED%8C%8C%EC%9D%BC%20%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20%EA%B5%AC%ED%98%84 + * https://docs.aws.amazon.com/ko_kr/sdk-for-java/v1/developer-guide/examples-s3-objects.html#upload-object + */ +@RequiredArgsConstructor +public class S3FileManager implements FileManager { + private final FileUtil fileUtil; + + + @Override + public String getEncodedImage(Files files) { + return null; + } + + @Override + public FileDTO upload(MultipartFile multipartFile, FileType fileType) { + return null; + } + + @Override + public FileDTO copy(Files files, FileType fileType) { + return null; + } + + @Override + public UpdateDTO update(Files files, MultipartFile multipartFile) { + return null; + } + + @Override + public void deleteInStorage(Files files) { + } +} diff --git a/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java b/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java index 82439227..a420e497 100644 --- a/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java +++ b/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java @@ -1,6 +1,7 @@ package com.genius.gitget.global.security.dto; public record SignupResponse( + Long userId, String identifier ) { } diff --git a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java index 5958b7a0..b3169b6f 100644 --- a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java @@ -1,20 +1,41 @@ package com.genius.gitget.global.util.config; +import com.genius.gitget.global.file.service.FileManager; +import com.genius.gitget.global.file.service.FileUtil; +import com.genius.gitget.global.file.service.LocalFileManager; +import com.genius.gitget.global.file.service.S3FileManager; import com.genius.gitget.global.util.formatter.LocalDateFormatter; import com.genius.gitget.global.util.formatter.LocalDateTimeFormatter; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.security.crypto.encrypt.AesBytesEncryptor; +@Slf4j @Configuration -@PropertySource("classpath:application-common.yml") @RequiredArgsConstructor public class AppConfig { private final Environment env; + @Bean + public FileUtil fileUtil() { + return new FileUtil(); + } + + @Bean + public FileManager fileManager() { + final String fileMode = env.getProperty("file.mode"); + final String UPLOAD_PATH = env.getProperty("file.upload.path"); + assert fileMode != null; + + if (fileMode.equals("local")) { + return new LocalFileManager(fileUtil(), UPLOAD_PATH); + } + return new S3FileManager(fileUtil()); + } + @Bean public AesBytesEncryptor aesBytesEncryptor() { return new AesBytesEncryptor( diff --git a/src/main/java/com/genius/gitget/challenge/config/WebConfig.java b/src/main/java/com/genius/gitget/global/util/config/WebConfig.java similarity index 90% rename from src/main/java/com/genius/gitget/challenge/config/WebConfig.java rename to src/main/java/com/genius/gitget/global/util/config/WebConfig.java index 153a8eb4..3d0de4b4 100644 --- a/src/main/java/com/genius/gitget/challenge/config/WebConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/WebConfig.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.config; +package com.genius.gitget.global.util.config; import com.genius.gitget.challenge.instance.service.StringToEnum; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 1cf5a37c..d34110cd 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -43,6 +43,7 @@ public enum ErrorCode { JWT_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."), + MULTIPART_FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "MultipartFile이 전달되지 않았습니다."), FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "해당 파일(이미지)이 존재하지 않습니다."), NOT_SUPPORTED_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 확장자입니다."), NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 타입입니다."), diff --git a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java index 90178329..3b6ff110 100644 --- a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java +++ b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java @@ -1,11 +1,13 @@ package com.genius.gitget.profile.controller; +import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; + import com.genius.gitget.global.security.domain.UserPrincipal; -import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import com.genius.gitget.profile.dto.UserChallengeResultResponse; import com.genius.gitget.profile.dto.UserDetailsInformationResponse; +import com.genius.gitget.profile.dto.UserIndexResponse; import com.genius.gitget.profile.dto.UserInformationRequest; import com.genius.gitget.profile.dto.UserInformationResponse; import com.genius.gitget.profile.dto.UserInformationUpdateRequest; @@ -22,9 +24,7 @@ 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.RequestPart; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; @RestController @RequiredArgsConstructor @@ -39,7 +39,7 @@ public ResponseEntity> getUserDet UserDetailsInformationResponse userInformation = profileService.getUserDetailsInformation( userPrincipal.getUser()); return ResponseEntity.ok() - .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userInformation) ); } @@ -50,22 +50,23 @@ public ResponseEntity> getUserInformatio @RequestBody UserInformationRequest userInformationRequest) { UserInformationResponse userInformation = profileService.getUserInformation(userInformationRequest.getUserId()); return ResponseEntity.ok() - .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userInformation) ); } // 마이페이지 - 회원 정보 수정 @PostMapping("/information") - public ResponseEntity updateUserInformation(@AuthenticationPrincipal UserPrincipal userPrincipal, - @RequestPart(value = "data") UserInformationUpdateRequest userInformationUpdateRequest, - @RequestPart(value = "files", required = false) MultipartFile multipartFile, - @RequestPart(value = "type") String type) { - profileService.updateUserInformation(userPrincipal.getUser(), userInformationUpdateRequest, multipartFile, - type); + public ResponseEntity> updateUserInformation( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestBody UserInformationUpdateRequest userInformationUpdateRequest) { - return ResponseEntity.ok() - .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); + Long userId = profileService.updateUserInformation(userPrincipal.getUser(), userInformationUpdateRequest); + UserIndexResponse userIndexResponse = new UserIndexResponse(userId); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userIndexResponse) + ); } // 마이페이지 - 관심사 조회 @@ -75,7 +76,7 @@ public ResponseEntity> getUserInterest( UserInterestResponse userInterest = profileService.getUserInterest(userPrincipal.getUser()); return ResponseEntity.ok() - .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userInterest)); } @@ -87,7 +88,7 @@ public ResponseEntity updateUserTags(@AuthenticationPrincipal Us profileService.updateUserTags(userPrincipal.getUser(), userInterestUpdateRequest); return ResponseEntity.ok() - .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); + .body(new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage())); } @@ -98,7 +99,7 @@ public ResponseEntity> getUserChalle UserChallengeResultResponse userChallengeResult = profileService.getUserChallengeResult( userPrincipal.getUser()); return ResponseEntity.ok() - .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userChallengeResult)); } @@ -110,7 +111,7 @@ public ResponseEntity deleteUserInformation(@AuthenticationPrinc profileService.deleteUserInformation(userPrincipal.getUser(), userSignoutRequest.getReason()); return ResponseEntity.ok() - .body(new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage())); + .body(new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage())); } @@ -121,7 +122,7 @@ public ResponseEntity> getUserPoint( UserPointResponse userPoint = profileService.getUserPoint(userPrincipal.getUser()); return ResponseEntity.ok() - .body(new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), + .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userPoint)); } } diff --git a/src/main/java/com/genius/gitget/profile/dto/UserDetailsInformationResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserDetailsInformationResponse.java index f3a71885..234f30e9 100644 --- a/src/main/java/com/genius/gitget/profile/dto/UserDetailsInformationResponse.java +++ b/src/main/java/com/genius/gitget/profile/dto/UserDetailsInformationResponse.java @@ -1,14 +1,13 @@ package com.genius.gitget.profile.dto; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import java.util.Optional; import lombok.Builder; import lombok.Data; @Data public class UserDetailsInformationResponse { + private Long userId; private String identifier; private String nickname; private String information; @@ -17,32 +16,27 @@ public class UserDetailsInformationResponse { private FileResponse fileResponse; @Builder - public UserDetailsInformationResponse(String identifier, String nickname, String information, Long point, - Files files, - int progressBar) { + public UserDetailsInformationResponse(Long userId, String identifier, String nickname, String information, + Long point, int progressBar, FileResponse fileResponse) { + this.userId = userId; this.identifier = identifier; this.nickname = nickname; this.information = information; this.point = point; - this.fileResponse = convertToFileResponse(Optional.ofNullable(files)); + this.fileResponse = fileResponse; this.progressBar = progressBar; } - public static UserDetailsInformationResponse createByEntity(User findUser, Files files, int participantCount) { + public static UserDetailsInformationResponse createByEntity(User findUser, int participantCount, + FileResponse fileResponse) { return UserDetailsInformationResponse.builder() + .userId(findUser.getId()) .identifier(findUser.getIdentifier()) .nickname(findUser.getNickname()) .information(findUser.getInformation()) .point(findUser.getPoint()) - .files(files) .progressBar(participantCount) + .fileResponse(fileResponse) .build(); } - - private static FileResponse convertToFileResponse(Optional files) { - if (files.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(files.get()); - } } diff --git a/src/main/java/com/genius/gitget/profile/dto/UserIndexResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserIndexResponse.java new file mode 100644 index 00000000..b19399c8 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/dto/UserIndexResponse.java @@ -0,0 +1,6 @@ +package com.genius.gitget.profile.dto; + +public record UserIndexResponse( + Long userId +) { +} diff --git a/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java b/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java index e868594a..cc7234ef 100644 --- a/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java +++ b/src/main/java/com/genius/gitget/profile/dto/UserInformationResponse.java @@ -1,40 +1,35 @@ package com.genius.gitget.profile.dto; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import java.util.Optional; import lombok.Builder; import lombok.Data; @Data public class UserInformationResponse { + private Long userId; private String identifier; private String nickname; private Long frameId; private FileResponse fileResponse; @Builder - public UserInformationResponse(String identifier, String nickname, Long frameId, Files files) { + public UserInformationResponse(Long userId, String identifier, String nickname, Long frameId, + FileResponse fileResponse) { + this.userId = userId; this.identifier = identifier; this.nickname = nickname; this.frameId = frameId; - this.fileResponse = convertToFileResponse(Optional.ofNullable(files)); + this.fileResponse = fileResponse; } - public static UserInformationResponse createByEntity(User findUser, Long frameId, Files files) { + public static UserInformationResponse createByEntity(User findUser, Long frameId, FileResponse fileResponse) { return UserInformationResponse.builder() + .userId(findUser.getId()) .identifier(findUser.getIdentifier()) .nickname(findUser.getNickname()) .frameId(frameId) - .files(files) + .fileResponse(fileResponse) .build(); } - - private static FileResponse convertToFileResponse(Optional files) { - if (files.isEmpty()) { - return FileResponse.createNotExistFile(); - } - return FileResponse.createExistFile(files.get()); - } } diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileService.java index b67c8eb1..3e446632 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileService.java @@ -11,9 +11,7 @@ import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.domain.FileType; -import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.repository.FilesRepository; +import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; @@ -33,7 +31,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; @Service @RequiredArgsConstructor @@ -41,15 +38,10 @@ @Transactional(readOnly = true) public class ProfileService { private final UserRepository userRepository; - private final FilesRepository filesRepository; private final FilesService filesService; private final SignoutRepository signoutRepository; private final OrdersProvider ordersProvider; - private static boolean isProfileFileType(Files files) { - return files != null && files.getFileType().equals(FileType.PROFILE); - } - // 포인트 조회 public UserPointResponse getUserPoint(User user) { return UserPointResponse.builder() @@ -62,12 +54,9 @@ public UserPointResponse getUserPoint(User user) { public UserInformationResponse getUserInformation(Long userId) { User findUser = getUserById(userId); Long frameId = ordersProvider.getUsingFrameItem(userId).getId(); - Files files = getFiles(findUser); - if (isProfileFileType(files)) { - return UserInformationResponse.createByEntity(findUser, frameId, files); - } else { - return UserInformationResponse.createByEntity(findUser, frameId, null); - } + + FileResponse fileResponse = filesService.convertToFileResponse(findUser.getFiles()); + return UserInformationResponse.createByEntity(findUser, frameId, fileResponse); } // 마이페이지 - 사용자 정보 상세 조회 @@ -82,39 +71,30 @@ public UserDetailsInformationResponse getUserDetailsInformation(User user) { participantCount = (joinResult == SUCCESS) ? participantCount + 1 : participantCount - 1; } } - Files files = getFiles(findUser); - if (isProfileFileType(files)) { - return UserDetailsInformationResponse.createByEntity(findUser, files, participantCount); - } else { - return UserDetailsInformationResponse.createByEntity(findUser, null, participantCount); - } + FileResponse fileResponse = filesService.convertToFileResponse(findUser.getFiles()); + return UserDetailsInformationResponse.createByEntity(findUser, participantCount, fileResponse); } // 마이페이지 - 사용자 정보 수정 @Transactional - public void updateUserInformation(User user, UserInformationUpdateRequest userInformationUpdateRequest, - MultipartFile multipartFile, String type) { + public Long updateUserInformation(User user, UserInformationUpdateRequest userInformationUpdateRequest) { User findUser = getUserByIdentifier(user.getIdentifier()); findUser.updateUserInformation( userInformationUpdateRequest.getNickname(), userInformationUpdateRequest.getInformation()); - if (multipartFile != null) { - if (findUser.getFiles().isEmpty()) { - Files uploadedFile = filesService.uploadFile(multipartFile, type); - findUser.setFiles(uploadedFile); - } else { - filesService.updateFile(findUser.getFiles().get().getId(), multipartFile); - } - } - userRepository.save(findUser); + User updatedUser = userRepository.save(findUser); + return updatedUser.getId(); } // 마이페이지 - 회원 탈퇴 @Transactional public void deleteUserInformation(User user, String reason) { User findUser = getUserByIdentifier(user.getIdentifier()); + + filesService.deleteFile(findUser.getFiles()); findUser.setFiles(null); + findUser.deleteLikesList(); userRepository.deleteById(findUser.getId()); signoutRepository.save( @@ -191,14 +171,6 @@ private User getUserByIdentifier(String identifier) { .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); } - private Files getFiles(User findUser) { - if (findUser.getFiles().isPresent()) { - return filesRepository.findById(findUser.getFiles().get().getId()).orElse(null); - } else { - return null; - } - } - private User getUserById(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java index 02a3d7d9..a85d65fd 100644 --- a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java @@ -11,11 +11,9 @@ import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; -import com.genius.gitget.util.file.FileTestUtil; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -27,7 +25,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.multipart.MultipartFile; @SpringBootTest @Transactional @@ -116,9 +113,6 @@ public void setup() { private Topic getSavedTopic() { - MultipartFile filename = FileTestUtil.getMultipartFile("sky"); - Files files = filesService.uploadFile(filename, "topic"); - Topic topic = topicRepository.save( Topic.builder() .title("title") @@ -128,8 +122,6 @@ private Topic getSavedTopic() { .pointPerPerson(100) .build() ); - topic.setFiles(files); - return topic; } } diff --git a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java index 6309f961..1ad86ab3 100644 --- a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java @@ -6,7 +6,6 @@ import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.util.file.FileTestUtil; import jakarta.transaction.Transactional; import java.util.Optional; import org.assertj.core.api.Assertions; @@ -51,8 +50,7 @@ public void setup() { //given TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); - Long savedTopicId = topicService.createTopic(topicCreateRequest, FileTestUtil.getMultipartFile("name"), - "topic"); + Long savedTopicId = topicService.createTopic(topicCreateRequest); //when TopicDetailResponse topicById = topicService.getTopicById(savedTopicId); @@ -65,8 +63,7 @@ public void setup() { public void 토픽_수정() throws Exception { //given TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); - Long savedTopicId = topicService.createTopic(topicCreateRequest, FileTestUtil.getMultipartFile("name"), - "topic"); + Long savedTopicId = topicService.createTopic(topicCreateRequest); //when TopicUpdateRequest topicUpdateRequest = TopicUpdateRequest.builder() @@ -76,7 +73,7 @@ public void setup() { .pointPerPerson(topic.getPointPerPerson()) .notice(topic.getNotice()).build(); - topicService.updateTopic(savedTopicId, topicUpdateRequest, FileTestUtil.getMultipartFile("name"), fileType); + topicService.updateTopic(savedTopicId, topicUpdateRequest); //then Optional findTopic = topicRepository.findById(savedTopicId); @@ -88,8 +85,7 @@ public void setup() { public void 토픽_삭제() throws Exception { //given TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); - Long savedTopicId = topicService.createTopic(topicCreateRequest, FileTestUtil.getMultipartFile("name"), - fileType); + Long savedTopicId = topicService.createTopic(topicCreateRequest); //when topicService.deleteTopic(savedTopicId); diff --git a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java index d050d2c2..d6bfe860 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java @@ -14,6 +14,7 @@ import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.util.TokenTestUtil; @@ -35,12 +36,13 @@ @SpringBootTest @Transactional public class InstanceControllerTest { + private static Topic savedTopic1, savedTopic2; + private static Instance savedInstance1, savedInstance2; MockMvc mockMvc; @Autowired WebApplicationContext context; @Autowired TokenTestUtil tokenTestUtil; - @Autowired TopicRepository topicRepository; @Autowired @@ -48,9 +50,6 @@ public class InstanceControllerTest { @Autowired FilesService filesService; - private static Topic savedTopic1, savedTopic2; - private static Instance savedInstance1, savedInstance2; - @BeforeEach public void setup() { mockMvc = MockMvcBuilders @@ -147,7 +146,7 @@ public void setup() { private Topic getSavedTopic() { MultipartFile filename = FileTestUtil.getMultipartFile("sky"); - Files files = filesService.uploadFile(filename, "topic"); + Files files = filesService.uploadFile(filename, FileType.TOPIC); Topic topic = topicRepository.save( Topic.builder() diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index a940dc98..f0e11a1f 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -195,7 +195,6 @@ private void createInstance(Topic savedTopic, Instance instance, String title) { .pointPerPerson(instance.getPointPerPerson()) .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()).build(), - FileTestUtil.getMultipartFile("name"), "instance", instance.getStartedDate().minusDays(3).toLocalDate()); } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java index cfb6b883..2f6fb164 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java @@ -9,7 +9,6 @@ import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.instance.repository.SearchRepository; -import com.genius.gitget.util.file.FileTestUtil; import java.io.IOException; import java.time.LocalDateTime; import lombok.extern.slf4j.Slf4j; @@ -91,7 +90,6 @@ private void createInstance(Topic savedTopic, Instance instance, String title) t .pointPerPerson(instance.getPointPerPerson()) .startedAt(instance.getStartedDate()) .completedAt(instance.getCompletedDate()).build(), - FileTestUtil.getMultipartFile("name"), "instance", instance.getCompletedDate().minusDays(3).toLocalDate()); } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java index ab1d1606..2e085a3a 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java @@ -17,7 +17,6 @@ import com.genius.gitget.global.file.repository.FilesRepository; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.util.file.FileTestUtil; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -32,7 +31,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; @SpringBootTest @Transactional @@ -51,6 +49,7 @@ public class InstanceServiceTest { private Instance instance, instance1, instance2; private Topic topic; + private Files topicFiles; private String fileType; @BeforeEach @@ -83,8 +82,7 @@ public void setup() { InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); //when - instanceService.createInstance(instanceCreateRequest, - FileTestUtil.getMultipartFile("name"), fileType, currentDate); + instanceService.createInstance(instanceCreateRequest, currentDate); //then List all = instanceRepository.findAll(); @@ -99,8 +97,7 @@ public void setup() { Topic savedTopic = topicRepository.save(topic); InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); - Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, - FileTestUtil.getMultipartFile("name"), fileType, currentDate); + Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, currentDate); InstanceUpdateRequest instanceUpdateRequest = InstanceUpdateRequest.builder() .topicId(savedTopic.getId()) @@ -111,8 +108,7 @@ public void setup() { .build(); //when - Long updatedInstanceId = instanceService.updateInstance(savedInstanceId, instanceUpdateRequest, - FileTestUtil.getMultipartFile("name"), fileType); + Long updatedInstanceId = instanceService.updateInstance(savedInstanceId, instanceUpdateRequest); //then Optional byId = instanceRepository.findById(updatedInstanceId); @@ -126,8 +122,7 @@ public void setup() { Topic savedTopic = topicRepository.save(topic); InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); - Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, - FileTestUtil.getMultipartFile("name"), fileType, currentDate); + Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, currentDate); //when InstanceDetailResponse instanceById = instanceService.getInstanceById(savedInstanceId); @@ -206,52 +201,6 @@ public void setup() { }); } - @Nested - public class 인스턴스_삭제할_때 { - private Topic topic; - private Instance instance1, instance2, instance3; - - @BeforeEach - public void setup() { - topic = getSavedTopic("1일 1공부", "BE, ML"); - instance1 = getSavedInstance("1일 1공부", "BE, ML", 100); - instance2 = getSavedInstance("1일 3공부", "BE, ML", 100); - instance3 = getSavedInstance("1일 3공부", "BE, ML", 100); - instance1.setTopic(topic); - instance2.setTopic(topic); - } - - @Test - public void 해당_아이디가_존재한다면_삭제할_수_있다() { - Long id = instance1.getId(); - instanceService.deleteInstance(id); - - assertThrows(BusinessException.class, () -> { - instanceService.getInstanceById(id); - }); - } - - @Test - public void 해당_아이디가_존재하지_않는다면_삭제할_수_없다() { - Long id = instance3.getId() + 1L; - assertThrows(BusinessException.class, () -> { - instanceService.deleteInstance(id); - }); - } - - @Test - public void 해당_인스턴스에_파일이_존재한다면_같이_삭제한다() { - MultipartFile filename = FileTestUtil.getMultipartFile("sky"); - Files files1 = filesService.uploadFile(filename, "instance"); - - instance1.setFiles(files1); - instanceRepository.save(instance1); - - instanceService.deleteInstance(instance1.getId()); - } - } - - private Topic getSavedTopic(String title, String tags) { Topic topic = topicRepository.save( Topic.builder() @@ -305,4 +254,38 @@ private Files getSavedFiles(String originalFilename, String savedFilename, Strin .build() ); } + + @Nested + public class 인스턴스_삭제할_때 { + private Topic topic; + private Instance instance1, instance2, instance3; + + @BeforeEach + public void setup() { + topic = getSavedTopic("1일 1공부", "BE, ML"); + instance1 = getSavedInstance("1일 1공부", "BE, ML", 100); + instance2 = getSavedInstance("1일 3공부", "BE, ML", 100); + instance3 = getSavedInstance("1일 3공부", "BE, ML", 100); + instance1.setTopic(topic); + instance2.setTopic(topic); + } + + @Test + public void 해당_아이디가_존재한다면_삭제할_수_있다() { + Long id = instance1.getId(); + instanceService.deleteInstance(id); + + assertThrows(BusinessException.class, () -> { + instanceService.getInstanceById(id); + }); + } + + @Test + public void 해당_아이디가_존재하지_않는다면_삭제할_수_없다() { + Long id = instance3.getId() + 1L; + assertThrows(BusinessException.class, () -> { + instanceService.deleteInstance(id); + }); + } + } } diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index 159a368f..c17c5d83 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -67,42 +67,6 @@ class ItemServiceTest { @Autowired private CertificationRepository certificationRepository; - @Nested - class 유저_포인트가_충분할_때 { - - @ParameterizedTest - @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) - public void 아이템을_구매할_수_있다_1(ItemCategory itemCategory) { - User user = getSavedUser(); - Item item = getSavedItem(itemCategory); - getSavedOrder(user, item, itemCategory, 0); - user.setPoint(1000L); - - ItemResponse itemResponse = itemService.orderItem(user, item.getId()); - - assertThat(itemResponse.getItemCategory()).isEqualTo(itemCategory); - } - - @ParameterizedTest - @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) - public void 아이템을_구매할_수_있다_2(ItemCategory itemCategory) { - User user = getSavedUser(); - Item item = getSavedItem(itemCategory); - getSavedOrder(user, item, itemCategory, 0); - user.setPoint(1000L); - - ItemResponse itemResponse1 = itemService.orderItem(user, item.getId()); - - assertThat(user.getPoint()).isEqualTo(900L); - assertThat(itemResponse1.getCount()).isEqualTo(1); - - ItemResponse itemResponse2 = itemService.orderItem(user, item.getId()); - - assertThat(user.getPoint()).isEqualTo(800L); - assertThat(itemResponse2.getCount()).isEqualTo(2); - } - } - @Test @DisplayName("데이터베이스에 저장되어 있는 모든 아이템 정보들을 받아올 수 있다.") public void should_getAllItems_when_itemsSaved() { @@ -489,7 +453,6 @@ public void should_throwException_when_equipStatusIsNotIS_USE() { assertThat(profileResponses.size()).isEqualTo(0); } - private User getSavedUser() { return userRepository.save( User.builder() @@ -550,7 +513,6 @@ private Participant getSavedParticipant(User user, Instance instance) { return participant; } - private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, Participant participant) { int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); @@ -563,4 +525,40 @@ private Certification getSavedCertification(CertificateStatus status, LocalDate certification.setParticipant(participant); return certificationRepository.save(certification); } + + @Nested + class 유저_포인트가_충분할_때 { + + @ParameterizedTest + @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) + public void 아이템을_구매할_수_있다_1(ItemCategory itemCategory) { + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + getSavedOrder(user, item, itemCategory, 0); + user.updatePoints(1000L); + + ItemResponse itemResponse = itemService.orderItem(user, item.getId()); + + assertThat(itemResponse.getItemCategory()).isEqualTo(itemCategory); + } + + @ParameterizedTest + @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) + public void 아이템을_구매할_수_있다_2(ItemCategory itemCategory) { + User user = getSavedUser(); + Item item = getSavedItem(itemCategory); + getSavedOrder(user, item, itemCategory, 0); + user.updatePoints(1000L); + + ItemResponse itemResponse1 = itemService.orderItem(user, item.getId()); + + assertThat(user.getPoint()).isEqualTo(900L); + assertThat(itemResponse1.getCount()).isEqualTo(1); + + ItemResponse itemResponse2 = itemService.orderItem(user, item.getId()); + + assertThat(user.getPoint()).isEqualTo(800L); + assertThat(itemResponse2.getCount()).isEqualTo(2); + } + } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java index ac085f68..eb9f78ed 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java @@ -21,6 +21,7 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.constants.ProviderInfo; @@ -229,7 +230,7 @@ private User getSavedUser() { private Topic getSavedTopic() { MultipartFile filename = FileTestUtil.getMultipartFile("sky"); - Files files = filesService.uploadFile(filename, "topic"); + Files files = filesService.uploadFile(filename, FileType.TOPIC); Topic topic = topicRepository.save( Topic.builder() diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index e7a7b8cf..d90d857c 100644 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -9,8 +9,6 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.domain.FileType; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.util.exception.BusinessException; @@ -60,12 +58,11 @@ public void should_matchValues_when_signupUser() { .information("information") .interest(List.of("관심사1", "관심사2")) .build(); - MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); //when User user = userService.findUserByIdentifier(identifier); - Long signupUserId = userService.signup(signupRequest, multipartFile); + Long signupUserId = userService.signup(signupRequest); User foundUser = userService.findUserById(signupUserId); //then @@ -75,10 +72,6 @@ public void should_matchValues_when_signupUser() { assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); assertThat(user.getTags()).isEqualTo(foundUser.getTags()); assertThat(user.getRole()).isEqualTo(Role.USER); - - Files files = user.getFiles().get(); - assertThat(files.getFileType()).isEqualTo(FileType.PROFILE); - assertThat(files.getOriginalFilename()).contains(multipartFile.getOriginalFilename()); } @Test @@ -96,7 +89,7 @@ public void should_setRoleAdmin_when_identifierMatchesWithAdmin() { MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); //when - Long signupUserId = userService.signup(signupRequest, multipartFile); + Long signupUserId = userService.signup(signupRequest); User signupUser = userService.findUserById(signupUserId); //then @@ -119,10 +112,10 @@ public void should_throwException_when_requestRegisterAgain() { //when User user = userService.findUserByIdentifier(identifier); - Long signupUserId = userService.signup(signupRequest, multipartFile); + Long signupUserId = userService.signup(signupRequest); //then - assertThatThrownBy(() -> userService.signup(signupRequest, multipartFile)) + assertThatThrownBy(() -> userService.signup(signupRequest)) .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.ALREADY_REGISTERED.getMessage()); } diff --git a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java index 99337796..cbaf07e4 100644 --- a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java @@ -10,10 +10,8 @@ import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.CopyDTO; -import com.genius.gitget.global.file.dto.UpdateDTO; -import com.genius.gitget.global.file.dto.UploadDTO; +import com.genius.gitget.global.file.dto.FileDTO; import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.util.file.FileTestUtil; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -32,6 +30,8 @@ @Transactional @ActiveProfiles({"file"}) class FileUtilTest { + @Autowired + private FileUtil fileUtil; @Autowired private FilesService filesService; @Value("${file.upload.path}") @@ -44,7 +44,7 @@ public void should_throwException_when_originFilenameIsNull() { MultipartFile multipartFile = getTestMultiPartFile(null); //when&then - assertThatThrownBy(() -> FileUtil.validateFile(multipartFile)) + assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) .hasMessageContaining(FILE_NOT_EXIST.getMessage()); } @@ -56,7 +56,7 @@ public void should_throwException_when_originFilenameIsBlank() { MultipartFile multipartFile = getTestMultiPartFile(""); //when&then - assertThatThrownBy(() -> FileUtil.validateFile(multipartFile)) + assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) .hasMessageContaining(FILE_NOT_EXIST.getMessage()); } @@ -68,7 +68,7 @@ public void should_throwException_when_notSupportedExtension() { MultipartFile multipartFile = getTestMultiPartFile("sky.pdf"); //when&then - assertThatThrownBy(() -> FileUtil.validateFile(multipartFile)) + assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) .hasMessageContaining(NOT_SUPPORTED_EXTENSION.getMessage()); } @@ -80,27 +80,10 @@ public void should_returnTargetFileInstance_when_passValidFile() { MultipartFile multipartFile = getTestMultiPartFile("sky.png"); //when - UploadDTO uploadDTO = FileUtil.getUploadInfo(multipartFile, "profile", UPLOAD_PATH); - - //then - assertThat(uploadDTO.fileURI()).contains(UPLOAD_PATH); - } - - @Test - @DisplayName("갱신 대상인 File을 전달했을 때, 갱신해야 할 정보들을 담은 UpdateDTO를 반환받는다.") - public void should_returnUpdateDTO_when_passUpdateTargetFile() { - //given - String originalFilename = "sky.png"; - MultipartFile multipartFile = getTestMultiPartFile(originalFilename); - FileType fileType = FileType.PROFILE; - - //when - UpdateDTO updateDTO = FileUtil.getUpdateInfo(multipartFile, fileType, UPLOAD_PATH); + FileDTO fileDTO = fileUtil.getFileDTO(multipartFile, FileType.PROFILE, UPLOAD_PATH); //then - assertThat(updateDTO.originalFilename()).isEqualTo(originalFilename); - assertThat(updateDTO.fileURI()).contains(UPLOAD_PATH); - assertThat(updateDTO.fileURI()).contains(updateDTO.savedFilename()); + assertThat(fileDTO.fileURI()).contains(UPLOAD_PATH); } @Test @@ -115,27 +98,13 @@ public void should_passInformation_when_tryToCopy() { .build(); //when - CopyDTO copyDTO = FileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); + CopyDTO copyDTO = fileUtil.getCopyInfo(files, INSTANCE, UPLOAD_PATH); //then assertThat(copyDTO.fileType()).isEqualTo(INSTANCE); assertThat(copyDTO.fileURI()).contains(UPLOAD_PATH); } - @Test - @DisplayName("FileTestUtil을 통해 받은 MultipartFile을 통해 인코딩 파일을 받을 수 있다") - public void should_getEncodedFiles() { - //given - MultipartFile multipartFile = FileTestUtil.getMultipartFile("filename"); - Files files = filesService.uploadFile(multipartFile, "topic"); - - //when - String encoded = FileUtil.encodedImage(files); - - //then - log.info(encoded); - } - private MultipartFile getTestMultiPartFile(String originalFilename) { return new MultipartFile() { diff --git a/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java index 716467bb..bbc6dfc3 100644 --- a/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java @@ -13,12 +13,14 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.JwtRule; import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.repository.TokenRepository; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; import jakarta.servlet.http.Cookie; import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -35,6 +37,8 @@ @Slf4j @ActiveProfiles({"jwt"}) class JwtServiceTest { + @Autowired + private TokenRepository tokenRepository; @Autowired private JwtService jwtService; @Autowired @@ -42,6 +46,11 @@ class JwtServiceTest { @Autowired private TokenTestUtil tokenTestUtil; + @AfterEach + void clearMongo() { + tokenRepository.deleteAll(); + } + @Test @DisplayName("사용자 정보를 받아서 access-token을 생성할 수 있다.") public void should_generateAccess_when_passUserInfo() { diff --git a/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java index 2ec125c8..b2cc263d 100644 --- a/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java @@ -4,6 +4,7 @@ import com.genius.gitget.global.security.domain.Token; import com.genius.gitget.global.security.repository.TokenRepository; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,6 +20,11 @@ class TokenServiceTest { @Autowired private TokenRepository tokenRepository; + @AfterEach + void clearMongo() { + tokenRepository.deleteAll(); + } + @Test @DisplayName("특정 리프레시 토큰을 identifier를 통해 DB에서 값을 조회할 수 있어야 한다.") public void should_findToken_when_findByIdentifier() { diff --git a/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java index 293072a2..4b91807d 100644 --- a/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java +++ b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java @@ -87,7 +87,7 @@ private User getSavedUser() { User user = getSavedUser(); Item item = getSavedItem(itemCategory); getSavedOrder(user, item, itemCategory, 0); - user.setPoint(1000L); + user.updatePoints(1000L); ItemResponse itemResponse = itemService.orderItem(user, item.getId()); assertThat(itemResponse.getItemCategory()).isEqualTo(itemCategory); diff --git a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java index f668002e..c2b7b1dd 100644 --- a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java +++ b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java @@ -19,6 +19,7 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.constants.ProviderInfo; @@ -278,7 +279,7 @@ private User getSavedUser() { private Topic getSavedTopic() { MultipartFile filename = FileTestUtil.getMultipartFile("sky"); - Files files = filesService.uploadFile(filename, "topic"); + Files files = filesService.uploadFile(filename, FileType.TOPIC); Topic topic = topicRepository.save( Topic.builder() diff --git a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java index 927abbc4..0e19d837 100644 --- a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java +++ b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java @@ -113,7 +113,7 @@ void setup() { UserInformationUpdateRequest.builder() .nickname("수정된 nickname") .information("수정된 information") - .build(), null, "profile"); + .build()); User user = userRepository.findByIdentifier(user1.getIdentifier()) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); @@ -159,7 +159,7 @@ void setup() { void 유저_포인트_조회() { User user = userRepository.findByIdentifier(user1.getIdentifier()) .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - user.setPoint(1500L); + user.updatePoints(1500L); userRepository.save(user); UserPointResponse userPoint = profileService.getUserPoint(user1); Assertions.assertThat(userPoint.getPoint()).isEqualTo(1500); diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java index 6956ef01..6b76ae5c 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java @@ -6,7 +6,6 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.service.CustomUserDetailsService; -import com.genius.gitget.util.file.FileTestUtil; import java.util.List; import java.util.Objects; import lombok.RequiredArgsConstructor; @@ -50,8 +49,7 @@ public SecurityContext createSecurityContext(WithMockCustomUser customUser) { .build(); User savedUser = userRepository.save(user); - Long signupId = userService.signup(signupRequest, - FileTestUtil.getMultipartFile(customUser.profileName())); + Long signupId = userService.signup(signupRequest); savedUser.updateRole(customUser.role()); UserDetails principal = customUserDetailsService.loadUserByUsername(String.valueOf(signupId)); From 6ab5fe777feac357fbe187f8be358f1da31fd355 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:55:04 +0900 Subject: [PATCH 161/234] =?UTF-8?q?[FEAT]=20AWS=20S3=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B4=80=EB=A0=A8=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20(#166)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: AWS 의존성 추가 * feat: AWS S3를 이용한 파일 시스템 기능 개발 - 파일 업로드, 삭제, 갱신, 복사에 대한 기능 개발 - S3 이용에 필요한 값 및 Bean을 생성하는 S3Config 클래스 작성 * chore: 테스트 통과 테스트를 위한 yml 파일 작성 * test: 파일 관련 테스트 코드 수정 * chore: Github actions yml 파일 수정 --- .github/workflows/test-task.yml | 10 +-- build.gradle | 3 + .../gitget/global/file/service/FileUtil.java | 16 ---- .../global/file/service/S3FileManager.java | 73 +++++++++++++++---- .../gitget/global/util/config/AppConfig.java | 10 ++- .../gitget/global/util/config/S3Config.java | 34 +++++++++ .../controller/InstanceControllerTest.java | 9 --- .../likes/controller/LikesControllerTest.java | 9 --- .../controller/ProfileControllerTest.java | 8 -- 9 files changed, 107 insertions(+), 65 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/util/config/S3Config.java diff --git a/.github/workflows/test-task.yml b/.github/workflows/test-task.yml index 9534fa80..73378d98 100644 --- a/.github/workflows/test-task.yml +++ b/.github/workflows/test-task.yml @@ -1,10 +1,10 @@ -name: Build test about test codes +name: Run gradlew clean test on: push: - branches: [ "refactor/162-file-system-structure" ] ## 테스트하고자하는 브랜치 작성 - pull_request: - branches: [ "refactor/162-file-system-structure" ] + branches: [ "feat/161-aws-s3-image" ] ## 테스트하고자하는 브랜치 작성 +# pull_request: +# branches: [ "feat/161-aws-s3-image" ] jobs: deploy: @@ -46,5 +46,5 @@ jobs: shell: bash - name: Build and Test - run: ./gradlew clean build test + run: ./gradlew clean test diff --git a/build.gradle b/build.gradle index bdc2449c..c3eff9b1 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + // AWS + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.1.RELEASE' + // json implementation 'com.googlecode.json-simple:json-simple:1.1.1' diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index d6a3309d..a5d965bb 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -1,7 +1,6 @@ package com.genius.gitget.global.file.service; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; -import static com.genius.gitget.global.util.exception.ErrorCode.IMAGE_NOT_ENCODED; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import com.genius.gitget.global.file.domain.FileType; @@ -9,13 +8,9 @@ import com.genius.gitget.global.file.dto.CopyDTO; import com.genius.gitget.global.file.dto.FileDTO; import com.genius.gitget.global.util.exception.BusinessException; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.List; import java.util.Objects; import java.util.UUID; -import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; @@ -23,17 +18,6 @@ public class FileUtil { private final List validExtensions = List.of("jpg", "jpeg", "png", "gif"); - public static String encodedImage(Files files) { - try { - //TODO: local 환경에 종속된 메서드이므로 종속되지 않게 수정 필요 - UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); - - byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); - return new String(encode, StandardCharsets.UTF_8); - } catch (IOException e) { - throw new BusinessException(IMAGE_NOT_ENCODED); - } - } public FileDTO getFileDTO(MultipartFile file, FileType fileType, final String UPLOAD_PATH) { String originalFilename = file.getOriginalFilename(); diff --git a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java b/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java index 15c28f09..8566b51a 100644 --- a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java @@ -1,46 +1,91 @@ package com.genius.gitget.global.file.service; +import com.amazonaws.SdkClientException; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CopyObjectRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.dto.CopyDTO; import com.genius.gitget.global.file.dto.FileDTO; import com.genius.gitget.global.file.dto.UpdateDTO; -import lombok.RequiredArgsConstructor; +import com.genius.gitget.global.util.exception.BusinessException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.springframework.core.io.UrlResource; import org.springframework.web.multipart.MultipartFile; -/** - * !!!이 클래스의 모든 주석은 삭제해도 무방합니다!!! - *

- * S3 bucket에 이미지를 업로드하는 코드를 구현하는 곳 - * 파일 시스템 구조 확장에 참고한 링크 - * https://chb2005.tistory.com/200#3.5.%20%ED%8C%8C%EC%9D%BC%20%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20%EA%B5%AC%ED%98%84 - * https://docs.aws.amazon.com/ko_kr/sdk-for-java/v1/developer-guide/examples-s3-objects.html#upload-object - */ -@RequiredArgsConstructor public class S3FileManager implements FileManager { + private final AmazonS3 amazonS3; private final FileUtil fileUtil; + private final String bucket; + public S3FileManager(AmazonS3 amazonS3, FileUtil fileUtil, String bucket) { + this.amazonS3 = amazonS3; + this.fileUtil = fileUtil; + this.bucket = bucket; + } @Override public String getEncodedImage(Files files) { - return null; + try { + UrlResource urlResource = new UrlResource(amazonS3.getUrl(bucket, files.getFileURI())); + byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); + return new String(encode, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new BusinessException(e); + } } @Override public FileDTO upload(MultipartFile multipartFile, FileType fileType) { - return null; + try { + fileUtil.validateFile(multipartFile); + FileDTO fileDTO = fileUtil.getFileDTO(multipartFile, fileType, ""); + + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(multipartFile.getSize()); + objectMetadata.setContentType(multipartFile.getContentType()); + + amazonS3.putObject(bucket, fileDTO.fileURI(), multipartFile.getInputStream(), objectMetadata); + return fileDTO; + } catch (IOException e) { + throw new BusinessException(e); + } } @Override public FileDTO copy(Files files, FileType fileType) { - return null; + CopyDTO copyDTO = fileUtil.getCopyInfo(files, fileType, ""); + + CopyObjectRequest copyObjectRequest = new CopyObjectRequest( + bucket, files.getFileURI(), + bucket, copyDTO.fileURI() + ); + amazonS3.copyObject(copyObjectRequest); + return FileDTO.builder() + .fileType(fileType) + .originalFilename(copyDTO.originalFilename()) + .savedFilename(copyDTO.savedFilename()) + .fileURI(copyDTO.fileURI()) + .build(); } @Override public UpdateDTO update(Files files, MultipartFile multipartFile) { - return null; + deleteInStorage(files); + FileDTO fileDTO = upload(multipartFile, files.getFileType()); + + return UpdateDTO.of(fileDTO); } @Override public void deleteInStorage(Files files) { + try { + amazonS3.deleteObject(bucket, files.getFileURI()); + } catch (SdkClientException e) { + throw new BusinessException(e); + } } } diff --git a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java index b3169b6f..03461aac 100644 --- a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java @@ -7,18 +7,18 @@ import com.genius.gitget.global.util.formatter.LocalDateFormatter; import com.genius.gitget.global.util.formatter.LocalDateTimeFormatter; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.security.crypto.encrypt.AesBytesEncryptor; -@Slf4j @Configuration @RequiredArgsConstructor public class AppConfig { + private final S3Config s3Config; private final Environment env; + @Bean public FileUtil fileUtil() { return new FileUtil(); @@ -27,13 +27,15 @@ public FileUtil fileUtil() { @Bean public FileManager fileManager() { final String fileMode = env.getProperty("file.mode"); - final String UPLOAD_PATH = env.getProperty("file.upload.path"); assert fileMode != null; if (fileMode.equals("local")) { + final String UPLOAD_PATH = env.getProperty("file.upload.path"); return new LocalFileManager(fileUtil(), UPLOAD_PATH); } - return new S3FileManager(fileUtil()); + + final String bucket = env.getProperty("cloud.aws.s3.bucket"); + return new S3FileManager(s3Config.amazonS3Client(), fileUtil(), bucket); } @Bean diff --git a/src/main/java/com/genius/gitget/global/util/config/S3Config.java b/src/main/java/com/genius/gitget/global/util/config/S3Config.java new file mode 100644 index 00000000..9472c752 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/util/config/S3Config.java @@ -0,0 +1,34 @@ +package com.genius.gitget.global.util.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + private final String accessKey; + private final String secretKey; + private final String region; + + public S3Config(@Value("${cloud.aws.credentials.accessKey}") String accessKey, + @Value("${cloud.aws.credentials.secretKey}") String secretKey, + @Value("${cloud.aws.region.static}") String region) { + this.accessKey = accessKey; + this.secretKey = secretKey; + this.region = region; + } + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} + diff --git a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java index d6bfe860..66e5626c 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java @@ -14,12 +14,9 @@ import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.global.file.domain.FileType; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; -import com.genius.gitget.util.file.FileTestUtil; import java.time.LocalDateTime; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -31,7 +28,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.multipart.MultipartFile; @SpringBootTest @Transactional @@ -145,9 +141,6 @@ public void setup() { private Topic getSavedTopic() { - MultipartFile filename = FileTestUtil.getMultipartFile("sky"); - Files files = filesService.uploadFile(filename, FileType.TOPIC); - Topic topic = topicRepository.save( Topic.builder() .title("title") @@ -157,8 +150,6 @@ private Topic getSavedTopic() { .pointPerPerson(100) .build() ); - topic.setFiles(files); - return topic; } diff --git a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java index eb9f78ed..151fa76f 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java @@ -21,13 +21,10 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.domain.FileType; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; -import com.genius.gitget.util.file.FileTestUtil; import java.time.LocalDateTime; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -40,7 +37,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.multipart.MultipartFile; @SpringBootTest @Transactional @@ -229,9 +225,6 @@ private User getSavedUser() { private Topic getSavedTopic() { - MultipartFile filename = FileTestUtil.getMultipartFile("sky"); - Files files = filesService.uploadFile(filename, FileType.TOPIC); - Topic topic = topicRepository.save( Topic.builder() .title("title") @@ -241,8 +234,6 @@ private Topic getSavedTopic() { .pointPerPerson(100) .build() ); - topic.setFiles(files); - return topic; } diff --git a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java index c2b7b1dd..eb91d8a7 100644 --- a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java +++ b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java @@ -19,13 +19,10 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.domain.FileType; -import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; -import com.genius.gitget.util.file.FileTestUtil; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -43,7 +40,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.multipart.MultipartFile; @SpringBootTest @@ -278,9 +274,6 @@ private User getSavedUser() { } private Topic getSavedTopic() { - MultipartFile filename = FileTestUtil.getMultipartFile("sky"); - Files files = filesService.uploadFile(filename, FileType.TOPIC); - Topic topic = topicRepository.save( Topic.builder() .title("title") @@ -290,7 +283,6 @@ private Topic getSavedTopic() { .pointPerPerson(100) .build() ); - topic.setFiles(files); return topic; } From 0c80b79ccda31156d683a0654c78131f626ff78d Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:11:47 +0900 Subject: [PATCH 162/234] =?UTF-8?q?[FEAT]=20main=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EC=B9=98=EC=97=90=20PR=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C,=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=90=EB=8F=99=ED=99=94=20?= =?UTF-8?q?=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20(#171)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: main에 PR 요청 시 자동으로 테스트가 실행되는 워크플로우 추가 - main으로 PR 요청 시, 테스트(gradlew clean test)가 실행되는 워크플로우 yml 파일 추가 - 테스트 결과에 대해 결과를 별도로 생성 * fix: 테스트 결과를 로그로 남기는 기능 버그 픽스 --- .../workflows/{test-task.yml => prTest.yml} | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) rename .github/workflows/{test-task.yml => prTest.yml} (78%) diff --git a/.github/workflows/test-task.yml b/.github/workflows/prTest.yml similarity index 78% rename from .github/workflows/test-task.yml rename to .github/workflows/prTest.yml index 73378d98..d199931b 100644 --- a/.github/workflows/test-task.yml +++ b/.github/workflows/prTest.yml @@ -1,17 +1,13 @@ -name: Run gradlew clean test +name: Run gradlew clean test when PR on: - push: - branches: [ "feat/161-aws-s3-image" ] ## 테스트하고자하는 브랜치 작성 -# pull_request: -# branches: [ "feat/161-aws-s3-image" ] + pull_request: + branches: [ "main" ] jobs: deploy: runs-on: ubuntu-latest - permissions: - contents: read - packages: write + permissions: write-all steps: - uses: actions/checkout@v4 @@ -48,3 +44,9 @@ jobs: - name: Build and Test run: ./gradlew clean test + # Test 후 Report 생성 + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + junit_files: '**/build/test-results/test/TEST-*.xml' \ No newline at end of file From 20d10ba7b965c979504fb1185f927d6bbe555ec9 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:26:43 +0900 Subject: [PATCH 163/234] =?UTF-8?q?fix:=20PR=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=ED=99=94=20Job=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 배포 시 사용하는 Job의 이름과 차별화를 두기 위해 Job의 이름을 PRTest로 변경 --- .github/workflows/prTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prTest.yml b/.github/workflows/prTest.yml index d199931b..1b91fbf2 100644 --- a/.github/workflows/prTest.yml +++ b/.github/workflows/prTest.yml @@ -5,7 +5,7 @@ on: branches: [ "main" ] jobs: - deploy: + PRTest: runs-on: ubuntu-latest permissions: write-all steps: From 51ebf78e8a4c9d0059061399d632bc04d1f4df41 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:42:41 +0900 Subject: [PATCH 164/234] =?UTF-8?q?fix:=20Instance=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=97=90=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=B4=20=ED=8F=AC=ED=95=A8=EB=90=98?= =?UTF-8?q?=EC=96=B4=20=EC=9E=88=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#172)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/search/InstanceSearchResponse.java | 5 ++- .../repository/SearchRepositoryCustom.java | 4 +- .../repository/SearchRepositoryImpl.java | 14 +++---- .../service/InstanceSearchService.java | 26 +++++++++++-- .../InstanceSearchRepositoryTest.java | 37 +++++++++---------- 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java index a618cb8f..129ecbbe 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/search/InstanceSearchResponse.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.instance.dto.search; +import com.genius.gitget.global.file.dto.FileResponse; import com.querydsl.core.annotations.QueryProjection; import lombok.Builder; import lombok.Data; @@ -13,15 +14,17 @@ public class InstanceSearchResponse { private String keyword; private int pointPerPerson; private int participantCount; + private FileResponse fileResponse; @Builder @QueryProjection public InstanceSearchResponse(Long topicId, Long instanceId, String keyword, int pointPerPerson, - int participantCount) { + int participantCount, FileResponse fileResponse) { this.topicId = topicId; this.instanceId = instanceId; this.keyword = keyword; this.pointPerPerson = pointPerPerson; this.participantCount = participantCount; + this.fileResponse = fileResponse; } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryCustom.java b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryCustom.java index 206366c8..95b22c7a 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryCustom.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryCustom.java @@ -1,10 +1,10 @@ package com.genius.gitget.challenge.instance.repository; +import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface SearchRepositoryCustom { - Page search(Progress progress, String title, Pageable pageable); + Page search(Progress progress, String title, Pageable pageable); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java index d147362c..90b791c7 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/SearchRepositoryImpl.java @@ -2,9 +2,8 @@ import static com.genius.gitget.challenge.instance.domain.QInstance.instance; +import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; -import com.genius.gitget.challenge.instance.dto.search.QInstanceSearchResponse; import com.querydsl.core.BooleanBuilder; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -23,7 +22,7 @@ public SearchRepositoryImpl(EntityManager em) { } @Override - public Page search(Progress progressCond, String titleCond, Pageable pageable) { + public Page search(Progress progressCond, String titleCond, Pageable pageable) { BooleanBuilder builder = new BooleanBuilder(); if (progressCond != null) { @@ -33,11 +32,8 @@ public Page search(Progress progressCond, String titleCo builder.and(instance.title.contains(titleCond)); } - List content = queryFactory - .select(new QInstanceSearchResponse( - instance.topic.id, instance.id, instance.title, instance.pointPerPerson, - instance.participantCount)) - .from(instance) + List content = queryFactory + .selectFrom(instance) .where(builder) .orderBy(instance.startedDate.desc()) .offset(pageable.getOffset()) @@ -51,4 +47,4 @@ public Page search(Progress progressCond, String titleCo return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); } -} +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java index b449b0ec..cbc921c5 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java @@ -1,8 +1,11 @@ package com.genius.gitget.challenge.instance.service; +import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.repository.SearchRepository; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -15,13 +18,15 @@ @Transactional(readOnly = true) @Slf4j public class InstanceSearchService { - + private final FilesService filesService; private final SearchRepository searchRepository; private final StringToEnum stringToEnum; private final String[] progressData = {"PREACTIVITY", "ACTIVITY", "DONE"}; - public Page searchInstances(String keyword, String progress, Pageable pageable){ + public Page searchInstances(String keyword, String progress, Pageable pageable) { + + Page search; Progress convertProgress; boolean flag = false; @@ -32,9 +37,22 @@ public Page searchInstances(String keyword, String progr } if (flag) { convertProgress = stringToEnum.convert(progress); - return searchRepository.search(convertProgress, keyword, pageable); + search = searchRepository.search(convertProgress, keyword, pageable); } else { - return searchRepository.search(null, keyword, pageable); + search = searchRepository.search(null, keyword, pageable); } + return search.map(this::convertToSearchResponse); + } + + private InstanceSearchResponse convertToSearchResponse(Instance instance) { + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + return InstanceSearchResponse.builder() + .topicId(instance.getTopic().getId()) + .instanceId(instance.getId()) + .keyword(instance.getTitle()) + .pointPerPerson(instance.getPointPerPerson()) + .participantCount(instance.getParticipantCount()) + .fileResponse(fileResponse) + .build(); } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index f0e11a1f..6c61ed1b 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -8,7 +8,6 @@ import com.genius.gitget.admin.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.service.InstanceSearchService; import com.genius.gitget.challenge.instance.service.InstanceService; import com.genius.gitget.util.file.FileTestUtil; @@ -109,9 +108,9 @@ public void setup() throws IOException { public void 검색_조건_없이_테스트() throws Exception { for (int i = 0; i < 5; i++) { PageRequest pageRequest = PageRequest.of(i, 2); - Page result = searchRepository.search(null, null, pageRequest); - for (InstanceSearchResponse instanceSearchResponse : result) { - System.out.println("instanceSearchResponse = " + instanceSearchResponse.getInstanceId()); + Page result = searchRepository.search(null, null, pageRequest); + for (Instance instance : result) { + System.out.println("instanceSearchResponse = " + instance.getId()); } System.out.println("========== " + i + 1 + " 번째 끝 ========="); } @@ -120,10 +119,10 @@ public void setup() throws IOException { @Test public void 챌린지_제목으로_검색_테스트() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(null, "2", pageRequest); + Page result = searchRepository.search(null, "2", pageRequest); int cnt = 0; - for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) { + for (Instance instance : result) { + if (instance != null) { cnt++; } } @@ -134,10 +133,10 @@ public void setup() throws IOException { @Test public void 챌린지_현황으로_검색_테스트() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(PREACTIVITY, null, pageRequest); + Page result = searchRepository.search(PREACTIVITY, null, pageRequest); int cnt = 0; - for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) { + for (Instance instance : result) { + if (instance != null) { cnt++; } } @@ -147,10 +146,10 @@ public void setup() throws IOException { @Test public void 챌린지_현황으로_검색_테스트2() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(DONE, null, pageRequest); + Page result = searchRepository.search(DONE, null, pageRequest); int cnt = 0; - for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) { + for (Instance instance : result) { + if (instance != null) { cnt++; } } @@ -160,10 +159,10 @@ public void setup() throws IOException { @Test public void 챌린지_현황으로_검색_테스트3() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(ACTIVITY, null, pageRequest); + Page result = searchRepository.search(ACTIVITY, null, pageRequest); int cnt = 0; - for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) { + for (Instance instance : result) { + if (instance != null) { cnt++; } } @@ -173,10 +172,10 @@ public void setup() throws IOException { @Test public void 챌린지_현황과_챌린지_제목으로_검색_테스트() throws Exception { PageRequest pageRequest = PageRequest.of(0, 10); - Page result = searchRepository.search(PREACTIVITY, "3", pageRequest); + Page result = searchRepository.search(PREACTIVITY, "3", pageRequest); int cnt = 0; - for (InstanceSearchResponse instanceSearchResponse : result) { - if (instanceSearchResponse != null) { + for (Instance instance : result) { + if (instance != null) { cnt++; } } From 31863e2bcecdf8d34127377c74ba1604e7164b53 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Mon, 22 Apr 2024 21:35:43 +0900 Subject: [PATCH 165/234] =?UTF-8?q?[BUG]=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#169)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bug: 좋아요 목록 조회 오류 해결 * bug: 좋아요 목록 조회 오류 테스트 완료 * bug : 좋아요 목록 조회 버그 수정 * chore: 불필요한 h2 주석처리 * bug: 버그해결 --- build.gradle | 6 +++--- .../gitget/challenge/likes/service/LikesService.java | 11 ++++++++--- .../challenge/likes/service/LikesServiceTest.java | 8 ++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index c3eff9b1..169588d1 100644 --- a/build.gradle +++ b/build.gradle @@ -56,9 +56,9 @@ dependencies { // testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo' // H2 - implementation 'com.h2database:h2' - runtimeOnly 'com.h2database:h2:2.2.222' - testRuntimeOnly 'com.h2database:h2' + //implementation 'com.h2database:h2' + //runtimeOnly 'com.h2database:h2:2.2.222' + testRuntimeOnly 'com.h2database:h2:2.2.222' // Github API for Java implementation 'org.kohsuke:github-api:1.318' diff --git a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java index df3c6030..f9a40e73 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java +++ b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java @@ -12,7 +12,9 @@ import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -41,8 +43,8 @@ public Page getLikesList(User user, Pageable pageable) { likes = userObject.getLikesList(); } } - List userLikesResponses = new ArrayList<>(); + Deque userLikesResponses = new ArrayDeque<>(); for (Likes like : likes) { Instance instance = like.getInstance(); FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); @@ -55,10 +57,13 @@ public Page getLikesList(User user, Pageable pageable) { .fileResponse(fileResponse) .build(); - userLikesResponses.add(userLikesResponse); + userLikesResponses.addFirst(userLikesResponse); } - return new PageImpl<>(userLikesResponses, pageable, userLikesResponses.size()); + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), userLikesResponses.size()); + return new PageImpl<>(userLikesResponses.stream().toList().subList(start, end), pageable, + userLikesResponses.size()); } @Transactional diff --git a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java index 876a57bd..f3a8e294 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java @@ -132,14 +132,14 @@ void setup() { + likesResponse.getPointPerPerson()); } assertThat(likesResponses.getContent().size()).isEqualTo(3); - assertThat(likesResponses.getContent().get(0).getTitle()).isEqualTo("1일 1커밋"); - assertThat(likesResponses.getContent().get(0).getPointPerPerson()).isEqualTo(100); + assertThat(likesResponses.getContent().get(2).getTitle()).isEqualTo("1일 1커밋"); + assertThat(likesResponses.getContent().get(2).getPointPerPerson()).isEqualTo(100); assertThat(likesResponses.getContent().get(1).getTitle()).isEqualTo("1일 1커밋"); assertThat(likesResponses.getContent().get(1).getPointPerPerson()).isEqualTo(150); - assertThat(likesResponses.getContent().get(2).getTitle()).isEqualTo("1일 1알고리즘"); - assertThat(likesResponses.getContent().get(2).getPointPerPerson()).isEqualTo(200); + assertThat(likesResponses.getContent().get(0).getTitle()).isEqualTo("1일 1알고리즘"); + assertThat(likesResponses.getContent().get(0).getPointPerPerson()).isEqualTo(200); } From d100714412ed3d090cd3d8e0f87c9de801854c16 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 20:39:09 +0900 Subject: [PATCH 166/234] =?UTF-8?q?=EB=AC=B4=EC=A4=91=EB=8B=A8=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 21a46197..19571b1f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Build and Deploy to EC2 on: push: - branches: [ "production" ] + branches: [ "production", "main" ] pull_request: branches: [ "production" ] From e1ed74f0f6ec914a197709e61a449bf4782b34e6 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 20:40:38 +0900 Subject: [PATCH 167/234] =?UTF-8?q?=EB=AC=B4=EC=A4=91=EB=8B=A8=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 19571b1f..1f31497a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,7 +4,7 @@ on: push: branches: [ "production", "main" ] pull_request: - branches: [ "production" ] + branches: [ "production", "main" ] env: AWS_REGION: ap-northeast-2 From 24f5d669925610a26b6b8096e95f2bc5403f8bd5 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 21:30:28 +0900 Subject: [PATCH 168/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 64 +++++++++++++++++++++--------------------- scripts/health.sh | 43 ++++++++++++++++++++++++++++ scripts/run_new_was.sh | 30 ++++++++++++++++++++ scripts/switch.sh | 29 +++++++++++++++++++ 4 files changed, 134 insertions(+), 32 deletions(-) create mode 100644 scripts/health.sh create mode 100644 scripts/run_new_was.sh create mode 100644 scripts/switch.sh diff --git a/scripts/deploy.sh b/scripts/deploy.sh index e36ec46a..3bbe0f30 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,42 +1,42 @@ -#!/bin/bash - -BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) -JAR_NAME=$(basename $BUILD_JAR) -echo ">>> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log - -echo ">>> build 파일 복사" >> /home/ubuntu/deploy.log -DEPLOY_PATH=/home/ubuntu/app/ -cp $BUILD_JAR $DEPLOY_PATH - -echo ">>> 현재 실행중인 애플리케이션 pid 확인 후 일괄 종료" >> /home/ubuntu/deploy.log -sudo ps -ef | grep java | awk '{print $2}' | xargs kill -15 - -DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME -echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log -echo ">>> $DEPLOY_JAR의 $JAR_NAME를 실행합니다" >> /home/ubunru/deploy.log -nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & - -# backup +##!/bin/bash +# #BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) #JAR_NAME=$(basename $BUILD_JAR) #echo ">>> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log # #echo ">>> build 파일 복사" >> /home/ubuntu/deploy.log -#DEPLOY_PATH=/home/ubuntu/ +#DEPLOY_PATH=/home/ubuntu/app/ #cp $BUILD_JAR $DEPLOY_PATH # -#echo ">>> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/deploy.log -#CURRENT_PID=$(pgrep -f $JAR_NAME) -# -#if [ -z $CURRENT_PID ] -#then -# echo ">>> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ubuntu/deploy.log -#else -# echo ">>> kill -15 $CURRENT_PID" -# kill -15 $CURRENT_PID -# sleep 5 -#fi +#echo ">>> 현재 실행중인 애플리케이션 pid 확인 후 일괄 종료" >> /home/ubuntu/deploy.log +#sudo ps -ef | grep java | awk '{print $2}' | xargs kill -15 # #DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME #echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log -#nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & \ No newline at end of file +#echo ">>> $DEPLOY_JAR의 $JAR_NAME를 실행합니다" >> /home/ubunru/deploy.log +#nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & +# +## backup +##BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) +##JAR_NAME=$(basename $BUILD_JAR) +##echo ">>> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log +## +##echo ">>> build 파일 복사" >> /home/ubuntu/deploy.log +##DEPLOY_PATH=/home/ubuntu/ +##cp $BUILD_JAR $DEPLOY_PATH +## +##echo ">>> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/deploy.log +##CURRENT_PID=$(pgrep -f $JAR_NAME) +## +##if [ -z $CURRENT_PID ] +##then +## echo ">>> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ubuntu/deploy.log +##else +## echo ">>> kill -15 $CURRENT_PID" +## kill -15 $CURRENT_PID +## sleep 5 +##fi +## +##DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME +##echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log +##nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & \ No newline at end of file diff --git a/scripts/health.sh b/scripts/health.sh new file mode 100644 index 00000000..58aadb38 --- /dev/null +++ b/scripts/health.sh @@ -0,0 +1,43 @@ +# health.sh + +#!/bin/bash + +# Crawl current connected port of WAS +CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) +TARGET_PORT=0 + +# Toggle port Number +if [ ${CURRENT_PORT} -eq 8081 ]; then + TARGET_PORT=8082 +elif [ ${CURRENT_PORT} -eq 8082 ]; then + TARGET_PORT=8081 +else + echo "> No WAS is connected to nginx" + exit 1 +fi + + +echo "> Start health check of WAS at http://localhost:${TARGET_PORT}/api/auth/health-check ..." + +for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10 +do + echo "> #${RETRY_COUNT} trying..." + RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${TARGET_PORT}/api/auth/health-check) + + if [ ${RESPONSE_CODE} -eq 200 ]; then + echo "> New WAS successfully running" + exit 0 + elif [ ${RETRY_COUNT} -eq 10 ]; then + echo "> Health check failed." + exit 1 + fi + sleep 10 + +# if [ ${CURRENT_PORT} -eq ${TARGET_PORT} ]; then +# echo "> Health Check Failed." +# exit 1 +# else +# echo "> New WAS Successfully running." +# exit 0 +# fi +done \ No newline at end of file diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh new file mode 100644 index 00000000..9949e688 --- /dev/null +++ b/scripts/run_new_was.sh @@ -0,0 +1,30 @@ +# run_new_was.sh + +#!/bin/bash + +# 현재 포트 읽어오기 +CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) +TARGET_PORT=0 + +echo "> Current port of running WAS is ${CURRENT_PORT}." + +# 현재 포트가 8081이면 새로 WAS를 띄울 타겟 포트는 8082, 반대라면 8081 +if [ ${CURRENT_PORT} -eq 8081 ]; then + TARGET_PORT=8082 +elif [ ${CURRENT_PORT} -eq 8082 ]; then + TARGET_PORT=8081 +else + echo "> No WAS is connected to nginx" +fi + +TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+') + +# 만약 타겟 포트에도 WAS가 떠있다면, kill하고 새롭게 WAS를 띄움 +if [ ! -z ${TARGET_PID} ]; then + echo "> Kill WAS running at ${TARGET_PORT}." + sudo kill ${TARGET_PID} +fi + +nohup java -jar -Dserver.port=${TARGET_PORT} /home/ubuntu/app/build/libs/* > /home/ubuntu/nohup.out 2>&1 & +echo "> Now new WAS runs at ${TARGET_PORT}." +exit 0 \ No newline at end of file diff --git a/scripts/switch.sh b/scripts/switch.sh new file mode 100644 index 00000000..a9d928c2 --- /dev/null +++ b/scripts/switch.sh @@ -0,0 +1,29 @@ +# switch.sh + +#!/bin/bash + +# Crawl current connected port of WAS +CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) +TARGET_PORT=0 + +echo "> Nginx currently proxies to ${CURRENT_PORT}." + +# Toggle port number +if [ ${CURRENT_PORT} -eq 8081 ]; then + TARGET_PORT=8082 +elif [ ${CURRENT_PORT} -eq 8082 ]; then + TARGET_PORT=8081 +else + echo "> No WAS is connected to nginx" + exit 1 +fi + +# Change proxying port into target port +echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ubuntu/service_url.inc + +echo "> Now Nginx proxies to ${TARGET_PORT}." + +# Reload nginx +sudo service nginx reload + +echo "> Nginx reloaded." \ No newline at end of file From fb83b6bc1eb1541a209414f9249185e6b87e17a6 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 21:36:49 +0900 Subject: [PATCH 169/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 31a137b7..5dca3adb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,9 @@ name: Build and Deploy to EC2 on: push: - branches: [ "production" ] ## 나중에 production으로 변경하기 + branches: [ "production", "deploy-test" ] pull_request: - branches: [ "production" ] + branches: [ "production", "deploy-test" ] env: AWS_REGION: ap-northeast-2 @@ -19,7 +19,9 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v4 + # Actions + - name: Checkout + uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 @@ -27,6 +29,7 @@ jobs: java-version: '17' distribution: 'temurin' + # 프로젝트 내 yml 파일 실행 - name: make application.yml run: | mkdir -p ./src/main/resources @@ -34,6 +37,7 @@ jobs: touch ./application.yml touch ./application-common.yml touch ./application-prod.yml + echo "${{ secrets.APPLICATION }}" > ./application.yml echo "${{ secrets.COMMON }}" > ./application-common.yml echo "${{ secrets.PROD }}" > ./application-prod.yml @@ -47,18 +51,22 @@ jobs: echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml echo "${{ secrets.TEST }}" > ./application-test.yml + - name: Grant execute permission for gradlew run: chmod +x ./gradlew shell: bash - - name: Build and Test - run: ./gradlew build -x test #./gradlew build test + + - name: Build with Gradle # and Test + run: ./gradlew clean build -x test #./gradlew build test + - name: Make zip file run: zip -r ./$GITHUB_SHA.zip . shell: bash - - name: AWS credential 설정 + + - name: Deliver to AWS S3 (AWS credential 설정) uses: aws-actions/configure-aws-credentials@v1 with: aws-region: ${{ env.AWS_REGION }} @@ -69,8 +77,6 @@ jobs: - name: Upload to S3 run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip - - name: EC2에 배포 - run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip - - + - name: Code Deploy (EC2에 배포) + run: aws deploy create-deployment --application-name GitGet-Application-HEY --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name GitGet-CICD-group-hey --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip \ No newline at end of file From 061dd7745f8b012ae9d409bcc33cdb465b9e9987 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 21:39:13 +0900 Subject: [PATCH 170/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appspec.yml | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/appspec.yml b/appspec.yml index fa35b32c..245f0963 100644 --- a/appspec.yml +++ b/appspec.yml @@ -1,17 +1,46 @@ -version: 0.0 -os: linux +#version: 0.0 +#os: linux +# +#files: +# - source: / +# destination: /home/ubuntu/app +# overwrite: yes +# +#permissions: +# - object: / +# owner: ubuntu +# group: ubuntu +# +#hooks: +# ApplicationStart: +# - location: scripts/deploy.sh +# timeout: 60 + +version: 0.0 # CodeDeploy Version. + +os: linux # 배포할 서버의 운영체제 files: - - source: / - destination: /home/ubuntu/app - overwrite: yes + - source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정 (루트 경로 : 전체 파일) + destination: /home/ubuntu/app # source에서 지정된 파일을 받을 위치 + overwrite: yes # 기존 파일들을 덮어 쓰기 +# CodeDeploy에서 EC2로 넘겨준 파일들을 모두 ec2-user 권한 부여. permissions: - object: / + pattern: "**" owner: ubuntu group: ubuntu +# CodeDeploy 배포 단계에서 실행할 명령어를 지정 (차례대로 스크립트들이 실행) hooks: ApplicationStart: - - location: scripts/deploy.sh - timeout: 60 + - location: scripts/run_new_was.sh + timeout: 180 + runas: ubuntu + - location: scripts/health.sh + timeout: 180 + runas: ubuntu + - location: scripts/switch.sh + timeout: 180 + runas: ubuntu \ No newline at end of file From 2fa0385771caae0faa04072e7e49ff5b4e31f413 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 21:46:22 +0900 Subject: [PATCH 171/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appspec.yml b/appspec.yml index 245f0963..b4a5f0ca 100644 --- a/appspec.yml +++ b/appspec.yml @@ -22,7 +22,7 @@ os: linux # 배포할 서버의 운영체제 files: - source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정 (루트 경로 : 전체 파일) - destination: /home/ubuntu/app # source에서 지정된 파일을 받을 위치 + destination: /home/ubuntu/app/libs/ # source에서 지정된 파일을 받을 위치 overwrite: yes # 기존 파일들을 덮어 쓰기 # CodeDeploy에서 EC2로 넘겨준 파일들을 모두 ec2-user 권한 부여. From 53c3329f2fa5feb8499d5c817b97728b47479de5 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 21:50:47 +0900 Subject: [PATCH 172/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appspec.yml b/appspec.yml index b4a5f0ca..5203092c 100644 --- a/appspec.yml +++ b/appspec.yml @@ -22,7 +22,7 @@ os: linux # 배포할 서버의 운영체제 files: - source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정 (루트 경로 : 전체 파일) - destination: /home/ubuntu/app/libs/ # source에서 지정된 파일을 받을 위치 + destination: /home/ubuntu/app/ # source에서 지정된 파일을 받을 위치 overwrite: yes # 기존 파일들을 덮어 쓰기 # CodeDeploy에서 EC2로 넘겨준 파일들을 모두 ec2-user 권한 부여. From d01a16a6f3db2f9b6c5ca07f0d36185894205829 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 21:59:17 +0900 Subject: [PATCH 173/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/deploy.sh | 36 ++++++++++++++++++------------------ scripts/run_new_was.sh | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 3bbe0f30..c27ee85d 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,21 +1,21 @@ -##!/bin/bash -# -#BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) -#JAR_NAME=$(basename $BUILD_JAR) -#echo ">>> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log -# -#echo ">>> build 파일 복사" >> /home/ubuntu/deploy.log -#DEPLOY_PATH=/home/ubuntu/app/ -#cp $BUILD_JAR $DEPLOY_PATH -# -#echo ">>> 현재 실행중인 애플리케이션 pid 확인 후 일괄 종료" >> /home/ubuntu/deploy.log -#sudo ps -ef | grep java | awk '{print $2}' | xargs kill -15 -# -#DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME -#echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log -#echo ">>> $DEPLOY_JAR의 $JAR_NAME를 실행합니다" >> /home/ubunru/deploy.log -#nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & -# +#!/bin/bash + +BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) +JAR_NAME=$(basename $BUILD_JAR) +echo ">>> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log + +echo ">>> build 파일 복사" >> /home/ubuntu/deploy.log +DEPLOY_PATH=/home/ubuntu/app/ +cp $BUILD_JAR $DEPLOY_PATH + +echo ">>> 현재 실행중인 애플리케이션 pid 확인 후 일괄 종료" >> /home/ubuntu/deploy.log +sudo ps -ef | grep java | awk '{print $2}' | xargs kill -15 + +DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME +echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log +echo ">>> $DEPLOY_JAR의 $JAR_NAME를 실행합니다" >> /home/ubunru/deploy.log +nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & + ## backup ##BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar) ##JAR_NAME=$(basename $BUILD_JAR) diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh index 9949e688..2270799c 100644 --- a/scripts/run_new_was.sh +++ b/scripts/run_new_was.sh @@ -25,6 +25,6 @@ if [ ! -z ${TARGET_PID} ]; then sudo kill ${TARGET_PID} fi -nohup java -jar -Dserver.port=${TARGET_PORT} /home/ubuntu/app/build/libs/* > /home/ubuntu/nohup.out 2>&1 & +nohup java -jar -Dserver.port=${TARGET_PORT} /home/ubuntu/app/build/libs/*.jar > /home/ubuntu/nohup.out 2>&1 & echo "> Now new WAS runs at ${TARGET_PORT}." exit 0 \ No newline at end of file From 8bed1f3ed9c32402de393d152caddda4cb3e2869 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 22:17:13 +0900 Subject: [PATCH 174/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/run_new_was.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh index 2270799c..6a2324ad 100644 --- a/scripts/run_new_was.sh +++ b/scripts/run_new_was.sh @@ -26,5 +26,5 @@ if [ ! -z ${TARGET_PID} ]; then fi nohup java -jar -Dserver.port=${TARGET_PORT} /home/ubuntu/app/build/libs/*.jar > /home/ubuntu/nohup.out 2>&1 & -echo "> Now new WAS runs at ${TARGET_PORT}." +echo "> Now new WAS runs at ${TARGET_PORT}. " exit 0 \ No newline at end of file From 266047674b940b1e799e5614825f6e64b8b7dab8 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 22:23:14 +0900 Subject: [PATCH 175/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/run_new_was.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh index 6a2324ad..b1c3c756 100644 --- a/scripts/run_new_was.sh +++ b/scripts/run_new_was.sh @@ -26,5 +26,6 @@ if [ ! -z ${TARGET_PID} ]; then fi nohup java -jar -Dserver.port=${TARGET_PORT} /home/ubuntu/app/build/libs/*.jar > /home/ubuntu/nohup.out 2>&1 & -echo "> Now new WAS runs at ${TARGET_PORT}. " -exit 0 \ No newline at end of file +echo "> Now new WAS runs at ${TARGET_PORT}." +exit 0 +lsof \ No newline at end of file From a5db98dd2b6bdad89b93f6e40b450b44eaf82dac Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 23:02:09 +0900 Subject: [PATCH 176/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/run_new_was.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh index b1c3c756..434d05cf 100644 --- a/scripts/run_new_was.sh +++ b/scripts/run_new_was.sh @@ -24,8 +24,8 @@ if [ ! -z ${TARGET_PID} ]; then echo "> Kill WAS running at ${TARGET_PORT}." sudo kill ${TARGET_PID} fi - -nohup java -jar -Dserver.port=${TARGET_PORT} /home/ubuntu/app/build/libs/*.jar > /home/ubuntu/nohup.out 2>&1 & +# -Dserver.port=${TARGET_PORT} +nohup java -jar /home/ubuntu/app/build/libs/*.jar > /home/ubuntu/nohup.out 2>&1 & echo "> Now new WAS runs at ${TARGET_PORT}." exit 0 lsof \ No newline at end of file From fd4a00f6739c2796a77aba6c9fc87e250ba87379 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 23:19:26 +0900 Subject: [PATCH 177/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/switch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/switch.sh b/scripts/switch.sh index a9d928c2..a526d77e 100644 --- a/scripts/switch.sh +++ b/scripts/switch.sh @@ -21,7 +21,7 @@ fi # Change proxying port into target port echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ubuntu/service_url.inc -echo "> Now Nginx proxies to ${TARGET_PORT}." +echo "> Now Nginx proxies to ${TARGET_PORT}. " # Reload nginx sudo service nginx reload From 4df0336a22a1a41b0f3985e7a8893967772bca23 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 23:29:03 +0900 Subject: [PATCH 178/234] =?UTF-8?q?feat:=20=EB=AC=B4=EC=A4=91=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/switch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/switch.sh b/scripts/switch.sh index a526d77e..a9d928c2 100644 --- a/scripts/switch.sh +++ b/scripts/switch.sh @@ -21,7 +21,7 @@ fi # Change proxying port into target port echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ubuntu/service_url.inc -echo "> Now Nginx proxies to ${TARGET_PORT}. " +echo "> Now Nginx proxies to ${TARGET_PORT}." # Reload nginx sudo service nginx reload From 312613e73dff22cb342d99c055576ae2342160cd Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 23 Apr 2024 23:41:27 +0900 Subject: [PATCH 179/234] =?UTF-8?q?feat:=20=EC=9E=90=EB=8F=99=ED=99=94=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appspec.yml | 43 +++++++----------------------------------- scripts/health.sh | 43 ------------------------------------------ scripts/run_new_was.sh | 31 ------------------------------ scripts/switch.sh | 29 ---------------------------- 4 files changed, 7 insertions(+), 139 deletions(-) delete mode 100644 scripts/health.sh delete mode 100644 scripts/run_new_was.sh delete mode 100644 scripts/switch.sh diff --git a/appspec.yml b/appspec.yml index 5203092c..fa35b32c 100644 --- a/appspec.yml +++ b/appspec.yml @@ -1,46 +1,17 @@ -#version: 0.0 -#os: linux -# -#files: -# - source: / -# destination: /home/ubuntu/app -# overwrite: yes -# -#permissions: -# - object: / -# owner: ubuntu -# group: ubuntu -# -#hooks: -# ApplicationStart: -# - location: scripts/deploy.sh -# timeout: 60 - -version: 0.0 # CodeDeploy Version. - -os: linux # 배포할 서버의 운영체제 +version: 0.0 +os: linux files: - - source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정 (루트 경로 : 전체 파일) - destination: /home/ubuntu/app/ # source에서 지정된 파일을 받을 위치 - overwrite: yes # 기존 파일들을 덮어 쓰기 + - source: / + destination: /home/ubuntu/app + overwrite: yes -# CodeDeploy에서 EC2로 넘겨준 파일들을 모두 ec2-user 권한 부여. permissions: - object: / - pattern: "**" owner: ubuntu group: ubuntu -# CodeDeploy 배포 단계에서 실행할 명령어를 지정 (차례대로 스크립트들이 실행) hooks: ApplicationStart: - - location: scripts/run_new_was.sh - timeout: 180 - runas: ubuntu - - location: scripts/health.sh - timeout: 180 - runas: ubuntu - - location: scripts/switch.sh - timeout: 180 - runas: ubuntu \ No newline at end of file + - location: scripts/deploy.sh + timeout: 60 diff --git a/scripts/health.sh b/scripts/health.sh deleted file mode 100644 index 58aadb38..00000000 --- a/scripts/health.sh +++ /dev/null @@ -1,43 +0,0 @@ -# health.sh - -#!/bin/bash - -# Crawl current connected port of WAS -CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) -TARGET_PORT=0 - -# Toggle port Number -if [ ${CURRENT_PORT} -eq 8081 ]; then - TARGET_PORT=8082 -elif [ ${CURRENT_PORT} -eq 8082 ]; then - TARGET_PORT=8081 -else - echo "> No WAS is connected to nginx" - exit 1 -fi - - -echo "> Start health check of WAS at http://localhost:${TARGET_PORT}/api/auth/health-check ..." - -for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10 -do - echo "> #${RETRY_COUNT} trying..." - RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${TARGET_PORT}/api/auth/health-check) - - if [ ${RESPONSE_CODE} -eq 200 ]; then - echo "> New WAS successfully running" - exit 0 - elif [ ${RETRY_COUNT} -eq 10 ]; then - echo "> Health check failed." - exit 1 - fi - sleep 10 - -# if [ ${CURRENT_PORT} -eq ${TARGET_PORT} ]; then -# echo "> Health Check Failed." -# exit 1 -# else -# echo "> New WAS Successfully running." -# exit 0 -# fi -done \ No newline at end of file diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh deleted file mode 100644 index 434d05cf..00000000 --- a/scripts/run_new_was.sh +++ /dev/null @@ -1,31 +0,0 @@ -# run_new_was.sh - -#!/bin/bash - -# 현재 포트 읽어오기 -CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) -TARGET_PORT=0 - -echo "> Current port of running WAS is ${CURRENT_PORT}." - -# 현재 포트가 8081이면 새로 WAS를 띄울 타겟 포트는 8082, 반대라면 8081 -if [ ${CURRENT_PORT} -eq 8081 ]; then - TARGET_PORT=8082 -elif [ ${CURRENT_PORT} -eq 8082 ]; then - TARGET_PORT=8081 -else - echo "> No WAS is connected to nginx" -fi - -TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+') - -# 만약 타겟 포트에도 WAS가 떠있다면, kill하고 새롭게 WAS를 띄움 -if [ ! -z ${TARGET_PID} ]; then - echo "> Kill WAS running at ${TARGET_PORT}." - sudo kill ${TARGET_PID} -fi -# -Dserver.port=${TARGET_PORT} -nohup java -jar /home/ubuntu/app/build/libs/*.jar > /home/ubuntu/nohup.out 2>&1 & -echo "> Now new WAS runs at ${TARGET_PORT}." -exit 0 -lsof \ No newline at end of file diff --git a/scripts/switch.sh b/scripts/switch.sh deleted file mode 100644 index a9d928c2..00000000 --- a/scripts/switch.sh +++ /dev/null @@ -1,29 +0,0 @@ -# switch.sh - -#!/bin/bash - -# Crawl current connected port of WAS -CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1) -TARGET_PORT=0 - -echo "> Nginx currently proxies to ${CURRENT_PORT}." - -# Toggle port number -if [ ${CURRENT_PORT} -eq 8081 ]; then - TARGET_PORT=8082 -elif [ ${CURRENT_PORT} -eq 8082 ]; then - TARGET_PORT=8081 -else - echo "> No WAS is connected to nginx" - exit 1 -fi - -# Change proxying port into target port -echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ubuntu/service_url.inc - -echo "> Now Nginx proxies to ${TARGET_PORT}." - -# Reload nginx -sudo service nginx reload - -echo "> Nginx reloaded." \ No newline at end of file From 8db33cf814d6d2741adba9129b9c608ad2044b60 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Tue, 14 May 2024 09:20:43 +0900 Subject: [PATCH 180/234] =?UTF-8?q?fix:=20Github=20actions=20workflow=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e7d6fc97..10cf1741 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,7 +4,7 @@ on: push: branches: [ "production" ] pull_request: - branches: [ "production", "deploy-test" ] + branches: [ "production" ] env: AWS_REGION: ap-northeast-2 @@ -57,8 +57,8 @@ jobs: shell: bash - - name: Build with Gradle # and Test - run: ./gradlew clean build -x test #./gradlew build test + - name: Build with Gradle and Test + run: ./gradlew build test - name: Make zip file From 8e647f07ca1e76ceca5f42b9916af49b4fde5b56 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Fri, 24 May 2024 16:03:38 +0900 Subject: [PATCH 181/234] =?UTF-8?q?[FEAT]=20AWS=20S3=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: aws s3 bucket access issue - @CrossOrigin 어노테이션 추가 * feat: aws 적용 테스트 --- .github/workflows/main.yml | 4 +--- .../myChallenge/controller/MyChallengeController.java | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 21a46197..b11c06cb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Build and Deploy to EC2 on: push: - branches: [ "production" ] + branches: [ "production", "feat/177-aws-s3-issue" ] pull_request: branches: [ "production" ] @@ -19,7 +19,6 @@ jobs: contents: read packages: write steps: - # Actions - name: Checkout uses: actions/checkout@v4 @@ -29,7 +28,6 @@ jobs: java-version: '17' distribution: 'temurin' - # 프로젝트 내 yml 파일 실행 - name: make application.yml run: | mkdir -p ./src/main/resources diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java index aa2a5b2a..ddf51e80 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java @@ -15,6 +15,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -23,6 +24,7 @@ @RestController @RequestMapping("/api/challenges") @RequiredArgsConstructor +@CrossOrigin public class MyChallengeController { private final MyChallengeService myChallengeService; From f167233a90b5814db38505b36cdb2474e64ce748 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Fri, 24 May 2024 16:14:30 +0900 Subject: [PATCH 182/234] =?UTF-8?q?chore:=20main=20yml=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#182)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b11c06cb..e2bed461 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Build and Deploy to EC2 on: push: - branches: [ "production", "feat/177-aws-s3-issue" ] + branches: [ "production" ] pull_request: branches: [ "production" ] From 9c99f5da5049db66c4b5623d23181dd0f636ca6e Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Tue, 28 May 2024 12:17:46 +0900 Subject: [PATCH 183/234] =?UTF-8?q?[FEAT]=20nginx=20&=20docker-compose=20?= =?UTF-8?q?=EB=AC=B4=EC=A4=91=EB=8B=A8=20=EB=B0=B0=ED=8F=AC=20=20(#184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 무중단배포 테스트 * 무중단 배포 테스트 * feat: main.yml 수정 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * git actions & docker TEST * git actions & docker TEST * git actions & docker TEST * self-hosted runners & docker compose test * self-hosted runners & docker compose test * self-hosted runners & docker compose test * self-hosted runners & docker compose test * docker compose & nginx test * 최종 테스트 * 최종 테스트 * 최종 테스트 * 최종 테스트 * 최종 테스트 * 테스트 * feat: 무중단 배포 적용 완료 * feat: 무중단 배포 적용 완료 * feat: 무중단 배포 적용 완료 --- .github/workflows/main.yml | 49 +++++++------------ Dockerfile | 11 +++++ appspec.yml | 28 ----------- scripts/health.sh | 1 - .../security/config/SecurityConfig.java | 3 +- 5 files changed, 30 insertions(+), 62 deletions(-) create mode 100644 Dockerfile delete mode 100644 appspec.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e2bed461..1bee53be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,23 +1,12 @@ name: Build and Deploy to EC2 on: - push: - branches: [ "production" ] pull_request: branches: [ "production" ] -env: - AWS_REGION: ap-northeast-2 - AWS_S3_BUCKET: gitget-bucket-hey - AWS_CODE_DEPLOY_APPLICATION: GitGet-Application-HEY - AWS_CODE_DEPLOY_GROUP: GitGet-CICD-group-hey - jobs: - deploy: + build-docker-image: runs-on: ubuntu-latest - permissions: - contents: read - packages: write steps: - name: Checkout uses: actions/checkout@v4 @@ -49,32 +38,28 @@ jobs: echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml echo "${{ secrets.TEST }}" > ./application-test.yml - - name: Grant execute permission for gradlew run: chmod +x ./gradlew shell: bash + - name: Build with Gradle + run: ./gradlew clean build - - name: Build with Gradle # and Test - run: ./gradlew clean build -x test #./gradlew build test - - - - name: Make zip file - run: zip -r ./$GITHUB_SHA.zip . - shell: bash - - - - name: Deliver to AWS S3 (AWS credential 설정) - uses: aws-actions/configure-aws-credentials@v1 + - name: docker login + uses: docker /login-action@v2 with: - aws-region: ${{ env.AWS_REGION }} - aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY_HEY }} - aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY_HEY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + - name: docker image build + run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/gitget-application . - - name: Upload to S3 - run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip + - name: dockerhub push + run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/gitget-application - - - name: Code Deploy (EC2에 배포) - run: aws deploy create-deployment --application-name GitGet-Application-HEY --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name GitGet-CICD-group-hey --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip + run-docker-image-on-ec2: + needs: build-docker-image + runs-on: self-hosted + steps: + - name: execute deploy.sh + run: sh /home/ec2-user/deploy.sh \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..95e32324 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# Start with a base image containing Java runtime. +FROM openjdk:17-jdk + +# The application's jar file. +ARG JAR_FILE=./build/libs/GitGetApplication.jar + +# Add the application's jar to the container. +COPY ${JAR_FILE} App.jar + +# Run the jar file. +CMD ["java", "-jar", "App.jar"] \ No newline at end of file diff --git a/appspec.yml b/appspec.yml deleted file mode 100644 index 3f9d6147..00000000 --- a/appspec.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: 0.0 # CodeDeploy Version. - -os: linux # 배포할 서버의 운영체제 - -files: - - source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정 (루트 경로 : 전체 파일) - destination: /home/ubuntu/app # source에서 지정된 파일을 받을 위치 - overwrite: yes # 기존 파일들을 덮어 쓰기 - -# CodeDeploy에서 EC2로 넘겨준 파일들을 모두 ec2-user 권한 부여. -permissions: - - object: / - pattern: "**" - owner: ubuntu - group: ubuntu - -# CodeDeploy 배포 단계에서 실행할 명령어를 지정 (차례대로 스크립트들이 실행) -hooks: - ApplicationStart: - - location: scripts/run_new_was.sh - timeout: 180 - runas: ubuntu - - location: scripts/health.sh - timeout: 180 - runas: ubuntu - - location: scripts/switch.sh - timeout: 180 - runas: ubuntu diff --git a/scripts/health.sh b/scripts/health.sh index baa66cc7..e9539eb8 100644 --- a/scripts/health.sh +++ b/scripts/health.sh @@ -16,7 +16,6 @@ else exit 1 fi - echo "> Start health check of WAS at http://localhost:${TARGET_PORT}/api/auth/health-check ..." for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10 diff --git a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java index b189ab5c..2bd56770 100644 --- a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java @@ -27,7 +27,8 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**", "/login", "/favicon.ico"}; + public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**", "/login", + "/favicon.ico"}; private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; From 2e1a2d310f215dfff78285b004fba0cd49dd4bfa Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Tue, 28 May 2024 12:23:00 +0900 Subject: [PATCH 184/234] =?UTF-8?q?chore:=20yml=20conflict=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 639150bf..1bee53be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Build and Deploy to EC2 on: pull_request: - branches: [ "production", "main" ] + branches: [ "production" ] jobs: build-docker-image: From 9372bd43d8dcb1b7fcbcf1f662316678285e36c0 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 28 May 2024 12:37:54 +0900 Subject: [PATCH 185/234] =?UTF-8?q?[REFACTOR]=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=B4=20=EC=A0=80=EC=9E=A5=EC=86=8C=EC=97=90=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=84=20=EB=95=8C?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B6=94=EA=B0=80=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20(#183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 파일이 존재하지 않을 때 반환 값 변경 - 저장소에 파일이 존재하지 않을 때 반환 값을 빈 문자열("")로 변경 - 주석 추가 * refactor: 예외 코드 추가 및 변경 - 예외 상황에 맞도록 예외 코드 추가 * feat: 파일 복사 시 예외 상황 추가 처리 - 원본 파일을 이용하는 파일 복사 로직에서, 원본 파일이 존재하지 않으면 예외 발생하는 코드 추가 * test: 예외 코드 추가에 따른 테스트 코드 변경 --- .../gitget/global/file/dto/FileResponse.java | 2 +- .../gitget/global/file/service/FileManager.java | 15 ++++++++++++++- .../gitget/global/file/service/FileUtil.java | 4 ++-- .../global/file/service/LocalFileManager.java | 15 ++++++++++++++- .../gitget/global/file/service/S3FileManager.java | 14 +++++++++++++- .../gitget/global/util/exception/ErrorCode.java | 2 ++ .../gitget/global/file/service/FileUtilTest.java | 6 +++--- 7 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index ec485146..6505d331 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -9,6 +9,6 @@ public static FileResponse createExistFile(Long filesId, String encodedFile) { } public static FileResponse createNotExistFile() { - return new FileResponse(0L, "none"); + return new FileResponse(0L, ""); } } diff --git a/src/main/java/com/genius/gitget/global/file/service/FileManager.java b/src/main/java/com/genius/gitget/global/file/service/FileManager.java index 1fb41eeb..90263563 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileManager.java @@ -17,7 +17,8 @@ public interface FileManager { * Files 내에 저장된 값들을 통해 UrlResource 등으로 다운받은 후, base64로 인코딩한 결과 반환 * * @param files 얻기 원하는 파일의 정보를 담고 있는 Files 객체 - * @return base64로 encode한 결과 값(문자열) + * @return base64로 encode한 결과 값(문자열) 반환 + * 파일을 받아오지 못한 경우에는 빈 문자열("") 반환 */ String getEncodedImage(Files files); @@ -32,10 +33,12 @@ public interface FileManager { /** * 기존에 저장소에 저장되어 있던 파일을 특정 타입에 복사 후, Files 객체 생성에 필요한 정보들을 반환 + * NOTE!! 복사 이전에 원본이 되는 파일이 저장소에 존재하는지 `validateFileExist()`를 통해 확인 필요 * * @param files 복사하고자하는 파일의 정보를 담고 있는 Files 객체 * @param fileType 복사해서 적용하고 싶은 대상의 파일 타입(TOPIC/INSTANCE/PROFILE 중 택 1) * @return Files 객체 생성에 필요한 정보(UploadDTO) 반환 + * @throws BusinessException 원본이 되는 파일이 저장소에 존재하지 않는 경우 FILE_NOT_EXIST 발생 */ FileDTO copy(Files files, FileType fileType); @@ -55,4 +58,14 @@ public interface FileManager { * @throws BusinessException 삭제에 실패했을 때 발생 */ void deleteInStorage(Files files); + + /** + * Files 객체 내의 정보를 활용하여 저장소에 파일이 저장이 되어 있는지 확인 후 boolean 반환 + * 각 저장소의 특성에 맞춰 Files 내의 메타데이터를 통해 저장소 내에 파일이 제대로 저장되어 있는지 확인 + * 파일이 존재하지 않는 경우 FILE_NOT_EXIST 예외 발생 시킬 것 + * + * @param files 저장소에 저장되어 있는지 확인하고자하는 Files 객체 + * @throws FILE_NOT_EXIST 저장소에서 파일(이미지)을 찾을 수 없을 때 발생 + */ + void validateFileExist(Files files); } diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index a5d965bb..22e0a582 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -1,6 +1,6 @@ package com.genius.gitget.global.file.service; -import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_FILE_NAME; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import com.genius.gitget.global.file.domain.FileType; @@ -48,7 +48,7 @@ public void validateFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (originalFilename == null || Objects.equals(originalFilename, "")) { - throw new BusinessException(FILE_NOT_EXIST); + throw new BusinessException(INVALID_FILE_NAME); } String extension = extractExtension(originalFilename); diff --git a/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java b/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java index 5a5fabec..e249ff3f 100644 --- a/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java @@ -2,6 +2,7 @@ import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_COPIED; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_DELETED; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_SAVED; import com.genius.gitget.global.file.domain.FileType; @@ -52,12 +53,15 @@ public String getEncodedImage(Files files) { byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); return new String(encode, StandardCharsets.UTF_8); } catch (IOException e) { - throw new BusinessException(e); + //TODO: 불러오는 중의 예외에 대해 Logging 추가하기 + return ""; } } @Override public FileDTO copy(Files files, FileType fileType) { + validateFileExist(files); + CopyDTO copyDTO = fileUtil.getCopyInfo(files, fileType, UPLOAD_PATH); createPath(copyDTO.folderURI()); @@ -95,6 +99,15 @@ public void deleteInStorage(Files files) { } } + @Override + public void validateFileExist(Files files) { + String fileURI = files.getFileURI(); + File file = new File(fileURI); + if (!file.exists()) { + throw new BusinessException(FILE_NOT_EXIST); + } + } + private void createPath(String uri) { File file = new File(uri); if (!file.exists()) { diff --git a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java b/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java index 8566b51a..5fc5c069 100644 --- a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java @@ -1,5 +1,7 @@ package com.genius.gitget.global.file.service; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; + import com.amazonaws.SdkClientException; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.CopyObjectRequest; @@ -34,7 +36,8 @@ public String getEncodedImage(Files files) { byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); return new String(encode, StandardCharsets.UTF_8); } catch (IOException e) { - throw new BusinessException(e); + //TODO: 불러오는 중의 예외에 대해 Logging 추가하기 + return ""; } } @@ -57,6 +60,8 @@ public FileDTO upload(MultipartFile multipartFile, FileType fileType) { @Override public FileDTO copy(Files files, FileType fileType) { + validateFileExist(files); + CopyDTO copyDTO = fileUtil.getCopyInfo(files, fileType, ""); CopyObjectRequest copyObjectRequest = new CopyObjectRequest( @@ -88,4 +93,11 @@ public void deleteInStorage(Files files) { throw new BusinessException(e); } } + + @Override + public void validateFileExist(Files files) { + if (!amazonS3.doesObjectExist(bucket, files.getFileURI())) { + throw new BusinessException(FILE_NOT_EXIST); + } + } } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index d34110cd..58cfa721 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -21,6 +21,7 @@ public enum ErrorCode { TOPIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 토픽을 찾을 수 없습니다."), TOPIC_HAVE_INSTANCE(HttpStatus.BAD_REQUEST, "해당 토픽은 인스턴스를 가지고 있으므로 삭제할 수 없습니다."), + TOPIC_IMAGE_NOT_FOUND(HttpStatus.BAD_REQUEST, "토픽 이미지가 존재하지 않습니다. 토픽 이미지를 설정해주세요."), INVALID_INSTANCE_DATE(HttpStatus.BAD_REQUEST, "인스턴스 생성/종료 일자는 현재 일자 이후여야 합니다."), INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), @@ -45,6 +46,7 @@ public enum ErrorCode { MULTIPART_FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "MultipartFile이 전달되지 않았습니다."), FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "해당 파일(이미지)이 존재하지 않습니다."), + INVALID_FILE_NAME(HttpStatus.BAD_REQUEST, "전달받은 파일(이미지)의 이름이 null이거나 빈 문자열입니다."), NOT_SUPPORTED_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 확장자입니다."), NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 타입입니다."), FILE_NOT_DELETED(HttpStatus.BAD_REQUEST, "파일(이미지)이 정상적으로 삭제되지 않았습니다."), diff --git a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java index cbaf07e4..339a4a98 100644 --- a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java @@ -2,7 +2,7 @@ import static com.genius.gitget.global.file.domain.FileType.INSTANCE; import static com.genius.gitget.global.file.domain.FileType.TOPIC; -import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_FILE_NAME; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -46,7 +46,7 @@ public void should_throwException_when_originFilenameIsNull() { //when&then assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(FILE_NOT_EXIST.getMessage()); + .hasMessageContaining(INVALID_FILE_NAME.getMessage()); } @Test @@ -58,7 +58,7 @@ public void should_throwException_when_originFilenameIsBlank() { //when&then assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(FILE_NOT_EXIST.getMessage()); + .hasMessageContaining(INVALID_FILE_NAME.getMessage()); } @Test From 65e8739eedb27e7ee6287508c94df19ee1d2beea Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Wed, 29 May 2024 12:06:04 +0900 Subject: [PATCH 186/234] =?UTF-8?q?[FEAT]=20Slack=20Webhook=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20(#186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Slack Webhook 적용 * feat: yml 수정 --- .github/workflows/main.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1bee53be..455d4aad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,4 +62,17 @@ jobs: runs-on: self-hosted steps: - name: execute deploy.sh - run: sh /home/ec2-user/deploy.sh \ No newline at end of file + run: sh /home/ec2-user/deploy.sh + + # Slack Webhook 설정 + - name: action-slack + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + author_name: Backend + fields: repo,commit,message,author + mention: here + if_mention: failure,cancelled + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: always() \ No newline at end of file From 66a6f763d9b7ebdaf7ed48ea8be206a1b26077cb Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Wed, 29 May 2024 15:57:50 +0900 Subject: [PATCH 187/234] =?UTF-8?q?[FIX]=20=EC=B6=94=EC=B2=9C=20=EC=B1=8C?= =?UTF-8?q?=EB=A6=B0=EC=A7=80=EA=B0=80=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#187)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 추천 인스턴스가 제대로 뜨지 않는 버그 픽스 - 인스턴스의 태그의 개수 >= 사용자의 태그의 개수일 때, 추천 인스턴스가 제대로 뜨지 않는 버그 픽스 - 쿼리문을 in에서 like문으로 수정 * test: 추천 인스턴스 반환과 관련된 테스트 코드 작성 - 인스턴스가 여러 개의 태그를 가지고 있을 때, 제대로 반환하는지 확인 - 반환하는 데이터의 개수가 pageSize보다 적을 때/많을 때 페이징이 제대로 작동하는지 확인하는 테스트 코드 추가 --- .../repository/InstanceRepository.java | 4 +- .../instance/service/InstanceHomeService.java | 20 +++++-- .../home/service/InstanceHomeServiceTest.java | 56 ++++++++++++++++--- .../repository/InstanceRepositoryTest.java | 32 +++++------ 4 files changed, 79 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java index d3500c3a..de98aaa6 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java @@ -18,8 +18,8 @@ public interface InstanceRepository extends JpaRepository { @Query("select i from Instance i where i.topic.id = :topicId") Page findInstancesByTopicId(Pageable pageable, Long topicId); - @Query("select i from Instance i where i.progress = :progress and i.tags in :userTags") - Slice findRecommendations(@Param("userTags") List userTags, Progress progress, Pageable pageable); + @Query("select i from Instance i where i.progress = :progress and i.tags like %:userTag%") + List findRecommendations(@Param("userTag") String userTag, Progress progress); @Query("select i from Instance i where i.progress = :progress") Slice findPagesByProgress(@Param("progress") Progress progress, Pageable pageable); diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java index 9d361b97..bf187a2f 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java @@ -8,10 +8,12 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -26,12 +28,20 @@ public class InstanceHomeService { private final InstanceRepository instanceRepository; public Slice getRecommendations(User user, Pageable pageable) { + List instances = new ArrayList<>(); List userTags = Arrays.stream(user.getTags().split(",")).toList(); - - Slice recommendations = instanceRepository.findRecommendations(userTags, PREACTIVITY, - pageable); - - return recommendations.map(this::mapToHomeInstanceResponse); + for (String userTag : userTags) { + instances.addAll(instanceRepository.findRecommendations(userTag, PREACTIVITY)); + } + + List recommendations = instances.stream() + .distinct() + .map(this::mapToHomeInstanceResponse) + .toList(); + + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), recommendations.size()); + return new PageImpl<>(recommendations.subList(start, end), pageable, recommendations.size()); } public Slice getInstancesByCondition(Pageable pageable) { diff --git a/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java b/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java index 50687f61..03776d0e 100644 --- a/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java @@ -32,22 +32,22 @@ class InstanceHomeServiceTest { InstanceRepository instanceRepository; @Test - @DisplayName("사용자가 설정한 태그에 맞는 추천 인스턴스들을 페이징 형태로 받아올 수 있다.") + @DisplayName("사용자가 설정한 태그와 하나라도 맞는 시작 전인 인스턴스의 수 만큼 반환한다.") public void should_getSuggestions_when_passUserTags() { //given PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); - getSavedInstance("title1", "BE", 20); - getSavedInstance("title2", "BE", 10); - getSavedInstance("title3", "FE", 10); - getSavedInstance("title4", "FE", 12); + getSavedInstance("title1", "BE,AI", 20); + getSavedInstance("title2", "BE,Spring", 10); + getSavedInstance("title3", "FE,BE", 10); + getSavedInstance("title4", "FE,React", 12); - User user = User.builder().tags("BE").build(); + User user = User.builder().tags("BE,React").build(); //when Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); //then - assertThat(recommendations.getContent().size()).isEqualTo(2); + assertThat(recommendations.getContent().size()).isEqualTo(4); assertThat(recommendations.getContent().get(0).title()).isEqualTo("title1"); assertThat(recommendations.getContent().get(0).participantCnt()).isEqualTo(20); assertThat(recommendations.getContent().get(0).pointPerPerson()).isEqualTo(100); @@ -57,6 +57,48 @@ public void should_getSuggestions_when_passUserTags() { assertThat(recommendations.getContent().get(1).pointPerPerson()).isEqualTo(100); } + @Test + @DisplayName("조건에 맞는 인스턴스의 개수가 pageSize보다 많다면, hasNext()가 true여야 한다.") + public void should_hasNextIsTrue_when_instanceSizeOverThanPageSize() { + //given + PageRequest pageRequest = PageRequest.of(0, 2, Sort.by(Direction.DESC, "participantCount")); + getSavedInstance("title1", "BE,AI", 20); + getSavedInstance("title2", "BE,Spring", 10); + getSavedInstance("title3", "FE,BE", 10); + getSavedInstance("title4", "FE,React", 12); + + User user = User.builder().tags("BE").build(); + + //when + Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); + + //then + assertThat(recommendations.getContent().size()).isEqualTo(2); + assertThat(recommendations.getContent().get(0).title()).isEqualTo("title1"); + assertThat(recommendations.getContent().get(1).title()).isEqualTo("title2"); + assertThat(recommendations.hasNext()).isTrue(); + } + + @Test + @DisplayName("조건에 맞는 인스턴스의 개수가 pageSize보다 적다면, hasNext()가 false여야 한다.") + public void should_hasNextIsTrue_when_instanceSizeLessThanPageSize() { + //given + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); + getSavedInstance("title1", "BE,AI", 20); + getSavedInstance("title2", "BE,Spring", 10); + getSavedInstance("title3", "FE,BE", 10); + getSavedInstance("title4", "FE,React", 12); + + User user = User.builder().tags("BE").build(); + + //when + Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); + + //then + assertThat(recommendations.getContent().size()).isEqualTo(3); + assertThat(recommendations.hasNext()).isFalse(); + } + private Instance getSavedInstance(String title, String tags, int participantCnt) { LocalDateTime now = LocalDateTime.now(); Instance instance = instanceRepository.save( diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java index f494bf12..5f387808 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -158,32 +158,26 @@ class InstanceRepositoryTest { @Test - @DisplayName("인스턴스들 중, 사용자의 tag가 포함되어 있는 인스턴스들을 반환받을 수 있다.") + @DisplayName("인스턴스들 중, 사용자의 태그와 하나라도 겹친다면 추천 챌린지 결과로 반환받아야 한다.") public void should_returnInstances_containsUserTags() { //given - List userTags = List.of("BE", "FE", "AI"); - PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); + String userTag = "BE"; //when - getSavedInstance("title1", "BE", 10); - getSavedInstance("title2", "FE", 3); + getSavedInstance("title1", "BE,AI", 10); + getSavedInstance("title2", "FE,BE", 3); getSavedInstance("title3", "FE", 20); - Slice suggestions = instanceRepository.findRecommendations(userTags, Progress.PREACTIVITY, - pageRequest); + List recommendations = instanceRepository.findRecommendations(userTag, Progress.PREACTIVITY); //then - assertThat(suggestions.getContent().size()).isEqualTo(3); - assertThat(suggestions.getContent().get(0).getTitle()).isEqualTo("title3"); - assertThat(suggestions.getContent().get(0).getTags()).isEqualTo("FE"); - assertThat(suggestions.getContent().get(0).getParticipantCount()).isEqualTo(20); - - assertThat(suggestions.getContent().get(1).getTitle()).isEqualTo("title1"); - assertThat(suggestions.getContent().get(1).getTags()).isEqualTo("BE"); - assertThat(suggestions.getContent().get(1).getParticipantCount()).isEqualTo(10); - - assertThat(suggestions.getContent().get(2).getTitle()).isEqualTo("title2"); - assertThat(suggestions.getContent().get(2).getTags()).isEqualTo("FE"); - assertThat(suggestions.getContent().get(2).getParticipantCount()).isEqualTo(3); + assertThat(recommendations.size()).isEqualTo(2); + assertThat(recommendations.get(0).getTitle()).isEqualTo("title1"); + assertThat(recommendations.get(0).getTags()).isEqualTo("BE,AI"); + assertThat(recommendations.get(0).getParticipantCount()).isEqualTo(10); + + assertThat(recommendations.get(1).getTitle()).isEqualTo("title2"); + assertThat(recommendations.get(1).getTags()).isEqualTo("FE,BE"); + assertThat(recommendations.get(1).getParticipantCount()).isEqualTo(3); } @Test From b6c895fae05bcefa6c96cb30ccd428cda1fa0401 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 30 May 2024 20:17:26 +0900 Subject: [PATCH 188/234] =?UTF-8?q?[FIX]=20FE=EC=97=90=EC=84=9C=20Item?= =?UTF-8?q?=EC=9D=84=20=EC=8B=9D=EB=B3=84=ED=95=98=EB=8A=94=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EA=B0=80=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Item 식별 전용 Column 생성 - Item 식별 시 사용할 전용 Column 생성 - 식별 전용으로 사용할 것이기 때문에 unique=true로 설정 * feat: data.sql의 insert문을 변경된 구조에 맞게 수정 * feat: 식별자를 통해 Item을 반환하는 코드 및 테스트 코드 추가 - ItemProvider, ItemService에 식별자(identifier)를 통해 Item 객체를 찾는 코드 추가 - ItemController에서 전달받은 값(식별자)를 통해 Item 객체를 받은 후 전달하는 코드 추가 - 관련 테스트 코드 추가 --- .../store/item/controller/ItemController.java | 19 ++++++++----- .../genius/gitget/store/item/domain/Item.java | 7 ++++- .../gitget/store/item/dto/ItemResponse.java | 4 +-- .../store/item/repository/ItemRepository.java | 3 +++ .../store/item/service/ItemProvider.java | 9 +++++-- src/main/resources/data.sql | 26 ++++++++++-------- .../item/service/ItemProviderTest.java | 27 +++++++++++++++---- .../item/service/ItemServiceTest.java | 5 ++-- 8 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java index a68ae787..204d77e0 100644 --- a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java +++ b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java @@ -6,10 +6,12 @@ import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.ListResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; +import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.dto.ItemResponse; import com.genius.gitget.store.item.dto.ItemUseResponse; import com.genius.gitget.store.item.dto.ProfileResponse; +import com.genius.gitget.store.item.service.ItemProvider; import com.genius.gitget.store.item.service.ItemService; import java.time.LocalDate; import java.util.List; @@ -28,6 +30,7 @@ @RequestMapping("/api") public class ItemController { private final ItemService itemService; + private final ItemProvider itemProvider; @GetMapping("/items") public ResponseEntity> getItemList( @@ -47,26 +50,28 @@ public ResponseEntity> getItemList( ); } - @PostMapping("/items/order/{itemId}") + @PostMapping("/items/order/{identifier}") public ResponseEntity> purchaseItem( @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long itemId + @PathVariable int identifier ) { - ItemResponse itemResponse = itemService.orderItem(userPrincipal.getUser(), itemId); + Item item = itemProvider.findByIdentifier(identifier); + ItemResponse itemResponse = itemService.orderItem(userPrincipal.getUser(), item.getId()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemResponse) ); } - @PostMapping("/items/use/{itemId}") + @PostMapping("/items/use/{identifier}") public ResponseEntity useItem( @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long itemId, + @PathVariable int identifier, @RequestParam(required = false) Long instanceId ) { - ItemUseResponse itemUseResponse = itemService.useItem(userPrincipal.getUser(), itemId, instanceId, - LocalDate.now()); + Item item = itemProvider.findByIdentifier(identifier); + ItemUseResponse itemUseResponse = itemService.useItem(userPrincipal.getUser(), item.getId(), + instanceId, LocalDate.now()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemUseResponse) diff --git a/src/main/java/com/genius/gitget/store/item/domain/Item.java b/src/main/java/com/genius/gitget/store/item/domain/Item.java index a0ae350f..6597a5c4 100644 --- a/src/main/java/com/genius/gitget/store/item/domain/Item.java +++ b/src/main/java/com/genius/gitget/store/item/domain/Item.java @@ -28,6 +28,9 @@ public class Item extends BaseTimeEntity { @OneToMany(mappedBy = "item") private List ordersList = new ArrayList<>(); + @Column(unique = true) + private int identifier; + private String name; private int cost; @@ -38,9 +41,11 @@ public class Item extends BaseTimeEntity { private String details; @Builder - public Item(String name, int cost, ItemCategory itemCategory, String details) { + public Item(String name, int cost, int identifier, + ItemCategory itemCategory, String details) { this.name = name; this.cost = cost; + this.identifier = identifier; this.itemCategory = itemCategory; this.details = details; } diff --git a/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java b/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java index 1b5a6574..d89b3749 100644 --- a/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java @@ -6,7 +6,7 @@ @Data public class ItemResponse { - private Long itemId; + private int itemId; private ItemCategory itemCategory; private String name; private String details; @@ -14,7 +14,7 @@ public class ItemResponse { private int count; protected ItemResponse(Item item, int count) { - this.itemId = item.getId(); + this.itemId = item.getIdentifier(); this.itemCategory = item.getItemCategory(); this.name = item.getName(); this.details = item.getDetails(); diff --git a/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java b/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java index 788eca12..067e47a1 100644 --- a/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java +++ b/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java @@ -3,6 +3,7 @@ import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -11,4 +12,6 @@ public interface ItemRepository extends JpaRepository { @Query("select i from Item i where i.itemCategory = :category") List findAllByCategory(@Param("category") ItemCategory itemCategory); + + Optional findByIdentifier(@Param("identifier") int identifier); } diff --git a/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java b/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java index e38204ed..e85e3607 100644 --- a/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java +++ b/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java @@ -1,10 +1,10 @@ package com.genius.gitget.store.item.service; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,6 +21,11 @@ public Item findById(Long itemId) { .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); } + public Item findByIdentifier(int identifier) { + return itemRepository.findByIdentifier(identifier) + .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); + } + public List findAllByCategory(ItemCategory itemCategory) { return itemRepository.findAllByCategory(itemCategory); } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 349fcdea..bf9f77ab 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,21 +1,25 @@ -INSERT INTO item (cost, created_at, deleted_at, updated_at, details, name, item_category) +INSERT INTO item (identifier, cost, details, name, item_category) SELECT * -FROM (SELECT 100 AS cost, - NULL AS created_at, - NULL AS deleted_at, - NULL AS updated_at, +FROM (SELECT 1 AS identifier, + 100 AS cost, '프로필을 꾸밀 수 있는 프레임입니다.' AS details, '성탄절 프레임' AS name, 'PROFILE_FRAME' AS item_category UNION ALL - SELECT 100, NULL, NULL, NULL, '프로필을 꾸밀 수 있는 프레임입니다.', '어둠의 힘 프레임', 'PROFILE_FRAME' + SELECT 2, + 100, + '프로필을 꾸밀 수 있는 프레임입니다.', + '어둠의 힘 프레임', + 'PROFILE_FRAME' UNION ALL - SELECT 100, NULL, NULL, NULL, '오늘의 인증을 넘길 수 있는 아이템입니다.', '인증 패스권', 'CERTIFICATION_PASSER' + SELECT 3, + 100, + '오늘의 인증을 넘길 수 있는 아이템입니다.', + '인증 패스권', + 'CERTIFICATION_PASSER' UNION ALL - SELECT 100, - NULL, - NULL, - NULL, + SELECT 4, + 100, '아이템 사용 시, 챌린지 성공 보상을 2배로 획득할 수 있는 아이템입니다.', '챌린지 보상 획득 2배 아이템', 'POINT_MULTIPLIER') AS new_items diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java index 2e78d383..4cfd292a 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java @@ -1,13 +1,14 @@ package com.genius.gitget.challenge.item.service; +import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.service.ItemProvider; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -34,7 +35,7 @@ class ItemProviderTest { @EnumSource(mode = Mode.INCLUDE, names = {"POINT_MULTIPLIER", "CERTIFICATION_PASSER"}) public void should_findItems_when_passCategory(ItemCategory itemCategory) { //given - Item item = getSavedItem(itemCategory); + Item item = getSavedItem(10, itemCategory); //when List items = itemProvider.findAllByCategory(itemCategory); @@ -49,7 +50,7 @@ public void should_findItems_when_passCategory(ItemCategory itemCategory) { @DisplayName("DB에 저장되어 있는 아이템을 식별자 PK를 통해 조회할 수 있다.") public void should_findItem_when_passPK() { //given - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + Item item = getSavedItem(10, CERTIFICATION_PASSER); //when Item foundItem = itemProvider.findById(item.getId()); @@ -68,9 +69,25 @@ public void should_throwException_when_pkNotExist() { .hasMessageContaining(ErrorCode.ITEM_NOT_FOUND.getMessage()); } - private Item getSavedItem(ItemCategory itemCategory) { + @Test + @DisplayName("식별 전용 값인 identifier를 통해 아이템을 조회할 수 있다.") + public void should_findItem_by_identifier() { + //given + int identifier = 10; + Item item = getSavedItem(identifier, CERTIFICATION_PASSER); + + //when + Item byIdentifier = itemProvider.findByIdentifier(identifier); + + //then + assertThat(item.getId()).isEqualTo(byIdentifier.getId()); + assertThat(byIdentifier.getItemCategory()).isEqualTo(CERTIFICATION_PASSER); + } + + private Item getSavedItem(int identifier, ItemCategory itemCategory) { return itemRepository.save( Item.builder() + .identifier(identifier) .cost(100) .itemCategory(itemCategory) .build() diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index c17c5d83..765c00d4 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -113,7 +113,7 @@ public void should_purchaseItem_when_passPK(ItemCategory itemCategory) { ItemResponse itemResponse = itemService.orderItem(user, item.getId()); //then - assertThat(itemResponse.getItemId()).isEqualTo(item.getId()); + assertThat(itemResponse.getItemId()).isEqualTo(item.getIdentifier()); assertThat(itemResponse.getName()).isEqualTo(item.getName()); assertThat(itemResponse.getCost()).isEqualTo(item.getCost()); assertThat(itemResponse.getCount()).isEqualTo(1); @@ -416,7 +416,7 @@ public void should_unmountFrame_when_mountAlready() { ProfileResponse profileResponse = itemService.unmountFrame(user).get(0); //then - assertThat(profileResponse.getItemId()).isEqualTo(item.getId()); + assertThat(profileResponse.getItemId()).isEqualTo(item.getIdentifier()); assertThat(profileResponse.getCost()).isEqualTo(item.getCost()); assertThat(profileResponse.getItemCategory()).isEqualTo(ItemCategory.PROFILE_FRAME); assertThat(profileResponse.getEquipStatus()).isEqualTo(EquipStatus.AVAILABLE.getTag()); @@ -466,6 +466,7 @@ private User getSavedUser() { private Item getSavedItem(ItemCategory itemCategory) { return itemRepository.save(Item.builder() + .identifier(10) .itemCategory(itemCategory) .cost(100) .name(itemCategory.getName()) From 0beb3065149ddd5a8aae980e8466fd484ad1f8f3 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:47:09 +0900 Subject: [PATCH 189/234] =?UTF-8?q?refactor:=20topic=EA=B3=BC=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=EB=90=9C=20=ED=8C=8C=EC=9D=BC=20=EC=A1=B0=EC=9E=91=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C=20(#199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 어드민의 Topic 생성 페이지에서 이미지를 받지 않기로 비지니스 로직을 수정함에 따라, FilesController 및 FilesService에서 Topic과 관련된 코드 삭제 --- .../global/file/controller/FilesController.java | 7 +------ .../gitget/global/file/service/FilesService.java | 13 ------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java index 71e270c0..3a3adc48 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -40,12 +40,7 @@ public ResponseEntity> uploadFile( FileHolder fileHolder = finder.findByInfo(id, fileType); Files files; - if (multipartFile == null && fileType == FileType.INSTANCE) { - files = filesService.copyTopicToInstance(fileHolder); - } else { - files = filesService.uploadFile(fileHolder, multipartFile, fileType); - } - + files = filesService.uploadFile(fileHolder, multipartFile, fileType); FileResponse fileResponse = filesService.convertToFileResponse(Optional.ofNullable(files)); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index 42e8a4c5..2b044a56 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -3,7 +3,6 @@ import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; import static com.genius.gitget.global.util.exception.ErrorCode.MULTIPART_FILE_NOT_EXIST; -import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.domain.FileHolder; import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; @@ -58,18 +57,6 @@ public Files uploadFile(FileHolder fileHolder, MultipartFile multipartFile, File return filesRepository.save(file); } - @Transactional - public Files copyTopicToInstance(FileHolder fileHolder) { - Instance instance = (Instance) fileHolder; - Files topicFiles = instance.getTopic().getFiles() - .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - - Files instanceFiles = copyFile(topicFiles, FileType.INSTANCE); - instance.setFiles(instanceFiles); - - return instanceFiles; - } - @Transactional public Files copyFile(Files files, FileType fileType) { FileDTO fileDTO = fileManager.copy(files, fileType); From eef0b99c54d3becc817702a72c8d21425b2d32f2 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:47:24 +0900 Subject: [PATCH 190/234] =?UTF-8?q?[FIX]=20=EB=8B=B9=EC=9D=BC=EC=97=90=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=ED=95=98=EB=8A=94=20=EC=B1=8C=EB=A6=B0?= =?UTF-8?q?=EC=A7=80=EC=97=90=20=EC=B0=B8=EC=97=AC=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20?= =?UTF-8?q?(#198)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 챌린지 참여 취소 성공 응답 메세지 변경 - 챌린지 참여 취소 성공 시 응답 메세지가 "참여 성공"으로 설정되어 있던 것을 "참여 취소 성공"으로 변경 * fix: 인스턴스 시작일에도 참여할 수 있는 버그 픽스 - 현재의 날짜가 인스턴스의 시작일보다 이전일 때에만 인스턴스에 참여할 수 있도록 조건 추가 - 관련 테스트 코드 수정 --- .../controller/InstanceDetailController.java | 5 ++- .../instance/dto/detail/JoinRequest.java | 4 +- .../service/InstanceDetailService.java | 29 ++++++++++----- .../global/util/exception/ErrorCode.java | 1 + .../global/util/exception/SuccessCode.java | 1 + .../service/InstanceDetailServiceTest.java | 37 ++++++++++++++++++- .../instance/service/ProgressServiceTest.java | 2 + 7 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java index 88b64242..c9fe7dc0 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java @@ -1,6 +1,7 @@ package com.genius.gitget.challenge.instance.controller; import static com.genius.gitget.global.util.exception.SuccessCode.JOIN_SUCCESS; +import static com.genius.gitget.global.util.exception.SuccessCode.QUIT_SUCCESS; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; @@ -9,6 +10,7 @@ import com.genius.gitget.challenge.instance.service.InstanceDetailService; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.SingleResponse; +import java.time.LocalDate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -51,6 +53,7 @@ public ResponseEntity> joinChallenge( JoinRequest joinRequest = JoinRequest.builder() .instanceId(instanceId) .repository(repo) + .todayDate(LocalDate.now()) .build(); JoinResponse joinResponse = instanceDetailService.joinNewChallenge(userPrincipal.getUser(), joinRequest); @@ -67,7 +70,7 @@ public ResponseEntity> quitChallenge( JoinResponse joinResponse = instanceDetailService.quitChallenge(userPrincipal.getUser(), instanceId); return ResponseEntity.ok().body( - new SingleResponse<>(JOIN_SUCCESS.getStatus(), JOIN_SUCCESS.getMessage(), joinResponse) + new SingleResponse<>(QUIT_SUCCESS.getStatus(), QUIT_SUCCESS.getMessage(), joinResponse) ); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java index bd31983b..b1144d80 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java @@ -1,10 +1,12 @@ package com.genius.gitget.challenge.instance.dto.detail; +import java.time.LocalDate; import lombok.Builder; @Builder public record JoinRequest( Long instanceId, - String repository + String repository, + LocalDate todayDate ) { } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index a270009f..ecf7f2b2 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -20,6 +20,7 @@ import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; +import java.time.LocalDate; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -69,9 +70,9 @@ public JoinResponse joinNewChallenge(User user, JoinRequest joinRequest) { String repository = joinRequest.repository(); - if (!verifyGithub(persistUser, repository) || !canJoinChallenge(persistUser, instance)) { - throw new BusinessException(CAN_NOT_JOIN_INSTANCE); - } + validateJoinDate(instance, joinRequest.todayDate()); + validateInstanceCondition(persistUser, instance); + validateGithub(persistUser, repository); instance.updateParticipantCount(1); Participant participant = Participant.createDefaultParticipant(repository); @@ -79,17 +80,27 @@ public JoinResponse joinNewChallenge(User user, JoinRequest joinRequest) { return JoinResponse.createJoinResponse(participantProvider.save(participant)); } - private boolean canJoinChallenge(User user, Instance instance) { - boolean b = !participantProvider.hasParticipant(user.getId(), instance.getId()); - return (instance.getProgress() == Progress.PREACTIVITY) && - !participantProvider.hasParticipant(user.getId(), instance.getId()); + private void validateJoinDate(Instance instance, LocalDate todayDate) { + LocalDate startedDate = instance.getStartedDate().toLocalDate(); + + if (todayDate.isBefore(startedDate)) { + return; + } + throw new BusinessException(CAN_NOT_JOIN_INSTANCE); + } + + private void validateInstanceCondition(User user, Instance instance) { + boolean isParticipated = participantProvider.hasParticipant(user.getId(), instance.getId()); + if ((instance.getProgress() == Progress.PREACTIVITY) && !isParticipated) { + return; + } + throw new BusinessException(CAN_NOT_JOIN_INSTANCE); } - private boolean verifyGithub(User user, String repository) { + private void validateGithub(User user, String repository) { GitHub gitHub = githubProvider.getGithubConnection(user); String repositoryFullName = githubProvider.getRepoFullName(gitHub, repository); githubProvider.validateGithubRepository(gitHub, repositoryFullName); - return true; } @Transactional diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 58cfa721..f5e1c600 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -62,6 +62,7 @@ public enum ErrorCode { GITHUB_PR_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 레포지토리에 PR이 존재하지 않습니다."), CAN_NOT_JOIN_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스에 참여할 수 없습니다."), + INVALID_JOIN_DATE(HttpStatus.BAD_REQUEST, "인스턴스 시작 당일에는 신규 참여할 수 없습니다."), CAN_NOT_QUIT_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스의 참여를 취소할 수 없습니다."), PR_TEMPLATE_NOT_FOUND(HttpStatus.NOT_FOUND, "인증에 사용하는 PR의 내용에 PR Template가 포함되어야 합니다."), diff --git a/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java b/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java index 05cdd534..e1650444 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java @@ -11,6 +11,7 @@ public enum SuccessCode { // 200 OK SUCCESS(HttpStatus.OK, "요청이 정상적으로 처리되었습니다."), JOIN_SUCCESS(HttpStatus.OK, "챌린지 참여가 정상적으로 처리되었습니다."), + QUIT_SUCCESS(HttpStatus.OK, "챌린지 참여 취소가 정상적으로 처리되었습니다."), // 201 CREATED CREATED(HttpStatus.CREATED, "정상적으로 생성되었습니다."); diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index d973c1ea..03366e0b 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -26,6 +26,7 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import java.time.LocalDate; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; @@ -70,9 +71,11 @@ public void should_saveParticipantInfo_when_passInfo() { //given User savedUser = getSavedUser(githubId); Instance instance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .instanceId(instance.getId()) .repository(targetRepo) + .todayDate(todayDate) .build(); //when @@ -107,9 +110,11 @@ public void should_throwException_when_instanceProgressNotPreactivity(Progress p //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(progress); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .repository(targetRepo) .instanceId(savedInstance.getId()) + .todayDate(todayDate) .build(); //when & then @@ -124,9 +129,11 @@ public void should_throwException_when_userAlreadyJoined() { //given User user = getSavedUser(githubId); Instance instance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .repository(targetRepo) .instanceId(instance.getId()) + .todayDate(todayDate) .build(); //when @@ -138,15 +145,37 @@ public void should_throwException_when_userAlreadyJoined() { .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); } + @Test + @DisplayName("챌린지 시작 당일에 챌린지 참여 요청을 하면 예외가 발생한다") + public void should_throwException_when_joinAtStartedDate() { + //given + LocalDate today = LocalDate.of(2024, 1, 30); + + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(Progress.PREACTIVITY, today); + JoinRequest joinRequest = JoinRequest.builder() + .repository(targetRepo) + .instanceId(instance.getId()) + .todayDate(today) + .build(); + + //when + assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(user, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.CAN_NOT_JOIN_INSTANCE.getMessage()); + } + @Test @DisplayName("아직 시작하지 않은 챌린지에 대해 취소 요청을 하면 ParticipantInfo가 삭제된다.") public void should_joinStatusIsNo_when_quitChallenge() { //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); //when - instanceDetailService.joinNewChallenge(savedUser, new JoinRequest(savedInstance.getId(), targetRepo)); + instanceDetailService.joinNewChallenge(savedUser, + new JoinRequest(savedInstance.getId(), targetRepo, todayDate)); JoinResponse joinResponse = instanceDetailService.quitChallenge(savedUser, savedInstance.getId()); //then @@ -162,9 +191,11 @@ public void should_changeParticipantInfo_when_requestQuitInstance() { //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .instanceId(savedInstance.getId()) .repository(targetRepo) + .todayDate(todayDate) .build(); //when @@ -211,9 +242,11 @@ public void should_throwException_when_progressIsDONE() { //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .instanceId(savedInstance.getId()) .repository(targetRepo) + .todayDate(todayDate) .build(); //when @@ -232,9 +265,11 @@ public void should_returnValues_when_joinedInstance() { //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .instanceId(savedInstance.getId()) .repository(targetRepo) + .todayDate(todayDate) .build(); //when diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java index 894aaf36..b64c5f85 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java @@ -66,6 +66,7 @@ class ProgressServiceTest { @DisplayName("PRE_ACTIVITY 인스턴스들 중, 특정 조건에 해당하는 인스턴스들을 ACTIVITY로 상태를 바꿀 수 있다.") public void should_updateToActivity_when_conditionMatches() { //given + LocalDate todayDate = LocalDate.of(2024, 1, 30); LocalDate startedDate = LocalDate.of(2024, 3, 1); LocalDate completedDate = LocalDate.of(2024, 3, 30); LocalDate currentDate = LocalDate.of(2024, 3, 6); @@ -81,6 +82,7 @@ public void should_updateToActivity_when_conditionMatches() { JoinRequest.builder() .repository(targetRepo) .instanceId(instance1.getId()) + .todayDate(todayDate) .build() ); From 8f12f0743b2d91314de1154d6d3937590601c8d0 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:47:40 +0900 Subject: [PATCH 191/234] =?UTF-8?q?[FEAT]=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=ED=94=84=EB=A0=88=EC=9E=84=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#196)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: data.sql의 insert문에 프로필 프레임 3개 추가 * test: 테스트 코드 수정 --- src/main/resources/data.sql | 21 ++++++++++++++++++- .../item/service/ItemServiceTest.java | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index bf9f77ab..d25726d5 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -22,5 +22,24 @@ FROM (SELECT 1 AS identifier, 100, '아이템 사용 시, 챌린지 성공 보상을 2배로 획득할 수 있는 아이템입니다.', '챌린지 보상 획득 2배 아이템', - 'POINT_MULTIPLIER') AS new_items + 'POINT_MULTIPLIER' + + UNION ALL + SELECT 5, + 100, + '프로필을 꾸밀 수 있는 프레임입니다.', + '불태워라 프레임', + 'PROFILE_FRAME' + UNION ALL + SELECT 6, + 100, + '프로필을 꾸밀 수 있는 프레임입니다.', + '끈적이는 프레임', + 'PROFILE_FRAME' + UNION ALL + SELECT 7, + 100, + '프로필을 꾸밀 수 있는 프레임입니다.', + '무섭지롱 프레임', + 'PROFILE_FRAME') AS new_items WHERE (SELECT COUNT(*) FROM item) < 3; \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index 765c00d4..11d199f3 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -77,7 +77,7 @@ public void should_getAllItems_when_itemsSaved() { List items = itemService.getAllItems(user); //then - assertThat(items.size()).isEqualTo(4); + assertThat(items.size()).isGreaterThan(1); } @ParameterizedTest From 55df2e3791b91c187bf7696ae3e8b6d7fb39c07e Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:48:01 +0900 Subject: [PATCH 192/234] =?UTF-8?q?[FEAT]=20=EB=B0=B0=ED=8F=AC=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=ED=99=94=20=EC=A0=81=EC=9A=A9=20(#195)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 배포자동화 Github Actions 워크플로우 작성 * feat: appspec.yml 파일 추가 * fix: 감지 브랜치를 production으로 국한 --- .github/workflows/main.yml | 61 +++++++++++++++++--------------------- appspec.yml | 17 +++++++++++ 2 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 appspec.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 455d4aad..7aaf18fd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,15 +1,25 @@ name: Build and Deploy to EC2 on: + push: + branches: [ "production" ] pull_request: branches: [ "production" ] +env: + AWS_REGION: ap-northeast-2 + AWS_S3_BUCKET: gitget-deploy-bucket + AWS_CODE_DEPLOY_APPLICATION: GitGet-Application-CD + AWS_CODE_DEPLOY_GROUP: GitGet-Deployment-Group + jobs: - build-docker-image: + deploy: runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 @@ -24,7 +34,6 @@ jobs: touch ./application.yml touch ./application-common.yml touch ./application-prod.yml - echo "${{ secrets.APPLICATION }}" > ./application.yml echo "${{ secrets.COMMON }}" > ./application-common.yml echo "${{ secrets.PROD }}" > ./application-prod.yml @@ -42,37 +51,23 @@ jobs: run: chmod +x ./gradlew shell: bash - - name: Build with Gradle - run: ./gradlew clean build + - name: Build and Test + run: ./gradlew build test - - name: docker login - uses: docker /login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} + - name: Make zip file + run: zip -r ./$GITHUB_SHA.zip . + shell: bash - - name: docker image build - run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/gitget-application . + - name: AWS credential 설정 + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ${{ env.AWS_REGION }} + aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY }} - - name: dockerhub push - run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/gitget-application - run-docker-image-on-ec2: - needs: build-docker-image - runs-on: self-hosted - steps: - - name: execute deploy.sh - run: sh /home/ec2-user/deploy.sh + - name: Upload to S3 + run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip - # Slack Webhook 설정 - - name: action-slack - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - author_name: Backend - fields: repo,commit,message,author - mention: here - if_mention: failure,cancelled - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - if: always() \ No newline at end of file + - name: EC2에 배포 + run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip \ No newline at end of file diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 00000000..8c60e1c8 --- /dev/null +++ b/appspec.yml @@ -0,0 +1,17 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/app + overwrite: yes + +permissions: + - object: / + owner: ubuntu + group: ubuntu + +hooks: + ApplicationStart: + - location: scripts/deploy.sh + timeout: 60 \ No newline at end of file From 9a807b6dd133a7d4cde1603c532249dd80dc0863 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:59:02 +0900 Subject: [PATCH 193/234] =?UTF-8?q?fix:=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C?= =?UTF-8?q?=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B0=9C=EC=88=98=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자가 해당 인스턴스에 좋아요를 하지 않았을 때, 좋아요 개수를 0으로 보내던 문제 해결 --- .../gitget/challenge/instance/dto/detail/LikesInfo.java | 4 ++-- .../challenge/instance/service/InstanceDetailService.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java index a46a43f9..489da6b4 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java @@ -17,11 +17,11 @@ public static LikesInfo createExist(Long likesId, int likesCount) { .build(); } - public static LikesInfo createNotExist() { + public static LikesInfo createNotExist(int likesCount) { return LikesInfo.builder() .likesId(0L) .isLiked(false) - .likesCount(0) + .likesCount(likesCount) .build(); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index ecf7f2b2..d3c64735 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -60,7 +60,7 @@ private LikesInfo getLikesInfo(Long userId, Instance instance) { return LikesInfo.createExist(likes.getId(), instance.getLikesCount()); } - return LikesInfo.createNotExist(); + return LikesInfo.createNotExist(instance.getLikesCount()); } @Transactional From da4b6be6783e1b6789afc720abdc6d1c216d9bd7 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:21:33 +0900 Subject: [PATCH 194/234] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 95e32324..00000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# Start with a base image containing Java runtime. -FROM openjdk:17-jdk - -# The application's jar file. -ARG JAR_FILE=./build/libs/GitGetApplication.jar - -# Add the application's jar to the container. -COPY ${JAR_FILE} App.jar - -# Run the jar file. -CMD ["java", "-jar", "App.jar"] \ No newline at end of file From f1c9e8748aeb76f449749413be87e6bb7edbb871 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:29:03 +0900 Subject: [PATCH 195/234] =?UTF-8?q?fix:=20=EB=88=84=EB=9D=BD=EB=90=9C=20Sl?= =?UTF-8?q?ack=20webhook=20=EC=84=A4=EC=A0=95=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 23801e1f..bd4c8f93 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -70,4 +70,19 @@ jobs: run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip - name: EC2에 배포 - run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip \ No newline at end of file + run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip + + Slack-notification: + runs-on: ubuntu-latest + steps: + - name: action-slack + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + author_name: Backend + fields: repo,commit,message,author + mention: here + if_mention: failure,cancelled + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: always() \ No newline at end of file From fcab3b506579569d2159f3d7ae9cd6b5692e439f Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:33:09 +0900 Subject: [PATCH 196/234] =?UTF-8?q?fix:=20slack=20webhook=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bd4c8f93..a92ad711 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,10 +72,7 @@ jobs: - name: EC2에 배포 run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip - Slack-notification: - runs-on: ubuntu-latest - steps: - - name: action-slack + - name: action-slack (Slack notification after deploy) uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} From 0c26b1d27d8a23b291bf403733d40768912d6620 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 8 Jun 2024 10:26:27 +0900 Subject: [PATCH 197/234] =?UTF-8?q?1=EC=B0=A8=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=9D=B4=ED=9B=84=20Slack=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0=20(#201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 무중단배포 테스트 * 무중단 배포 테스트 * [FEAT] AWS S3 파일 저장 이슈 해결 (#181) * feat: aws s3 bucket access issue - @CrossOrigin 어노테이션 추가 * feat: aws 적용 테스트 * chore: main yml 수정 (#182) * [FEAT] nginx & docker-compose 무중단 배포 (#184) * 무중단배포 테스트 * 무중단 배포 테스트 * feat: main.yml 수정 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * feat: docker build and push script 작성 * git actions & docker TEST * git actions & docker TEST * git actions & docker TEST * self-hosted runners & docker compose test * self-hosted runners & docker compose test * self-hosted runners & docker compose test * self-hosted runners & docker compose test * docker compose & nginx test * 최종 테스트 * 최종 테스트 * 최종 테스트 * 최종 테스트 * 최종 테스트 * 테스트 * feat: 무중단 배포 적용 완료 * feat: 무중단 배포 적용 완료 * feat: 무중단 배포 적용 완료 * chore: yml conflict 해결 * [REFACTOR] 파일이 저장소에 존재하지 않을 때에 대한 응답 데이터 리팩토링 및 추가 예외 처리 (#183) * refactor: 파일이 존재하지 않을 때 반환 값 변경 - 저장소에 파일이 존재하지 않을 때 반환 값을 빈 문자열("")로 변경 - 주석 추가 * refactor: 예외 코드 추가 및 변경 - 예외 상황에 맞도록 예외 코드 추가 * feat: 파일 복사 시 예외 상황 추가 처리 - 원본 파일을 이용하는 파일 복사 로직에서, 원본 파일이 존재하지 않으면 예외 발생하는 코드 추가 * test: 예외 코드 추가에 따른 테스트 코드 변경 * [FEAT] Slack Webhook 적용 (#186) * feat: Slack Webhook 적용 * feat: yml 수정 * [FIX] 추천 챌린지가 제대로 전달되지 않는 버그 픽스 (#187) * fix: 추천 인스턴스가 제대로 뜨지 않는 버그 픽스 - 인스턴스의 태그의 개수 >= 사용자의 태그의 개수일 때, 추천 인스턴스가 제대로 뜨지 않는 버그 픽스 - 쿼리문을 in에서 like문으로 수정 * test: 추천 인스턴스 반환과 관련된 테스트 코드 작성 - 인스턴스가 여러 개의 태그를 가지고 있을 때, 제대로 반환하는지 확인 - 반환하는 데이터의 개수가 pageSize보다 적을 때/많을 때 페이징이 제대로 작동하는지 확인하는 테스트 코드 추가 * [FIX] FE에서 Item을 식별하는 코드가 제대로 설정되지 않는 버그 픽스 (#189) * refactor: Item 식별 전용 Column 생성 - Item 식별 시 사용할 전용 Column 생성 - 식별 전용으로 사용할 것이기 때문에 unique=true로 설정 * feat: data.sql의 insert문을 변경된 구조에 맞게 수정 * feat: 식별자를 통해 Item을 반환하는 코드 및 테스트 코드 추가 - ItemProvider, ItemService에 식별자(identifier)를 통해 Item 객체를 찾는 코드 추가 - ItemController에서 전달받은 값(식별자)를 통해 Item 객체를 받은 후 전달하는 코드 추가 - 관련 테스트 코드 추가 * refactor: topic과 관련된 파일 조작 코드 삭제 (#199) - 어드민의 Topic 생성 페이지에서 이미지를 받지 않기로 비지니스 로직을 수정함에 따라, FilesController 및 FilesService에서 Topic과 관련된 코드 삭제 * [FIX] 당일에 시작하는 챌린지에 참여할 수 있는 버그 픽스 (#198) * refactor: 챌린지 참여 취소 성공 응답 메세지 변경 - 챌린지 참여 취소 성공 시 응답 메세지가 "참여 성공"으로 설정되어 있던 것을 "참여 취소 성공"으로 변경 * fix: 인스턴스 시작일에도 참여할 수 있는 버그 픽스 - 현재의 날짜가 인스턴스의 시작일보다 이전일 때에만 인스턴스에 참여할 수 있도록 조건 추가 - 관련 테스트 코드 수정 * [FEAT] 프로필 프레임 아이템 추가 (#196) * feat: data.sql의 insert문에 프로필 프레임 3개 추가 * test: 테스트 코드 수정 * [FEAT] 배포 자동화 적용 (#195) * feat: 배포자동화 Github Actions 워크플로우 작성 * feat: appspec.yml 파일 추가 * fix: 감지 브랜치를 production으로 국한 * fix: 인스턴스 상세 조회 시 좋아요 개수 버그 픽스 (#200) - 사용자가 해당 인스턴스에 좋아요를 하지 않았을 때, 좋아요 개수를 0으로 보내던 문제 해결 * chore: 사용하지 않는 파일 삭제 * fix: 누락된 Slack webhook 설정 코드 추가 * fix: slack webhook 위치 조정 --------- Co-authored-by: kimdozzi --- .github/workflows/main.yml | 37 ++++++------ appspec.yml | 25 +++------ scripts/health.sh | 1 - .../controller/InstanceDetailController.java | 5 +- .../instance/dto/detail/JoinRequest.java | 4 +- .../instance/dto/detail/LikesInfo.java | 4 +- .../repository/InstanceRepository.java | 4 +- .../service/InstanceDetailService.java | 31 ++++++---- .../instance/service/InstanceHomeService.java | 20 +++++-- .../controller/MyChallengeController.java | 2 + .../file/controller/FilesController.java | 7 +-- .../gitget/global/file/dto/FileResponse.java | 2 +- .../global/file/service/FileManager.java | 15 ++++- .../gitget/global/file/service/FileUtil.java | 4 +- .../global/file/service/FilesService.java | 13 ----- .../global/file/service/LocalFileManager.java | 15 ++++- .../global/file/service/S3FileManager.java | 14 ++++- .../security/config/SecurityConfig.java | 3 +- .../global/util/exception/ErrorCode.java | 3 + .../global/util/exception/SuccessCode.java | 1 + .../store/item/controller/ItemController.java | 19 ++++--- .../genius/gitget/store/item/domain/Item.java | 7 ++- .../gitget/store/item/dto/ItemResponse.java | 4 +- .../store/item/repository/ItemRepository.java | 3 + .../store/item/service/ItemProvider.java | 9 ++- src/main/resources/data.sql | 47 ++++++++++++---- .../home/service/InstanceHomeServiceTest.java | 56 ++++++++++++++++--- .../repository/InstanceRepositoryTest.java | 32 +++++------ .../service/InstanceDetailServiceTest.java | 37 +++++++++++- .../instance/service/ProgressServiceTest.java | 2 + .../item/service/ItemProviderTest.java | 27 +++++++-- .../item/service/ItemServiceTest.java | 7 ++- .../global/file/service/FileUtilTest.java | 6 +- 33 files changed, 321 insertions(+), 145 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 10cf1741..a92ad711 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,9 +8,9 @@ on: env: AWS_REGION: ap-northeast-2 - AWS_S3_BUCKET: gitget-bucket-hey - AWS_CODE_DEPLOY_APPLICATION: GitGet-Application-HEY - AWS_CODE_DEPLOY_GROUP: GitGet-CICD-group-hey + AWS_S3_BUCKET: gitget-deploy-bucket + AWS_CODE_DEPLOY_APPLICATION: GitGet-Application-CD + AWS_CODE_DEPLOY_GROUP: GitGet-Deployment-Group jobs: deploy: @@ -19,9 +19,7 @@ jobs: contents: read packages: write steps: - # Actions - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 @@ -29,7 +27,6 @@ jobs: java-version: '17' distribution: 'temurin' - # 프로젝트 내 yml 파일 실행 - name: make application.yml run: | mkdir -p ./src/main/resources @@ -37,7 +34,6 @@ jobs: touch ./application.yml touch ./application-common.yml touch ./application-prod.yml - echo "${{ secrets.APPLICATION }}" > ./application.yml echo "${{ secrets.COMMON }}" > ./application-common.yml echo "${{ secrets.PROD }}" > ./application-prod.yml @@ -51,32 +47,39 @@ jobs: echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml echo "${{ secrets.TEST }}" > ./application-test.yml - - name: Grant execute permission for gradlew run: chmod +x ./gradlew shell: bash - - name: Build with Gradle and Test run: ./gradlew build test - - name: Make zip file run: zip -r ./$GITHUB_SHA.zip . shell: bash - - - name: Deliver to AWS S3 (AWS credential 설정) + - name: AWS credential 설정 uses: aws-actions/configure-aws-credentials@v1 with: aws-region: ${{ env.AWS_REGION }} - aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY_HEY }} - aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY_HEY }} + aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY }} - name: Upload to S3 run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip + - name: EC2에 배포 + run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip - - name: Code Deploy (EC2에 배포) - run: aws deploy create-deployment --application-name GitGet-Application-HEY --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name GitGet-CICD-group-hey --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip \ No newline at end of file + - name: action-slack (Slack notification after deploy) + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + author_name: Backend + fields: repo,commit,message,author + mention: here + if_mention: failure,cancelled + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: always() \ No newline at end of file diff --git a/appspec.yml b/appspec.yml index 3f9d6147..8c60e1c8 100644 --- a/appspec.yml +++ b/appspec.yml @@ -1,28 +1,17 @@ -version: 0.0 # CodeDeploy Version. - -os: linux # 배포할 서버의 운영체제 +version: 0.0 +os: linux files: - - source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정 (루트 경로 : 전체 파일) - destination: /home/ubuntu/app # source에서 지정된 파일을 받을 위치 - overwrite: yes # 기존 파일들을 덮어 쓰기 + - source: / + destination: /home/ubuntu/app + overwrite: yes -# CodeDeploy에서 EC2로 넘겨준 파일들을 모두 ec2-user 권한 부여. permissions: - object: / - pattern: "**" owner: ubuntu group: ubuntu -# CodeDeploy 배포 단계에서 실행할 명령어를 지정 (차례대로 스크립트들이 실행) hooks: ApplicationStart: - - location: scripts/run_new_was.sh - timeout: 180 - runas: ubuntu - - location: scripts/health.sh - timeout: 180 - runas: ubuntu - - location: scripts/switch.sh - timeout: 180 - runas: ubuntu + - location: scripts/deploy.sh + timeout: 60 \ No newline at end of file diff --git a/scripts/health.sh b/scripts/health.sh index baa66cc7..e9539eb8 100644 --- a/scripts/health.sh +++ b/scripts/health.sh @@ -16,7 +16,6 @@ else exit 1 fi - echo "> Start health check of WAS at http://localhost:${TARGET_PORT}/api/auth/health-check ..." for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10 diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java index 88b64242..c9fe7dc0 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java @@ -1,6 +1,7 @@ package com.genius.gitget.challenge.instance.controller; import static com.genius.gitget.global.util.exception.SuccessCode.JOIN_SUCCESS; +import static com.genius.gitget.global.util.exception.SuccessCode.QUIT_SUCCESS; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; @@ -9,6 +10,7 @@ import com.genius.gitget.challenge.instance.service.InstanceDetailService; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.SingleResponse; +import java.time.LocalDate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -51,6 +53,7 @@ public ResponseEntity> joinChallenge( JoinRequest joinRequest = JoinRequest.builder() .instanceId(instanceId) .repository(repo) + .todayDate(LocalDate.now()) .build(); JoinResponse joinResponse = instanceDetailService.joinNewChallenge(userPrincipal.getUser(), joinRequest); @@ -67,7 +70,7 @@ public ResponseEntity> quitChallenge( JoinResponse joinResponse = instanceDetailService.quitChallenge(userPrincipal.getUser(), instanceId); return ResponseEntity.ok().body( - new SingleResponse<>(JOIN_SUCCESS.getStatus(), JOIN_SUCCESS.getMessage(), joinResponse) + new SingleResponse<>(QUIT_SUCCESS.getStatus(), QUIT_SUCCESS.getMessage(), joinResponse) ); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java index bd31983b..b1144d80 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/JoinRequest.java @@ -1,10 +1,12 @@ package com.genius.gitget.challenge.instance.dto.detail; +import java.time.LocalDate; import lombok.Builder; @Builder public record JoinRequest( Long instanceId, - String repository + String repository, + LocalDate todayDate ) { } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java index a46a43f9..489da6b4 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/LikesInfo.java @@ -17,11 +17,11 @@ public static LikesInfo createExist(Long likesId, int likesCount) { .build(); } - public static LikesInfo createNotExist() { + public static LikesInfo createNotExist(int likesCount) { return LikesInfo.builder() .likesId(0L) .isLiked(false) - .likesCount(0) + .likesCount(likesCount) .build(); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java index d3500c3a..de98aaa6 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java +++ b/src/main/java/com/genius/gitget/challenge/instance/repository/InstanceRepository.java @@ -18,8 +18,8 @@ public interface InstanceRepository extends JpaRepository { @Query("select i from Instance i where i.topic.id = :topicId") Page findInstancesByTopicId(Pageable pageable, Long topicId); - @Query("select i from Instance i where i.progress = :progress and i.tags in :userTags") - Slice findRecommendations(@Param("userTags") List userTags, Progress progress, Pageable pageable); + @Query("select i from Instance i where i.progress = :progress and i.tags like %:userTag%") + List findRecommendations(@Param("userTag") String userTag, Progress progress); @Query("select i from Instance i where i.progress = :progress") Slice findPagesByProgress(@Param("progress") Progress progress, Pageable pageable); diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index a270009f..d3c64735 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -20,6 +20,7 @@ import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; +import java.time.LocalDate; import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -59,7 +60,7 @@ private LikesInfo getLikesInfo(Long userId, Instance instance) { return LikesInfo.createExist(likes.getId(), instance.getLikesCount()); } - return LikesInfo.createNotExist(); + return LikesInfo.createNotExist(instance.getLikesCount()); } @Transactional @@ -69,9 +70,9 @@ public JoinResponse joinNewChallenge(User user, JoinRequest joinRequest) { String repository = joinRequest.repository(); - if (!verifyGithub(persistUser, repository) || !canJoinChallenge(persistUser, instance)) { - throw new BusinessException(CAN_NOT_JOIN_INSTANCE); - } + validateJoinDate(instance, joinRequest.todayDate()); + validateInstanceCondition(persistUser, instance); + validateGithub(persistUser, repository); instance.updateParticipantCount(1); Participant participant = Participant.createDefaultParticipant(repository); @@ -79,17 +80,27 @@ public JoinResponse joinNewChallenge(User user, JoinRequest joinRequest) { return JoinResponse.createJoinResponse(participantProvider.save(participant)); } - private boolean canJoinChallenge(User user, Instance instance) { - boolean b = !participantProvider.hasParticipant(user.getId(), instance.getId()); - return (instance.getProgress() == Progress.PREACTIVITY) && - !participantProvider.hasParticipant(user.getId(), instance.getId()); + private void validateJoinDate(Instance instance, LocalDate todayDate) { + LocalDate startedDate = instance.getStartedDate().toLocalDate(); + + if (todayDate.isBefore(startedDate)) { + return; + } + throw new BusinessException(CAN_NOT_JOIN_INSTANCE); + } + + private void validateInstanceCondition(User user, Instance instance) { + boolean isParticipated = participantProvider.hasParticipant(user.getId(), instance.getId()); + if ((instance.getProgress() == Progress.PREACTIVITY) && !isParticipated) { + return; + } + throw new BusinessException(CAN_NOT_JOIN_INSTANCE); } - private boolean verifyGithub(User user, String repository) { + private void validateGithub(User user, String repository) { GitHub gitHub = githubProvider.getGithubConnection(user); String repositoryFullName = githubProvider.getRepoFullName(gitHub, repository); githubProvider.validateGithubRepository(gitHub, repositoryFullName); - return true; } @Transactional diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java index 9d361b97..bf187a2f 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java @@ -8,10 +8,12 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -26,12 +28,20 @@ public class InstanceHomeService { private final InstanceRepository instanceRepository; public Slice getRecommendations(User user, Pageable pageable) { + List instances = new ArrayList<>(); List userTags = Arrays.stream(user.getTags().split(",")).toList(); - - Slice recommendations = instanceRepository.findRecommendations(userTags, PREACTIVITY, - pageable); - - return recommendations.map(this::mapToHomeInstanceResponse); + for (String userTag : userTags) { + instances.addAll(instanceRepository.findRecommendations(userTag, PREACTIVITY)); + } + + List recommendations = instances.stream() + .distinct() + .map(this::mapToHomeInstanceResponse) + .toList(); + + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), recommendations.size()); + return new PageImpl<>(recommendations.subList(start, end), pageable, recommendations.size()); } public Slice getInstancesByCondition(Pageable pageable) { diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java index aa2a5b2a..ddf51e80 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java @@ -15,6 +15,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -23,6 +24,7 @@ @RestController @RequestMapping("/api/challenges") @RequiredArgsConstructor +@CrossOrigin public class MyChallengeController { private final MyChallengeService myChallengeService; diff --git a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java index 71e270c0..3a3adc48 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -40,12 +40,7 @@ public ResponseEntity> uploadFile( FileHolder fileHolder = finder.findByInfo(id, fileType); Files files; - if (multipartFile == null && fileType == FileType.INSTANCE) { - files = filesService.copyTopicToInstance(fileHolder); - } else { - files = filesService.uploadFile(fileHolder, multipartFile, fileType); - } - + files = filesService.uploadFile(fileHolder, multipartFile, fileType); FileResponse fileResponse = filesService.convertToFileResponse(Optional.ofNullable(files)); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index ec485146..6505d331 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -9,6 +9,6 @@ public static FileResponse createExistFile(Long filesId, String encodedFile) { } public static FileResponse createNotExistFile() { - return new FileResponse(0L, "none"); + return new FileResponse(0L, ""); } } diff --git a/src/main/java/com/genius/gitget/global/file/service/FileManager.java b/src/main/java/com/genius/gitget/global/file/service/FileManager.java index 1fb41eeb..90263563 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileManager.java @@ -17,7 +17,8 @@ public interface FileManager { * Files 내에 저장된 값들을 통해 UrlResource 등으로 다운받은 후, base64로 인코딩한 결과 반환 * * @param files 얻기 원하는 파일의 정보를 담고 있는 Files 객체 - * @return base64로 encode한 결과 값(문자열) + * @return base64로 encode한 결과 값(문자열) 반환 + * 파일을 받아오지 못한 경우에는 빈 문자열("") 반환 */ String getEncodedImage(Files files); @@ -32,10 +33,12 @@ public interface FileManager { /** * 기존에 저장소에 저장되어 있던 파일을 특정 타입에 복사 후, Files 객체 생성에 필요한 정보들을 반환 + * NOTE!! 복사 이전에 원본이 되는 파일이 저장소에 존재하는지 `validateFileExist()`를 통해 확인 필요 * * @param files 복사하고자하는 파일의 정보를 담고 있는 Files 객체 * @param fileType 복사해서 적용하고 싶은 대상의 파일 타입(TOPIC/INSTANCE/PROFILE 중 택 1) * @return Files 객체 생성에 필요한 정보(UploadDTO) 반환 + * @throws BusinessException 원본이 되는 파일이 저장소에 존재하지 않는 경우 FILE_NOT_EXIST 발생 */ FileDTO copy(Files files, FileType fileType); @@ -55,4 +58,14 @@ public interface FileManager { * @throws BusinessException 삭제에 실패했을 때 발생 */ void deleteInStorage(Files files); + + /** + * Files 객체 내의 정보를 활용하여 저장소에 파일이 저장이 되어 있는지 확인 후 boolean 반환 + * 각 저장소의 특성에 맞춰 Files 내의 메타데이터를 통해 저장소 내에 파일이 제대로 저장되어 있는지 확인 + * 파일이 존재하지 않는 경우 FILE_NOT_EXIST 예외 발생 시킬 것 + * + * @param files 저장소에 저장되어 있는지 확인하고자하는 Files 객체 + * @throws FILE_NOT_EXIST 저장소에서 파일(이미지)을 찾을 수 없을 때 발생 + */ + void validateFileExist(Files files); } diff --git a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java index a5d965bb..22e0a582 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileUtil.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileUtil.java @@ -1,6 +1,6 @@ package com.genius.gitget.global.file.service; -import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_FILE_NAME; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import com.genius.gitget.global.file.domain.FileType; @@ -48,7 +48,7 @@ public void validateFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (originalFilename == null || Objects.equals(originalFilename, "")) { - throw new BusinessException(FILE_NOT_EXIST); + throw new BusinessException(INVALID_FILE_NAME); } String extension = extractExtension(originalFilename); diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesService.java index 42e8a4c5..2b044a56 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesService.java @@ -3,7 +3,6 @@ import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; import static com.genius.gitget.global.util.exception.ErrorCode.MULTIPART_FILE_NOT_EXIST; -import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.domain.FileHolder; import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; @@ -58,18 +57,6 @@ public Files uploadFile(FileHolder fileHolder, MultipartFile multipartFile, File return filesRepository.save(file); } - @Transactional - public Files copyTopicToInstance(FileHolder fileHolder) { - Instance instance = (Instance) fileHolder; - Files topicFiles = instance.getTopic().getFiles() - .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - - Files instanceFiles = copyFile(topicFiles, FileType.INSTANCE); - instance.setFiles(instanceFiles); - - return instanceFiles; - } - @Transactional public Files copyFile(Files files, FileType fileType) { FileDTO fileDTO = fileManager.copy(files, fileType); diff --git a/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java b/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java index 5a5fabec..e249ff3f 100644 --- a/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java @@ -2,6 +2,7 @@ import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_COPIED; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_DELETED; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_SAVED; import com.genius.gitget.global.file.domain.FileType; @@ -52,12 +53,15 @@ public String getEncodedImage(Files files) { byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); return new String(encode, StandardCharsets.UTF_8); } catch (IOException e) { - throw new BusinessException(e); + //TODO: 불러오는 중의 예외에 대해 Logging 추가하기 + return ""; } } @Override public FileDTO copy(Files files, FileType fileType) { + validateFileExist(files); + CopyDTO copyDTO = fileUtil.getCopyInfo(files, fileType, UPLOAD_PATH); createPath(copyDTO.folderURI()); @@ -95,6 +99,15 @@ public void deleteInStorage(Files files) { } } + @Override + public void validateFileExist(Files files) { + String fileURI = files.getFileURI(); + File file = new File(fileURI); + if (!file.exists()) { + throw new BusinessException(FILE_NOT_EXIST); + } + } + private void createPath(String uri) { File file = new File(uri); if (!file.exists()) { diff --git a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java b/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java index 8566b51a..5fc5c069 100644 --- a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java @@ -1,5 +1,7 @@ package com.genius.gitget.global.file.service; +import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; + import com.amazonaws.SdkClientException; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.CopyObjectRequest; @@ -34,7 +36,8 @@ public String getEncodedImage(Files files) { byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); return new String(encode, StandardCharsets.UTF_8); } catch (IOException e) { - throw new BusinessException(e); + //TODO: 불러오는 중의 예외에 대해 Logging 추가하기 + return ""; } } @@ -57,6 +60,8 @@ public FileDTO upload(MultipartFile multipartFile, FileType fileType) { @Override public FileDTO copy(Files files, FileType fileType) { + validateFileExist(files); + CopyDTO copyDTO = fileUtil.getCopyInfo(files, fileType, ""); CopyObjectRequest copyObjectRequest = new CopyObjectRequest( @@ -88,4 +93,11 @@ public void deleteInStorage(Files files) { throw new BusinessException(e); } } + + @Override + public void validateFileExist(Files files) { + if (!amazonS3.doesObjectExist(bucket, files.getFileURI())) { + throw new BusinessException(FILE_NOT_EXIST); + } + } } diff --git a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java index b189ab5c..2bd56770 100644 --- a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java @@ -27,7 +27,8 @@ @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { - public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**", "/login", "/favicon.ico"}; + public static final String PERMITTED_URI[] = {"/v3/**", "/swagger-ui/**", "/api/auth/**", "/login", + "/favicon.ico"}; private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index d34110cd..f5e1c600 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -21,6 +21,7 @@ public enum ErrorCode { TOPIC_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 토픽을 찾을 수 없습니다."), TOPIC_HAVE_INSTANCE(HttpStatus.BAD_REQUEST, "해당 토픽은 인스턴스를 가지고 있으므로 삭제할 수 없습니다."), + TOPIC_IMAGE_NOT_FOUND(HttpStatus.BAD_REQUEST, "토픽 이미지가 존재하지 않습니다. 토픽 이미지를 설정해주세요."), INVALID_INSTANCE_DATE(HttpStatus.BAD_REQUEST, "인스턴스 생성/종료 일자는 현재 일자 이후여야 합니다."), INSTANCE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 인스턴스를 찾을 수 없습니다."), @@ -45,6 +46,7 @@ public enum ErrorCode { MULTIPART_FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "MultipartFile이 전달되지 않았습니다."), FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "해당 파일(이미지)이 존재하지 않습니다."), + INVALID_FILE_NAME(HttpStatus.BAD_REQUEST, "전달받은 파일(이미지)의 이름이 null이거나 빈 문자열입니다."), NOT_SUPPORTED_EXTENSION(HttpStatus.BAD_REQUEST, "지원하지 않는 확장자입니다."), NOT_SUPPORTED_IMAGE_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 타입입니다."), FILE_NOT_DELETED(HttpStatus.BAD_REQUEST, "파일(이미지)이 정상적으로 삭제되지 않았습니다."), @@ -60,6 +62,7 @@ public enum ErrorCode { GITHUB_PR_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 레포지토리에 PR이 존재하지 않습니다."), CAN_NOT_JOIN_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스에 참여할 수 없습니다."), + INVALID_JOIN_DATE(HttpStatus.BAD_REQUEST, "인스턴스 시작 당일에는 신규 참여할 수 없습니다."), CAN_NOT_QUIT_INSTANCE(HttpStatus.BAD_REQUEST, "해당 인스턴스의 참여를 취소할 수 없습니다."), PR_TEMPLATE_NOT_FOUND(HttpStatus.NOT_FOUND, "인증에 사용하는 PR의 내용에 PR Template가 포함되어야 합니다."), diff --git a/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java b/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java index 05cdd534..e1650444 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/SuccessCode.java @@ -11,6 +11,7 @@ public enum SuccessCode { // 200 OK SUCCESS(HttpStatus.OK, "요청이 정상적으로 처리되었습니다."), JOIN_SUCCESS(HttpStatus.OK, "챌린지 참여가 정상적으로 처리되었습니다."), + QUIT_SUCCESS(HttpStatus.OK, "챌린지 참여 취소가 정상적으로 처리되었습니다."), // 201 CREATED CREATED(HttpStatus.CREATED, "정상적으로 생성되었습니다."); diff --git a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java index a68ae787..204d77e0 100644 --- a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java +++ b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java @@ -6,10 +6,12 @@ import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.ListResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; +import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.dto.ItemResponse; import com.genius.gitget.store.item.dto.ItemUseResponse; import com.genius.gitget.store.item.dto.ProfileResponse; +import com.genius.gitget.store.item.service.ItemProvider; import com.genius.gitget.store.item.service.ItemService; import java.time.LocalDate; import java.util.List; @@ -28,6 +30,7 @@ @RequestMapping("/api") public class ItemController { private final ItemService itemService; + private final ItemProvider itemProvider; @GetMapping("/items") public ResponseEntity> getItemList( @@ -47,26 +50,28 @@ public ResponseEntity> getItemList( ); } - @PostMapping("/items/order/{itemId}") + @PostMapping("/items/order/{identifier}") public ResponseEntity> purchaseItem( @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long itemId + @PathVariable int identifier ) { - ItemResponse itemResponse = itemService.orderItem(userPrincipal.getUser(), itemId); + Item item = itemProvider.findByIdentifier(identifier); + ItemResponse itemResponse = itemService.orderItem(userPrincipal.getUser(), item.getId()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemResponse) ); } - @PostMapping("/items/use/{itemId}") + @PostMapping("/items/use/{identifier}") public ResponseEntity useItem( @AuthenticationPrincipal UserPrincipal userPrincipal, - @PathVariable Long itemId, + @PathVariable int identifier, @RequestParam(required = false) Long instanceId ) { - ItemUseResponse itemUseResponse = itemService.useItem(userPrincipal.getUser(), itemId, instanceId, - LocalDate.now()); + Item item = itemProvider.findByIdentifier(identifier); + ItemUseResponse itemUseResponse = itemService.useItem(userPrincipal.getUser(), item.getId(), + instanceId, LocalDate.now()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemUseResponse) diff --git a/src/main/java/com/genius/gitget/store/item/domain/Item.java b/src/main/java/com/genius/gitget/store/item/domain/Item.java index a0ae350f..6597a5c4 100644 --- a/src/main/java/com/genius/gitget/store/item/domain/Item.java +++ b/src/main/java/com/genius/gitget/store/item/domain/Item.java @@ -28,6 +28,9 @@ public class Item extends BaseTimeEntity { @OneToMany(mappedBy = "item") private List ordersList = new ArrayList<>(); + @Column(unique = true) + private int identifier; + private String name; private int cost; @@ -38,9 +41,11 @@ public class Item extends BaseTimeEntity { private String details; @Builder - public Item(String name, int cost, ItemCategory itemCategory, String details) { + public Item(String name, int cost, int identifier, + ItemCategory itemCategory, String details) { this.name = name; this.cost = cost; + this.identifier = identifier; this.itemCategory = itemCategory; this.details = details; } diff --git a/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java b/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java index 1b5a6574..d89b3749 100644 --- a/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/ItemResponse.java @@ -6,7 +6,7 @@ @Data public class ItemResponse { - private Long itemId; + private int itemId; private ItemCategory itemCategory; private String name; private String details; @@ -14,7 +14,7 @@ public class ItemResponse { private int count; protected ItemResponse(Item item, int count) { - this.itemId = item.getId(); + this.itemId = item.getIdentifier(); this.itemCategory = item.getItemCategory(); this.name = item.getName(); this.details = item.getDetails(); diff --git a/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java b/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java index 788eca12..067e47a1 100644 --- a/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java +++ b/src/main/java/com/genius/gitget/store/item/repository/ItemRepository.java @@ -3,6 +3,7 @@ import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -11,4 +12,6 @@ public interface ItemRepository extends JpaRepository { @Query("select i from Item i where i.itemCategory = :category") List findAllByCategory(@Param("category") ItemCategory itemCategory); + + Optional findByIdentifier(@Param("identifier") int identifier); } diff --git a/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java b/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java index e38204ed..e85e3607 100644 --- a/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java +++ b/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java @@ -1,10 +1,10 @@ package com.genius.gitget.store.item.service; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,6 +21,11 @@ public Item findById(Long itemId) { .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); } + public Item findByIdentifier(int identifier) { + return itemRepository.findByIdentifier(identifier) + .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); + } + public List findAllByCategory(ItemCategory itemCategory) { return itemRepository.findAllByCategory(itemCategory); } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 349fcdea..d25726d5 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,22 +1,45 @@ -INSERT INTO item (cost, created_at, deleted_at, updated_at, details, name, item_category) +INSERT INTO item (identifier, cost, details, name, item_category) SELECT * -FROM (SELECT 100 AS cost, - NULL AS created_at, - NULL AS deleted_at, - NULL AS updated_at, +FROM (SELECT 1 AS identifier, + 100 AS cost, '프로필을 꾸밀 수 있는 프레임입니다.' AS details, '성탄절 프레임' AS name, 'PROFILE_FRAME' AS item_category UNION ALL - SELECT 100, NULL, NULL, NULL, '프로필을 꾸밀 수 있는 프레임입니다.', '어둠의 힘 프레임', 'PROFILE_FRAME' + SELECT 2, + 100, + '프로필을 꾸밀 수 있는 프레임입니다.', + '어둠의 힘 프레임', + 'PROFILE_FRAME' UNION ALL - SELECT 100, NULL, NULL, NULL, '오늘의 인증을 넘길 수 있는 아이템입니다.', '인증 패스권', 'CERTIFICATION_PASSER' + SELECT 3, + 100, + '오늘의 인증을 넘길 수 있는 아이템입니다.', + '인증 패스권', + 'CERTIFICATION_PASSER' UNION ALL - SELECT 100, - NULL, - NULL, - NULL, + SELECT 4, + 100, '아이템 사용 시, 챌린지 성공 보상을 2배로 획득할 수 있는 아이템입니다.', '챌린지 보상 획득 2배 아이템', - 'POINT_MULTIPLIER') AS new_items + 'POINT_MULTIPLIER' + + UNION ALL + SELECT 5, + 100, + '프로필을 꾸밀 수 있는 프레임입니다.', + '불태워라 프레임', + 'PROFILE_FRAME' + UNION ALL + SELECT 6, + 100, + '프로필을 꾸밀 수 있는 프레임입니다.', + '끈적이는 프레임', + 'PROFILE_FRAME' + UNION ALL + SELECT 7, + 100, + '프로필을 꾸밀 수 있는 프레임입니다.', + '무섭지롱 프레임', + 'PROFILE_FRAME') AS new_items WHERE (SELECT COUNT(*) FROM item) < 3; \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java b/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java index 50687f61..03776d0e 100644 --- a/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java @@ -32,22 +32,22 @@ class InstanceHomeServiceTest { InstanceRepository instanceRepository; @Test - @DisplayName("사용자가 설정한 태그에 맞는 추천 인스턴스들을 페이징 형태로 받아올 수 있다.") + @DisplayName("사용자가 설정한 태그와 하나라도 맞는 시작 전인 인스턴스의 수 만큼 반환한다.") public void should_getSuggestions_when_passUserTags() { //given PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); - getSavedInstance("title1", "BE", 20); - getSavedInstance("title2", "BE", 10); - getSavedInstance("title3", "FE", 10); - getSavedInstance("title4", "FE", 12); + getSavedInstance("title1", "BE,AI", 20); + getSavedInstance("title2", "BE,Spring", 10); + getSavedInstance("title3", "FE,BE", 10); + getSavedInstance("title4", "FE,React", 12); - User user = User.builder().tags("BE").build(); + User user = User.builder().tags("BE,React").build(); //when Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); //then - assertThat(recommendations.getContent().size()).isEqualTo(2); + assertThat(recommendations.getContent().size()).isEqualTo(4); assertThat(recommendations.getContent().get(0).title()).isEqualTo("title1"); assertThat(recommendations.getContent().get(0).participantCnt()).isEqualTo(20); assertThat(recommendations.getContent().get(0).pointPerPerson()).isEqualTo(100); @@ -57,6 +57,48 @@ public void should_getSuggestions_when_passUserTags() { assertThat(recommendations.getContent().get(1).pointPerPerson()).isEqualTo(100); } + @Test + @DisplayName("조건에 맞는 인스턴스의 개수가 pageSize보다 많다면, hasNext()가 true여야 한다.") + public void should_hasNextIsTrue_when_instanceSizeOverThanPageSize() { + //given + PageRequest pageRequest = PageRequest.of(0, 2, Sort.by(Direction.DESC, "participantCount")); + getSavedInstance("title1", "BE,AI", 20); + getSavedInstance("title2", "BE,Spring", 10); + getSavedInstance("title3", "FE,BE", 10); + getSavedInstance("title4", "FE,React", 12); + + User user = User.builder().tags("BE").build(); + + //when + Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); + + //then + assertThat(recommendations.getContent().size()).isEqualTo(2); + assertThat(recommendations.getContent().get(0).title()).isEqualTo("title1"); + assertThat(recommendations.getContent().get(1).title()).isEqualTo("title2"); + assertThat(recommendations.hasNext()).isTrue(); + } + + @Test + @DisplayName("조건에 맞는 인스턴스의 개수가 pageSize보다 적다면, hasNext()가 false여야 한다.") + public void should_hasNextIsTrue_when_instanceSizeLessThanPageSize() { + //given + PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); + getSavedInstance("title1", "BE,AI", 20); + getSavedInstance("title2", "BE,Spring", 10); + getSavedInstance("title3", "FE,BE", 10); + getSavedInstance("title4", "FE,React", 12); + + User user = User.builder().tags("BE").build(); + + //when + Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); + + //then + assertThat(recommendations.getContent().size()).isEqualTo(3); + assertThat(recommendations.hasNext()).isFalse(); + } + private Instance getSavedInstance(String title, String tags, int participantCnt) { LocalDateTime now = LocalDateTime.now(); Instance instance = instanceRepository.save( diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java index f494bf12..5f387808 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceRepositoryTest.java @@ -158,32 +158,26 @@ class InstanceRepositoryTest { @Test - @DisplayName("인스턴스들 중, 사용자의 tag가 포함되어 있는 인스턴스들을 반환받을 수 있다.") + @DisplayName("인스턴스들 중, 사용자의 태그와 하나라도 겹친다면 추천 챌린지 결과로 반환받아야 한다.") public void should_returnInstances_containsUserTags() { //given - List userTags = List.of("BE", "FE", "AI"); - PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Direction.DESC, "participantCount")); + String userTag = "BE"; //when - getSavedInstance("title1", "BE", 10); - getSavedInstance("title2", "FE", 3); + getSavedInstance("title1", "BE,AI", 10); + getSavedInstance("title2", "FE,BE", 3); getSavedInstance("title3", "FE", 20); - Slice suggestions = instanceRepository.findRecommendations(userTags, Progress.PREACTIVITY, - pageRequest); + List recommendations = instanceRepository.findRecommendations(userTag, Progress.PREACTIVITY); //then - assertThat(suggestions.getContent().size()).isEqualTo(3); - assertThat(suggestions.getContent().get(0).getTitle()).isEqualTo("title3"); - assertThat(suggestions.getContent().get(0).getTags()).isEqualTo("FE"); - assertThat(suggestions.getContent().get(0).getParticipantCount()).isEqualTo(20); - - assertThat(suggestions.getContent().get(1).getTitle()).isEqualTo("title1"); - assertThat(suggestions.getContent().get(1).getTags()).isEqualTo("BE"); - assertThat(suggestions.getContent().get(1).getParticipantCount()).isEqualTo(10); - - assertThat(suggestions.getContent().get(2).getTitle()).isEqualTo("title2"); - assertThat(suggestions.getContent().get(2).getTags()).isEqualTo("FE"); - assertThat(suggestions.getContent().get(2).getParticipantCount()).isEqualTo(3); + assertThat(recommendations.size()).isEqualTo(2); + assertThat(recommendations.get(0).getTitle()).isEqualTo("title1"); + assertThat(recommendations.get(0).getTags()).isEqualTo("BE,AI"); + assertThat(recommendations.get(0).getParticipantCount()).isEqualTo(10); + + assertThat(recommendations.get(1).getTitle()).isEqualTo("title2"); + assertThat(recommendations.get(1).getTags()).isEqualTo("FE,BE"); + assertThat(recommendations.get(1).getParticipantCount()).isEqualTo(3); } @Test diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index d973c1ea..03366e0b 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -26,6 +26,7 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import java.time.LocalDate; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; @@ -70,9 +71,11 @@ public void should_saveParticipantInfo_when_passInfo() { //given User savedUser = getSavedUser(githubId); Instance instance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .instanceId(instance.getId()) .repository(targetRepo) + .todayDate(todayDate) .build(); //when @@ -107,9 +110,11 @@ public void should_throwException_when_instanceProgressNotPreactivity(Progress p //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(progress); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .repository(targetRepo) .instanceId(savedInstance.getId()) + .todayDate(todayDate) .build(); //when & then @@ -124,9 +129,11 @@ public void should_throwException_when_userAlreadyJoined() { //given User user = getSavedUser(githubId); Instance instance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .repository(targetRepo) .instanceId(instance.getId()) + .todayDate(todayDate) .build(); //when @@ -138,15 +145,37 @@ public void should_throwException_when_userAlreadyJoined() { .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); } + @Test + @DisplayName("챌린지 시작 당일에 챌린지 참여 요청을 하면 예외가 발생한다") + public void should_throwException_when_joinAtStartedDate() { + //given + LocalDate today = LocalDate.of(2024, 1, 30); + + User user = getSavedUser(githubId); + Instance instance = getSavedInstance(Progress.PREACTIVITY, today); + JoinRequest joinRequest = JoinRequest.builder() + .repository(targetRepo) + .instanceId(instance.getId()) + .todayDate(today) + .build(); + + //when + assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(user, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.CAN_NOT_JOIN_INSTANCE.getMessage()); + } + @Test @DisplayName("아직 시작하지 않은 챌린지에 대해 취소 요청을 하면 ParticipantInfo가 삭제된다.") public void should_joinStatusIsNo_when_quitChallenge() { //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); //when - instanceDetailService.joinNewChallenge(savedUser, new JoinRequest(savedInstance.getId(), targetRepo)); + instanceDetailService.joinNewChallenge(savedUser, + new JoinRequest(savedInstance.getId(), targetRepo, todayDate)); JoinResponse joinResponse = instanceDetailService.quitChallenge(savedUser, savedInstance.getId()); //then @@ -162,9 +191,11 @@ public void should_changeParticipantInfo_when_requestQuitInstance() { //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .instanceId(savedInstance.getId()) .repository(targetRepo) + .todayDate(todayDate) .build(); //when @@ -211,9 +242,11 @@ public void should_throwException_when_progressIsDONE() { //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .instanceId(savedInstance.getId()) .repository(targetRepo) + .todayDate(todayDate) .build(); //when @@ -232,9 +265,11 @@ public void should_returnValues_when_joinedInstance() { //given User savedUser = getSavedUser(githubId); Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); + LocalDate todayDate = LocalDate.of(2024, 1, 30); JoinRequest joinRequest = JoinRequest.builder() .instanceId(savedInstance.getId()) .repository(targetRepo) + .todayDate(todayDate) .build(); //when diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java index 894aaf36..b64c5f85 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java @@ -66,6 +66,7 @@ class ProgressServiceTest { @DisplayName("PRE_ACTIVITY 인스턴스들 중, 특정 조건에 해당하는 인스턴스들을 ACTIVITY로 상태를 바꿀 수 있다.") public void should_updateToActivity_when_conditionMatches() { //given + LocalDate todayDate = LocalDate.of(2024, 1, 30); LocalDate startedDate = LocalDate.of(2024, 3, 1); LocalDate completedDate = LocalDate.of(2024, 3, 30); LocalDate currentDate = LocalDate.of(2024, 3, 6); @@ -81,6 +82,7 @@ public void should_updateToActivity_when_conditionMatches() { JoinRequest.builder() .repository(targetRepo) .instanceId(instance1.getId()) + .todayDate(todayDate) .build() ); diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java index 2e78d383..4cfd292a 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java @@ -1,13 +1,14 @@ package com.genius.gitget.challenge.item.service; +import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.service.ItemProvider; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -34,7 +35,7 @@ class ItemProviderTest { @EnumSource(mode = Mode.INCLUDE, names = {"POINT_MULTIPLIER", "CERTIFICATION_PASSER"}) public void should_findItems_when_passCategory(ItemCategory itemCategory) { //given - Item item = getSavedItem(itemCategory); + Item item = getSavedItem(10, itemCategory); //when List items = itemProvider.findAllByCategory(itemCategory); @@ -49,7 +50,7 @@ public void should_findItems_when_passCategory(ItemCategory itemCategory) { @DisplayName("DB에 저장되어 있는 아이템을 식별자 PK를 통해 조회할 수 있다.") public void should_findItem_when_passPK() { //given - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); + Item item = getSavedItem(10, CERTIFICATION_PASSER); //when Item foundItem = itemProvider.findById(item.getId()); @@ -68,9 +69,25 @@ public void should_throwException_when_pkNotExist() { .hasMessageContaining(ErrorCode.ITEM_NOT_FOUND.getMessage()); } - private Item getSavedItem(ItemCategory itemCategory) { + @Test + @DisplayName("식별 전용 값인 identifier를 통해 아이템을 조회할 수 있다.") + public void should_findItem_by_identifier() { + //given + int identifier = 10; + Item item = getSavedItem(identifier, CERTIFICATION_PASSER); + + //when + Item byIdentifier = itemProvider.findByIdentifier(identifier); + + //then + assertThat(item.getId()).isEqualTo(byIdentifier.getId()); + assertThat(byIdentifier.getItemCategory()).isEqualTo(CERTIFICATION_PASSER); + } + + private Item getSavedItem(int identifier, ItemCategory itemCategory) { return itemRepository.save( Item.builder() + .identifier(identifier) .cost(100) .itemCategory(itemCategory) .build() diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java index c17c5d83..11d199f3 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java @@ -77,7 +77,7 @@ public void should_getAllItems_when_itemsSaved() { List items = itemService.getAllItems(user); //then - assertThat(items.size()).isEqualTo(4); + assertThat(items.size()).isGreaterThan(1); } @ParameterizedTest @@ -113,7 +113,7 @@ public void should_purchaseItem_when_passPK(ItemCategory itemCategory) { ItemResponse itemResponse = itemService.orderItem(user, item.getId()); //then - assertThat(itemResponse.getItemId()).isEqualTo(item.getId()); + assertThat(itemResponse.getItemId()).isEqualTo(item.getIdentifier()); assertThat(itemResponse.getName()).isEqualTo(item.getName()); assertThat(itemResponse.getCost()).isEqualTo(item.getCost()); assertThat(itemResponse.getCount()).isEqualTo(1); @@ -416,7 +416,7 @@ public void should_unmountFrame_when_mountAlready() { ProfileResponse profileResponse = itemService.unmountFrame(user).get(0); //then - assertThat(profileResponse.getItemId()).isEqualTo(item.getId()); + assertThat(profileResponse.getItemId()).isEqualTo(item.getIdentifier()); assertThat(profileResponse.getCost()).isEqualTo(item.getCost()); assertThat(profileResponse.getItemCategory()).isEqualTo(ItemCategory.PROFILE_FRAME); assertThat(profileResponse.getEquipStatus()).isEqualTo(EquipStatus.AVAILABLE.getTag()); @@ -466,6 +466,7 @@ private User getSavedUser() { private Item getSavedItem(ItemCategory itemCategory) { return itemRepository.save(Item.builder() + .identifier(10) .itemCategory(itemCategory) .cost(100) .name(itemCategory.getName()) diff --git a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java index cbaf07e4..339a4a98 100644 --- a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java @@ -2,7 +2,7 @@ import static com.genius.gitget.global.file.domain.FileType.INSTANCE; import static com.genius.gitget.global.file.domain.FileType.TOPIC; -import static com.genius.gitget.global.util.exception.ErrorCode.FILE_NOT_EXIST; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_FILE_NAME; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_SUPPORTED_EXTENSION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -46,7 +46,7 @@ public void should_throwException_when_originFilenameIsNull() { //when&then assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(FILE_NOT_EXIST.getMessage()); + .hasMessageContaining(INVALID_FILE_NAME.getMessage()); } @Test @@ -58,7 +58,7 @@ public void should_throwException_when_originFilenameIsBlank() { //when&then assertThatThrownBy(() -> fileUtil.validateFile(multipartFile)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(FILE_NOT_EXIST.getMessage()); + .hasMessageContaining(INVALID_FILE_NAME.getMessage()); } @Test From 3c6b0353b7911e815d716aa140e0e6bc6f24a338 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 11 Jun 2024 08:17:03 +0900 Subject: [PATCH 198/234] =?UTF-8?q?fix:=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=EB=8B=B9=EC=9D=BC=EC=9D=B4=20=EB=90=98?= =?UTF-8?q?=EC=97=88=EC=9D=84=20=EB=95=8C=20=EC=83=81=ED=83=9C=EA=B0=80=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B2=84=EA=B7=B8=20=ED=94=BD=EC=8A=A4=20(#203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `ProgressService` - `updateToActivity()`의 조건문에서 챌린지 시작 당일일 때에도 상태 업데이트가 되도록 수정 - 챌린지 시작 당일에도 업데이트가 되는지 여부를 확인하는 테스트 코드 추가 --- .../schedule/service/ProgressService.java | 6 ++- .../instance/service/ProgressServiceTest.java | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/genius/gitget/schedule/service/ProgressService.java b/src/main/java/com/genius/gitget/schedule/service/ProgressService.java index 758e2c3e..1158d3dd 100644 --- a/src/main/java/com/genius/gitget/schedule/service/ProgressService.java +++ b/src/main/java/com/genius/gitget/schedule/service/ProgressService.java @@ -33,12 +33,16 @@ public void updateToActivity(LocalDate currentDate) { LocalDate startedDate = preActivity.getStartedDate().toLocalDate(); LocalDate completedDate = preActivity.getCompletedDate().toLocalDate(); - if (currentDate.isAfter(startedDate) && currentDate.isBefore(completedDate)) { + if (isUpdatableToActivity(startedDate, currentDate) && currentDate.isBefore(completedDate)) { updateActivityInstance(preActivity); } } } + private boolean isUpdatableToActivity(LocalDate startedDate, LocalDate currentDate) { + return currentDate.isEqual(startedDate) || currentDate.isAfter(startedDate); + } + private void updateActivityInstance(Instance preActivity) { preActivity.updateProgress(Progress.ACTIVITY); for (Participant participant : preActivity.getParticipantList()) { diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java index b64c5f85..1f446e8c 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java @@ -101,6 +101,45 @@ public void should_updateToActivity_when_conditionMatches() { assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.PROCESSING); } + @Test + @DisplayName("PREACTIVITY 인스턴스들 중, 시작날짜가 된 인스턴스들을 ACTIVITY 상태로 바꿀 수 있다.") + public void should_updateToActivity_when_dDay() { + //given + LocalDate todayDate = LocalDate.of(2024, 1, 30); + LocalDate startedDate = LocalDate.of(2024, 3, 1); + LocalDate completedDate = LocalDate.of(2024, 3, 30); + LocalDate currentDate = LocalDate.of(2024, 3, 1); + + User user = getSavedUser("nickname1", githubId); + Instance instance1 = getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + getSavedInstance(startedDate, completedDate); + + githubService.registerGithubPersonalToken(user, personalKey); + instanceDetailService.joinNewChallenge( + user, + JoinRequest.builder() + .repository(targetRepo) + .instanceId(instance1.getId()) + .todayDate(todayDate) + .build() + ); + + Participant participant1 = participantRepository.findByJoinInfo(user.getId(), instance1.getId()) + .orElseThrow(() -> new BusinessException(ErrorCode.PARTICIPANT_NOT_FOUND)); + + //when + List preActivities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.READY); + scheduleService.updateToActivity(currentDate); + List activities = instanceProvider.findAllByProgress(Progress.ACTIVITY); + + //then + assertThat(preActivities.size()).isEqualTo(3); + assertThat(activities.size()).isEqualTo(3); + assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.PROCESSING); + } + @Test @DisplayName("ACTIVITY 인스턴스들 중, 특정 조건에 해당하는 인스턴스들을 DONE 상태로 바꿀 수 있다.") public void should_updateToDone_when_conditionMatches() { From dda68b045b792cd538c7b3f41493068717e3e4d3 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:45:55 +0900 Subject: [PATCH 199/234] =?UTF-8?q?[FIX]=2000=EC=8B=9C~=EC=98=A4=EC=A0=84?= =?UTF-8?q?=20=EB=8F=99=EC=95=88=20PR=20=EC=9D=B8=EC=8B=9D=EC=9D=84=20?= =?UTF-8?q?=EB=AA=BB=ED=95=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=ED=94=BD?= =?UTF-8?q?=EC=8A=A4=20(#206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Date -> KST 변환 메서드 이름 변경 - Date를 LocalDate(KST)로 변환하는 메서드의 이름을 더 직관적으로 변경 * fix: 요청 인증 일자를 페이로드에서 받아와서 사용하도록 수정 - LocalDate는 서버의 타임 존에 영향을 받기 때문에, LocalDate.now()가 아닌, 페이로드에서 요청 일자를 받아와서 사용하도록 코드 수정 * fix: 시스템의 타임존에 상관없이 KST로 변환하는 로직으로 변경 시스템의 타임존에 상관없이 KST 기준 시간으로 변환하는 로직 추가 * fix: 인증 컨트롤러에서 현재 일자를 받는 부분 수정 - 인증 컨트롤러에서 LocalDate.now()를 호출하던 부분을 DateUtil의 convertToKST를 통해 KST로 변환된 값을 입력하도록 수정 - * fix: LocalDate.now()를 타임존에 영향받지 않는 코드로 변경 - LocalDate.now()를 DateUtil.convertToKST()로 변경하여 시스템 타임존에 영향받지 않도록 변경 --- .../controller/CertificationController.java | 21 ++++++++++-------- .../controller/GithubController.java | 7 +++--- .../certification/service/GithubProvider.java | 2 +- .../certification/util/DateUtil.java | 22 +++++++++++++++++-- .../controller/InstanceController.java | 5 ++++- .../controller/InstanceDetailController.java | 5 ++++- .../instance/dto/detail/InstanceResponse.java | 4 +++- .../controller/MyChallengeController.java | 12 +++++----- .../controller/ProgressController.java | 4 +++- .../schedule/service/ScheduleService.java | 10 +++++---- .../store/item/controller/ItemController.java | 5 +++-- .../certification/util/DateUtilTest.java | 2 +- 12 files changed, 68 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index d29452c0..81330437 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -9,6 +9,7 @@ import com.genius.gitget.challenge.certification.dto.TotalResponse; import com.genius.gitget.challenge.certification.dto.WeekResponse; import com.genius.gitget.challenge.certification.service.CertificationService; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.service.InstanceProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; @@ -20,6 +21,7 @@ import com.genius.gitget.global.util.response.dto.SingleResponse; import com.genius.gitget.global.util.response.dto.SlicingResponse; import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; @@ -63,8 +65,7 @@ public ResponseEntity> certificateByGithub @RequestBody CertificationRequest certificationRequest ) { CertificationResponse certificationResponse = certificationService.updateCertification( - userPrincipal.getUser(), - new CertificationRequest(certificationRequest.instanceId(), LocalDate.now())); + userPrincipal.getUser(), certificationRequest); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certificationResponse) @@ -78,8 +79,7 @@ public ResponseEntity> passCertification( ) { User user = userPrincipal.getUser(); ActivatedResponse activatedResponse = certificationService.passCertification( - user.getId(), - new CertificationRequest(certificationRequest.instanceId(), LocalDate.now())); + user.getId(), certificationRequest); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), activatedResponse) @@ -91,9 +91,9 @@ public ResponseEntity> getWeekCertification( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long instanceId ) { + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); Participant participant = participantProvider.findByJoinInfo(userPrincipal.getUser().getId(), instanceId); - WeekResponse weekResponse = certificationService.getMyWeekCertifications( - participant.getId(), LocalDate.now()); + WeekResponse weekResponse = certificationService.getMyWeekCertifications(participant.getId(), kstDate); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), weekResponse) @@ -106,9 +106,10 @@ public ResponseEntity> getAllUserWeekCertification @PathVariable Long instanceId, @PageableDefault Pageable pageable ) { + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); User user = userPrincipal.getUser(); Slice certifications = certificationService.getOthersWeekCertifications( - user.getId(), instanceId, LocalDate.now(), pageable); + user.getId(), instanceId, kstDate, pageable); return ResponseEntity.ok().body( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certifications) @@ -120,10 +121,11 @@ public ResponseEntity> getTotalCertifications( @PathVariable Long instanceId, @RequestParam Long userId ) { + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); User user = userService.findUserById(userId); Participant participant = participantProvider.findByJoinInfo(user.getId(), instanceId); TotalResponse totalResponse = certificationService.getTotalCertification( - participant.getId(), LocalDate.now()); + participant.getId(), kstDate); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), totalResponse) @@ -136,13 +138,14 @@ public ResponseEntity> getCertification @PathVariable Long instanceId ) { + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); Instance instance = instanceProvider.findById(instanceId); Participant participant = participantProvider.findByJoinInfo( userPrincipal.getUser().getId(), instanceId); CertificationInformation certificationInformation = certificationService.getCertificationInformation( - instance, participant, LocalDate.now()); + instance, participant, kstDate); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certificationInformation) diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java index ac371057..42f353f1 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java @@ -5,10 +5,11 @@ import com.genius.gitget.challenge.certification.dto.github.GithubTokenRequest; import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; import com.genius.gitget.challenge.certification.service.GithubService; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.ListResponse; -import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -56,7 +57,7 @@ public ResponseEntity verifyGithubToken( @AuthenticationPrincipal UserPrincipal userPrincipal ) { githubService.verifyGithubToken(userPrincipal.getUser()); - + return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); @@ -82,7 +83,7 @@ public ResponseEntity> verifyPullRequest( ) { List pullRequestResponses = githubService.verifyPullRequest( - userPrincipal.getUser(), repo, LocalDate.now() + userPrincipal.getUser(), repo, DateUtil.convertToKST(LocalDateTime.now()) ); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java b/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java index e7b96305..32f5e465 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java @@ -112,7 +112,7 @@ public List getPullRequestByDate(GitHub gitHub, String repository private boolean isEqualToKST(GHPullRequest ghPullRequest, LocalDate targetDate) { try { - LocalDate kst = DateUtil.convertToLocalDate(ghPullRequest.getCreatedAt()); + LocalDate kst = DateUtil.convertToKST(ghPullRequest.getCreatedAt()); return kst.isEqual(targetDate); } catch (IOException e) { throw new BusinessException(e); diff --git a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java index 2130c390..f93be2df 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java +++ b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java @@ -1,10 +1,14 @@ package com.genius.gitget.challenge.certification.util; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Date; +import lombok.extern.slf4j.Slf4j; +@Slf4j public final class DateUtil { public static int getRemainDaysToStart(LocalDate startDate, LocalDate targetDate) { @@ -37,17 +41,31 @@ public static LocalDate getWeekStartDate(LocalDate challengeStartDate, LocalDate return mondayOfWeek; } - public static LocalDate convertToLocalDate(Date date) { + public static LocalDate convertToKST(Date date) { return LocalDate.ofInstant( date.toInstant(), ZoneId.of("Asia/Seoul") ); } + public static LocalDate convertToKST(LocalDateTime nowLocal) { + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId koreaZone = ZoneId.of("Asia/Seoul"); + + // 현재 시스템의 ZoneId를 이용하여 ZonedDateTime을 생성 + ZonedDateTime nowZone = ZonedDateTime.of(nowLocal, systemZone); + + // KST(Asia/Seoul)로 변환 + ZonedDateTime koreaTime = nowZone.withZoneSameInstant(koreaZone); + + // LocalDateTime으로 변환하여 반환 + return koreaTime.toLocalDate(); + } + private static boolean isFirstWeek(LocalDate challengeStartDate, LocalDate currentDate) { LocalDate mondayOfWeek = challengeStartDate.minusDays(challengeStartDate.getDayOfWeek().ordinal()); LocalDate sundayOfWeek = mondayOfWeek.plusDays(6); - + if (currentDate.isAfter(mondayOfWeek.minusDays(1)) && currentDate.isBefore(sundayOfWeek.plusDays(1))) { return true; diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index e0f6da8c..d85e94c5 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -3,6 +3,7 @@ import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceIndexResponse; @@ -13,6 +14,7 @@ import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -75,7 +77,8 @@ public ResponseEntity> getInstanceById(@P @PostMapping("/instance") public ResponseEntity> createInstance( @RequestBody InstanceCreateRequest instanceCreateRequest) { - Long instanceId = instanceService.createInstance(instanceCreateRequest, LocalDate.now()); + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); + Long instanceId = instanceService.createInstance(instanceCreateRequest, kstDate); InstanceIndexResponse instanceIndexResponse = new InstanceIndexResponse(instanceId); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java index c9fe7dc0..7134a5ce 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java @@ -4,6 +4,7 @@ import static com.genius.gitget.global.util.exception.SuccessCode.QUIT_SUCCESS; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; @@ -11,6 +12,7 @@ import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -50,10 +52,11 @@ public ResponseEntity> joinChallenge( @PathVariable Long instanceId, @RequestParam String repo ) { + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); JoinRequest joinRequest = JoinRequest.builder() .instanceId(instanceId) .repository(repo) - .todayDate(LocalDate.now()) + .todayDate(kstDate) .build(); JoinResponse joinResponse = instanceDetailService.joinNewChallenge(userPrincipal.getUser(), joinRequest); diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java index de2e1fcb..76a96695 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/detail/InstanceResponse.java @@ -6,6 +6,7 @@ import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.global.file.dto.FileResponse; import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.Builder; @Builder @@ -28,13 +29,14 @@ public record InstanceResponse( public static InstanceResponse createByEntity(Instance instance, LikesInfo likesInfo, JoinStatus joinStatus, FileResponse fileResponse) { + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); LocalDate startedLocalDate = instance.getStartedDate().toLocalDate(); LocalDate completedLocalDate = instance.getCompletedDate().toLocalDate(); return InstanceResponse.builder() .instanceId(instance.getId()) .progress(instance.getProgress()) .title(instance.getTitle()) - .remainDays(DateUtil.getRemainDaysToStart(startedLocalDate, LocalDate.now())) + .remainDays(DateUtil.getRemainDaysToStart(startedLocalDate, kstDate)) .startedDate(startedLocalDate) .completedDate(completedLocalDate) .participantCount(instance.getParticipantCount()) diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java index ddf51e80..ef3a2d72 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java @@ -2,6 +2,7 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; @@ -11,6 +12,7 @@ import com.genius.gitget.global.util.response.dto.ListResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -35,7 +37,7 @@ public ResponseEntity> getPreActivityChallenge ) { List preActivityInstances = myChallengeService.getPreActivityInstances( userPrincipal.getUser(), - LocalDate.now()); + DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), preActivityInstances) @@ -49,7 +51,7 @@ public ResponseEntity> getActivatedChallenges( ) { List activatedInstances = myChallengeService.getActivatedInstances( userPrincipal.getUser(), - LocalDate.now()); + DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), activatedInstances) @@ -62,7 +64,7 @@ public ResponseEntity> getDoneChallenges( ) { List doneInstances = myChallengeService.getDoneInstances( userPrincipal.getUser(), - LocalDate.now()); + DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), doneInstances) @@ -74,8 +76,8 @@ public ResponseEntity> getRewards( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long instanceId ) { - - RewardRequest rewardRequest = new RewardRequest(userPrincipal.getUser(), instanceId, LocalDate.now()); + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); + RewardRequest rewardRequest = new RewardRequest(userPrincipal.getUser(), instanceId, kstDate); DoneResponse doneResponse = myChallengeService.getRewards(rewardRequest, false); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/schedule/controller/ProgressController.java b/src/main/java/com/genius/gitget/schedule/controller/ProgressController.java index 747fabb4..27cd183b 100644 --- a/src/main/java/com/genius/gitget/schedule/controller/ProgressController.java +++ b/src/main/java/com/genius/gitget/schedule/controller/ProgressController.java @@ -2,9 +2,11 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.schedule.service.ProgressService; import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -19,7 +21,7 @@ public class ProgressController { @GetMapping("/challenges/update") public ResponseEntity updateProgress() { - LocalDate currentDate = LocalDate.now(); + LocalDate currentDate = DateUtil.convertToKST(LocalDateTime.now()); scheduleService.updateToActivity(currentDate); scheduleService.updateToDone(currentDate); diff --git a/src/main/java/com/genius/gitget/schedule/service/ScheduleService.java b/src/main/java/com/genius/gitget/schedule/service/ScheduleService.java index dc5a5c44..5d695a8f 100644 --- a/src/main/java/com/genius/gitget/schedule/service/ScheduleService.java +++ b/src/main/java/com/genius/gitget/schedule/service/ScheduleService.java @@ -1,6 +1,8 @@ package com.genius.gitget.schedule.service; +import com.genius.gitget.challenge.certification.util.DateUtil; import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; @@ -17,11 +19,11 @@ public class ScheduleService { @Transactional @Scheduled(cron = "${schedule.cron}") public void run() { - LocalDate now = LocalDate.now(); + LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); - log.info(now + ": Schedule 설정에 따라 instance의 Progress 업데이트 진행"); + log.info(kstDate + ": Schedule 설정에 따라 instance의 Progress 업데이트 진행"); - scheduleService.updateToActivity(now); - scheduleService.updateToDone(now); + scheduleService.updateToActivity(kstDate); + scheduleService.updateToDone(kstDate); } } diff --git a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java index 204d77e0..563f0954 100644 --- a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java +++ b/src/main/java/com/genius/gitget/store/item/controller/ItemController.java @@ -2,6 +2,7 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.ListResponse; @@ -13,7 +14,7 @@ import com.genius.gitget.store.item.dto.ProfileResponse; import com.genius.gitget.store.item.service.ItemProvider; import com.genius.gitget.store.item.service.ItemService; -import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -71,7 +72,7 @@ public ResponseEntity useItem( ) { Item item = itemProvider.findByIdentifier(identifier); ItemUseResponse itemUseResponse = itemService.useItem(userPrincipal.getUser(), item.getId(), - instanceId, LocalDate.now()); + instanceId, DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemUseResponse) diff --git a/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java b/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java index 3f535c66..2a135113 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/util/DateUtilTest.java @@ -61,7 +61,7 @@ public void should_convertToLocalDate_when_passDate() { Date date = new Date(1725000000000L); //when - LocalDate localDate = DateUtil.convertToLocalDate(date); + LocalDate localDate = DateUtil.convertToKST(date); //then assertThat(localDate).isEqualTo(LocalDate.of(2024, 8, 30)); From 1f958a37ca673534108528b1b27e6bb5e6598e7b Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:46:10 +0900 Subject: [PATCH 200/234] =?UTF-8?q?[FIX]=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B0=B8=EA=B0=80=20=EC=B7=A8=EC=86=8C=20=EC=8B=9C?= =?UTF-8?q?=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=20=EC=B0=B8=EC=97=AC=20?= =?UTF-8?q?=EC=9D=B8=EC=9B=90,=20=EC=83=81=ED=83=9C=20=EB=B3=80=ED=99=94?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=B2=84=EA=B7=B8=20=ED=94=BD?= =?UTF-8?q?=EC=8A=A4=20(#212)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 참여자 수 갱신 시 음수가 될 수 있는 버그 픽스 - Instance 클래스 내에 참여자 수 갱신 메서드에서, 갱신된 값이 음수가 될 수 있는 경우에는 갱신시키지 않도록 처리 * fix: 인스턴스 참여 여부 확인 메서드 수정 - 인스턴스 참여 여부를 확인하는 로직에 오류가 있어 수정 - 참여 상태가 No일 때에도 참여로 간주하고 있어, No가 아닐 때에만 true를 반환하도록 수정 * fix: 진행 중인 챌린지 취소 시, 챌린지 현황의 시작 전 개수가 증가하는 버그 픽스 - JoinStatus가 NO(실패)일 때에, 챌린지 현황 조회에 포함되도록 로직 수정 - 챌린지 현황 조회에 사용하는 HashMap의 Key에 시작 전에 해당하는 READY 포함 - for문(반복문)을 Iterator로 변경 및 indent를 2에서 1로 감소 --- .../challenge/instance/domain/Instance.java | 3 +++ .../service/InstanceDetailService.java | 4 +-- .../service/ParticipantProvider.java | 12 +++------ .../profile/service/ProfileService.java | 25 ++++++++++--------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 996bd8f3..4f83fec1 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -144,6 +144,9 @@ public void updateInstance(String description, String notice, int pointPerPerson * 참가자 수 정보 수정 * */ public void updateParticipantCount(int amount) { + if (amount < 0 && this.participantCount + amount < 0) { + return; + } this.participantCount += amount; } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index d3c64735..5d331418 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -46,7 +46,7 @@ public InstanceResponse getInstanceDetailInformation(User user, Long instanceId) FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); LikesInfo likesInfo = getLikesInfo(user.getId(), instance); - if (participantProvider.hasParticipant(user.getId(), instanceId)) { + if (participantProvider.hasJoinedParticipant(user.getId(), instanceId)) { return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.YES, fileResponse); } @@ -90,7 +90,7 @@ private void validateJoinDate(Instance instance, LocalDate todayDate) { } private void validateInstanceCondition(User user, Instance instance) { - boolean isParticipated = participantProvider.hasParticipant(user.getId(), instance.getId()); + boolean isParticipated = participantProvider.hasJoinedParticipant(user.getId(), instance.getId()); if ((instance.getProgress() == Progress.PREACTIVITY) && !isParticipated) { return; } diff --git a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java index ef418e7a..97d963bf 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java +++ b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java @@ -8,7 +8,6 @@ import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.repository.ParticipantRepository; import com.genius.gitget.global.util.exception.BusinessException; -import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -65,12 +64,9 @@ public List findJoinedByProgress(Long userId, Progress progress) { return participantRepository.findAllJoinedByProgress(userId, progress); } - public boolean hasParticipant(Long userId, Long instanceId) { - return participantRepository.findByJoinInfo(userId, instanceId).isPresent(); - } - - public LocalDate getInstanceStartDate(Long participantId) { - return getInstanceById(participantId).getStartedDate().toLocalDate(); + public boolean hasJoinedParticipant(Long userId, Long instanceId) { + return participantRepository.findByJoinInfo(userId, instanceId) + .map(participant -> participant.getJoinStatus() != JoinStatus.NO) + .orElse(false); } - } diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileService.java index 3e446632..b6d22643 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileService.java @@ -2,6 +2,7 @@ import static com.genius.gitget.challenge.participant.domain.JoinResult.FAIL; import static com.genius.gitget.challenge.participant.domain.JoinResult.PROCESSING; +import static com.genius.gitget.challenge.participant.domain.JoinResult.READY; import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; import static com.genius.gitget.challenge.participant.domain.JoinStatus.YES; @@ -135,34 +136,34 @@ public UserChallengeResultResponse getUserChallengeResult(User user) { User findUser = getUserByIdentifier(user.getIdentifier()); HashMap> participantHashMap = new HashMap<>() { { + put(READY, new ArrayList<>()); put(FAIL, new ArrayList<>()); put(PROCESSING, new ArrayList<>()); put(SUCCESS, new ArrayList<>()); } }; - List participantInfoList = findUser.getParticipantList(); // 유저의 참여 정보를 담고 있는 리스트 - int participanTotalCount = participantInfoList.size(); - - for (int i = 0; i < participantInfoList.size(); i++) { // 각 참여 정보를 받아옴. - if (participantInfoList.get(i).getJoinStatus() == YES) { - JoinResult joinResult = participantInfoList.get(i).getJoinResult(); - if (participantHashMap.containsKey(joinResult)) { // hashmap에 저장된 key와 일치 여부 확인 - List participantIdList = participantHashMap.get(joinResult); - participantIdList.add(participantInfoList.get(i).getId()); // 일치한다면, 해당 key의 value인 list에 id 저장 - participantHashMap.put(joinResult, participantIdList); // 최종적으로 hashmap에 저장 - } + List participantInfos = findUser.getParticipantList(); + for (Participant participant : participantInfos) { + JoinResult joinResult = participant.getJoinResult(); + if (!participantHashMap.containsKey(joinResult)) { + continue; } + + List participantIds = participantHashMap.get(joinResult); + participantIds.add(participant.getId()); + participantHashMap.put(joinResult, participantIds); } + int beforeStart = participantHashMap.get(READY).size(); int fail = participantHashMap.get(FAIL).size(); int success = participantHashMap.get(SUCCESS).size(); int processing = participantHashMap.get(PROCESSING).size(); return UserChallengeResultResponse.builder() + .beforeStart(beforeStart) .fail(fail) .success(success) .processing(processing) - .beforeStart(participanTotalCount - fail - success - processing) .build(); } From 3e26e8f6fddc1ad6e57ef375bd795db9123eeeca Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Sun, 7 Jul 2024 12:31:26 +0900 Subject: [PATCH 201/234] =?UTF-8?q?[CHORE]=20GitGet=20Readme=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=20(#216)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * [CHORE] README 업데이트 * chore: README 업데이트 --- README.md | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..876d4371 --- /dev/null +++ b/README.md @@ -0,0 +1,287 @@ +![image](https://github.com/TeamTheGenius/TeamTheGenius_Server/assets/95005061/9e508d5c-ff0d-4e82-95d0-3e2d1d6217c4) + +
+ +## :raising_hand_man: 프로젝트 소개 +🔥 GitGet은 챌린지 참여와 인증 활동을 통해 규칙적인 공부 습관을 도와주는 서비스입니다.

+🙋🏻‍♂️Github 계정 연동을 통해 챌린지 활동을 인증할 수 있으며, 다른 참여자들의 인증 현황을 조회할 수 있습니다.

+🎯 챌린지에 설정되어 있는 목표 달성 시 포인트가 주어지며, 이를 통해 아이템을 구매하고 사용할 수 있습니다.


+ + +## :desktop_computer: 기술 스택 +Framework - + +ORM - + +Authorization - + +Test - + +Database - + +DevOps - + +Monitoring - + +Other - + +

+ +## 개발 환경 +``` +Java : 17 +Spring Boot : 3.2.1 +build : gradle +``` + +

+ +## 다운로드 방법 + +``` +git clone https://github.com/TeamTheGenius/TeamTheGenius_Server.git +``` + +

+ +## 화면 설계서 +![image](https://github.com/TeamTheGenius/TeamTheGenius_Server/assets/95005061/52d8e894-14a7-40ad-b94c-3af1a03fedd9) + +

+ +## 주요 기능 +### 로그인 / Github 연동 +* 사용자는 회원가입을 통해 서비스를 이용할 수 있습니다. +* 챌린지에 참여하기 위해서는 Github Access Token 인증 과정을 **필수**로 진행해야 합니다. +* Pull Request 작업으로 사용자 Repository와 서비스가 연결되었는 지 확인이 필요합니다. 참여하고자 하는 브랜치에서 아무 작업을 진행하고, PR을 등록하여 등록 여부를 확인해주세요. + +
+ +![image](https://github.com/TeamTheGenius/TeamTheGenius_Server/assets/95005061/019ad04b-b223-45f8-be21-a3a2bf63c0f2) + +
+ +### 홈 화면 +* 사용자는 참여하고자 하는 챌린지를 둘러볼 수 있습니다. 인기, 신규, 추천 카테고리를 이용 가능합니다. +* 검색을 통해 종료된 챌린지, 진행 중인 챌린지, 참여가 가능한 챌린지 목록을 확인할 수 있습니다. + +
+ +![image](https://github.com/TeamTheGenius/TeamTheGenius_Server/assets/95005061/45a77dd6-9cf4-40b7-8935-4ca7a93dbec3) + +
+ +### 챌린지 인증 현황 +* 참가자 인증 현황을 클릭하면 본인을 포함한 다른 참여자들의 인증 현황을 일주일 단위로 조회할 수 있습니다. +* 인증 내역을 확인하고 싶은 일자를 선택하면 그 날의 인증에 사용된 Github PR 목록 조회가 가능합니다. +* 조회한 Github PR 목록 중 구경하고 싶은 PR이 있다면, 해당 링크를 눌러 이동이 가능합니다. + +
+ +![image](https://github.com/TeamTheGenius/TeamTheGenius_Server/assets/95005061/9e83841d-0709-4339-bd5e-9e8e29e731c4) + +

+ +## 배포 플로우 +![image](https://github.com/TeamTheGenius/TeamTheGenius_Server/assets/95005061/8612cb5b-67de-4bf6-ad4e-40a46cafdadc) + +

+ +## 데이터베이스 +![image](https://github.com/kimdozzi/Java/assets/95005061/b8930f51-22b4-4574-b5f3-58ffd9bbac01) + +

+ +## 아키텍처 +```bash +. +├── main +│   ├── java +│   │   └── com +│   │   └── genius +│   │   └── gitget +│   │   ├── admin +│   │   │   ├── signout +│   │   │   └── topic +│   │   │   ├── controller +│   │   │   ├── domain +│   │   │   ├── dto +│   │   │   ├── repository +│   │   │   └── service +│   │   ├── challenge +│   │   │   ├── certification +│   │   │   │   ├── controller +│   │   │   │   ├── domain +│   │   │   │   ├── dto +│   │   │   │   │   └── github +│   │   │   │   ├── repository +│   │   │   │   ├── service +│   │   │   │   └── util +│   │   │   ├── instance +│   │   │   │   ├── controller +│   │   │   │   ├── domain +│   │   │   │   ├── dto +│   │   │   │   │   ├── crud +│   │   │   │   │   ├── detail +│   │   │   │   │   ├── home +│   │   │   │   │   └── search +│   │   │   │   ├── repository +│   │   │   │   └── service +│   │   │   ├── likes +│   │   │   │   ├── controller +│   │   │   │   ├── domain +│   │   │   │   ├── dto +│   │   │   │   ├── repository +│   │   │   │   └── service +│   │   │   ├── myChallenge +│   │   │   │   ├── controller +│   │   │   │   ├── dto +│   │   │   │   └── service +│   │   │   ├── participant +│   │   │   │   ├── domain +│   │   │   │   ├── repository +│   │   │   │   └── service +│   │   │   ├── report +│   │   │   │   ├── controller +│   │   │   │   ├── domain +│   │   │   │   ├── dto +│   │   │   │   ├── repository +│   │   │   │   └── service +│   │   │   └── user +│   │   │   ├── controller +│   │   │   ├── domain +│   │   │   ├── dto +│   │   │   ├── repository +│   │   │   └── service +│   │   ├── global +│   │   │   ├── file +│   │   │   │   ├── controller +│   │   │   │   ├── domain +│   │   │   │   ├── dto +│   │   │   │   ├── repository +│   │   │   │   └── service +│   │   │   ├── security +│   │   │   │   ├── config +│   │   │   │   ├── constants +│   │   │   │   ├── controller +│   │   │   │   ├── domain +│   │   │   │   ├── dto +│   │   │   │   ├── filter +│   │   │   │   ├── handler +│   │   │   │   ├── info +│   │   │   │   │   └── impl +│   │   │   │   ├── repository +│   │   │   │   └── service +│   │   │   └── util +│   │   │   ├── config +│   │   │   ├── domain +│   │   │   ├── exception +│   │   │   ├── formatter +│   │   │   └── response +│   │   │   └── dto +│   │   ├── profile +│   │   │   ├── controller +│   │   │   ├── dto +│   │   │   └── service +│   │   ├── schedule +│   │   │   ├── controller +│   │   │   └── service +│   │   └── store +│   │   ├── item +│   │   │   ├── controller +│   │   │   ├── domain +│   │   │   ├── dto +│   │   │   ├── repository +│   │   │   └── service +│   │   └── payment +│   │   ├── config +│   │   ├── controller +│   │   ├── domain +│   │   ├── dto +│   │   ├── repository +│   │   └── service +│   └── resources +└── test + ├── java + │   └── com + │   └── genius + │   └── gitget + │   ├── admin + │   │   └── topic + │   │   ├── controller + │   │   ├── repository + │   │   └── service + │   ├── challenge + │   │   ├── certification + │   │   │   ├── controller + │   │   │   ├── repository + │   │   │   ├── service + │   │   │   └── util + │   │   ├── home + │   │   │   ├── controller + │   │   │   └── service + │   │   ├── instance + │   │   │   ├── controller + │   │   │   ├── repository + │   │   │   └── service + │   │   ├── item + │   │   │   └── service + │   │   ├── likes + │   │   │   ├── controller + │   │   │   └── service + │   │   ├── myChallenge + │   │   │   └── service + │   │   ├── participant + │   │   │   └── service + │   │   └── user + │   │   ├── controller + │   │   ├── domain + │   │   ├── repository + │   │   └── service + │   ├── global + │   │   ├── file + │   │   │   ├── domain + │   │   │   ├── repository + │   │   │   └── service + │   │   └── security + │   │   ├── config + │   │   ├── controller + │   │   └── service + │   ├── payment + │   │   ├── controller + │   │   └── service + │   ├── profile + │   │   ├── controller + │   │   └── service + │   └── util + │   └── file + └── resources + +``` + +

+ +## 기여자 + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+ From 36c6ce595b92f00685ed82677845cecdce429ffc Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Wed, 10 Jul 2024 01:32:00 +0900 Subject: [PATCH 202/234] =?UTF-8?q?[FEAT]=20=EC=98=88=EC=99=B8=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EC=8B=9C,=20Slack=20=EC=B1=84=EB=84=90=EC=97=90=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=EB=A5=BC=20=EB=B3=B4=EB=82=B4?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(#214)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: slack 의존성 추가 및 불필요한 주석 제거 * chore: favicon error 해결을 위해 favicon.ico 추가 * feat: BusinessException 발생 시, Slack 채널에 메세지 보내는 기능 추가 - BusinessException 발생 시, Slack의 #exception-log 채널에 메세지를 보내는 기능 추가 - GlobalException에 대한 추가 처리도 필요 - .yml 파일에 slack 정보 추가 필요 - 스택 트레이스 가독성 개선 필요 * feat: Exception 발생 시 보내는 메세지 포멧 변경 - Exception 발생 시 전송하는 Slack 메세지에 예외 발생 시각 데이터 추가 * feat: 처리하지 않은 예외 외에 다른 예외 발생 시에도 Slack 메세지 보내도록 설정 * fix: prod 프로파일이 활성화되어 있을 때에만 메세지를 보내도록 설정 - Environment를 의존성 주입받아 prod(application-prod.yml) 프로파일이 활성화되어 있을 때에만 Slack 메세지를 보내도록 설정 - 각 ExceptionHandler가 MessageSender 인터페이스를 상속받고, sendSlackMessage() 메서드를 구현 - 테스트 및 개발 환경에서 Slack 메세지를 보내지 않는 점 확인 --- build.gradle | 8 +-- .../certification/util/DateUtil.java | 17 ++++- .../BusinessExceptionHandler.java | 20 +++++- .../{ => handler}/FileExceptionHandler.java | 21 +++++- .../{ => handler}/GlobalExceptionHandler.java | 18 ++++- .../util/exception/handler/MessageSender.java | 15 +++++ .../slack/service/SlackMessageUtil.java | 61 +++++++++++++++++ .../gitget/slack/service/SlackService.java | 7 ++ .../slack/service/SlackServiceImpl.java | 62 ++++++++++++++++++ src/main/resources/static/favicon.ico | Bin 0 -> 153626 bytes 10 files changed, 218 insertions(+), 11 deletions(-) rename src/main/java/com/genius/gitget/global/util/exception/{ => handler}/BusinessExceptionHandler.java (54%) rename src/main/java/com/genius/gitget/global/util/exception/{ => handler}/FileExceptionHandler.java (60%) rename src/main/java/com/genius/gitget/global/util/exception/{ => handler}/GlobalExceptionHandler.java (60%) create mode 100644 src/main/java/com/genius/gitget/global/util/exception/handler/MessageSender.java create mode 100644 src/main/java/com/genius/gitget/slack/service/SlackMessageUtil.java create mode 100644 src/main/java/com/genius/gitget/slack/service/SlackService.java create mode 100644 src/main/java/com/genius/gitget/slack/service/SlackServiceImpl.java create mode 100644 src/main/resources/static/favicon.ico diff --git a/build.gradle b/build.gradle index 169588d1..b8c91f7f 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ dependencies { // AWS implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.1.RELEASE' - + // json implementation 'com.googlecode.json-simple:json-simple:1.1.1' @@ -53,16 +53,16 @@ dependencies { // MongoDB implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' -// testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo' // H2 - //implementation 'com.h2database:h2' - //runtimeOnly 'com.h2database:h2:2.2.222' testRuntimeOnly 'com.h2database:h2:2.2.222' // Github API for Java implementation 'org.kohsuke:github-api:1.318' + // Slack + implementation 'com.slack.api:slack-api-client:1.25.1' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' diff --git a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java index f93be2df..c36950c9 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java +++ b/src/main/java/com/genius/gitget/challenge/certification/util/DateUtil.java @@ -48,10 +48,12 @@ public static LocalDate convertToKST(Date date) { ); } - public static LocalDate convertToKST(LocalDateTime nowLocal) { + public static LocalDateTime getKstLocalTime() { ZoneId systemZone = ZoneId.systemDefault(); ZoneId koreaZone = ZoneId.of("Asia/Seoul"); + // LocalDate.now()를 호출하여 LocalDateTime을 생성 + LocalDateTime nowLocal = LocalDateTime.now(); // 현재 시스템의 ZoneId를 이용하여 ZonedDateTime을 생성 ZonedDateTime nowZone = ZonedDateTime.of(nowLocal, systemZone); @@ -59,6 +61,19 @@ public static LocalDate convertToKST(LocalDateTime nowLocal) { ZonedDateTime koreaTime = nowZone.withZoneSameInstant(koreaZone); // LocalDateTime으로 변환하여 반환 + return koreaTime.toLocalDateTime(); + } + + public static LocalDate convertToKST(LocalDateTime nowLocal) { + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId koreaZone = ZoneId.of("Asia/Seoul"); + + // 현재 시스템의 ZoneId를 이용하여 ZonedDateTime을 생성 + ZonedDateTime nowZone = ZonedDateTime.of(nowLocal, systemZone); + + // KST(Asia/Seoul)로 변환 + ZonedDateTime koreaTime = nowZone.withZoneSameInstant(koreaZone); + return koreaTime.toLocalDate(); } diff --git a/src/main/java/com/genius/gitget/global/util/exception/BusinessExceptionHandler.java b/src/main/java/com/genius/gitget/global/util/exception/handler/BusinessExceptionHandler.java similarity index 54% rename from src/main/java/com/genius/gitget/global/util/exception/BusinessExceptionHandler.java rename to src/main/java/com/genius/gitget/global/util/exception/handler/BusinessExceptionHandler.java index c9151504..56f98053 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/BusinessExceptionHandler.java +++ b/src/main/java/com/genius/gitget/global/util/exception/handler/BusinessExceptionHandler.java @@ -1,8 +1,11 @@ -package com.genius.gitget.global.util.exception; +package com.genius.gitget.global.util.exception.handler; +import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.slack.service.SlackService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.Environment; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -10,13 +13,26 @@ @Slf4j @RestControllerAdvice @RequiredArgsConstructor -public class BusinessExceptionHandler { +public class BusinessExceptionHandler implements MessageSender { + private final Environment env; + private final SlackService slackService; + @ExceptionHandler(BusinessException.class) protected ResponseEntity globalBusinessExceptionHandler(BusinessException e) { log.error("[ERROR]" + e.getMessage(), e); + sendSlackMessage(e); return ResponseEntity.badRequest().body( new CommonResponse(e.getStatus(), e.getMessage()) ); } + + @Override + public void sendSlackMessage(Exception exception) { + if (!env.matchesProfiles("prod")) { + return; + } + slackService.sendMessage(exception); + } + } diff --git a/src/main/java/com/genius/gitget/global/util/exception/FileExceptionHandler.java b/src/main/java/com/genius/gitget/global/util/exception/handler/FileExceptionHandler.java similarity index 60% rename from src/main/java/com/genius/gitget/global/util/exception/FileExceptionHandler.java rename to src/main/java/com/genius/gitget/global/util/exception/handler/FileExceptionHandler.java index b23bb3e9..e2a3a83b 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/FileExceptionHandler.java +++ b/src/main/java/com/genius/gitget/global/util/exception/handler/FileExceptionHandler.java @@ -1,9 +1,12 @@ -package com.genius.gitget.global.util.exception; +package com.genius.gitget.global.util.exception.handler; import static com.genius.gitget.global.util.exception.ErrorCode.FILE_MAX_SIZE_EXCEED; import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.slack.service.SlackService; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -12,11 +15,25 @@ @Slf4j @RestControllerAdvice -public class FileExceptionHandler { +@RequiredArgsConstructor +public class FileExceptionHandler implements MessageSender { + private final Environment env; + private final SlackService slackService; + @ExceptionHandler(MaxUploadSizeExceededException.class) protected ResponseEntity globalExceptionHandler(Exception e) { log.error("Multipart 용량이 최대 크기를 초과하여 예외가 발생했습니다."); + sendSlackMessage(e); + return ResponseEntity.badRequest().body( new CommonResponse(HttpStatus.BAD_REQUEST, FILE_MAX_SIZE_EXCEED.getMessage())); } + + @Override + public void sendSlackMessage(Exception exception) { + if (!env.matchesProfiles("prod")) { + return; + } + slackService.sendMessage(exception); + } } diff --git a/src/main/java/com/genius/gitget/global/util/exception/GlobalExceptionHandler.java b/src/main/java/com/genius/gitget/global/util/exception/handler/GlobalExceptionHandler.java similarity index 60% rename from src/main/java/com/genius/gitget/global/util/exception/GlobalExceptionHandler.java rename to src/main/java/com/genius/gitget/global/util/exception/handler/GlobalExceptionHandler.java index 189caa53..ec18ab7d 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/genius/gitget/global/util/exception/handler/GlobalExceptionHandler.java @@ -1,8 +1,10 @@ -package com.genius.gitget.global.util.exception; +package com.genius.gitget.global.util.exception.handler; import com.genius.gitget.global.util.response.dto.CommonResponse; +import com.genius.gitget.slack.service.SlackService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -11,13 +13,25 @@ @Slf4j @RestControllerAdvice @RequiredArgsConstructor -public class GlobalExceptionHandler { +public class GlobalExceptionHandler implements MessageSender { + private final Environment env; + private final SlackService slackService; @ExceptionHandler(Exception.class) protected ResponseEntity globalExceptionHandler(Exception e) { log.error("예외처리 되지 않은 Exception 발생 - 처리 필요"); log.error("[UNHANDLED ERROR] " + e.getMessage(), e); + sendSlackMessage(e); + return ResponseEntity.badRequest().body( new CommonResponse(HttpStatus.BAD_REQUEST, e.getMessage())); } + + @Override + public void sendSlackMessage(Exception exception) { + if (!env.matchesProfiles("prod")) { + return; + } + slackService.sendMessage(exception); + } } diff --git a/src/main/java/com/genius/gitget/global/util/exception/handler/MessageSender.java b/src/main/java/com/genius/gitget/global/util/exception/handler/MessageSender.java new file mode 100644 index 00000000..aeca1ac1 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/util/exception/handler/MessageSender.java @@ -0,0 +1,15 @@ +package com.genius.gitget.global.util.exception.handler; + +public interface MessageSender { + /** + * 예외가 발생했을 때 Slack에 예외 발생에 대한 메세지를 보내는 메서드 + *

+ * 주의 사항!! + * 활성화 된 profile이 "prod"일 때에만 작동하도록 해야 합니다. + * Environment의 matchProfiles()를 통해 특정 프로파일이 활성화되어 있는지 확인 가능 + * if(!environment.matchesProfiles("prod")) return; + * + * @param exception 발생한 예외 + */ + void sendSlackMessage(Exception exception); +} diff --git a/src/main/java/com/genius/gitget/slack/service/SlackMessageUtil.java b/src/main/java/com/genius/gitget/slack/service/SlackMessageUtil.java new file mode 100644 index 00000000..e845129b --- /dev/null +++ b/src/main/java/com/genius/gitget/slack/service/SlackMessageUtil.java @@ -0,0 +1,61 @@ +package com.genius.gitget.slack.service; + +import static com.slack.api.model.block.Blocks.divider; +import static com.slack.api.model.block.Blocks.section; +import static com.slack.api.model.block.composition.BlockCompositions.markdownText; + +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.slack.api.model.Attachment; +import com.slack.api.model.block.LayoutBlock; +import com.slack.api.model.block.composition.TextObject; +import java.util.ArrayList; +import java.util.List; + +public class SlackMessageUtil { + + private static final String ERROR_TITLE = "*Exception 발생 시각:* "; + private static final String ERROR_MESSAGE = "*Exception Message:*\n"; + private static final String ERROR_STACK = "*Exception Stack:*\n"; + private static final String FILTER_STRING = "gitget"; + + + public static String createErrorTitle() { + return ERROR_TITLE + DateUtil.getKstLocalTime(); + } + + public static List createAttachments(String color, List data) { + List attachments = new ArrayList<>(); + Attachment attachment = new Attachment(); + attachment.setColor(color); + attachment.setBlocks(data); + attachments.add(attachment); + return attachments; + } + + public static List createProdErrorMessage(Exception exception) { + StackTraceElement[] stacks = exception.getStackTrace(); + + List layoutBlockList = new ArrayList<>(); + + List sectionInFields = new ArrayList<>(); + sectionInFields.add(markdownText(ERROR_MESSAGE + exception.getMessage())); + sectionInFields.add(markdownText(ERROR_STACK + exception)); + layoutBlockList.add(section(section -> section.fields(sectionInFields))); + + layoutBlockList.add(divider()); + layoutBlockList.add(section(section -> section.text(markdownText(filterErrorStack(stacks))))); + return layoutBlockList; + } + + private static String filterErrorStack(StackTraceElement[] stacks) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("```"); + for (StackTraceElement stack : stacks) { + if (stack.toString().contains(FILTER_STRING)) { + stringBuilder.append(stack).append("\n"); + } + } + stringBuilder.append("```"); + return stringBuilder.toString(); + } +} diff --git a/src/main/java/com/genius/gitget/slack/service/SlackService.java b/src/main/java/com/genius/gitget/slack/service/SlackService.java new file mode 100644 index 00000000..d191ea20 --- /dev/null +++ b/src/main/java/com/genius/gitget/slack/service/SlackService.java @@ -0,0 +1,7 @@ +package com.genius.gitget.slack.service; + +public interface SlackService { + void sendMessage(String message); + + void sendMessage(Exception exception); +} diff --git a/src/main/java/com/genius/gitget/slack/service/SlackServiceImpl.java b/src/main/java/com/genius/gitget/slack/service/SlackServiceImpl.java new file mode 100644 index 00000000..a82d3bed --- /dev/null +++ b/src/main/java/com/genius/gitget/slack/service/SlackServiceImpl.java @@ -0,0 +1,62 @@ +package com.genius.gitget.slack.service; + +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.SlackApiException; +import com.slack.api.methods.request.chat.ChatPostMessageRequest; +import com.slack.api.model.Attachment; +import com.slack.api.model.block.LayoutBlock; +import java.io.IOException; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class SlackServiceImpl implements SlackService { + private static final String ATTACHMENTS_ERROR_COLOR = "#eb4034"; + @Value("${slack.token}") + private String token; + @Value("${slack.channel}") + private String channel; + + @Override + public void sendMessage(String message) { + try { + MethodsClient methods = Slack.getInstance().methods(token); + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(channel) + .text(message) + .build(); + + methods.chatPostMessage(request); + + } catch (SlackApiException | IOException e) { + log.error(e.getMessage()); + } + } + + @Override + public void sendMessage(Exception exception) { + try { + String errorTitle = SlackMessageUtil.createErrorTitle(); + List layoutBlocks = SlackMessageUtil.createProdErrorMessage(exception); + List attachments = SlackMessageUtil.createAttachments(ATTACHMENTS_ERROR_COLOR, + layoutBlocks); + + MethodsClient methods = Slack.getInstance().methods(token); + ChatPostMessageRequest request = ChatPostMessageRequest.builder() + .channel(channel) + .attachments(attachments) + .text(errorTitle) + .build(); + + methods.chatPostMessage(request); + log.info("slack 메세지 전송 성공"); + + } catch (SlackApiException | IOException e) { + log.error(e.getMessage()); + } + } +} diff --git a/src/main/resources/static/favicon.ico b/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7ccb7f4dddd25195eb55224d0b009fa0699fdde4 GIT binary patch literal 153626 zcmeHQ>6cVRmapzJ-}>X6GoR<<{2g<;d)n?Er&Ux$WS4!DMNrvAP-GENuw`u&T<8W- z7z9za23l=wWfhSfgj%zzvNE&w==;kes-qq+D_`b|_g+NC4Nv*9ipyY!R0ICfN|im z{L6c1954=~EVz7S954=CmVbHgj047jlm(Zsj047j%knSpopHc8kh0+Nm2tp0a9RH4 zy)zCN2T~SXzA_FN2QJIMym!U{<3P%S%U8w$rzUaQVtOU>vwC|MK1$ z2aE$L3oc(72aE%kWx?euhK*T)W&M zAyD*s<9dhq?EkakjaPg2i*bYN#L$OoSeJ5njW6Zi4aC8iLABz@XE}lOznqq;?-Ccz zw2MRgbK>0%X|ZTlN<97>UL#9hBTJDZ+uv&-4z_P@5xMNY^r3RFAV1U5(Jik2*pc5~ zSPpnCC@2Gl;o$kn4WhMG4+bO_aJ_T8Q_P={;vmM*HwXvgNvA*ak2Y0&wZ6V^ zrcF#7R>xR~N|tQy*AN^G`hB(7@lK0qZ_~dQs9-uw=Z+DN)-o1smf0xAZU_z*(pk2N z?4eP&&Q2k|KG`b9KFL@ps4GVyXQuaQ01gH}SS>D|Z;u)Rz5U}1{>h&+V%Q^$1;)ZX z+kpW%SiictS4QJ{k;@9PWnDAtN2agSag8rQ93Wn3;j<}GTOGYVeyfVL$u@Kj~YwGtTgo7u3S0g_9AS2q6g+U%~*C>u} z#@Kq+kxZ4*IL4I_4(3d45YS6ZXo%mqe6d{&dzjD7&YqhSz`>};YsH1L@!5xhVhuXl z_`!??C$SJS4v;tS;nwK=WkF?MeP0KKw;rUJ)e)5Q-rux(wJj)?rK8Dr}6xwZ%P=ET%d2HEIh!oi|h z4dgGQ;2uj_H`$P`Qa;?gX$_pyrb!nW`DjnRInH7D`T9hgnDdMw^BV&WCXc8WKVCHV zOi&1#-4y!z!-e+zv(mLDy4Vlm21XC8rG21pj$tR|NHx@TiEW$GVg$u{VZTUhej>&J zI43^+I3p0}X={U@Z3Iuj!ww!eMAJLky5`rEPA{COJOJ+aMoggC%j zkZ$g0GxSb^1;4+)B`pR%P+SgTdwvG)72pPWj`GZ}Tq^%ec;{B9Sh<+&bq~aZg$Qu~ zS$OB|v^MSu6JP=z?{%`LG7n7QwP4BIRNgL9OcyHlJewN3#m@IKlsg!MJ{mC&z@>%U zueJy{D2mNvRR4bWPN!J0xRLFtK6}yZiS=4_VPNU(cWq+v^Qma(J7OGQPe2?e;&#HW z4^hc=IsK*@x&&+hF6%$)y+c<-9!FH?LGV!R-IWnzo*?@wojug=*77}PNW`6+ShM)~J~pbz=$6RiR*!suh>>!BQ1n-rWIf)quu(ParrAahf?psrFEOl3x?5T*w-Mls}a86399XgQH zg8{4yG2;N(1z*tze`-l;`Xc#W_?)p5DxHMr;Ml* z$G&KdoYORt4WK}$&=XLjN9!3*S%;Iv!^iDcGF#Cdk=DNcLg%m)4YZNfM}Jk|o@v}-kh$4Dm@ zFMZ!GP}e8P^;_3>6HQ*!&QP7I(g?B8ds46$1V;sARBYID3!aY#wVujr6{Zy$)&o;|5PS&kB#U$1^PopOm! zf751+E`)i**<>r_GseQ_Vki#ebK15sxp+pe4D0&+-08M_9y)Y!d7nvMpPkUdtD@`v zboGN8!b9WY;x`-z!yl=kTsE`z>afolHl(j8p9E`glJbx5o;s>dT)t%9Sqyr>s+VHS z12-H8cvfQv)rqf;SC+#WTyEc;B3s~)7<6ABJqYtUZ+e5c{6q9PWTmbfuoWVY7rZ~Q ztrHgDK(1R?el+iymHLdm@BegnHW3@rk~xieZXxe@O!)mGacRb$?`jDSo$+r**CkbHyzHF0q zA=GIEMq`(MOZNlJ6MV9$Ia_J%Hu0W3N8Hf5WdUo)rnSw+>O#=b62$uf z<^SXB%CaE=*N7+6DHFiJ#y6WqeXW`MCoq70N7gSbSt1yf=ob?i@w z)dt@)YNv#i3FPl0ucNlwObldNy2Yk9)3Kb3iq56QIDkL)CE~rjb)z(HaUJp+bsr~> zwuZ8w=P|If(O8>8E-S>^72vb(|2$Y!ex-gN_G&Q>Fh{_`o3A#BMmlF!QiIPKXP|Q8 zEb$q{W|_JVz}^Sm9<6*6j^hAWKuq2y(t(k)WOMWCcc>kgr5*-+u*joqGBn4v znd)J{-Z$uXhU^0pKbG$Ya^D0`%AdE{nb(Opg4;JcMV(Slw2de{_U_Dx7iXsebHIQf z4ZJF*azddd%ogH89sFR#xdc+SOSx7E4uA*b5`quOyRMqBN7U7H(b#eVF_hq9gWiLN zSpSoBE{1&IJ+d7pj-FE1Oyu#szoo^f+yQwnxZ-|M6UNv)qzb|T;?yTm4y%baLU2BU z$7S;HdhzV|daBEu5-Ta55}aSCoq>FQsn;Pdei7{ha<2<=MZBJgF>?|N;655_GLKCK z;Xuj@Vzkt?RX&TPt~alDP~Nq_J`8 zfaeD>-B&3-7jl?n1Mf?z_Yws6+TT-wIKUiXorPT@ov_^m=YglGmb~9{gZ_Vl`~=>H zbHs4+m-~Ij?{BC9u!qjnvOX((55O1dJuf7#K*UsBz1$aT-tUa!clgF}X7d}faeu2& z9AKWoNBG{Rmc-7@sI3Wqu3!EoaQY{zp#kn@oC~1SVE@9;Vd5@)x&-+dO*O+#ec8hi z3p*3)5_sio{p;x{MBTSf?19LevSJ^Qx={t=0J1QM=i9}!r^}D41MjD7@H_bFVIzsJ7k;@Hrrf{R;QiR2fT1uAv7<`~U$M)u)NR6C z9l(KnF3U*gK#ZfV1|QgmwEh>10RnI9yTm_p_op5?!{;C~&>vt=S@lw5sJa%Yiy_-e z>Py~lsaNP@6M`loE)cN}KU%Et4`N#VZ-wm6Nl*d9>$RjvfeqI$bBDWLt_AAUbY}5 zGGzN#+d;BHmG$hQ4|sl{H=YVm`(Wqx;%6go-$wuLFb=Tx;e3bM1VJ0@?DJ<7=al&9 z{l0t}x@5o)2P;TF*U>-hUI#w8=ZGjUA>dkqk3`r0qhs7!`fwNrm_O|68>lXJP#Y5L zuFGHSU+&sc&TO};Whq8gwm168Z3y~ZhxQjf6JRco>k6NE(EhA^ALF3+`9eQHYysll z{2F8(+|-Z>oGB46S6AB)Xp~OE$_CE#s3-Lu)ow!V1hh-vcZl)ilMdT=fA(Y4?wK+& z{C9*MW8|M_9Q2VR#0D&&7-cW-3G^Yv0_`F@&{>KtttCH#^kd=Q@P61-3;l$7ju_Lt zX$|7WwT|As>gKi1d>xN4{o=FeTgH4Su+8g8*D3A}ersSb?DrV|dB%b7GhVy0ne_bP zF?rCbzaZZXU~Atq2KEqe3?Rm^*!Mf}?~H@k%3hOM_1dHm5PfcXvgJ6@ecdF;a{H#Wu`1h#d!5htXi80jE*e z^%k*yh`;u>_V#YEZ+9jiCns$~ZA7QK|Km*P?+iO;<37(gh*c(_<4B$r$St@`!}_d- zP@nHn4G?gpdK+RSWIQ!|(WsX*f;hd5lf(YbuyOnw;~-Xa?ofeRSxd04p|{zwhQ z1Qq7sfS!b0GRA!^yuh4t#OojYGp36LrkW*%Po26l&ND&XcYL8z<{71$1{+ zkB{m#6Tc2nX07cD&o-TPwt`AO2f}IAI+0;s9}GsMSJX za2A0JMfzM-IhC)$^*((}xcG;0Pu1STLf`!c+%KibBXqZ^qa4bg*RB5)>d#>uj2Tob zP7sHkz6D(~Lcz{p+VfCeAB3H86A6JhM?*#h-A%C+S(4&26p z8vE~+ef4)>0Cgb4<_74JH{>lGQtBjQtS64<@_fU(zQgH$9NQ22^AkuMfE%J`AMmUh zKIh)JWCwme@cgO`oRT;*#5IPB{vOA0u=VY&?kF4ff$dcD z%mlm8ee0Cx`C|OXkMs>xx=r={7R*eQl_ON_`<(fA$8dn>I+OAhpq&7c2aGiI{K{2e!)z>{_!YQe7P4r?CBAj2J)TfaNH}S;Yt$ z)-is@0pmc29NFFn}^ zfN@~E4C@#_w*k!#c*#IA9#;kR#js zU>qli=dfN`Kh zj%@FPalkmRU50gxpK-uA&>=^*_rW+|9M~?yI>t{psN%*mz%#%zz%#%zz%#%zz%#%z cz%#%zz%#%zz%#%zz%#%zz%#%zVDSw66IIUN{Qv*} literal 0 HcmV?d00001 From ab7f0a6114ad35bf936db83626cff5991d752add Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:26:23 +0900 Subject: [PATCH 203/234] =?UTF-8?q?chore:=20Deploy=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - push, pull request에서 push로 변경 --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a92ad711..2f76c2e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,8 +3,8 @@ name: Build and Deploy to EC2 on: push: branches: [ "production" ] - pull_request: - branches: [ "production" ] +# pull_request: +# branches: [ "production" ] env: AWS_REGION: ap-northeast-2 From 2c3935be14276c5b7aebb1f02f65ac805214c873 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:26:52 +0900 Subject: [PATCH 204/234] =?UTF-8?q?chore:=20Deploy=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - push, pull request에서 push로 변경 --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a92ad711..2f76c2e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,8 +3,8 @@ name: Build and Deploy to EC2 on: push: branches: [ "production" ] - pull_request: - branches: [ "production" ] +# pull_request: +# branches: [ "production" ] env: AWS_REGION: ap-northeast-2 From eefe60406de8c9686730b6d35bb7ead456e162ff Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Sun, 14 Jul 2024 14:03:39 +0900 Subject: [PATCH 205/234] [FEAT] facade pattern - Topic (#218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * facade pattern - topic - topicController - topicService - topicFacade - topicFacadeImpl * refactor: 정적 펙터리 메서드 네이밍 규칙 적용 * refactor: else 예약어 제거 및 변수명 수정 * feat: topic repository 테스트 코드 리펙토링 - describe context it 패턴 적용 - facade pattern 적용 후 코드 리펙토링 * feat: topic service -> facade 테스트 코드 리펙토링 - describe context it 패턴 적용 - topicServiceTest -> topicFacadeTest로 변경 - facade pattern에 맞게 수정 * refactor: 코드 리뷰 피드백 반영 - topicService.createTopicByTopic -> TopicCreateRequest.from * refactor: TopicFacadeTest 리펙토링 - getTopicUpdateRequest 메서드 추출 --- .../admin/topic/dto/TopicCreateRequest.java | 13 -- .../admin/topic/service/TopicService.java | 87 -------- .../challenge/instance/domain/Instance.java | 2 +- .../instance/service/InstanceService.java | 4 +- .../global/file/service/FileHolderFinder.java | 2 +- .../profile/service/ProfileService.java | 4 +- .../gitget/{admin => }/signout/Signout.java | 2 +- .../signout/SignoutRepository.java | 2 +- .../topic/controller/TopicController.java | 40 ++-- .../{admin => }/topic/domain/Topic.java | 3 +- .../gitget/topic/dto/TopicCreateRequest.java | 23 +++ .../topic/dto/TopicDetailResponse.java | 7 +- .../topic/dto/TopicIndexResponse.java | 2 +- .../topic/dto/TopicPagingResponse.java | 6 +- .../topic/dto/TopicUpdateRequest.java | 2 +- .../topic/repository/TopicRepository.java | 4 +- .../gitget/topic/service/TopicService.java | 42 ++++ .../topic/serviceFacade/TopicFacade.java | 21 ++ .../topic/serviceFacade/TopicFacadeImpl.java | 67 +++++++ .../topic/controller/TopicControllerTest.java | 4 +- .../topic/repository/TopicRepositoryTest.java | 189 +++++++++++++----- .../admin/topic/service/TopicFacadeTest.java | 139 +++++++++++++ .../admin/topic/service/TopicServiceTest.java | 110 ---------- .../InstanceHomeControllerTest.java | 4 +- .../home/service/InstanceHomeServiceTest.java | 4 +- .../controller/InstanceControllerTest.java | 4 +- .../InstanceSearchRepositoryTest.java | 4 +- .../service/InstanceSearchServiceTest.java | 4 +- .../instance/service/InstanceServiceTest.java | 4 +- .../gitget/challenge/likes/LikesTest.java | 4 +- .../likes/controller/LikesControllerTest.java | 4 +- .../likes/service/LikesServiceTest.java | 4 +- .../controller/PaymentControllerTest.java | 2 +- .../payment/service/PaymentServiceTest.java | 2 +- .../controller/ProfileControllerTest.java | 4 +- .../profile/service/ProfileServiceTest.java | 8 +- 36 files changed, 506 insertions(+), 321 deletions(-) delete mode 100644 src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java delete mode 100644 src/main/java/com/genius/gitget/admin/topic/service/TopicService.java rename src/main/java/com/genius/gitget/{admin => }/signout/Signout.java (96%) rename src/main/java/com/genius/gitget/{admin => }/signout/SignoutRepository.java (88%) rename src/main/java/com/genius/gitget/{admin => }/topic/controller/TopicController.java (77%) rename src/main/java/com/genius/gitget/{admin => }/topic/domain/Topic.java (96%) create mode 100644 src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java rename src/main/java/com/genius/gitget/{admin => }/topic/dto/TopicDetailResponse.java (78%) rename src/main/java/com/genius/gitget/{admin => }/topic/dto/TopicIndexResponse.java (59%) rename src/main/java/com/genius/gitget/{admin => }/topic/dto/TopicPagingResponse.java (67%) rename src/main/java/com/genius/gitget/{admin => }/topic/dto/TopicUpdateRequest.java (81%) rename src/main/java/com/genius/gitget/{admin => }/topic/repository/TopicRepository.java (79%) create mode 100644 src/main/java/com/genius/gitget/topic/service/TopicService.java create mode 100644 src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacade.java create mode 100644 src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacadeImpl.java create mode 100644 src/test/java/com/genius/gitget/admin/topic/service/TopicFacadeTest.java delete mode 100644 src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java b/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java deleted file mode 100644 index 1ed238fd..00000000 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicCreateRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.genius.gitget.admin.topic.dto; - -import lombok.Builder; - -@Builder -public record TopicCreateRequest( - String title, - String tags, - String description, - int pointPerPerson, - String notice -) { -} diff --git a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java b/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java deleted file mode 100644 index 6b22a5d8..00000000 --- a/src/main/java/com/genius/gitget/admin/topic/service/TopicService.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.genius.gitget.admin.topic.service; - -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.dto.TopicCreateRequest; -import com.genius.gitget.admin.topic.dto.TopicDetailResponse; -import com.genius.gitget.admin.topic.dto.TopicPagingResponse; -import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; -import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Slf4j -@Transactional(readOnly = true) -public class TopicService { - private final TopicRepository topicRepository; - private final FilesService filesService; - - // 토픽 리스트 요청 - public Page getAllTopics(Pageable pageable) { - Page topics = topicRepository.findAllById(pageable); - return topics.map(this::mapToTopicPagingResponse); - } - - private TopicPagingResponse mapToTopicPagingResponse(Topic topic) { - FileResponse fileResponse = filesService.convertToFileResponse(topic.getFiles()); - return TopicPagingResponse.createByEntity(topic, fileResponse); - } - - // 토픽 상세정보 요청 - public TopicDetailResponse getTopicById(Long id) { - Topic topic = topicRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - FileResponse fileResponse = filesService.convertToFileResponse(topic.getFiles()); - return TopicDetailResponse.createByEntity(topic, fileResponse); - } - - // 토픽 생성 요청 - @Transactional - public Long createTopic(TopicCreateRequest topicCreateRequest) { - Topic topic = Topic.builder() - .title(topicCreateRequest.title()) - .description(topicCreateRequest.description()) - .tags(topicCreateRequest.tags()) - .pointPerPerson(topicCreateRequest.pointPerPerson()) - .notice(topicCreateRequest.notice()) - .build(); - - Topic savedTopic = topicRepository.save(topic); - - return savedTopic.getId(); - } - - // 토픽 업데이트 요청 - @Transactional - public Long updateTopic(Long id, TopicUpdateRequest topicUpdateRequest) { - Topic topic = topicRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - - // 서버에서 한번 더 검사 - boolean hasInstance = !topic.getInstanceList().isEmpty(); - if (hasInstance) { - topic.updateExistInstance(topicUpdateRequest.description()); - } else { - topic.updateNotExistInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), - topicUpdateRequest.tags(), topicUpdateRequest.notice(), topicUpdateRequest.pointPerPerson()); - } - return topicRepository.save(topic).getId(); - } - - // 토픽 삭제 요청 - @Transactional - public void deleteTopic(Long id) { - Topic topic = topicRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); - topicRepository.delete(topic); - } -} diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index 4f83fec1..f41047f7 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -1,7 +1,7 @@ package com.genius.gitget.challenge.instance.domain; -import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.likes.domain.Likes; diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index 54b06415..1bc30561 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -4,8 +4,8 @@ import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_INSTANCE_DATE; import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; diff --git a/src/main/java/com/genius/gitget/global/file/service/FileHolderFinder.java b/src/main/java/com/genius/gitget/global/file/service/FileHolderFinder.java index 5055e753..8fc5ce1e 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileHolderFinder.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileHolderFinder.java @@ -4,7 +4,7 @@ import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.file.domain.FileHolder; diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileService.java index b6d22643..cd977705 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileService.java @@ -6,8 +6,8 @@ import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; import static com.genius.gitget.challenge.participant.domain.JoinStatus.YES; -import com.genius.gitget.admin.signout.Signout; -import com.genius.gitget.admin.signout.SignoutRepository; +import com.genius.gitget.signout.Signout; +import com.genius.gitget.signout.SignoutRepository; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.user.domain.User; diff --git a/src/main/java/com/genius/gitget/admin/signout/Signout.java b/src/main/java/com/genius/gitget/signout/Signout.java similarity index 96% rename from src/main/java/com/genius/gitget/admin/signout/Signout.java rename to src/main/java/com/genius/gitget/signout/Signout.java index 5b807206..0590f5dc 100644 --- a/src/main/java/com/genius/gitget/admin/signout/Signout.java +++ b/src/main/java/com/genius/gitget/signout/Signout.java @@ -1,4 +1,4 @@ -package com.genius.gitget.admin.signout; +package com.genius.gitget.signout; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/genius/gitget/admin/signout/SignoutRepository.java b/src/main/java/com/genius/gitget/signout/SignoutRepository.java similarity index 88% rename from src/main/java/com/genius/gitget/admin/signout/SignoutRepository.java rename to src/main/java/com/genius/gitget/signout/SignoutRepository.java index 2a064d8a..1d8e1ca0 100644 --- a/src/main/java/com/genius/gitget/admin/signout/SignoutRepository.java +++ b/src/main/java/com/genius/gitget/signout/SignoutRepository.java @@ -1,4 +1,4 @@ -package com.genius.gitget.admin.signout; +package com.genius.gitget.signout; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java b/src/main/java/com/genius/gitget/topic/controller/TopicController.java similarity index 77% rename from src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java rename to src/main/java/com/genius/gitget/topic/controller/TopicController.java index 9fe968ae..947dc79a 100644 --- a/src/main/java/com/genius/gitget/admin/topic/controller/TopicController.java +++ b/src/main/java/com/genius/gitget/topic/controller/TopicController.java @@ -1,17 +1,17 @@ -package com.genius.gitget.admin.topic.controller; +package com.genius.gitget.topic.controller; import static com.genius.gitget.global.util.exception.SuccessCode.CREATED; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.admin.topic.dto.TopicCreateRequest; -import com.genius.gitget.admin.topic.dto.TopicDetailResponse; -import com.genius.gitget.admin.topic.dto.TopicIndexResponse; -import com.genius.gitget.admin.topic.dto.TopicPagingResponse; -import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; -import com.genius.gitget.admin.topic.service.TopicService; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; +import com.genius.gitget.topic.dto.TopicCreateRequest; +import com.genius.gitget.topic.dto.TopicDetailResponse; +import com.genius.gitget.topic.dto.TopicIndexResponse; +import com.genius.gitget.topic.dto.TopicPagingResponse; +import com.genius.gitget.topic.dto.TopicUpdateRequest; +import com.genius.gitget.topic.serviceFacade.TopicFacade; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -31,26 +31,26 @@ @RequiredArgsConstructor @RequestMapping("/api/admin/topic") public class TopicController { - - private final TopicService topicService; + private final TopicFacade topicFacade; // 토픽 리스트 요청 @GetMapping public ResponseEntity> getAllTopics( @PageableDefault(size = 5, direction = Sort.Direction.ASC) Pageable pageable) { - Page allTopics = topicService.getAllTopics(pageable); + Page topicPagingResponse = topicFacade.findTopics(pageable); return ResponseEntity.ok().body( - new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), allTopics) + new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), topicPagingResponse) ); } // 토픽 상세 정보 요청 @GetMapping("/{id}") public ResponseEntity> getTopicById(@PathVariable Long id) { - TopicDetailResponse topicDetail = topicService.getTopicById(id); + TopicDetailResponse topicDetailResponse = topicFacade.findOne(id); + return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), topicDetail) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), topicDetailResponse) ); } @@ -58,8 +58,9 @@ public ResponseEntity> getTopicById(@PathVar @PostMapping public ResponseEntity> createTopic( @RequestBody TopicCreateRequest topicCreateRequest) { - Long topicId = topicService.createTopic(topicCreateRequest); - TopicIndexResponse topicUpdateResponse = new TopicIndexResponse(topicId); + + Long topic = topicFacade.create(topicCreateRequest); + TopicIndexResponse topicUpdateResponse = new TopicIndexResponse(topic); return ResponseEntity.ok().body( new SingleResponse<>( @@ -72,8 +73,9 @@ public ResponseEntity> createTopic( public ResponseEntity> updateTopic( @PathVariable Long id, @RequestBody TopicUpdateRequest topicUpdateRequest) { - Long topicId = topicService.updateTopic(id, topicUpdateRequest); - TopicIndexResponse topicUpdateResponse = new TopicIndexResponse(topicId); + + Long updateTopic = topicFacade.update(id, topicUpdateRequest); + TopicIndexResponse topicUpdateResponse = new TopicIndexResponse(updateTopic); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), topicUpdateResponse) @@ -83,7 +85,9 @@ public ResponseEntity> updateTopic( // 토픽 삭제 요청 @DeleteMapping("/{id}") public ResponseEntity deleteTopic(@PathVariable Long id) { - topicService.deleteTopic(id); + + topicFacade.delete(id); + return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); diff --git a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java b/src/main/java/com/genius/gitget/topic/domain/Topic.java similarity index 96% rename from src/main/java/com/genius/gitget/admin/topic/domain/Topic.java rename to src/main/java/com/genius/gitget/topic/domain/Topic.java index 9f419fe2..7758d313 100644 --- a/src/main/java/com/genius/gitget/admin/topic/domain/Topic.java +++ b/src/main/java/com/genius/gitget/topic/domain/Topic.java @@ -1,4 +1,4 @@ -package com.genius.gitget.admin.topic.domain; +package com.genius.gitget.topic.domain; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.domain.FileHolder; @@ -60,7 +60,6 @@ public Topic(String title, String description, String tags, String notice, int p this.pointPerPerson = pointPerPerson; } - //== 비즈니스 로직 ==// public void updateExistInstance(String description) { this.description = description; } diff --git a/src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java b/src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java new file mode 100644 index 00000000..4af0369b --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/dto/TopicCreateRequest.java @@ -0,0 +1,23 @@ +package com.genius.gitget.topic.dto; + +import com.genius.gitget.topic.domain.Topic; +import lombok.Builder; + +@Builder +public record TopicCreateRequest( + String title, + String tags, + String description, + int pointPerPerson, + String notice +) { + public static Topic from(TopicCreateRequest topicCreateRequest) { + return Topic.builder() + .title(topicCreateRequest.title()) + .description(topicCreateRequest.description()) + .tags(topicCreateRequest.tags()) + .pointPerPerson(topicCreateRequest.pointPerPerson()) + .notice(topicCreateRequest.notice()) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java b/src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java similarity index 78% rename from src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java rename to src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java index f74be401..c13e4693 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicDetailResponse.java +++ b/src/main/java/com/genius/gitget/topic/dto/TopicDetailResponse.java @@ -1,7 +1,7 @@ -package com.genius.gitget.admin.topic.dto; +package com.genius.gitget.topic.dto; -import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.topic.domain.Topic; import lombok.Builder; @Builder @@ -13,7 +13,8 @@ public record TopicDetailResponse( String notice, int pointPerPerson, FileResponse fileResponse) { - public static TopicDetailResponse createByEntity(Topic topic, FileResponse fileResponse) { + + public static TopicDetailResponse of(Topic topic, FileResponse fileResponse) { return TopicDetailResponse.builder() .topicId(topic.getId()) .title(topic.getTitle()) diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicIndexResponse.java b/src/main/java/com/genius/gitget/topic/dto/TopicIndexResponse.java similarity index 59% rename from src/main/java/com/genius/gitget/admin/topic/dto/TopicIndexResponse.java rename to src/main/java/com/genius/gitget/topic/dto/TopicIndexResponse.java index 6e525cd3..e7d5769d 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicIndexResponse.java +++ b/src/main/java/com/genius/gitget/topic/dto/TopicIndexResponse.java @@ -1,4 +1,4 @@ -package com.genius.gitget.admin.topic.dto; +package com.genius.gitget.topic.dto; public record TopicIndexResponse( Long topicId diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java b/src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java similarity index 67% rename from src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java rename to src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java index a4e091ba..7b673edb 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicPagingResponse.java +++ b/src/main/java/com/genius/gitget/topic/dto/TopicPagingResponse.java @@ -1,13 +1,13 @@ -package com.genius.gitget.admin.topic.dto; +package com.genius.gitget.topic.dto; -import com.genius.gitget.admin.topic.domain.Topic; import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.topic.domain.Topic; import lombok.Builder; @Builder public record TopicPagingResponse(Long topicId, String title, FileResponse fileResponse) { - public static TopicPagingResponse createByEntity(Topic topic, FileResponse fileResponse) { + public static TopicPagingResponse of(Topic topic, FileResponse fileResponse) { return TopicPagingResponse.builder() .topicId(topic.getId()) .title(topic.getTitle()) diff --git a/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java b/src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java similarity index 81% rename from src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java rename to src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java index 6898bcd0..d0326bc2 100644 --- a/src/main/java/com/genius/gitget/admin/topic/dto/TopicUpdateRequest.java +++ b/src/main/java/com/genius/gitget/topic/dto/TopicUpdateRequest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.admin.topic.dto; +package com.genius.gitget.topic.dto; import lombok.Builder; diff --git a/src/main/java/com/genius/gitget/admin/topic/repository/TopicRepository.java b/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java similarity index 79% rename from src/main/java/com/genius/gitget/admin/topic/repository/TopicRepository.java rename to src/main/java/com/genius/gitget/topic/repository/TopicRepository.java index 4e0b7318..3ab98006 100644 --- a/src/main/java/com/genius/gitget/admin/topic/repository/TopicRepository.java +++ b/src/main/java/com/genius/gitget/topic/repository/TopicRepository.java @@ -1,6 +1,6 @@ -package com.genius.gitget.admin.topic.repository; +package com.genius.gitget.topic.repository; -import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.topic.domain.Topic; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/genius/gitget/topic/service/TopicService.java b/src/main/java/com/genius/gitget/topic/service/TopicService.java new file mode 100644 index 00000000..9a450145 --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/service/TopicService.java @@ -0,0 +1,42 @@ +package com.genius.gitget.topic.service; + +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class TopicService { + private final TopicRepository topicRepository; + + public Page findTopics(Pageable pageable) { + return topicRepository.findAllById(pageable); + } + + public Topic findOne(Long id) { + return topicRepository.findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + } + + @Transactional + public Long create(Topic topic) { + Topic savedTopic = topicRepository.save(topic); + return savedTopic.getId(); + } + + @Transactional + public void delete(Long id) { + Topic topic = topicRepository.findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.TOPIC_NOT_FOUND)); + topicRepository.delete(topic); + } +} diff --git a/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacade.java b/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacade.java new file mode 100644 index 00000000..18558cff --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacade.java @@ -0,0 +1,21 @@ +package com.genius.gitget.topic.serviceFacade; + +import com.genius.gitget.topic.dto.TopicCreateRequest; +import com.genius.gitget.topic.dto.TopicDetailResponse; +import com.genius.gitget.topic.dto.TopicPagingResponse; +import com.genius.gitget.topic.dto.TopicUpdateRequest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface TopicFacade { + + Page findTopics(Pageable pageable); + + TopicDetailResponse findOne(Long id); + + Long create(TopicCreateRequest topicCreateRequest); + + Long update(Long id, TopicUpdateRequest topicUpdateRequest); + + void delete(Long id); +} diff --git a/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacadeImpl.java b/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacadeImpl.java new file mode 100644 index 00000000..f91e2a07 --- /dev/null +++ b/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacadeImpl.java @@ -0,0 +1,67 @@ +package com.genius.gitget.topic.serviceFacade; + +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.dto.TopicCreateRequest; +import com.genius.gitget.topic.dto.TopicDetailResponse; +import com.genius.gitget.topic.dto.TopicPagingResponse; +import com.genius.gitget.topic.dto.TopicUpdateRequest; +import com.genius.gitget.topic.service.TopicService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Component +@Transactional +public class TopicFacadeImpl implements TopicFacade { + + private final FilesService filesService; + private final TopicService topicService; + + @Override + public Page findTopics(Pageable pageable) { + Page findTopics = topicService.findTopics(pageable); + return findTopics.map(this::convertToTopicPagingResponseDto); + } + + @Override + public TopicDetailResponse findOne(Long id) { + Topic findTopic = topicService.findOne(id); + FileResponse fileResponse = filesService.convertToFileResponse(findTopic.getFiles()); + return TopicDetailResponse.of(findTopic, fileResponse); + } + + @Override + public Long create(TopicCreateRequest topicCreateRequest) { + Topic topic = TopicCreateRequest.from(topicCreateRequest); + return topicService.create(topic); + } + + @Override + public Long update(Long id, TopicUpdateRequest topicUpdateRequest) { + Topic topic = topicService.findOne(id); + + if (!topic.getInstanceList().isEmpty()) { + topic.updateExistInstance(topicUpdateRequest.description()); + return topicService.create(topic); + } + + topic.updateNotExistInstance(topicUpdateRequest.title(), topicUpdateRequest.description(), + topicUpdateRequest.tags(), topicUpdateRequest.notice(), topicUpdateRequest.pointPerPerson()); + return topicService.create(topic); + } + + @Override + public void delete(Long id) { + topicService.delete(id); + } + + private TopicPagingResponse convertToTopicPagingResponseDto(Topic topic) { + FileResponse fileResponse = filesService.convertToFileResponse(topic.getFiles()); + return TopicPagingResponse.of(topic, fileResponse); + } +} diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java index a85d65fd..fbc14eb4 100644 --- a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java @@ -8,8 +8,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.util.TokenTestUtil; diff --git a/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java b/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java index 9e9c0870..4babf210 100644 --- a/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java @@ -1,12 +1,16 @@ package com.genius.gitget.admin.topic.repository; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import com.genius.gitget.admin.topic.domain.Topic; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import jakarta.transaction.Transactional; import java.util.Optional; -import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Assertions; 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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -14,24 +18,28 @@ import org.springframework.data.domain.PageRequest; import org.springframework.test.annotation.Rollback; +//@ExtendWith(SpringExtension.class) +//@DataJpaTest @SpringBootTest @Transactional @Rollback +@DisplayName("TopicRepository") public class TopicRepositoryTest { - public Topic topic, topicA; + Topic topicA, topicB; + @Autowired private TopicRepository topicRepository; @BeforeEach public void setup() { - topic = Topic.builder() + topicA = Topic.builder() .title("1일 1알고리즘") .description("하루에 한 문제씩 문제를 해결합니다.") .tags("BE, FE, CS") .pointPerPerson(100) .build(); - topicA = Topic.builder() + topicB = Topic.builder() .title("1일 2알고리즘") .description("하루에 한 문제씩 문제를 해결합니다.") .tags("BE, FE, CS") @@ -39,57 +47,148 @@ public void setup() { .build(); } - @Test - public void 토픽_저장() { - Topic savedTopic = topicRepository.save(topic); - assertEquals(topic.getId(), savedTopic.getId()); - assertEquals(topic.getTitle(), savedTopic.getTitle()); - assertEquals(topic.getDescription(), savedTopic.getDescription()); - } + @Nested + @DisplayName("save 메서드는") + class Describe_save { - @Test - public void 토픽_수정() { - Topic savedTopic = topicRepository.save(topic); - if (!topic.getInstanceList().isEmpty()) { - savedTopic.updateExistInstance("(수정) 하루에 두 문제씩 문제를 해결합니다."); - } else { - savedTopic.updateNotExistInstance("1일 1커밋", "하루에 1커밋 하기", "CS", "유의사항",300); + @BeforeEach + public void init() { + topicRepository.deleteAll(); } - assertEquals(topic.getId(), savedTopic.getId()); - assertEquals(topic.getTitle(), savedTopic.getTitle()); - assertEquals(topic.getDescription(), savedTopic.getDescription()); - } + @Nested + @DisplayName("토픽 객체가 주어질 때") + class Context_with_a_topic { + + @Test +// it_returns_4XX_if_saving_the_obj_fails + @DisplayName("객체 저장에 성공하면 저장된 객체를 반환합니다.") + public void it_returns_the_saved_obj_if_saving_an_obj_succeeds() { + Topic savedTopic = topicRepository.save(topicA); - @Test - public void 토픽_삭제() { - Topic savedTopic = topicRepository.save(topic); - topicRepository.delete(savedTopic); - Optional byId = topicRepository.findById(1L); - Assertions.assertThat(byId).isNotPresent(); + assertEquals(topicA.getId(), savedTopic.getId()); + assertEquals(topicA.getTitle(), savedTopic.getTitle()); + assertEquals(topicA.getDescription(), savedTopic.getDescription()); + } + } } - @Test - public void 토픽_리스트_조회() { - for (int i = 1; i <= 10; i++) { - topicRepository.save( - Topic.builder().title("user" + i + "L").build() - ); + @Nested + @DisplayName("search 메서드는") + class Describe_search { + + @Nested + @DisplayName("조회 조건에 따라") + class Context_with_a_topic { + + @BeforeEach + public void prepare() { + topicRepository.save(topicA); + topicRepository.save(topicB); + } + + @Test + @DisplayName("토픽 전체를 반환합니다.") + public void it_returns_topic_obj_list() { + Page topics = topicRepository.findAllById(PageRequest.of(0, 5)); + int topicCount = 0; + + for (Topic topic : topics) { + if (topic != null) { + topicCount++; + } + } + + assertThat(topicCount).isEqualTo(2); + } + + @Test + @DisplayName("특정 토픽을 반환합니다.") + public void it_returns_topic_obj() { + Optional topic = topicRepository.findById(topicA.getId()); + + assertThat(topic.get().getTitle()).isEqualTo("1일 1알고리즘"); + } } - Page allById = topicRepository.findAllById(PageRequest.of(0, 5)); - Assertions.assertThat(allById.getSize()).isEqualTo(5); + } + + + @Nested + @DisplayName("update 메서드는") + class Describe_update { + + @Nested + @DisplayName("토픽 정보를 수정하려고 할 때") + class Context_with_a_topic { + + @BeforeEach + public void init() { + topicRepository.save(topicA); + } + + @Test + @DisplayName("생성된 인스턴스가 존재하면 description만 수정할 수 있고, 없다면 모든 항목을 수정할 수 있다.") + public void it_returns_updated_obj() { + + Topic topic = topicRepository.findById(topicA.getId()).orElse(null); - for (Topic topic1 : allById) { - System.out.println("topic1.getTitle() = " + topic1.getTitle()); + boolean hasInstance = false; + if (!topic.getInstanceList().isEmpty()) { + hasInstance = true; + topic.updateExistInstance("(수정) 하루에 두 문제씩 문제를 해결합니다."); + } else { + topic.updateNotExistInstance("1일 2알고리즘", "(수정) 하루에 두 문제씩 문제를 해결합니다.", "CS", "유의사항", 30000); + } + + Topic savedTopic = topicRepository.save(topic); + + if (!hasInstance) { + assertEquals(topic.getId(), savedTopic.getId()); + assertEquals("(수정) 하루에 두 문제씩 문제를 해결합니다.", savedTopic.getDescription()); + } else { + assertEquals(topic.getId(), savedTopic.getId()); + assertEquals("(수정) 하루에 두 문제씩 문제를 해결합니다.", savedTopic.getDescription()); + assertEquals(30000, savedTopic.getPointPerPerson()); + } + } } } - @Test - public void 토픽_단건_조회() { - Topic savedTopic = topicRepository.save(topic); - Optional byId = topicRepository.findById(savedTopic.getId()); - Assertions.assertThat(byId.get().getTitle()).isEqualTo("1일 1알고리즘"); + @Nested + @DisplayName("delete 메서드는") + class Describe_delete { + + @Nested + @DisplayName("삭제하려는 토픽 ID가 주어질 때") + class Context_with_a_topic { + + @BeforeEach + public void init() { + topicRepository.save(topicA); + } + + @Test + @DisplayName("객체가 성공적으로 삭제되면, 다시 조회할 수 없다.") + public void it_cannot_be_retrieved_once_an_obj_is_successfully_deleted() { + Topic topic = topicRepository.findById(topicA.getId()).orElse(null); + assert topic != null; + topicRepository.delete(topic); + Topic findTopic = topicRepository.findById(topicA.getId()).orElse(null); + Assertions.assertThrows(NullPointerException.class, () -> { + findTopic.getId(); + }); + } + + @Test + @DisplayName("DB에 해당 객체가 없으면, 삭제할 수 없다.") + public void it_cannot_be_deleted_if_the_obj_does_not_exist() { + Assertions.assertThrows(Exception.class, () -> { + Topic topic = topicRepository.findById(topicB.getId()).orElse(null); + topicRepository.delete(topic); + }); + } + } } -} +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/admin/topic/service/TopicFacadeTest.java b/src/test/java/com/genius/gitget/admin/topic/service/TopicFacadeTest.java new file mode 100644 index 00000000..d1723c54 --- /dev/null +++ b/src/test/java/com/genius/gitget/admin/topic/service/TopicFacadeTest.java @@ -0,0 +1,139 @@ +package com.genius.gitget.admin.topic.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.dto.TopicCreateRequest; +import com.genius.gitget.topic.dto.TopicDetailResponse; +import com.genius.gitget.topic.dto.TopicUpdateRequest; +import com.genius.gitget.topic.serviceFacade.TopicFacade; +import jakarta.transaction.Transactional; +import org.assertj.core.api.Assertions; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +@Transactional +public class TopicFacadeTest { + Topic topicA, topicB; + String fileType; + + @Autowired + TopicFacade topicFacade; + + @BeforeEach + public void setup() { + topicA = Topic.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(100) + .build(); + + topicB = Topic.builder() + .title("1일 2알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE, CS") + .pointPerPerson(300) + .build(); + + fileType = "topic"; + } + + private TopicCreateRequest getTopicCreateRequest() { + return TopicCreateRequest.builder() + .title(topicA.getTitle()) + .description(topicA.getDescription()) + .tags(topicA.getTags()) + .pointPerPerson(topicA.getPointPerPerson()) + .notice(topicA.getNotice()) + .build(); + } + + private TopicUpdateRequest getTopicUpdateRequest(String title, String description, String tags, int pointPerPersion, + String notice) { + return TopicUpdateRequest.builder() + .title(title) + .description(description) + .tags(tags) + .pointPerPerson(pointPerPersion) + .notice(notice).build(); + } + + @Nested + @DisplayName("토픽 생성 메서드는") + class Describe_topic_create { + + @Nested + @DisplayName("topicCreateRequestDto가 들어오면") + class Context_with_a_topicCreateRequestDto { + + @Test + @DisplayName("토픽을 생성한다.") + public void it_returns_2XX_if_the_topic_was_created_successfully() { + TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); + + Long savedTopicId = topicFacade.create(topicCreateRequest); + + TopicDetailResponse topicById = topicFacade.findOne(savedTopicId); + + Assertions.assertThat(topicById.title()).isEqualTo(topicCreateRequest.title()); + } + } + } + + @Nested + @DisplayName("토픽 수정 메서드는") + class Describe_topic_update { + + @Nested + @DisplayName("TopicUpdateRequestDto가 들어오면") + class Context_with_a_TopicUpdateRequestDto { + + @Test + @DisplayName("토픽 내용을 수정한다.") + public void it_returns_2XX_if_the_topic_is_modified() { + TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); + Long savedTopicId = topicFacade.create(topicCreateRequest); + + TopicUpdateRequest topicUpdateRequest = getTopicUpdateRequest("1일 5커밋", topicA.getDescription(), + topicA.getTags(), topicA.getPointPerPerson(), topicA.getNotice()); + + topicFacade.update(savedTopicId, topicUpdateRequest); + + TopicDetailResponse findTopic = topicFacade.findOne(savedTopicId); + Assertions.assertThat(findTopic.title()).isEqualTo("1일 5커밋"); + } + } + } + + @Nested + @DisplayName("토픽 삭제 메서드는") + class Describe_topic_delete { + + @Nested + @DisplayName("삭제할 토픽 Id가 주어질 때") + class Context_with_a_TopicUpdateRequestDto { + + @Test + @DisplayName("해당 토픽을 삭제한다.") + public void it_returns_2XX_if_the_topic_is_successfully_deleted() throws Exception { + TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); + Long savedTopicId = topicFacade.create(topicCreateRequest); + + topicFacade.delete(savedTopicId); + + try { + topicFacade.findOne(savedTopicId); + } catch (BusinessException e) { + assertEquals("해당 토픽을 찾을 수 없습니다.", e.getMessage()); + } + } + } + } +} diff --git a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java b/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java deleted file mode 100644 index 1ad86ab3..00000000 --- a/src/test/java/com/genius/gitget/admin/topic/service/TopicServiceTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.genius.gitget.admin.topic.service; - -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.dto.TopicCreateRequest; -import com.genius.gitget.admin.topic.dto.TopicDetailResponse; -import com.genius.gitget.admin.topic.dto.TopicUpdateRequest; -import com.genius.gitget.admin.topic.repository.TopicRepository; -import com.genius.gitget.global.util.exception.BusinessException; -import jakarta.transaction.Transactional; -import java.util.Optional; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; - -@SpringBootTest -@Transactional -@Rollback -public class TopicServiceTest { - Topic topic, topicA; - String fileType; - @Autowired - TopicService topicService; - @Autowired - TopicRepository topicRepository; - - @BeforeEach - public void setup() { - topic = Topic.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .build(); - - topicA = Topic.builder() - .title("1일 2알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(300) - .build(); - - fileType = "topic"; - } - - @Test - public void 토픽_생성() throws Exception { - //given - TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); - - Long savedTopicId = topicService.createTopic(topicCreateRequest); - - //when - TopicDetailResponse topicById = topicService.getTopicById(savedTopicId); - - //then - Assertions.assertThat(topicById.title()).isEqualTo(topicCreateRequest.title()); - } - - @Test - public void 토픽_수정() throws Exception { - //given - TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); - Long savedTopicId = topicService.createTopic(topicCreateRequest); - - //when - TopicUpdateRequest topicUpdateRequest = TopicUpdateRequest.builder() - .title("1일 5커밋") - .description(topic.getDescription()) - .tags(topic.getTags()) - .pointPerPerson(topic.getPointPerPerson()) - .notice(topic.getNotice()).build(); - - topicService.updateTopic(savedTopicId, topicUpdateRequest); - - //then - Optional findTopic = topicRepository.findById(savedTopicId); - Topic findUpdatedTopic = findTopic.get(); - Assertions.assertThat(findUpdatedTopic.getTitle()).isEqualTo("1일 5커밋"); - } - - @Test - public void 토픽_삭제() throws Exception { - //given - TopicCreateRequest topicCreateRequest = getTopicCreateRequest(); - Long savedTopicId = topicService.createTopic(topicCreateRequest); - - //when - topicService.deleteTopic(savedTopicId); - - //then - try { - topicService.getTopicById(savedTopicId); - } catch (BusinessException e) { - org.junit.jupiter.api.Assertions.assertEquals("해당 토픽을 찾을 수 없습니다.", e.getMessage()); - } - } - - private TopicCreateRequest getTopicCreateRequest() { - return TopicCreateRequest.builder() - .title(topic.getTitle()) - .description(topic.getDescription()) - .tags(topic.getTags()) - .pointPerPerson(topic.getPointPerPerson()) - .notice(topic.getNotice()) - .build(); - } -} diff --git a/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java b/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java index c7dfa4f0..4a53a3a4 100644 --- a/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java @@ -5,8 +5,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; diff --git a/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java b/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java index 03776d0e..5030c4b6 100644 --- a/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java @@ -2,8 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; diff --git a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java index 66e5626c..cc041148 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java @@ -8,8 +8,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index 6c61ed1b..74493ecb 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -4,8 +4,8 @@ import static com.genius.gitget.challenge.instance.domain.Progress.DONE; import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.instance.service.InstanceSearchService; diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java index 2f6fb164..1f94bbfb 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java @@ -1,7 +1,7 @@ package com.genius.gitget.challenge.instance.service; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java index 2e085a3a..b2a2a082 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java @@ -3,8 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; diff --git a/src/test/java/com/genius/gitget/challenge/likes/LikesTest.java b/src/test/java/com/genius/gitget/challenge/likes/LikesTest.java index ac8bf0cf..5d306842 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/LikesTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/LikesTest.java @@ -4,8 +4,8 @@ import static com.genius.gitget.challenge.user.domain.Role.USER; import static com.genius.gitget.global.security.constants.ProviderInfo.GOOGLE; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; diff --git a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java index 151fa76f..8e0b4ca1 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java @@ -9,8 +9,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; diff --git a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java index f3a8e294..52880407 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java @@ -3,8 +3,8 @@ import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; diff --git a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java index cb76358d..8c56692d 100644 --- a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java +++ b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java @@ -7,7 +7,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; diff --git a/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java index 4b91807d..cec5771c 100644 --- a/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java +++ b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java @@ -4,7 +4,7 @@ import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.likes.repository.LikesRepository; import com.genius.gitget.challenge.likes.service.LikesService; diff --git a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java index eb91d8a7..46a2d58c 100644 --- a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java +++ b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java @@ -8,8 +8,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; diff --git a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java index 0e19d837..6974a374 100644 --- a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java +++ b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java @@ -3,10 +3,10 @@ import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.genius.gitget.admin.signout.Signout; -import com.genius.gitget.admin.signout.SignoutRepository; -import com.genius.gitget.admin.topic.domain.Topic; -import com.genius.gitget.admin.topic.repository.TopicRepository; +import com.genius.gitget.signout.Signout; +import com.genius.gitget.signout.SignoutRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; From c36bd15127e236228c5adc000d271d3f04c77a5f Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:00:34 +0900 Subject: [PATCH 206/234] =?UTF-8?q?[REFACTOR]=20JWT=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#223)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: JwtService에 인터페이스 적용 - JwtService에 interface 적용 및 구현 클래스 적용 - Cookie의 sameSite 옵션을 strict로 설정 * refactor: JWT 요청 API의 Request DTO 변경 - /api/auth JWT 요청 API의 Request DTO를 의도에 맞도록 수정(TokenRequest) * refactor: 필요없는 구문 삭제 * refactor: JwtService의 의존 관계 변경 - JwtService의 의존관계를 TokenRepository에서 TokenService로 변경 - TokenService에 필요한 메서드 추가 * refactor: tokenService를 활용하는 코드로 리팩토링 - tokenRepository를 활용하는 코드에서 tokenService를 활용하는 코드로 변경 * refactor: Facade 패턴 적용 * feat: Access token 처리 방식 변경 - Access token의 저장 위치를 Authorization header로 변경 - Access token의 생성/resolve 방식 변경 - Access token의 정상 작동 확인 - 불필요한 로직 제거 필요 - JWT 테스트 코드 변경 필요 * refactor: enum 이름 변경 - JWT 관련 enum 상수 이름 변경 * test: JWT 처리 방식 변경에 의한 테스트 코드 변경 - JWT 중 Access token을 Header에 저장하는 것으로 바꾸면서 Controller 및 JWT 관련 테스트 코드 정상 작동하도록 수정 * fix: JWT 에러 메세지 변경 및 버그 픽스 - JWT 관련 에러 메세지를 세부적으로 나눔 - Refresh token의 탈취 여부를 확인하는 로직 버그 픽스 - Access token 추출 시, 빈 문자열일 때에도 예외를 발생하도록 변경 * test: JwtFacade 테스트 코드에 DCI 패턴 적용 * test: TokenService 테스트 코드에 DCI 패턴 적용 - TokenService 테스트 코드에 DCI 패턴 적용 - JWT 관련 예외 메세지 추가 * feat: JWT 처리 도중 Exception 발생 시, logout하도록 처리 - JWT에서 예외 발생 시 처리하는 ExceptionHandlerFilter에서 JWT 관련 Exception 발생 시, Logout 로직이 실행되도록 처리 * fix: logout 처리 위치 변경 * fix: custom header가 전달되지 않는 버그 픽스 * fix: 브라우저에서 Authorization 헤더 접근이 안되는 버그 픽스 - CorsConfigurationSource에 .setExposedHeaders 옵션 설정 * feat: Access token 재발급 여부 관련 헤더 추가 - Access token 재발급 여부에 따라 token-reissued에 값 설정 * fix: 브라우저에서 token-reissued 헤더 접근이 안되는 버그 픽스 * refactor: 불필요한 로직 제거 - ExceptionHandlerFilter에서 불필요한 로직 제거 * refactor: Facade 구현체 이름 통일 - Facade 구현체 이름을 FacadeImpl에서 FacadeService로 통일 --- .../gitget/challenge/user/domain/User.java | 4 + .../config/CustomCorsConfigurationSource.java | 7 + .../security/config/SecurityConfig.java | 6 +- .../global/security/constants/JwtRule.java | 13 +- .../security/controller/AuthController.java | 24 +- .../global/security/dto/TokenRequest.java | 6 + .../filter/ExceptionHandlerFilter.java | 4 +- .../filter/JwtAuthenticationFilter.java | 24 +- .../service/CustomUserDetailsService.java | 5 +- .../global/security/service/JwtFacade.java | 28 ++ ...{JwtService.java => JwtFacadeService.java} | 113 ++++---- .../global/security/service/JwtGenerator.java | 2 +- .../global/security/service/JwtUtil.java | 15 +- .../global/security/service/TokenService.java | 18 +- .../global/util/exception/ErrorCode.java | 5 +- .../topic/controller/TopicControllerTest.java | 8 +- .../CertificationControllerTest.java | 8 +- .../InstanceHomeControllerTest.java | 2 +- .../controller/InstanceControllerTest.java | 12 +- .../likes/controller/LikesControllerTest.java | 14 +- .../security/config/SecurityConfigTest.java | 4 +- .../service/JwtFacadeServiceTest.java | 274 ++++++++++++++++++ .../security/service/JwtServiceTest.java | 253 ---------------- .../global/security/service/JwtUtilTest.java | 2 +- .../security/service/TokenServiceTest.java | 116 ++++---- .../controller/PaymentControllerTest.java | 10 +- .../controller/ProfileControllerTest.java | 24 +- .../com/genius/gitget/util/TokenTestUtil.java | 36 ++- 28 files changed, 584 insertions(+), 453 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/security/dto/TokenRequest.java create mode 100644 src/main/java/com/genius/gitget/global/security/service/JwtFacade.java rename src/main/java/com/genius/gitget/global/security/service/{JwtService.java => JwtFacadeService.java} (63%) create mode 100644 src/test/java/com/genius/gitget/global/security/service/JwtFacadeServiceTest.java delete mode 100644 src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index aefd1ec9..b339a91d 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -116,6 +116,10 @@ public long updatePoints(Long amount) { return this.point; } + public boolean isRegistered() { + return this.role != Role.NOT_REGISTERED; + } + @Override public Optional getFiles() { return Optional.ofNullable(this.files); diff --git a/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java b/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java index 635a69d2..3c7d475f 100644 --- a/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java +++ b/src/main/java/com/genius/gitget/global/security/config/CustomCorsConfigurationSource.java @@ -1,5 +1,8 @@ package com.genius.gitget.global.security.config; +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_HEADER; +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_REISSUED_HEADER; + import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; @@ -24,6 +27,10 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { config.setAllowedMethods(ALLOWED_METHODS); config.setAllowCredentials(true); config.setAllowedHeaders(Collections.singletonList("*")); + + config.setExposedHeaders(Collections.singletonList(ACCESS_HEADER.getValue())); + config.addExposedHeader(ACCESS_REISSUED_HEADER.getValue()); + config.setMaxAge(3600L); return config; } diff --git a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java index 2bd56770..80a5eae7 100644 --- a/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java +++ b/src/main/java/com/genius/gitget/global/security/config/SecurityConfig.java @@ -7,7 +7,7 @@ import com.genius.gitget.global.security.handler.OAuth2FailureHandler; import com.genius.gitget.global.security.handler.OAuth2SuccessHandler; import com.genius.gitget.global.security.service.CustomOAuth2UserService; -import com.genius.gitget.global.security.service.JwtService; +import com.genius.gitget.global.security.service.JwtFacade; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -32,7 +32,7 @@ public class SecurityConfig { private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"}; private final CustomCorsConfigurationSource customCorsConfigurationSource; private final CustomOAuth2UserService customOAuthService; - private final JwtService jwtService; + private final JwtFacade jwtFacade; private final UserService userService; private final OAuth2SuccessHandler successHandler; private final OAuth2FailureHandler failureHandler; @@ -57,7 +57,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // JWT 검증 필터 추가 - .addFilterBefore(new JwtAuthenticationFilter(jwtService, userService), + .addFilterBefore(new JwtAuthenticationFilter(jwtFacade, userService), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class) diff --git a/src/main/java/com/genius/gitget/global/security/constants/JwtRule.java b/src/main/java/com/genius/gitget/global/security/constants/JwtRule.java index 8892419d..0492a016 100644 --- a/src/main/java/com/genius/gitget/global/security/constants/JwtRule.java +++ b/src/main/java/com/genius/gitget/global/security/constants/JwtRule.java @@ -6,10 +6,15 @@ @RequiredArgsConstructor @Getter public enum JwtRule { - JWT_ISSUE_HEADER("Set-Cookie"), - JWT_RESOLVE_HEADER("Cookie"), - ACCESS_PREFIX("access"), - REFRESH_PREFIX("refresh"); + + ACCESS_HEADER("Authorization"), + ACCESS_PREFIX("Bearer "), + ACCESS_REISSUED_HEADER("token-reissued"), + + REFRESH_PREFIX("refresh"), + + REFRESH_ISSUE("Set-Cookie"), + REFRESH_RESOLVE("Cookie"); private final String value; } diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index d3377ede..9640b41f 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -1,13 +1,15 @@ package com.genius.gitget.global.security.controller; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.security.dto.AuthResponse; -import com.genius.gitget.global.security.dto.SignupResponse; -import com.genius.gitget.global.security.service.JwtService; +import com.genius.gitget.global.security.dto.TokenRequest; +import com.genius.gitget.global.security.service.JwtFacade; +import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import jakarta.servlet.http.HttpServletResponse; @@ -27,18 +29,20 @@ @RequestMapping("/api") public class AuthController { private final UserService userService; - private final JwtService jwtService; + private final JwtFacade jwtFacade; @PostMapping("/auth") public ResponseEntity> generateToken(HttpServletResponse response, - @RequestBody SignupResponse tokenRequest) { - User requestUser = userService.findUserByIdentifier(tokenRequest.identifier()); - jwtService.validateUser(requestUser); + @RequestBody TokenRequest tokenRequest) { + User user = userService.findUserByIdentifier(tokenRequest.identifier()); + if (!user.isRegistered()) { + throw new BusinessException(NOT_AUTHENTICATED_USER); + } - jwtService.generateAccessToken(response, requestUser); - jwtService.generateRefreshToken(response, requestUser); + jwtFacade.generateAccessToken(response, user); + jwtFacade.generateRefreshToken(response, user); - AuthResponse authResponse = userService.getUserAuthInfo(requestUser.getIdentifier()); + AuthResponse authResponse = userService.getUserAuthInfo(user.getIdentifier()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), authResponse) @@ -49,7 +53,7 @@ public ResponseEntity> generateToken(HttpServletRes public ResponseEntity logout( @AuthenticationPrincipal UserPrincipal userPrincipal, HttpServletResponse response) { - jwtService.logout(userPrincipal.getUser(), response); + jwtFacade.logout(response, userPrincipal.getUser().getIdentifier()); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/global/security/dto/TokenRequest.java b/src/main/java/com/genius/gitget/global/security/dto/TokenRequest.java new file mode 100644 index 00000000..d3a0f485 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/dto/TokenRequest.java @@ -0,0 +1,6 @@ +package com.genius.gitget.global.security.dto; + +public record TokenRequest( + String identifier +) { +} diff --git a/src/main/java/com/genius/gitget/global/security/filter/ExceptionHandlerFilter.java b/src/main/java/com/genius/gitget/global/security/filter/ExceptionHandlerFilter.java index 46226654..4e648f7c 100644 --- a/src/main/java/com/genius/gitget/global/security/filter/ExceptionHandlerFilter.java +++ b/src/main/java/com/genius/gitget/global/security/filter/ExceptionHandlerFilter.java @@ -8,18 +8,20 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.filter.OncePerRequestFilter; @Slf4j +@RequiredArgsConstructor public class ExceptionHandlerFilter extends OncePerRequestFilter { + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { filterChain.doFilter(request, response); } catch (BusinessException e) { - log.error("[ERROR]" + e.getMessage(), e); setErrorResponse(response, e); } } diff --git a/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java index e9054f65..d1e6778e 100644 --- a/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java @@ -2,10 +2,9 @@ import static com.genius.gitget.global.security.config.SecurityConfig.PERMITTED_URI; -import com.genius.gitget.global.security.constants.JwtRule; -import com.genius.gitget.global.security.service.JwtService; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.security.service.JwtFacade; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -19,7 +18,7 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtService jwtService; + private final JwtFacade jwtFacade; private final UserService userService; @Override @@ -32,26 +31,27 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - String accessToken = jwtService.resolveTokenFromCookie(request, JwtRule.ACCESS_PREFIX); - if (jwtService.validateAccessToken(accessToken)) { + String accessToken = jwtFacade.resolveAccessToken(request); + if (jwtFacade.validateAccessToken(accessToken)) { setAuthenticationToContext(accessToken); filterChain.doFilter(request, response); return; } - String refreshToken = jwtService.resolveTokenFromCookie(request, JwtRule.REFRESH_PREFIX); + String refreshToken = jwtFacade.resolveRefreshToken(request); User user = findUserByRefreshToken(refreshToken); - if (jwtService.validateRefreshToken(refreshToken, user.getIdentifier())) { - String reissuedAccessToken = jwtService.generateAccessToken(response, user); - jwtService.generateRefreshToken(response, user); + if (jwtFacade.validateRefreshToken(refreshToken, user.getIdentifier())) { + String reissuedAccessToken = jwtFacade.generateAccessToken(response, user); + jwtFacade.generateRefreshToken(response, user); + jwtFacade.setReissuedHeader(response); setAuthenticationToContext(reissuedAccessToken); filterChain.doFilter(request, response); return; } - jwtService.logout(user, response); + jwtFacade.logout(response, user.getIdentifier()); } private boolean isPermittedURI(String requestURI) { @@ -63,12 +63,12 @@ private boolean isPermittedURI(String requestURI) { } private User findUserByRefreshToken(String refreshToken) { - String identifier = jwtService.getIdentifierFromRefresh(refreshToken); + String identifier = jwtFacade.getIdentifierFromRefresh(refreshToken); return userService.findUserByIdentifier(identifier); } private void setAuthenticationToContext(String accessToken) { - Authentication authentication = jwtService.getAuthentication(accessToken); + Authentication authentication = jwtFacade.getAuthentication(accessToken); SecurityContextHolder.getContext().setAuthentication(authentication); } } diff --git a/src/main/java/com/genius/gitget/global/security/service/CustomUserDetailsService.java b/src/main/java/com/genius/gitget/global/security/service/CustomUserDetailsService.java index 3d69a7bc..782d2699 100644 --- a/src/main/java/com/genius/gitget/global/security/service/CustomUserDetailsService.java +++ b/src/main/java/com/genius/gitget/global/security/service/CustomUserDetailsService.java @@ -2,14 +2,13 @@ import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; -import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.exception.BusinessException; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service @@ -18,7 +17,7 @@ public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + public UserDetails loadUserByUsername(String username) { User user = userRepository.findById(Long.valueOf(username)) .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); diff --git a/src/main/java/com/genius/gitget/global/security/service/JwtFacade.java b/src/main/java/com/genius/gitget/global/security/service/JwtFacade.java new file mode 100644 index 00000000..b1e64bb3 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/service/JwtFacade.java @@ -0,0 +1,28 @@ +package com.genius.gitget.global.security.service; + +import com.genius.gitget.challenge.user.domain.User; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; + +public interface JwtFacade { + String generateAccessToken(HttpServletResponse response, User user); + + String generateRefreshToken(HttpServletResponse response, User user); + + String resolveAccessToken(HttpServletRequest request); + + String resolveRefreshToken(HttpServletRequest request); + + String getIdentifierFromRefresh(String refreshToken); + + boolean validateAccessToken(String accessToken); + + boolean validateRefreshToken(String refreshToken, String identifier); + + void setReissuedHeader(HttpServletResponse response); + + void logout(HttpServletResponse response, String identifier); + + Authentication getAuthentication(String accessToken); +} diff --git a/src/main/java/com/genius/gitget/global/security/service/JwtService.java b/src/main/java/com/genius/gitget/global/security/service/JwtFacadeService.java similarity index 63% rename from src/main/java/com/genius/gitget/global/security/service/JwtService.java rename to src/main/java/com/genius/gitget/global/security/service/JwtFacadeService.java index b8c9315a..17c761b4 100644 --- a/src/main/java/com/genius/gitget/global/security/service/JwtService.java +++ b/src/main/java/com/genius/gitget/global/security/service/JwtFacadeService.java @@ -1,17 +1,16 @@ package com.genius.gitget.global.security.service; +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_HEADER; import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.gitget.global.security.constants.JwtRule.JWT_ISSUE_HEADER; +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_REISSUED_HEADER; +import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_ISSUE; import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; -import static com.genius.gitget.global.util.exception.ErrorCode.JWT_TOKEN_NOT_FOUND; -import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; +import static com.genius.gitget.global.util.exception.ErrorCode.JWT_NOT_FOUND_IN_COOKIE; +import static com.genius.gitget.global.util.exception.ErrorCode.JWT_NOT_FOUND_IN_HEADER; -import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.global.security.constants.JwtRule; import com.genius.gitget.global.security.constants.TokenStatus; import com.genius.gitget.global.security.domain.Token; -import com.genius.gitget.global.security.repository.TokenRepository; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import io.jsonwebtoken.Jwts; @@ -31,54 +30,53 @@ @Service @Transactional(readOnly = true) @Slf4j -public class JwtService { +public class JwtFacadeService implements JwtFacade { private final CustomUserDetailsService customUserDetailsService; + private final TokenService tokenService; private final JwtGenerator jwtGenerator; private final JwtUtil jwtUtil; - private final TokenRepository tokenRepository; private final Key ACCESS_SECRET_KEY; private final Key REFRESH_SECRET_KEY; private final long ACCESS_EXPIRATION; private final long REFRESH_EXPIRATION; - public JwtService(CustomUserDetailsService customUserDetailsService, JwtGenerator jwtGenerator, - JwtUtil jwtUtil, TokenRepository tokenRepository, - @Value("${jwt.access-secret}") String ACCESS_SECRET_KEY, - @Value("${jwt.refresh-secret}") String REFRESH_SECRET_KEY, - @Value("${jwt.access-expiration}") long ACCESS_EXPIRATION, - @Value("${jwt.refresh-expiration}") long REFRESH_EXPIRATION) { + public JwtFacadeService(CustomUserDetailsService customUserDetailsService, + TokenService tokenService, + JwtGenerator jwtGenerator, JwtUtil jwtUtil, + @Value("${jwt.access-secret}") String ACCESS_SECRET_KEY, + @Value("${jwt.refresh-secret}") String REFRESH_SECRET_KEY, + @Value("${jwt.access-expiration}") long ACCESS_EXPIRATION, + @Value("${jwt.refresh-expiration}") long REFRESH_EXPIRATION) { this.customUserDetailsService = customUserDetailsService; + this.tokenService = tokenService; this.jwtGenerator = jwtGenerator; this.jwtUtil = jwtUtil; - this.tokenRepository = tokenRepository; this.ACCESS_SECRET_KEY = jwtUtil.getSigningKey(ACCESS_SECRET_KEY); this.REFRESH_SECRET_KEY = jwtUtil.getSigningKey(REFRESH_SECRET_KEY); this.ACCESS_EXPIRATION = ACCESS_EXPIRATION; this.REFRESH_EXPIRATION = REFRESH_EXPIRATION; } - public void validateUser(User requestUser) { - if (requestUser.getRole() == Role.NOT_REGISTERED) { - throw new BusinessException(NOT_AUTHENTICATED_USER); - } - } + @Override public String generateAccessToken(HttpServletResponse response, User requestUser) { String accessToken = jwtGenerator.generateAccessToken(ACCESS_SECRET_KEY, ACCESS_EXPIRATION, requestUser); - ResponseCookie cookie = setTokenToCookie(ACCESS_PREFIX.getValue(), accessToken, ACCESS_EXPIRATION / 1000); - response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); + String bearer = ACCESS_PREFIX.getValue() + accessToken; + response.setHeader(ACCESS_HEADER.getValue(), bearer); + response.setHeader(ACCESS_REISSUED_HEADER.getValue(), "False"); return accessToken; } + @Override @Transactional public String generateRefreshToken(HttpServletResponse response, User requestUser) { String refreshToken = jwtGenerator.generateRefreshToken(REFRESH_SECRET_KEY, REFRESH_EXPIRATION, requestUser); ResponseCookie cookie = setTokenToCookie(REFRESH_PREFIX.getValue(), refreshToken, REFRESH_EXPIRATION / 1000); - response.addHeader(JWT_ISSUE_HEADER.getValue(), cookie.toString()); + response.addHeader(REFRESH_ISSUE.getValue(), cookie.toString()); - tokenRepository.save(new Token(requestUser.getIdentifier(), refreshToken)); + tokenService.save(new Token(requestUser.getIdentifier(), refreshToken)); return refreshToken; } @@ -87,46 +85,48 @@ private ResponseCookie setTokenToCookie(String tokenPrefix, String token, long m .path("/") .maxAge(maxAgeSeconds) .httpOnly(true) - .sameSite("None") + .sameSite("Strict") .secure(true) .build(); } + @Override public boolean validateAccessToken(String token) { return jwtUtil.getTokenStatus(token, ACCESS_SECRET_KEY) == TokenStatus.AUTHENTICATED; } + @Override public boolean validateRefreshToken(String token, String identifier) { boolean isRefreshValid = jwtUtil.getTokenStatus(token, REFRESH_SECRET_KEY) == TokenStatus.AUTHENTICATED; + boolean isHijacked = tokenService.isRefreshHijacked(identifier, token); - Token storedToken = tokenRepository.findByIdentifier(identifier); - boolean isTokenMatched = storedToken.getToken().equals(token); - - return isRefreshValid && isTokenMatched; + return isRefreshValid && !isHijacked; } - public String resolveTokenFromCookie(HttpServletRequest request, JwtRule tokenPrefix) { - Cookie[] cookies = request.getCookies(); - if (cookies == null) { - throw new BusinessException(JWT_TOKEN_NOT_FOUND); - } - return jwtUtil.resolveTokenFromCookie(cookies, tokenPrefix); + @Override + public void setReissuedHeader(HttpServletResponse response) { + response.setHeader(ACCESS_REISSUED_HEADER.getValue(), "True"); } - public Authentication getAuthentication(String token) { - UserDetails principal = customUserDetailsService.loadUserByUsername(getUserPk(token, ACCESS_SECRET_KEY)); - return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities()); + @Override + public String resolveAccessToken(HttpServletRequest request) { + String bearerHeader = request.getHeader(ACCESS_HEADER.getValue()); + if (bearerHeader == null || bearerHeader.isEmpty()) { + throw new BusinessException(JWT_NOT_FOUND_IN_HEADER); + } + return bearerHeader.trim().substring(7); } - private String getUserPk(String token, Key secretKey) { - return Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody() - .getSubject(); + @Override + public String resolveRefreshToken(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies == null) { + throw new BusinessException(JWT_NOT_FOUND_IN_COOKIE); + } + return jwtUtil.resolveTokenFromCookie(cookies, REFRESH_PREFIX); } + @Override public String getIdentifierFromRefresh(String refreshToken) { try { return Jwts.parserBuilder() @@ -140,13 +140,26 @@ public String getIdentifierFromRefresh(String refreshToken) { } } - public void logout(User requestUser, HttpServletResponse response) { - tokenRepository.deleteById(requestUser.getIdentifier()); + @Override + public Authentication getAuthentication(String accessToken) { + UserDetails principal = customUserDetailsService.loadUserByUsername(getUserPk(accessToken, ACCESS_SECRET_KEY)); + return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities()); + } + + private String getUserPk(String token, Key secretKey) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + } - Cookie accessCookie = jwtUtil.resetToken(ACCESS_PREFIX); - Cookie refreshCookie = jwtUtil.resetToken(REFRESH_PREFIX); + @Override + public void logout(HttpServletResponse response, String identifier) { + tokenService.deleteById(identifier); - response.addCookie(accessCookie); + Cookie refreshCookie = jwtUtil.resetCookie(REFRESH_PREFIX); response.addCookie(refreshCookie); } } diff --git a/src/main/java/com/genius/gitget/global/security/service/JwtGenerator.java b/src/main/java/com/genius/gitget/global/security/service/JwtGenerator.java index 4c2fed46..c17d6d4d 100644 --- a/src/main/java/com/genius/gitget/global/security/service/JwtGenerator.java +++ b/src/main/java/com/genius/gitget/global/security/service/JwtGenerator.java @@ -10,8 +10,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -@Component @Slf4j +@Component public class JwtGenerator { public String generateAccessToken(final Key ACCESS_SECRET, final long ACCESS_EXPIRATION, User user) { diff --git a/src/main/java/com/genius/gitget/global/security/service/JwtUtil.java b/src/main/java/com/genius/gitget/global/security/service/JwtUtil.java index b11d7801..c499af54 100644 --- a/src/main/java/com/genius/gitget/global/security/service/JwtUtil.java +++ b/src/main/java/com/genius/gitget/global/security/service/JwtUtil.java @@ -2,6 +2,7 @@ import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_EXPIRED_JWT; import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.gitget.global.util.exception.ErrorCode.JWT_NOT_FOUND_IN_COOKIE; import com.genius.gitget.global.security.constants.JwtRule; import com.genius.gitget.global.security.constants.TokenStatus; @@ -15,15 +16,11 @@ import java.security.Key; import java.util.Arrays; import java.util.Base64; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.stereotype.Component; @Slf4j -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor +@Component public class JwtUtil { public TokenStatus getTokenStatus(String token, Key secretKey) { @@ -33,7 +30,7 @@ public TokenStatus getTokenStatus(String token, Key secretKey) { .build() .parseClaimsJws(token); return TokenStatus.AUTHENTICATED; - } catch (ExpiredJwtException | IllegalArgumentException e) { + } catch (ExpiredJwtException e) { log.error(INVALID_EXPIRED_JWT.getMessage()); return TokenStatus.EXPIRED; } catch (JwtException e) { @@ -46,7 +43,7 @@ public String resolveTokenFromCookie(Cookie[] cookies, JwtRule tokenPrefix) { .filter(cookie -> cookie.getName().equals(tokenPrefix.getValue())) .findFirst() .map(Cookie::getValue) - .orElse(""); + .orElseThrow(() -> new BusinessException(JWT_NOT_FOUND_IN_COOKIE)); } public Key getSigningKey(String secretKey) { @@ -58,7 +55,7 @@ private String encodeToBase64(String secretKey) { return Base64.getEncoder().encodeToString(secretKey.getBytes()); } - public Cookie resetToken(JwtRule tokenPrefix) { + public Cookie resetCookie(JwtRule tokenPrefix) { Cookie cookie = new Cookie(tokenPrefix.getValue(), null); cookie.setMaxAge(0); cookie.setPath("/"); diff --git a/src/main/java/com/genius/gitget/global/security/service/TokenService.java b/src/main/java/com/genius/gitget/global/security/service/TokenService.java index 4b4126c6..ccd2ef54 100644 --- a/src/main/java/com/genius/gitget/global/security/service/TokenService.java +++ b/src/main/java/com/genius/gitget/global/security/service/TokenService.java @@ -16,15 +16,23 @@ public class TokenService { private final TokenRepository tokenRepository; - public Token findTokenByIdentifier(String identifier) { + @Transactional + public String save(Token token) { + Token savedToken = tokenRepository.save(token); + return savedToken.getIdentifier(); + } + + public Token findByIdentifier(String identifier) { return tokenRepository.findById(identifier) - .orElseThrow(() -> new BusinessException(ErrorCode.JWT_TOKEN_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(ErrorCode.JWT_NOT_FOUND_IN_DB)); } public boolean isRefreshHijacked(String identifier, String refreshToken) { - Token token = findTokenByIdentifier(identifier); - return token.getToken().equals(refreshToken); + Token token = findByIdentifier(identifier); + return !token.getToken().equals(refreshToken); } - + public void deleteById(String identifier) { + tokenRepository.deleteById(identifier); + } } diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index f5e1c600..50631286 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -42,7 +42,10 @@ public enum ErrorCode { INVALID_JWT(HttpStatus.BAD_REQUEST, "JWT가 유효하지 않습니다."), INVALID_PROGRESS(HttpStatus.BAD_REQUEST, "존재하지 않는 정보입니다."), - JWT_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "Cookie에 토큰이 존재하지 않습니다."), + JWT_NOT_FOUND_IN_DB(HttpStatus.NOT_FOUND, "DB에 JWT 정보가 존재하지 않습니다."), + JWT_NOT_FOUND_IN_HEADER(HttpStatus.NOT_FOUND, "Header에 JWT 정보가 존재하지 않습니다."), + JWT_NOT_FOUND_IN_COOKIE(HttpStatus.NOT_FOUND, "Cookie에 JWT 정보가 존재하지 않습니다."), + REFRESH_TOKEN_NOT_MATCH(HttpStatus.BAD_REQUEST, "DB에 저장되어 있는 Refresh token과 일치하지 않습니다."), MULTIPART_FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "MultipartFile이 전달되지 않았습니다."), FILE_NOT_EXIST(HttpStatus.BAD_REQUEST, "해당 파일(이미지)이 존재하지 않습니다."), diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java index fbc14eb4..d9713ddb 100644 --- a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java @@ -58,7 +58,7 @@ public void setup() { Topic savedTopic = getSavedTopic(); Long id = savedTopic.getId(); - mockMvc.perform(get("/api/admin/topic/" + id).cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/admin/topic/" + id).headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.title").value("title")); @@ -74,7 +74,7 @@ public void setup() { mockMvc.perform(get("/api/admin/topic") .contentType(MediaType.APPLICATION_JSON) - .cookie(tokenTestUtil.createAccessCookie())) + .headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.numberOfElements").value(3)) @@ -91,7 +91,7 @@ public void setup() { Topic savedTopic = getSavedTopic(); Long id = savedTopic.getId(); - mockMvc.perform(delete("/api/admin/topic/" + id).cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(delete("/api/admin/topic/" + id).headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.numberOfElements").doesNotExist()); @@ -106,7 +106,7 @@ public void setup() { Topic savedTopic = getSavedTopic(); Long id = savedTopic.getId(); - mockMvc.perform(delete("/api/admin/topic/" + id + 1).cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(delete("/api/admin/topic/" + id + 1).headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().is4xxClientError()); } diff --git a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java index 37c61370..a87c8088 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java @@ -74,7 +74,7 @@ public void should_saveToken_when_tokenValid() throws Exception { //then mockMvc.perform(post("/api/certification/register/token") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(requestBody)) .andExpect(status().is2xxSuccessful()); @@ -93,7 +93,7 @@ public void should_throwException_when_unregisteredUser() throws Exception { @WithMockCustomUser(role = Role.NOT_REGISTERED) public void should_throwException_when_JWTNonExist() throws Exception { mockMvc.perform(post("/api/certification/register/token") - .cookie(tokenTestUtil.createAccessCookie())) + .headers(tokenTestUtil.createAccessHeaders())) .andExpect(status().is4xxClientError()); } @@ -106,7 +106,7 @@ public void should_throwException_when_accountIncorrect() throws Exception { //when & then mockMvc.perform(post("/api/certification/register/token") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(requestBody)) .andExpect(status().is4xxClientError()); @@ -125,7 +125,7 @@ public void should_saveToken_when_repositoryValid() throws Exception { //then mockMvc.perform(get("/api/certification/verify/repository?repo=" + targetRepo) - .cookie(tokenTestUtil.createAccessCookie())) + .headers(tokenTestUtil.createAccessHeaders())) .andExpect(status().is2xxSuccessful()); } diff --git a/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java b/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java index 4a53a3a4..73d1918f 100644 --- a/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java @@ -58,7 +58,7 @@ public void should_returnInstances_when_passUserTags() throws Exception { //when & then mockMvc.perform(get("/api/challenges/recommend") - .cookie(tokenTestUtil.createAccessCookie())) + .headers(tokenTestUtil.createAccessHeaders())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.numberOfElements").value(3)); } diff --git a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java index cc041148..4a35f7b3 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java @@ -67,7 +67,7 @@ public void setup() { @WithMockCustomUser(role = Role.ADMIN) @DisplayName("인스턴스 리스트 조회를 요청하면, 상태코드 200반환과 함께 인스턴스 리스트를 반환한다.") public void 인스턴스_리스트_조회() throws Exception { - mockMvc.perform(get("/api/admin/instance").cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/admin/instance").headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.numberOfElements").value(2)); @@ -79,7 +79,7 @@ public void setup() { public void 특정_토픽에_대한_리스트_조회_1() throws Exception { Long id = savedTopic1.getId(); - mockMvc.perform(get("/api/admin/topic/instances/" + id).cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/admin/topic/instances/" + id).headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.numberOfElements").value(2)) @@ -93,7 +93,7 @@ public void setup() { public void 특정_토픽에_대한_리스트_조회_2() throws Exception { Long id = savedTopic2.getId(); - mockMvc.perform(get("/api/admin/topic/instances/" + id).cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/admin/topic/instances/" + id).headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("data.numberOfElements").value(0)); @@ -107,7 +107,7 @@ public void setup() { Long instanceId = savedInstance2.getId(); - mockMvc.perform(get("/api/admin/instance/" + instanceId).cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/admin/instance/" + instanceId).headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("data.topicId").value(topicId)) @@ -120,7 +120,7 @@ public void setup() { public void 인스턴스_삭제_성공() throws Exception { Long instanceId = savedInstance1.getId(); - mockMvc.perform(delete("/api/admin/instance/" + instanceId).cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(delete("/api/admin/instance/" + instanceId).headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.numberOfElements").doesNotExist()); @@ -134,7 +134,7 @@ public void setup() { public void 인스턴스_삭제_성공_실패() throws Exception { Long instanceId = savedInstance1.getId(); - mockMvc.perform(delete("/api/admin/instance/" + instanceId + 1).cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(delete("/api/admin/instance/" + instanceId + 1).headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().is4xxClientError()); } diff --git a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java index 8e0b4ca1..24267550 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java @@ -94,7 +94,7 @@ public void setup() { likesService.addLikes(user, "kimdozzi", savedInstance1.getId()); mockMvc.perform(get("/api/profile/likes") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) @@ -109,7 +109,7 @@ public void setup() { public void 좋아요_목록_조회_성공_2() throws Exception { mockMvc.perform(get("/api/profile/likes") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(jsonPath("$.data.numberOfElements").value(0)) @@ -129,7 +129,7 @@ public void setup() { .build(); mockMvc.perform(post("/api/profile/likes") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -148,7 +148,7 @@ public void setup() { .build(); mockMvc.perform(post("/api/profile/likes") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -167,7 +167,7 @@ public void setup() { .build(); mockMvc.perform(post("/api/profile/likes") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .content(objectMapper.writeValueAsString(request))) .andDo(print()) .andExpect(status().is4xxClientError()); @@ -187,7 +187,7 @@ public void setup() { Long id = likes.getId(); mockMvc.perform(delete("/api/profile/likes/" + id) - .cookie(tokenTestUtil.createAccessCookie())) + .headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()); @@ -205,7 +205,7 @@ public void setup() { .build()); mockMvc.perform(delete("/api/profile/likes/" + 2) - .cookie(tokenTestUtil.createAccessCookie())) + .headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().is4xxClientError()); } diff --git a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java index f36cf928..c1ad59d9 100644 --- a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java +++ b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java @@ -61,7 +61,7 @@ public void should_status2xx_when_roleIsAdmin() throws Exception { //when & then mockMvc.perform(get("/api/admin/topic") - .cookie(tokenTestUtil.createAccessCookie())) + .headers(tokenTestUtil.createAccessHeaders())) .andExpect(status().is2xxSuccessful()); } @@ -70,7 +70,7 @@ public void should_status2xx_when_roleIsAdmin() throws Exception { @WithMockCustomUser(role = Role.USER) public void should_status4xx_when_roleNotAdmin() throws Exception { mockMvc.perform(get("/api/admin/topic") - .cookie(tokenTestUtil.createAccessCookie())) + .headers(tokenTestUtil.createAccessHeaders())) .andExpect(status().is4xxClientError()); } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/global/security/service/JwtFacadeServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/JwtFacadeServiceTest.java new file mode 100644 index 00000000..d365ac80 --- /dev/null +++ b/src/test/java/com/genius/gitget/global/security/service/JwtFacadeServiceTest.java @@ -0,0 +1,274 @@ +package com.genius.gitget.global.security.service; + +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_HEADER; +import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; +import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; +import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_JWT; +import static com.genius.gitget.global.util.exception.ErrorCode.JWT_NOT_FOUND_IN_COOKIE; +import static com.genius.gitget.global.util.exception.ErrorCode.JWT_NOT_FOUND_IN_HEADER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.security.domain.Token; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.security.repository.TokenRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import jakarta.servlet.http.Cookie; +import java.util.Collection; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.AfterEach; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Slf4j +@ActiveProfiles({"jwt"}) +class JwtFacadeServiceTest { + User user; + MockHttpServletRequest request; + MockHttpServletResponse response; + + @Autowired + private TokenRepository tokenRepository; + @Autowired + private JwtFacade jwtFacade; + @Autowired + private UserRepository userRepository; + + @BeforeEach + void setUp() { + user = userRepository.save(User.builder() + .providerInfo(ProviderInfo.GITHUB) + .nickname("nickname") + .identifier("identifier") + .role(Role.USER) + .tags("interest1,interest2") + .information("information") + .build()); + response = new MockHttpServletResponse(); + request = new MockHttpServletRequest(); + } + + @AfterEach + void clearMongo() { + tokenRepository.deleteAll(); + } + + + @Nested + @DisplayName("JWT 생성 시") + class describe_create_jwt { + @Nested + @DisplayName("사용자의 정보를 전달하면") + class context_pass_user_info { + @Test + @DisplayName("Access token을 생성하여 Authorization 헤더에 담는다.") + public void it_returns_headers_that_contain_access() { + String accessToken = jwtFacade.generateAccessToken(response, user); + Collection headerNames = response.getHeaderNames(); + + assertThat(headerNames).contains(ACCESS_HEADER.getValue()); + assertThat(response.getHeader(ACCESS_HEADER.getValue())).contains(accessToken); + } + + @Test + @DisplayName("Refresh token을 생성하여 Cookie에 담는다.") + public void it_returns_cookie_that_contain_refresh() { + String refreshToken = jwtFacade.generateRefreshToken(response, user); + Cookie cookie = response.getCookies()[0]; + + assertThat(cookie.getValue()).isEqualTo(refreshToken); + assertThat(cookie.getSecure()).isTrue(); + assertThat(cookie.getPath()).isEqualTo("/"); + } + } + } + + @Nested + @DisplayName("JWT 유효성 확인 시") + class describe_validate_jwt { + @Nested + @DisplayName("Access token을 전달한 경우") + class context_pass_access { + @Test + @DisplayName("유효기간이 만료되지 않았고, 토큰이 유효하다면 true를 반환한다.") + public void it_returns_true_when_token_not_expired_and_valid() { + String accessToken = jwtFacade.generateAccessToken(response, user); + boolean isAccessValid = jwtFacade.validateAccessToken(accessToken); + assertThat(isAccessValid).isTrue(); + } + + @Test + @DisplayName("토큰이 유효하지 않는다면 BusinessException 예외를 발생한다.") + public void it_throws_BusinessException_when_token_invalid() { + String accessToken = "invalid access token"; + assertThatThrownBy(() -> jwtFacade.validateAccessToken(accessToken)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INVALID_JWT.getMessage()); + } + } + + @Nested + @DisplayName("Refresh token을 전달한 경우") + class context_pass_refresh { + @Test + @DisplayName("토큰이 유효하고, DB에 저장된 토큰과 같다면 true를 반환한다.") + public void it_returns_true_when_token_valid_and_stored_db() { + String refreshToken = jwtFacade.generateRefreshToken(response, user); + boolean isRefreshValid = jwtFacade.validateRefreshToken(refreshToken, user.getIdentifier()); + assertThat(isRefreshValid).isTrue(); + } + + @Test + @DisplayName("토큰이 유효하지 않는다면 BusinessException 예외를 발생한다.") + public void it_throws_BusinessException_when_token_invalid() { + String refreshToken = "invalid refresh token"; + assertThatThrownBy(() -> jwtFacade.validateRefreshToken(refreshToken, user.getIdentifier())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INVALID_JWT.getMessage()); + } + + @Test + @DisplayName("DB에 저장된 토큰과 같지 않다면 false를 반환한다.") + public void it_returns_false_when_not_match_db() { + String refreshToken = jwtFacade.generateRefreshToken(response, user); + tokenRepository.save(new Token(user.getIdentifier(), "invalid refresh token")); + + boolean isRefreshValid = jwtFacade.validateRefreshToken(refreshToken, user.getIdentifier()); + assertThat(isRefreshValid).isFalse(); + } + } + } + + @Nested + @DisplayName("HttpServletRequest로부터") + class describe_from_HttpServletRequest { + @Nested + @DisplayName("access token을 추출하는 경우") + class context_resolve_access { + @Test + @DisplayName("Authorization 헤더에 유효한 토큰이 있는 경우 access token을 반환한다.") + public void it_returns_access_token() { + String accessToken = jwtFacade.generateAccessToken(response, user); + request.addHeader(ACCESS_HEADER.getValue(), ACCESS_PREFIX.getValue() + accessToken); + + String resolvedAccessToken = jwtFacade.resolveAccessToken(request); + assertThat(accessToken).isEqualTo(resolvedAccessToken); + } + + @Test + @DisplayName("Authorization 헤더에 빈 문자열이 있는 경우 BusinessException 예외가 발생한다.") + public void it_throws_BusinessException_when_authorization_is_empty() { + jwtFacade.generateAccessToken(response, user); + request.addHeader(ACCESS_HEADER.getValue(), ""); + assertThatThrownBy(() -> jwtFacade.resolveAccessToken(request)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(JWT_NOT_FOUND_IN_HEADER.getMessage()); + } + + @Test + @DisplayName("Authorization 헤더가 null인 경우 BusinessException 예외가 발생한다.") + public void it_throws_BusinessException_when_authorization_is_null() { + assertThatThrownBy(() -> jwtFacade.resolveAccessToken(request)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(JWT_NOT_FOUND_IN_HEADER.getMessage()); + } + } + + @Nested + @DisplayName("refresh token을 추출하는 경우") + class context_resolve_refresh { + @Test + @DisplayName("Cookie에 유효한 토큰이 있는 경우 Refresh token을 반환한다.") + public void it_returns_refresh_token() { + String refreshToken = jwtFacade.generateRefreshToken(response, user); + + request.setCookies(new Cookie(REFRESH_PREFIX.getValue(), refreshToken)); + String resolvedRefreshToken = jwtFacade.resolveRefreshToken(request); + assertThat(refreshToken).isEqualTo(resolvedRefreshToken); + } + + @Test + @DisplayName("Cookie에 refresh 토큰이 없는 경우 BusinessException 예외가 발생한다.") + public void it_throws_businessException_when_token_not_exist() { + assertThatThrownBy(() -> jwtFacade.resolveRefreshToken(request)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(JWT_NOT_FOUND_IN_COOKIE.getMessage()); + } + } + } + + @Nested + @DisplayName("사용자의 식별자를 확인 시") + class describe_check_user_identifier { + @Nested + @DisplayName("Refresh token을 전달했을 때") + class context_pass_refresh_token { + @Test + @DisplayName("토큰이 유효하다면 사용자의 identifier를 반환한다.") + public void it_returns_identifier_when_refresh_valid() { + String refreshToken = jwtFacade.generateRefreshToken(response, user); + String identifier = jwtFacade.getIdentifierFromRefresh(refreshToken); + assertThat(user.getIdentifier()).isEqualTo(identifier); + } + + @Test + @DisplayName("토큰이 유효하지 않으면 BusinessException 예외가 발생한다.") + public void it_throws_businessException_when_refresh_invalid() { + String invalidRefreshToken = "invalid refresh token"; + assertThatThrownBy(() -> jwtFacade.getIdentifierFromRefresh(invalidRefreshToken)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INVALID_JWT.getMessage()); + } + } + } + + @Nested + @DisplayName("SecurityContext에 저장할 객체를 받으려 할 때") + class describe_try_to_get_authentication { + @Nested + @DisplayName("Access token을 전달한 경우") + class context_pass_access_token { + @Test + @DisplayName("Authentication를 반환받을 수 있다.") + public void it_returns_identifier() { + String accessToken = jwtFacade.generateAccessToken(response, user); + Authentication authentication = jwtFacade.getAuthentication(accessToken); + + String identifier = ((UserPrincipal) authentication.getPrincipal()).getUser().getIdentifier(); + assertThat(identifier).isEqualTo(user.getIdentifier()); + } + } + } + + @Nested + @DisplayName("로그아웃 요청을 받았을 때") + class describe_logout { + @Nested + @DisplayName("사용자의 식별자 정보를 전달하면") + class context_pass_identifier { + @Test + @DisplayName("cookie를 비우고, DB의 토큰 정보도 삭제한다.") + public void it_clear_cookie_and_db() { + jwtFacade.generateRefreshToken(response, user); + jwtFacade.logout(response, user.getIdentifier()); + + assertThat(tokenRepository.findById(user.getIdentifier())).isNotPresent(); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java deleted file mode 100644 index bbc6dfc3..00000000 --- a/src/test/java/com/genius/gitget/global/security/service/JwtServiceTest.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.genius.gitget.global.security.service; - -import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; -import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; -import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_JWT; -import static com.genius.gitget.global.util.exception.ErrorCode.JWT_TOKEN_NOT_FOUND; -import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.JwtRule; -import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.global.security.repository.TokenRepository; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; -import jakarta.servlet.http.Cookie; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest -@Transactional -@Slf4j -@ActiveProfiles({"jwt"}) -class JwtServiceTest { - @Autowired - private TokenRepository tokenRepository; - @Autowired - private JwtService jwtService; - @Autowired - private UserRepository userRepository; - @Autowired - private TokenTestUtil tokenTestUtil; - - @AfterEach - void clearMongo() { - tokenRepository.deleteAll(); - } - - @Test - @DisplayName("사용자 정보를 받아서 access-token을 생성할 수 있다.") - public void should_generateAccess_when_passUserInfo() { - //given - User user = getSavedUser(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - //when - String accessToken = jwtService.generateAccessToken(response, user); - Cookie cookie = response.getCookies()[0]; - - //then - assertThat(cookie.getValue()).isEqualTo(accessToken); - assertThat(cookie.getSecure()).isTrue(); - assertThat(cookie.getPath()).isEqualTo("/"); - } - - @Test - @DisplayName("사용자 정보를 받아서 유효한 refresh-token를 생성할 수 있다.") - public void should_generateRefresh_when_passUserInfo() { - //given - User user = getSavedUser(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - //when - String refreshToken = jwtService.generateRefreshToken(response, user); - Cookie cookie = response.getCookies()[0]; - - //then - assertThat(cookie.getValue()).isEqualTo(refreshToken); - assertThat(cookie.getSecure()).isTrue(); - assertThat(cookie.getPath()).isEqualTo("/"); - } - - @Test - @DisplayName("생성한 access-token이 유효하다면 true를 반환한다.") - public void should_returnTrue_when_accessTokenIsValid() { - //given - User user = getSavedUser(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - //when - String accessToken = jwtService.generateAccessToken(response, user); - boolean isValid = jwtService.validateAccessToken(accessToken); - - //then - assertThat(isValid).isTrue(); - } - - @Test - @DisplayName("생성한 access-token가 유효하지 않다면 false를 반환한다.") - public void should_returnFalse_when_accessTokenIsInvalid() { - //given - - //when - String accessToken = "fake access token"; - - //then - assertThatThrownBy(() -> jwtService.validateAccessToken(accessToken)) - .isInstanceOf(BusinessException.class); - } - - @Test - @DisplayName("Cookie에서 access-token을 추출할 수 있다.") - public void should_extractAccessToken_when_passTokenType() { - //given - User user = getSavedUser(); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - //when - String accessToken = jwtService.generateAccessToken(response, user); - - request.setCookies(new Cookie(ACCESS_PREFIX.getValue(), accessToken)); - String resolvedToken = jwtService.resolveTokenFromCookie(request, ACCESS_PREFIX); - - //then - assertThat(accessToken).isEqualTo(resolvedToken); - } - - @Test - @DisplayName("Cookie에서 refresh-token을 추출할 수 있다.") - public void should_extractRefreshToken_when_passTokenType() { - //given - User user = getSavedUser(); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - //when - String refreshToken = jwtService.generateRefreshToken(response, user); - - request.setCookies(new Cookie(REFRESH_PREFIX.getValue(), refreshToken)); - String resolvedToken = jwtService.resolveTokenFromCookie(request, REFRESH_PREFIX); - - //then - assertThat(refreshToken).isEqualTo(resolvedToken); - } - - @Test - @DisplayName("Access-token은 없고 유효한 Refresh-token만 있을 때, Access-token을 추출한다면 \"\"을 반환해야 한다.") - @WithMockCustomUser - public void should_returnBlank_whenOnlyValidRefreshToken() { - //given - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setCookies(tokenTestUtil.createRefreshCookie()); - - //when - JwtRule accessTokenPrefix = ACCESS_PREFIX; - String resolvedToken = jwtService.resolveTokenFromCookie(request, accessTokenPrefix); - - //then - assertThat(resolvedToken).isEqualTo(""); - } - - @Test - @DisplayName("Cookie에 아무런 JWT 토큰이 존재하지 않는다면 예외를 발생해야 한다.") - public void should_throwException_when_noTokens() { - //given - MockHttpServletRequest request = new MockHttpServletRequest(); - - //when - JwtRule refreshTokenPrefix = REFRESH_PREFIX; - - //then - assertThatThrownBy(() -> jwtService.resolveTokenFromCookie(request, refreshTokenPrefix)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(JWT_TOKEN_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("사용자가 아직 가입하지 않은 회원이 JWT 발급을 요청한다면, 예외를 발생시킨다.") - public void should_throwException_when_userIsNotRegistered() { - //given - User user = getUnregisteredUser(); - - //when & then - assertThatThrownBy(() -> jwtService.validateUser(user)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(NOT_AUTHENTICATED_USER.getMessage()); - } - - @Test - @DisplayName("Refresh-token으로부터 사용자 식별 정보인 Identifier를 받아올 수 있다.") - @WithMockCustomUser(identifier = "testIdentifier") - public void should_getIdentifier_when_passRefreshToken() { - //given - String refreshToken = tokenTestUtil.createRefreshToken(); - - //when - String identifier = jwtService.getIdentifierFromRefresh(refreshToken); - - //then - assertThat(identifier).isEqualTo("testIdentifier"); - } - - @ParameterizedTest(name = "Refresh-token: {0}") - @DisplayName("Refresh-token이 빈 문자열이거나, 유효하지 않은 문자열이라면 예외가 발생해야 한다.") - @ValueSource(strings = {"invalid refresh token", ""}) - public void should_throwException_when_refreshTokenInvalid(String invalidRefreshToken) { - //given - - //when&then - assertThatThrownBy(() -> jwtService.getIdentifierFromRefresh(invalidRefreshToken)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.INVALID_JWT.getMessage()); - } - - @Test - @DisplayName("Refresh-token이 유효하지 않는다면 예외가 발생한다.") - @WithMockCustomUser - public void should_throwException_when_refreshTokenInvalid() { - //given - String refreshToken = "invalid refresh token"; - - //when - assertThatThrownBy(() -> jwtService.validateRefreshToken(refreshToken, "identifier")) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(INVALID_JWT.getMessage()); - } - - - private User getSavedUser() { - return userRepository.save(User.builder() - .providerInfo(ProviderInfo.GITHUB) - .nickname("nickname") - .identifier("identifier") - .role(Role.USER) - .tags("interest1,interest2") - .information("information") - .build()); - } - - private User getUnregisteredUser() { - return userRepository.save(User.builder() - .providerInfo(ProviderInfo.GITHUB) - .identifier("identifier") - .role(Role.NOT_REGISTERED) - .build()); - } -} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/global/security/service/JwtUtilTest.java b/src/test/java/com/genius/gitget/global/security/service/JwtUtilTest.java index 43b8c2f0..07163db9 100644 --- a/src/test/java/com/genius/gitget/global/security/service/JwtUtilTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/JwtUtilTest.java @@ -22,7 +22,7 @@ public void should_returnResetCookie() { //given //when - Cookie cookie = jwtUtil.resetToken(JwtRule.ACCESS_PREFIX); + Cookie cookie = jwtUtil.resetCookie(JwtRule.REFRESH_PREFIX); //then assertThat(cookie.getMaxAge()).isEqualTo(0); diff --git a/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java b/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java index b2cc263d..22aa3a9e 100644 --- a/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java +++ b/src/test/java/com/genius/gitget/global/security/service/TokenServiceTest.java @@ -1,11 +1,16 @@ package com.genius.gitget.global.security.service; +import static com.genius.gitget.global.util.exception.ErrorCode.JWT_NOT_FOUND_IN_DB; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.genius.gitget.global.security.domain.Token; import com.genius.gitget.global.security.repository.TokenRepository; +import com.genius.gitget.global.util.exception.BusinessException; import org.junit.jupiter.api.AfterEach; +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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -14,74 +19,83 @@ @SpringBootTest @Transactional class TokenServiceTest { + private String identifier = "identifier"; + private String refreshToken = "refresh token example"; + private Token token; + @Autowired private TokenService tokenService; @Autowired private TokenRepository tokenRepository; + + @BeforeEach + void setup() { + token = tokenRepository.save(new Token(identifier, refreshToken)); + } + @AfterEach void clearMongo() { tokenRepository.deleteAll(); } - @Test - @DisplayName("특정 리프레시 토큰을 identifier를 통해 DB에서 값을 조회할 수 있어야 한다.") - public void should_findToken_when_findByIdentifier() { - //given - String identifier = "SSung023"; - String refreshToken = "refresh token example"; - Token token = Token.builder() - .identifier(identifier) - .token(refreshToken) - .build(); - - //when - Token savedToken = tokenRepository.save(token); - Token tokenByIdentifier = tokenService.findTokenByIdentifier(identifier); - - //then - assertThat(savedToken.getIdentifier()).isEqualTo(tokenByIdentifier.getIdentifier()); - assertThat(savedToken.getToken()).isEqualTo(tokenByIdentifier.getToken()); - + @Nested + @DisplayName("DB에 저장되어 있는 Token 객체를 찾으려 할 때") + class describe_find_stored_token { + @Nested + @DisplayName("사용자의 식별자(identifier)를 전달하면") + class context_pass_identifier { + @Test + @DisplayName("저장되어 있던 Token 객체를 반환받을 수 있다.") + public void it_returns_stored_Token() { + Token byIdentifier = tokenService.findByIdentifier(identifier); + assertThat(byIdentifier.getIdentifier()).isEqualTo(identifier); + assertThat(byIdentifier.getToken()).isEqualTo(refreshToken); + } + } } - @Test - @DisplayName("리프레시 토큰 요청이 들어왔을 때, identifier-token 짝이 맞게 저장되어 있으면 true를 반환한다.") - public void should_returnTrue_when_tokenValid() { - //given - String identifier = "SSung023"; - String refreshToken = "refresh token example"; - Token token = Token.builder() - .identifier(identifier) - .token(refreshToken) - .build(); + @Nested + @DisplayName("Refresh token 탈취 여부를 확인할 때") + class describe_check_hijack { + @Nested + @DisplayName("사용자의 식별자와 요청받은 토큰을 전달하면") + class context_pass_identifier_and_token { + @Test + @DisplayName("저장되어 있던 토큰와 같으면 false를 반환한다.") + public void it_returns_false_token_same() { + boolean refreshHijacked = tokenService.isRefreshHijacked(identifier, refreshToken); + assertThat(refreshHijacked).isFalse(); + } - //when - tokenRepository.save(token); - boolean isRefreshHijacked = tokenService.isRefreshHijacked(identifier, refreshToken); + @Test + @DisplayName("저장되어 있던 토큰과 다르다면 true를 반환한다.") + public void it_returns_true_token_different() { + String fakeRefreshToken = "fake refresh token"; + tokenRepository.save(new Token(identifier, fakeRefreshToken)); - //then - assertThat(isRefreshHijacked).isTrue(); + boolean refreshHijacked = tokenService.isRefreshHijacked(identifier, refreshToken); + assertThat(refreshHijacked).isTrue(); + } + } } - @Test - @DisplayName("리프레시 토큰 요청이 들어왔을 때, identifier-token 짝이 맞게 저장되어 있으면 true를 반환한다.") - public void should_returnFalse_when_tokenInvalid() { - //given - String identifier = "SSung023"; - String refreshToken = "refresh token example"; - String fakeRefreshToken = "fake refresh token example"; - Token token = Token.builder() - .identifier(identifier) - .token(refreshToken) - .build(); - - //when - tokenRepository.save(token); - boolean isRefreshHijacked = tokenService.isRefreshHijacked(identifier, fakeRefreshToken); + @Nested + @DisplayName("저장되어 있던 토큰을 삭제하고자 할 때") + class describe_delete_token { + @Nested + @DisplayName("사용자의 식별자를 전달하면") + class context_pass_user_identifier { + @Test + @DisplayName("저장되어 있는 토큰을 삭제한다.") + public void it_delete_stored_token() { + tokenService.deleteById(identifier); - //then - assertThat(isRefreshHijacked).isFalse(); + assertThatThrownBy(() -> tokenService.findByIdentifier(identifier)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(JWT_NOT_FOUND_IN_DB.getMessage()); + } + } } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java index 8c56692d..1fcf0b4f 100644 --- a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java +++ b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java @@ -55,7 +55,7 @@ public void setup() { @DisplayName("결제 내역 조회를 요청하면, 상태코드 200을 반환한다.") public void 결제_내역_조회_성공() throws Exception { - mockMvc.perform(get("/api/payment").cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/payment").headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()); } @@ -71,7 +71,7 @@ public void setup() { input.put("pointAmount", 100L); input.put("userEmail", "kimdozzi"); - mockMvc.perform(post("/api/payment/toss").cookie(tokenTestUtil.createAccessCookie()) + mockMvc.perform(post("/api/payment/toss").headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(input))) .andDo(print()) @@ -88,7 +88,7 @@ public void setup() { input.put("pointAmount", 100L); input.put("userEmail", "test@gmail.com"); - mockMvc.perform(post("/api/payment/toss").cookie(tokenTestUtil.createAccessCookie()) + mockMvc.perform(post("/api/payment/toss").headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(input))) .andDo(print()) @@ -100,7 +100,7 @@ public void setup() { @DisplayName("결제 요청을 실패하면, 상태코드 4xx을 반환한다.") public void 결제_요청_실패_2() throws Exception { - mockMvc.perform(post("/api/payment/toss").cookie(tokenTestUtil.createAccessCookie()) + mockMvc.perform(post("/api/payment/toss").headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().is4xxClientError()); @@ -116,7 +116,7 @@ public void setup() { input.put("pointAmount", 100L); input.put("userEmail", "test@gmail.com"); - mockMvc.perform(post("/api/payment/toss").cookie(tokenTestUtil.createAccessCookie()) + mockMvc.perform(post("/api/payment/toss").headers(tokenTestUtil.createAccessHeaders()) .content(objectMapper.writeValueAsString(input))) .andDo(print()) .andExpect(status().is4xxClientError()); diff --git a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java index 46a2d58c..dba3d842 100644 --- a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java +++ b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java @@ -92,7 +92,7 @@ public void setup() { @DisplayName("사용자 상세 정보 조회에 성공하면, 상태 코드 200을 반환한다.") public void 사용자_상세_정보_조회_성공() throws Exception { - mockMvc.perform(get("/api/profile").cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/profile").headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()); } @@ -102,7 +102,7 @@ public void setup() { @DisplayName("사용자 상세 정보 조회 시 같은 사용자 정보가 있으면 실패하고, 4xx(IncorrectResultSizeDataAccessException)를 반환한다.") public void 사용자_상세_정보_조회_실패() throws Exception { User user = getSavedUser(); - mockMvc.perform(get("/api/profile").cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/profile").headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().is4xxClientError()); } @@ -123,7 +123,7 @@ public void setup() { input.put("userId", id); mockMvc.perform(post("/api/profile") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(input))) .andDo(print()) @@ -145,7 +145,7 @@ public void setup() { input.put("userId", id + 1); mockMvc.perform(post("/api/profile") - .cookie(tokenTestUtil.createAccessCookie()) + .headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(input))) .andDo(print()) @@ -157,7 +157,7 @@ public void setup() { @WithMockCustomUser(identifier = "kimdozzi") @DisplayName("사용자 관심사 조회에 성공하면, 상태 코드 200을 반환한다.") public void 사용자_관심사_조회_성공() throws Exception { - mockMvc.perform(get("/api/profile/interest").cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/profile/interest").headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()); } @@ -171,7 +171,7 @@ public void setup() { Map> input = new HashMap<>(); input.put("tags", new ArrayList<>(Arrays.asList("FE", "BE", "ML"))); - mockMvc.perform(post("/api/profile/interest").cookie(tokenTestUtil.createAccessCookie()) + mockMvc.perform(post("/api/profile/interest").headers(tokenTestUtil.createAccessHeaders()) .content(objectMapper.writeValueAsString(input)) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) @@ -196,7 +196,7 @@ public void setup() { Map> input = new HashMap<>(); input.put("tags", new ArrayList<>(Arrays.asList("FE", "BE", "ML"))); - mockMvc.perform(post("/api/profile/interest").cookie(tokenTestUtil.createAccessCookie()) + mockMvc.perform(post("/api/profile/interest").headers(tokenTestUtil.createAccessHeaders()) .content(objectMapper.writeValueAsString(input))) .andDo(print()) .andExpect(status().is4xxClientError()); @@ -207,7 +207,7 @@ public void setup() { @DisplayName("사용자 관심사 수정에 실패하면, 상태 코드 4xx을 반환한다.") public void 사용자_관심사_수정_실패_2() throws Exception { - mockMvc.perform(post("/api/profile/interest").cookie(tokenTestUtil.createAccessCookie()) + mockMvc.perform(post("/api/profile/interest").headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().is4xxClientError()); @@ -219,7 +219,7 @@ public void setup() { @WithMockCustomUser(identifier = "kimdozzi") @DisplayName("사용자 챌린지 현황 조회에 성공하면, 상태 코드 200을 반환한다.") public void 사용자_챌린지_현황_성공() throws Exception { - mockMvc.perform(get("/api/profile/challenges").cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/profile/challenges").headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()); } @@ -233,7 +233,7 @@ public void setup() { Map input = new HashMap<>(); input.put("reason", "이용이 불편해서"); - mockMvc.perform(delete("/api/profile").cookie(tokenTestUtil.createAccessCookie()) + mockMvc.perform(delete("/api/profile").headers(tokenTestUtil.createAccessHeaders()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(input))) .andDo(print()) @@ -244,7 +244,7 @@ public void setup() { @WithMockCustomUser(identifier = "kimdozzi") @DisplayName("사용자 탈퇴 사유없이 탈퇴를 요청하면 실패하고, 상태 코드 4xx을 반환한다.") public void 사용자_탈퇴_실패() throws Exception { - mockMvc.perform(delete("/api/profile").cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(delete("/api/profile").headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().is4xxClientError()); } @@ -254,7 +254,7 @@ public void setup() { @WithMockCustomUser(identifier = "kimdozzi") @DisplayName("사용자 포인트 조회에 성공하면, 상태 코드 200을 반환한다.") public void 사용자_포인트_조회_성공() throws Exception { - mockMvc.perform(get("/api/profile/point").cookie(tokenTestUtil.createAccessCookie())) + mockMvc.perform(get("/api/profile/point").headers(tokenTestUtil.createAccessHeaders())) .andDo(print()) .andExpect(status().isOk()); } diff --git a/src/test/java/com/genius/gitget/util/TokenTestUtil.java b/src/test/java/com/genius/gitget/util/TokenTestUtil.java index 16d987ae..44d30ade 100644 --- a/src/test/java/com/genius/gitget/util/TokenTestUtil.java +++ b/src/test/java/com/genius/gitget/util/TokenTestUtil.java @@ -3,31 +3,51 @@ import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; -import com.genius.gitget.global.security.domain.UserPrincipal; -import com.genius.gitget.global.security.service.JwtService; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.security.constants.JwtRule; +import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.security.service.JwtFacadeService; import jakarta.servlet.http.Cookie; +import java.util.Collections; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; @Component @RequiredArgsConstructor public class TokenTestUtil { - private final JwtService jwtService; + private final JwtFacadeService jwtFacade; - public Cookie createAccessCookie() { + public Cookie createAccessHeader() { UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() .getPrincipal(); User user = userPrincipal.getUser(); MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); - String accessCookie = jwtService.generateAccessToken(httpServletResponse, user); + String accessCookie = jwtFacade.generateAccessToken(httpServletResponse, user); return new Cookie(ACCESS_PREFIX.getValue(), accessCookie); } + public HttpHeaders createAccessHeaders() { + UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() + .getPrincipal(); + User user = userPrincipal.getUser(); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + String accessToken = jwtFacade.generateAccessToken(httpServletResponse, user); + String bearerAccess = ACCESS_PREFIX.getValue() + accessToken; + + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.put(JwtRule.ACCESS_HEADER.getValue(), Collections.singletonList(bearerAccess)); + return HttpHeaders.readOnlyHttpHeaders(headers); + } + public String createAccessToken() { UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication() .getPrincipal(); @@ -35,7 +55,7 @@ public String createAccessToken() { MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); - return jwtService.generateAccessToken(httpServletResponse, user); + return jwtFacade.generateAccessToken(httpServletResponse, user); } public Cookie createRefreshCookie() { @@ -45,7 +65,7 @@ public Cookie createRefreshCookie() { MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); - String refreshCookie = jwtService.generateRefreshToken(httpServletResponse, user); + String refreshCookie = jwtFacade.generateRefreshToken(httpServletResponse, user); return new Cookie(REFRESH_PREFIX.getValue(), refreshCookie); } @@ -56,6 +76,6 @@ public String createRefreshToken() { MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); - return jwtService.generateRefreshToken(httpServletResponse, user); + return jwtFacade.generateRefreshToken(httpServletResponse, user); } } From 2645179047cffd4994ecb85ffa3e8e7e5cd6f3d3 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:22:01 +0900 Subject: [PATCH 207/234] =?UTF-8?q?[FIX]=20=EC=8B=A4=ED=8C=A8=ED=95=9C=20?= =?UTF-8?q?=EC=B1=8C=EB=A6=B0=EC=A7=80=20=EA=B2=BD=EC=9A=B0=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=EC=B1=8C=EB=A6=B0=EC=A7=80=EC=9D=98=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=ED=83=AD=EC=97=90=20=EB=82=98=ED=83=80=EB=82=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=ED=94=BD?= =?UTF-8?q?=EC=8A=A4=20(#230)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 마이챌린지 완료탭에 실패한 챌린지가 뜨지 않는 버그 픽스 - ParticipantRepository에서 JoinStatus=YES로 설정되어 있어, 도중에 챌린지 참여 취소한 경우(실패 챌린지), 마이 챌린지의 완료 탭에 뜨지 않은 버그 픽스 - 완료한 챌린지에 실패한 챌린지도 검색되게끔 변경 * test: 실패한 챌린지 목록 조회 테스트 코드 작성 --- .../service/MyChallengeService.java | 2 +- .../repository/ParticipantRepository.java | 7 +- .../service/ParticipantProvider.java | 14 +++- .../service/ParticipantProviderTest.java | 66 +++++++++++++++++++ 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index 88210647..a9467d9c 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -71,7 +71,7 @@ public List getPreActivityInstances(User user, LocalDate ta public List getDoneInstances(User user, LocalDate targetDate) { List done = new ArrayList<>(); - List participants = participantProvider.findJoinedByProgress(user.getId(), Progress.DONE); + List participants = participantProvider.findDoneInstances(user.getId()); for (Participant participant : participants) { Instance instance = participant.getInstance(); diff --git a/src/main/java/com/genius/gitget/challenge/participant/repository/ParticipantRepository.java b/src/main/java/com/genius/gitget/challenge/participant/repository/ParticipantRepository.java index 50ccee77..41160945 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/repository/ParticipantRepository.java +++ b/src/main/java/com/genius/gitget/challenge/participant/repository/ParticipantRepository.java @@ -17,9 +17,10 @@ public interface ParticipantRepository extends JpaRepository Optional findByJoinInfo(@Param("userId") Long userId, @Param("instanceId") Long instanceId); - @Query("select p from Participant p where p.user.id = :userId and p.instance.progress = :progress and p.joinStatus = 'YES'") - List findAllJoinedByProgress(@Param("userId") Long userId, - @Param("progress") Progress progress); + @Query("select p from Participant p where p.user.id = :userId and p.instance.progress = :progress and p.joinStatus = :joinStatus") + List findAllByStatus(@Param("userId") Long userId, + @Param("progress") Progress progress, + @Param("joinStatus") JoinStatus joinStatus); @Query("select p from Participant p where p.instance.id = :instanceId and p.joinStatus = :joinStatus") Slice findAllByInstanceId(@Param("instanceId") Long instanceId, diff --git a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java index 97d963bf..a8241f21 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java +++ b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java @@ -8,6 +8,7 @@ import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.repository.ParticipantRepository; import com.genius.gitget.global.util.exception.BusinessException; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -61,7 +62,18 @@ public Instance getInstanceById(Long participantId) { } public List findJoinedByProgress(Long userId, Progress progress) { - return participantRepository.findAllJoinedByProgress(userId, progress); + return participantRepository.findAllByStatus(userId, progress, JoinStatus.YES); + } + + public List findDoneInstances(Long userId) { + List doneInstances = new ArrayList<>(); + // 실패한 챌린지 리스트 + doneInstances.addAll(participantRepository.findAllByStatus(userId, Progress.ACTIVITY, JoinStatus.NO)); + + // 성공한 챌린지 리스트 + doneInstances.addAll(participantRepository.findAllByStatus(userId, Progress.DONE, JoinStatus.YES)); + + return doneInstances; } public boolean hasJoinedParticipant(Long userId, Long instanceId) { diff --git a/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java b/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java index 65712780..821817ba 100644 --- a/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java @@ -1,6 +1,7 @@ package com.genius.gitget.challenge.participant.service; import static com.genius.gitget.challenge.instance.domain.Progress.ACTIVITY; +import static com.genius.gitget.challenge.instance.domain.Progress.DONE; import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; import static org.assertj.core.api.Assertions.assertThat; @@ -88,6 +89,63 @@ public void should_returnList_when_passProgress() { assertThat(participants.size()).isEqualTo(2); } + @Test + @DisplayName("Participant들 중, ACTIVITY에 해당하는 정보들을 불러올 수 있다.") + public void should_return_activity_participants() { + //given + User user = getSavedUser(); + Instance instance1 = getSavedInstance(PREACTIVITY); + Instance instance2 = getSavedInstance(PREACTIVITY); + Instance instance3 = getSavedInstance(ACTIVITY); + Participant participant1 = getSavedParticipant(user, instance1); + Participant participant2 = getSavedParticipant(user, instance2); + Participant participant3 = getSavedParticipant(user, instance3); + + //when + List participants = participantProvider.findJoinedByProgress(user.getId(), ACTIVITY); + + //then + assertThat(participants.size()).isEqualTo(1); + } + + @Test + @DisplayName("Participants들 중, 진행 중이지만 도중 참가 취소로 인해 실패한 챌린지 리스트를 불러올 수 있다.") + public void should_return_quit_instances_when_activity() { + //given + User user = getSavedUser(); + Instance instance1 = getSavedInstance(PREACTIVITY); + Instance instance2 = getSavedInstance(ACTIVITY); + Instance instance3 = getSavedInstance(ACTIVITY); + Participant participant3 = getSavedParticipant(user, instance1); + Participant participant2 = getSavedParticipant(user, instance2); + Participant participant1 = getSavedParticipant(user, instance3, JoinStatus.NO); + + //when + List participants = participantProvider.findDoneInstances(user.getId()); + + //then + assertThat(participants.size()).isEqualTo(1); + } + + @Test + @DisplayName("Participants들 중, 성공한 챌린지 리스트들을 불러올 수 있다.") + public void should_return_success_instances() { + //given + User user = getSavedUser(); + Instance instance1 = getSavedInstance(ACTIVITY); + Instance instance2 = getSavedInstance(DONE); + Instance instance3 = getSavedInstance(DONE); + Participant participant3 = getSavedParticipant(user, instance1); + Participant participant2 = getSavedParticipant(user, instance2); + Participant participant1 = getSavedParticipant(user, instance3); + + //when + List participants = participantProvider.findDoneInstances(user.getId()); + + //then + assertThat(participants.size()).isEqualTo(2); + } + private User getSavedUser() { return userRepository.save( @@ -117,4 +175,12 @@ private Participant getSavedParticipant(User user, Instance instance) { participant.setUserAndInstance(user, instance); return participantRepository.save(participant); } + + private Participant getSavedParticipant(User user, Instance instance, JoinStatus joinStatus) { + Participant participant = Participant.builder() + .joinStatus(joinStatus) + .build(); + participant.setUserAndInstance(user, instance); + return participantRepository.save(participant); + } } \ No newline at end of file From 8e0fe92ec48e4301e4a2d12980b10acd8a858dc1 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Mon, 29 Jul 2024 19:08:10 +0900 Subject: [PATCH 208/234] =?UTF-8?q?[FEAT]=20instanceController,=20instance?= =?UTF-8?q?HomeController=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20=20(#22?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: instanceHomeFacade 적용 - instanceFacade 인터페이스 생성 - instanceSearchService -> instanceFacadeService 적용 - instanceHomeService의 불분명한 네이밍 -> ChallengeRecommendationService로 변경 - Service Layer 리펙토링 * feat: instanceHomeFacade 적용 - instanceFacade 인터페이스 생성 - instanceSearchService -> instanceFacadeService 적용 - instanceHomeService의 불분명한 네이밍 -> ChallengeRecommendationService로 변경 - Service Layer 리펙토링 * feat: instanceHomeFacade 적용 - instanceFacade 인터페이스 생성 - instanceSearchService -> instanceFacadeService 적용 - instanceHomeService의 불분명한 네이밍 -> ChallengeRecommendationService로 변경 - Service Layer 리펙토링 * refactor: 코드 리펙토링 * refactor: TestDTOFactory & TestSetup 도입 * feat: instanceController, instanceHomeController Facade Pattern && Test Code DCI 적용 - instanceFacade, instanceHomeFacade 인터페이스 생성 - instanceFacadeService 는 인스턴스 생성, 삭제, 수정, 조회 기능 지원 - instanceFacadeService <-> instanceService - instanceUpdate 기능을 개발하는 과정에서 instanceUpdateDTO 생성 (request, response 시 주고받는 DTO와 다름. facade에서 service로 전달하는 DTO입니다.) - instanceFacadeTest 수행 완료 및 DCI 패턴 적용 - instanceHomeFacadeService 는 인스턴스 조회, 추천 기능 지원 - instanceHomeFacadeService <-> challengeRecommendationService, challengeSearchService - instanceHomeFacadeTest 수행 완료 및 DCI 패턴 적용 - 리펙토링 과정에서 instanceSearchRepositoryTest가 사용하던 클래스의 데이터 구조 변경으로 인해 코드 수정 - TestDTOFactory, TestSetup 클래스 생성 * refactor: 코드 리뷰 의견 반영 - 클래스명 challenge -> instance 변경 - progressData 리스트 선언 대신 enum 클래스 가져다 쓰기 --- .../controller/InstanceController.java | 81 ++-- .../controller/InstanceHomeController.java | 17 +- .../challenge/instance/domain/Instance.java | 16 +- .../dto/crud/InstanceCreateRequest.java | 18 +- .../dto/crud/InstanceDetailResponse.java | 17 +- .../dto/crud/InstancePagingResponse.java | 2 +- .../instance/dto/crud/InstanceUpdateDTO.java | 26 ++ .../instance/facade/InstanceFacade.java | 25 ++ .../facade/InstanceFacadeService.java | 81 ++++ .../instance/facade/InstanceHomeFacade.java | 19 + .../facade/InstanceHomeFacadeService.java | 84 +++++ .../instance/service/InstanceHomeService.java | 57 --- .../InstanceRecommendationService.java | 37 ++ .../service/InstanceSearchService.java | 43 +-- .../instance/service/InstanceService.java | 109 +++--- .../topic/controller/TopicController.java | 2 +- .../TopicFacade.java | 2 +- .../TopicFacadeService.java} | 5 +- ...> ChallengeRecommendationServiceTest.java} | 22 +- .../InstanceSearchRepositoryTest.java | 57 +-- .../instance/service/InstanceFacadeTest.java | 347 ++++++++++++++++++ .../service/InstanceHomeFacadeTest.java | 91 +++++ .../service/InstanceSearchServiceTest.java | 95 ----- .../instance/service/InstanceServiceTest.java | 291 --------------- .../instance/util/TestDTOFactory.java | 55 +++ .../challenge/instance/util/TestSetup.java | 100 +++++ .../topic/controller/TopicControllerTest.java | 6 +- .../topic/repository/TopicRepositoryTest.java | 3 +- .../topic/service/TopicFacadeTest.java | 4 +- 29 files changed, 1056 insertions(+), 656 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateDTO.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacade.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacadeService.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacade.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacadeService.java delete mode 100644 src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java create mode 100644 src/main/java/com/genius/gitget/challenge/instance/service/InstanceRecommendationService.java rename src/main/java/com/genius/gitget/topic/{serviceFacade => facade}/TopicFacade.java (92%) rename src/main/java/com/genius/gitget/topic/{serviceFacade/TopicFacadeImpl.java => facade/TopicFacadeService.java} (96%) rename src/test/java/com/genius/gitget/challenge/home/service/{InstanceHomeServiceTest.java => ChallengeRecommendationServiceTest.java} (91%) create mode 100644 src/test/java/com/genius/gitget/challenge/instance/service/InstanceFacadeTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/instance/service/InstanceHomeFacadeTest.java delete mode 100644 src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java delete mode 100644 src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/instance/util/TestDTOFactory.java create mode 100644 src/test/java/com/genius/gitget/challenge/instance/util/TestSetup.java rename src/test/java/com/genius/gitget/{admin => }/topic/controller/TopicControllerTest.java (99%) rename src/test/java/com/genius/gitget/{admin => }/topic/repository/TopicRepositoryTest.java (98%) rename src/test/java/com/genius/gitget/{admin => }/topic/service/TopicFacadeTest.java (97%) diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java index d85e94c5..9cb794a4 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceController.java @@ -9,7 +9,7 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceIndexResponse; import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; -import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.challenge.instance.facade.InstanceFacade; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -35,50 +35,14 @@ @RequestMapping("/api/admin") @RequiredArgsConstructor public class InstanceController { - private final InstanceService instanceService; - - // 인스턴스 리스트 조회 - @GetMapping("/instance") - public ResponseEntity> getAllInstances( - @PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id") Pageable pageable) { - Page instances = instanceService.getAllInstances(pageable); - - return ResponseEntity.ok().body( - new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instances) - ); - } - - // 특정 토픽에 대한 리스트 조회 - @GetMapping("topic/instances/{id}") - public ResponseEntity> getAllInstancesOfSpecificTopic( - @PageableDefault Pageable pageable, @PathVariable Long id) { - PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), - Sort.by("id")); - Page allInstancesOfSpecificTopic = instanceService.getAllInstancesOfSpecificTopic( - pageRequest, id); - - return ResponseEntity.ok().body( - new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), - allInstancesOfSpecificTopic) - ); - } - - - // 인스턴스 단건 조회 - @GetMapping("/instance/{id}") - public ResponseEntity> getInstanceById(@PathVariable Long id) { - InstanceDetailResponse instanceDetails = instanceService.getInstanceById(id); - return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instanceDetails) - ); - } + private final InstanceFacade instanceFacade; // 인스턴스 생성 @PostMapping("/instance") public ResponseEntity> createInstance( @RequestBody InstanceCreateRequest instanceCreateRequest) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); - Long instanceId = instanceService.createInstance(instanceCreateRequest, kstDate); + Long instanceId = instanceFacade.createInstance(instanceCreateRequest, kstDate); InstanceIndexResponse instanceIndexResponse = new InstanceIndexResponse(instanceId); return ResponseEntity.ok().body( @@ -92,7 +56,7 @@ public ResponseEntity> updateInstance( @PathVariable Long id, @RequestBody InstanceUpdateRequest instanceUpdateRequest) { - Long instanceId = instanceService.updateInstance(id, instanceUpdateRequest); + Long instanceId = instanceFacade.modifyInstance(id, instanceUpdateRequest); InstanceIndexResponse instanceIndexResponse = new InstanceIndexResponse(instanceId); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instanceIndexResponse) @@ -102,9 +66,44 @@ public ResponseEntity> updateInstance( // 인스턴스 삭제 @DeleteMapping("/instance/{id}") public ResponseEntity deleteInstance(@PathVariable Long id) { - instanceService.deleteInstance(id); + instanceFacade.removeInstance(id); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); } + + // 인스턴스 단건 조회 + @GetMapping("/instance/{id}") + public ResponseEntity> getInstanceById(@PathVariable Long id) { + InstanceDetailResponse instanceDetails = instanceFacade.findOne(id); + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instanceDetails) + ); + } + + // 인스턴스 리스트 조회 + @GetMapping("/instance") + public ResponseEntity> getAllInstances( + @PageableDefault(size = 5, direction = Sort.Direction.ASC, sort = "id") Pageable pageable) { + Page instances = instanceFacade.findAllInstances(pageable); + + return ResponseEntity.ok().body( + new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instances) + ); + } + + // 특정 토픽에 대한 리스트 조회 + @GetMapping("topic/instances/{id}") + public ResponseEntity> getAllInstancesOfSpecificTopic( + @PageableDefault Pageable pageable, @PathVariable Long id) { + PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), + Sort.by("id")); + Page allInstancesOfSpecificTopic = instanceFacade.getAllInstancesOfSpecificTopic( + pageRequest, id); + + return ResponseEntity.ok().body( + new PagingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), + allInstancesOfSpecificTopic) + ); + } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java index 9d34fb92..f0f83a3b 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java @@ -5,8 +5,7 @@ import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; -import com.genius.gitget.challenge.instance.service.InstanceHomeService; -import com.genius.gitget.challenge.instance.service.InstanceSearchService; +import com.genius.gitget.challenge.instance.facade.InstanceHomeFacade; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.PagingResponse; @@ -30,16 +29,14 @@ @RequiredArgsConstructor @RequestMapping("/api/challenges") public class InstanceHomeController { - private final InstanceHomeService instanceHomeService; - private final InstanceSearchService instanceSearchService; + private final InstanceHomeFacade instanceHomeFacade; @PostMapping("/search") public ResponseEntity> searchInstances( @RequestBody InstanceSearchRequest instanceSearchRequest, Pageable pageable) { Page searchResults - = instanceSearchService.searchInstances(instanceSearchRequest.keyword(), - instanceSearchRequest.progress(), pageable); + = instanceHomeFacade.searchInstancesByKeywordAndProgress(instanceSearchRequest, pageable); return ResponseEntity.ok().body( new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), searchResults) @@ -54,7 +51,7 @@ public ResponseEntity> getRecommendInstanc PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Direction.DESC, "participantCount")); - Slice recommendations = instanceHomeService.getRecommendations( + Slice recommendations = instanceHomeFacade.recommendInstances( userPrincipal.getUser(), pageRequest); return ResponseEntity.ok().body( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) @@ -66,7 +63,8 @@ public ResponseEntity> getPopularInstances PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Direction.DESC, "participantCount")); - Slice recommendations = instanceHomeService.getInstancesByCondition(pageRequest); + Slice recommendations = instanceHomeFacade.findInstancesByCondition( + pageRequest); return ResponseEntity.ok().body( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) ); @@ -77,7 +75,8 @@ public ResponseEntity> getLatestInstances( PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Direction.DESC, "startedDate")); - Slice recommendations = instanceHomeService.getInstancesByCondition(pageRequest); + Slice recommendations = instanceHomeFacade.findInstancesByCondition( + pageRequest); return ResponseEntity.ok().body( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) ); diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index f41047f7..be16bf0c 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -1,15 +1,14 @@ package com.genius.gitget.challenge.instance.domain; -import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.challenge.certification.util.DateUtil; -import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.file.domain.FileHolder; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.topic.domain.Topic; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -103,19 +102,6 @@ public Instance(String title, String description, String tags, int pointPerPerso this.completedDate = completedDate; } - public static Instance createByRequest(InstanceCreateRequest instanceCreateRequest) { - return Instance.builder() - .title(instanceCreateRequest.title()) - .tags(instanceCreateRequest.tags()) - .description(instanceCreateRequest.description()) - .pointPerPerson(instanceCreateRequest.pointPerPerson()) - .notice(instanceCreateRequest.notice()) - .startedDate(instanceCreateRequest.startedAt()) - .completedDate(instanceCreateRequest.completedAt()) - .certificationMethod(instanceCreateRequest.certificationMethod()) - .progress(Progress.PREACTIVITY) - .build(); - } //== 연관관계 편의 메서드 ==// public void setTopic(Topic topic) { diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java index 84a7d3aa..54d7e9d9 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceCreateRequest.java @@ -1,5 +1,7 @@ package com.genius.gitget.challenge.instance.dto.crud; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; import java.time.LocalDateTime; import lombok.Builder; @@ -13,6 +15,18 @@ public record InstanceCreateRequest( int pointPerPerson, LocalDateTime startedAt, LocalDateTime completedAt, - String certificationMethod -) { + String certificationMethod) { + public static Instance from(InstanceCreateRequest instanceCreateRequest) { + return Instance.builder() + .title(instanceCreateRequest.title()) + .tags(instanceCreateRequest.tags()) + .description(instanceCreateRequest.description()) + .pointPerPerson(instanceCreateRequest.pointPerPerson()) + .notice(instanceCreateRequest.notice()) + .startedDate(instanceCreateRequest.startedAt()) + .completedDate(instanceCreateRequest.completedAt()) + .certificationMethod(instanceCreateRequest.certificationMethod()) + .progress(Progress.PREACTIVITY) + .build(); + } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java index 3850a936..748793c5 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceDetailResponse.java @@ -6,12 +6,17 @@ import lombok.Builder; @Builder -public record InstanceDetailResponse(Long topicId, Long instanceId, String title, String description, - int pointPerPerson, - String tags, String notice, LocalDateTime startedAt, LocalDateTime completedAt, - String certificationMethod, - FileResponse fileResponse) { - public static InstanceDetailResponse createByEntity(Instance instance, FileResponse fileResponse) { +public record InstanceDetailResponse( + Long topicId, Long instanceId, + String title, String description, + int pointPerPerson, + String tags, + String notice, + LocalDateTime startedAt, + LocalDateTime completedAt, + String certificationMethod, + FileResponse fileResponse) { + public static InstanceDetailResponse of(Instance instance, FileResponse fileResponse) { return InstanceDetailResponse.builder() .topicId(instance.getTopic().getId()) .instanceId(instance.getId()) diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java index 26bfbbd8..cfa999d5 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstancePagingResponse.java @@ -13,7 +13,7 @@ public record InstancePagingResponse( LocalDateTime startedAt, LocalDateTime completedAt, FileResponse fileResponse) { - public static InstancePagingResponse createByEntity(Instance instance, FileResponse fileResponse) { + public static InstancePagingResponse of(Instance instance, FileResponse fileResponse) { return InstancePagingResponse.builder() .topicId(instance.getTopic().getId()) .instanceId(instance.getId()) diff --git a/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateDTO.java b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateDTO.java new file mode 100644 index 00000000..746d6ae6 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/dto/crud/InstanceUpdateDTO.java @@ -0,0 +1,26 @@ +package com.genius.gitget.challenge.instance.dto.crud; + +import java.time.LocalDateTime; +import lombok.Builder; + +@Builder +public record InstanceUpdateDTO( + String description, + String notice, + int pointPerPerson, + LocalDateTime startedDate, + + LocalDateTime completedDate, + String certificationMethod) { + public static InstanceUpdateDTO of(String description, String notice, int pointPerPerson, LocalDateTime startedDate, + LocalDateTime completedDate, String certificationMethod) { + return InstanceUpdateDTO.builder() + .description(description) + .notice(notice) + .pointPerPerson(pointPerPerson) + .startedDate(startedDate) + .completedDate(completedDate) + .certificationMethod(certificationMethod) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacade.java b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacade.java new file mode 100644 index 00000000..c5893d18 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacade.java @@ -0,0 +1,25 @@ +package com.genius.gitget.challenge.instance.facade; + +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; +import java.time.LocalDate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface InstanceFacade { + + Long createInstance(InstanceCreateRequest instanceCreateRequest, LocalDate currentDate); + + Page findAllInstances(Pageable pageable); + + Page getAllInstancesOfSpecificTopic(Pageable pageable, Long id); + + InstanceDetailResponse findOne(Long id); + + void removeInstance(Long id); + + Long modifyInstance(Long id, InstanceUpdateRequest instanceUpdateRequest); + +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacadeService.java b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacadeService.java new file mode 100644 index 00000000..e491d45c --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacadeService.java @@ -0,0 +1,81 @@ +package com.genius.gitget.challenge.instance.facade; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateDTO; +import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Component +@Slf4j +@Transactional +public class InstanceFacadeService implements InstanceFacade { + private final FilesService filesService; + private final InstanceService instanceService; + + // 인스턴스 생성 + @Override + public Long createInstance(InstanceCreateRequest instanceCreateRequest, LocalDate currentDate) { + Instance instance = InstanceCreateRequest.from(instanceCreateRequest); + LocalDate startDate = instanceCreateRequest.startedAt().toLocalDate(); + LocalDate completeDate = instanceCreateRequest.completedAt().toLocalDate(); + + return instanceService.createInstance(instance, instanceCreateRequest.topicId(), startDate, completeDate, + currentDate); + } + + // 인스턴스 수정 + @Override + public Long modifyInstance(Long id, InstanceUpdateRequest instanceUpdateRequest) { + InstanceUpdateDTO updateDTO = InstanceUpdateDTO.of(instanceUpdateRequest.description(), + instanceUpdateRequest.notice(), instanceUpdateRequest.pointPerPerson(), + instanceUpdateRequest.startedAt(), + instanceUpdateRequest.completedAt(), instanceUpdateRequest.certificationMethod()); + + return instanceService.modifyInstance(id, updateDTO); + } + + @Override + public void removeInstance(Long id) { + instanceService.removeInstance(id); + } + + // 인스턴스 단건 조회 + @Override + public InstanceDetailResponse findOne(Long id) { + Instance instance = instanceService.findInstanceById(id); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + + return InstanceDetailResponse.of(instance, fileResponse); + } + + // 인스턴스 전체 조회 + @Override + public Page findAllInstances(Pageable pageable) { + Page allInstances = instanceService.findAllInstances(pageable); + return allInstances.map(this::mapToInstancePagingResponse); + } + + @Override + public Page getAllInstancesOfSpecificTopic(Pageable pageable, Long id) { + Page allInstancesOfSpecificTopic = instanceService.getAllInstancesOfSpecificTopic(pageable, id); + return allInstancesOfSpecificTopic.map(this::mapToInstancePagingResponse); + } + + private InstancePagingResponse mapToInstancePagingResponse(Instance instance) { + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + return InstancePagingResponse.of(instance, fileResponse); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacade.java b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacade.java new file mode 100644 index 00000000..17496c4b --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacade.java @@ -0,0 +1,19 @@ +package com.genius.gitget.challenge.instance.facade; + +import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.user.domain.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +public interface InstanceHomeFacade { + + Page searchInstancesByKeywordAndProgress(InstanceSearchRequest instanceSearchRequest, + Pageable pageable); + + Slice recommendInstances(User user, Pageable pageable); + + Slice findInstancesByCondition(Pageable pageable); +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacadeService.java b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacadeService.java new file mode 100644 index 00000000..9f039b98 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacadeService.java @@ -0,0 +1,84 @@ +package com.genius.gitget.challenge.instance.facade; + + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.service.InstanceRecommendationService; +import com.genius.gitget.challenge.instance.service.InstanceSearchService; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Component +@Slf4j +@Transactional +public class InstanceHomeFacadeService implements InstanceHomeFacade { + + private final InstanceRecommendationService instanceRecommendationService; + private final InstanceSearchService instanceSearchService; + private final FilesService filesService; + + @Override + public Page searchInstancesByKeywordAndProgress(InstanceSearchRequest instanceSearchRequest, + Pageable pageable) { + Page searchedInstances = instanceSearchService.searchInstances(instanceSearchRequest.keyword(), + instanceSearchRequest.progress(), pageable); + + return searchedInstances.map(this::convertToSearchResponse); + } + + @Override + public Slice recommendInstances(User user, Pageable pageable) { + List instanceList = instanceRecommendationService.getRecommendations(user); + List recommendations = convertToHomeInstanceResponseList(instanceList); + return createPageFromList(recommendations, pageable); + } + + @Override + public Slice findInstancesByCondition(Pageable pageable) { + Slice instancesByCondition = instanceRecommendationService.getInstancesByCondition(pageable); + return instancesByCondition.map(this::mapToHomeInstanceResponse); + } + + private InstanceSearchResponse convertToSearchResponse(Instance instance) { + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + return InstanceSearchResponse.builder() + .topicId(instance.getTopic().getId()) + .instanceId(instance.getId()) + .keyword(instance.getTitle()) + .pointPerPerson(instance.getPointPerPerson()) + .participantCount(instance.getParticipantCount()) + .fileResponse(fileResponse) + .build(); + } + + private List convertToHomeInstanceResponseList(List instances) { + return instances.stream() + .map(this::mapToHomeInstanceResponse) + .collect(Collectors.toList()); + } + + private HomeInstanceResponse mapToHomeInstanceResponse(Instance instance) { + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + return HomeInstanceResponse.createByEntity(instance, fileResponse); + } + + private Slice createPageFromList(List list, Pageable pageable) { + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), list.size()); + return new PageImpl<>(list.subList(start, end), pageable, list.size()); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java deleted file mode 100644 index bf187a2f..00000000 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceHomeService.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.genius.gitget.challenge.instance.service; - -import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; - -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class InstanceHomeService { - private final FilesService filesService; - private final InstanceRepository instanceRepository; - - public Slice getRecommendations(User user, Pageable pageable) { - List instances = new ArrayList<>(); - List userTags = Arrays.stream(user.getTags().split(",")).toList(); - for (String userTag : userTags) { - instances.addAll(instanceRepository.findRecommendations(userTag, PREACTIVITY)); - } - - List recommendations = instances.stream() - .distinct() - .map(this::mapToHomeInstanceResponse) - .toList(); - - int start = (int) pageable.getOffset(); - int end = Math.min((start + pageable.getPageSize()), recommendations.size()); - return new PageImpl<>(recommendations.subList(start, end), pageable, recommendations.size()); - } - - public Slice getInstancesByCondition(Pageable pageable) { - - Slice instances = instanceRepository.findPagesByProgress(PREACTIVITY, pageable); - return instances.map(this::mapToHomeInstanceResponse); - } - - private HomeInstanceResponse mapToHomeInstanceResponse(Instance instance) { - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - return HomeInstanceResponse.createByEntity(instance, fileResponse); - } -} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceRecommendationService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceRecommendationService.java new file mode 100644 index 00000000..5145fb64 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceRecommendationService.java @@ -0,0 +1,37 @@ +package com.genius.gitget.challenge.instance.service; + +import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.user.domain.User; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class InstanceRecommendationService { + private final InstanceRepository instanceRepository; + + public List getRecommendations(User user) { + String[] userTags = user.getTags().split(","); + List instances = new ArrayList<>(); + for (String userTag : userTags) { + instances.addAll(instanceRepository.findRecommendations(userTag, PREACTIVITY)); + } + return instances.stream().distinct().collect(Collectors.toList()); + } + + public Slice getInstancesByCondition(Pageable pageable) { + return instanceRepository.findPagesByProgress(PREACTIVITY, pageable); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java index cbc921c5..96aabde9 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceSearchService.java @@ -2,10 +2,10 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.repository.SearchRepository; -import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -18,41 +18,24 @@ @Transactional(readOnly = true) @Slf4j public class InstanceSearchService { - private final FilesService filesService; private final SearchRepository searchRepository; private final StringToEnum stringToEnum; - private final String[] progressData = {"PREACTIVITY", "ACTIVITY", "DONE"}; + public Page searchInstances(String keyword, String progress, Pageable pageable) { + boolean isValidProgress = false; - public Page searchInstances(String keyword, String progress, Pageable pageable) { - - Page search; - Progress convertProgress; - boolean flag = false; + List progressData = Arrays.stream(Progress.values()).map(Objects::toString).toList(); for (String progressCond : progressData) { if (progressCond.equals(progress)) { - flag = true; + isValidProgress = true; + break; } } - if (flag) { - convertProgress = stringToEnum.convert(progress); - search = searchRepository.search(convertProgress, keyword, pageable); - } else { - search = searchRepository.search(null, keyword, pageable); + if (isValidProgress) { + Progress convertProgress = stringToEnum.convert(progress); + return searchRepository.search(convertProgress, keyword, pageable); } - return search.map(this::convertToSearchResponse); - } - - private InstanceSearchResponse convertToSearchResponse(Instance instance) { - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - return InstanceSearchResponse.builder() - .topicId(instance.getTopic().getId()) - .instanceId(instance.getId()) - .keyword(instance.getTitle()) - .pointPerPerson(instance.getPointPerPerson()) - .participantCount(instance.getParticipantCount()) - .fileResponse(fileResponse) - .build(); + return searchRepository.search(null, keyword, pageable); } -} +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java index 1bc30561..d51a71d3 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceService.java @@ -4,22 +4,19 @@ import static com.genius.gitget.global.util.exception.ErrorCode.INVALID_INSTANCE_DATE; import static com.genius.gitget.global.util.exception.ErrorCode.TOPIC_NOT_FOUND; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; -import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; -import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; -import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateDTO; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import java.time.LocalDate; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -33,68 +30,51 @@ public class InstanceService { private final TopicRepository topicRepository; private final FilesService filesService; + @NotNull + private String getUuid() { + String uuid = UUID.randomUUID().toString(); + return uuid.replaceAll("-", "").substring(0, 16); + } + + private void validatePeriod(LocalDate startDate, LocalDate completeDate, LocalDate currentDate) { + if (currentDate.isAfter(startDate) || currentDate.isAfter(completeDate)) { + throw new BusinessException(INVALID_INSTANCE_DATE); + } + } + // 인스턴스 생성 @Transactional - public Long createInstance(InstanceCreateRequest instanceCreateRequest, + public Long createInstance(Instance instance, Long id, LocalDate startDate, LocalDate completeDate, LocalDate currentDate) { - // 토픽 조회 - Topic topic = topicRepository.findById(instanceCreateRequest.topicId()) + Topic topic = topicRepository.findById(id) .orElseThrow(() -> new BusinessException(TOPIC_NOT_FOUND)); - // 인스턴스 생성 일자 검증 - validatePeriod(instanceCreateRequest, currentDate); - - // 인스턴스 고유 uuid 생성 - String uuid = UUID.randomUUID().toString(); - uuid = uuid.replaceAll("-", "").substring(0, 16); - - // from dto to entity 변환 및 uuid 설정 - Instance instance = Instance.createByRequest(instanceCreateRequest); + validatePeriod(startDate, completeDate, currentDate); + String uuid = getUuid(); instance.setInstanceUUID(uuid); - - // 연관 관계 설정 instance.setTopic(topic); return instanceRepository.save(instance).getId(); } - private void validatePeriod(InstanceCreateRequest instanceCreateRequest, LocalDate currentDate) { - LocalDate startedDate = instanceCreateRequest.startedAt().toLocalDate(); - LocalDate completedDate = instanceCreateRequest.completedAt().toLocalDate(); - - if (currentDate.isAfter(startedDate) || currentDate.isAfter(completedDate)) { - throw new BusinessException(INVALID_INSTANCE_DATE); - } - } - - // 인스턴스 리스트 조회 - public Page getAllInstances(Pageable pageable) { - Page instances = instanceRepository.findAllById(pageable); - return instances.map(this::mapToInstancePagingResponse); - } - + // 인스턴스 수정 + @Transactional + public Long modifyInstance(Long id, InstanceUpdateDTO instanceUpdateDTO) { + Instance existingInstance = instanceRepository.findById(id) + .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); - // 특정 토픽에 대한 리스트 조회 - public Page getAllInstancesOfSpecificTopic(Pageable pageable, Long id) { - Topic topic = topicRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - Page instancesByTopicId = instanceRepository.findInstancesByTopicId(pageable, topic.getId()); - return instancesByTopicId.map(this::mapToInstancePagingResponse); - } + existingInstance.updateInstance(instanceUpdateDTO.description(), instanceUpdateDTO.notice(), + instanceUpdateDTO.pointPerPerson(), instanceUpdateDTO.startedDate(), + instanceUpdateDTO.completedDate(), instanceUpdateDTO.certificationMethod()); + Instance savedInstance = instanceRepository.save(existingInstance); - // 인스턴스 단건 조회 - public InstanceDetailResponse getInstanceById(Long id) { - Instance instanceDetails = instanceRepository.findById(id) - .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); - FileResponse fileResponse = filesService.convertToFileResponse(instanceDetails.getFiles()); - return InstanceDetailResponse.createByEntity(instanceDetails, fileResponse); + return savedInstance.getId(); } - // 인스턴스 삭제 @Transactional - public void deleteInstance(Long id) { + public void removeInstance(Long id) { Instance instance = instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); @@ -108,24 +88,21 @@ public void deleteInstance(Long id) { instanceRepository.delete(instance); } - - // 인스턴스 수정 - @Transactional - public Long updateInstance(Long id, InstanceUpdateRequest instanceUpdateRequest) { - Instance existingInstance = instanceRepository.findById(id) + // 인스턴스 단건 조회 + public Instance findInstanceById(Long id) { + return instanceRepository.findById(id) .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); + } - existingInstance.updateInstance(instanceUpdateRequest.description(), instanceUpdateRequest.notice(), - instanceUpdateRequest.pointPerPerson(), instanceUpdateRequest.startedAt(), - instanceUpdateRequest.completedAt(), instanceUpdateRequest.certificationMethod()); - - Instance savedInstance = instanceRepository.save(existingInstance); - - return savedInstance.getId(); + // 인스턴스 리스트 조회 + public Page findAllInstances(Pageable pageable) { + return instanceRepository.findAllById(pageable); } - private InstancePagingResponse mapToInstancePagingResponse(Instance instance) { - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - return InstancePagingResponse.createByEntity(instance, fileResponse); + // 특정 토픽에 대한 리스트 조회 + public Page getAllInstancesOfSpecificTopic(Pageable pageable, Long id) { + Topic topic = topicRepository.findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + return instanceRepository.findInstancesByTopicId(pageable, topic.getId()); } } diff --git a/src/main/java/com/genius/gitget/topic/controller/TopicController.java b/src/main/java/com/genius/gitget/topic/controller/TopicController.java index 947dc79a..85d36eb4 100644 --- a/src/main/java/com/genius/gitget/topic/controller/TopicController.java +++ b/src/main/java/com/genius/gitget/topic/controller/TopicController.java @@ -11,7 +11,7 @@ import com.genius.gitget.topic.dto.TopicIndexResponse; import com.genius.gitget.topic.dto.TopicPagingResponse; import com.genius.gitget.topic.dto.TopicUpdateRequest; -import com.genius.gitget.topic.serviceFacade.TopicFacade; +import com.genius.gitget.topic.facade.TopicFacade; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacade.java b/src/main/java/com/genius/gitget/topic/facade/TopicFacade.java similarity index 92% rename from src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacade.java rename to src/main/java/com/genius/gitget/topic/facade/TopicFacade.java index 18558cff..da46ad92 100644 --- a/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacade.java +++ b/src/main/java/com/genius/gitget/topic/facade/TopicFacade.java @@ -1,4 +1,4 @@ -package com.genius.gitget.topic.serviceFacade; +package com.genius.gitget.topic.facade; import com.genius.gitget.topic.dto.TopicCreateRequest; import com.genius.gitget.topic.dto.TopicDetailResponse; diff --git a/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacadeImpl.java b/src/main/java/com/genius/gitget/topic/facade/TopicFacadeService.java similarity index 96% rename from src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacadeImpl.java rename to src/main/java/com/genius/gitget/topic/facade/TopicFacadeService.java index f91e2a07..45a31007 100644 --- a/src/main/java/com/genius/gitget/topic/serviceFacade/TopicFacadeImpl.java +++ b/src/main/java/com/genius/gitget/topic/facade/TopicFacadeService.java @@ -1,4 +1,4 @@ -package com.genius.gitget.topic.serviceFacade; +package com.genius.gitget.topic.facade; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; @@ -17,11 +17,12 @@ @RequiredArgsConstructor @Component @Transactional -public class TopicFacadeImpl implements TopicFacade { +public class TopicFacadeService implements TopicFacade { private final FilesService filesService; private final TopicService topicService; + @Override public Page findTopics(Pageable pageable) { Page findTopics = topicService.findTopics(pageable); diff --git a/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java b/src/test/java/com/genius/gitget/challenge/home/service/ChallengeRecommendationServiceTest.java similarity index 91% rename from src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java rename to src/test/java/com/genius/gitget/challenge/home/service/ChallengeRecommendationServiceTest.java index 5030c4b6..7ed985de 100644 --- a/src/test/java/com/genius/gitget/challenge/home/service/InstanceHomeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/service/ChallengeRecommendationServiceTest.java @@ -2,14 +2,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.home.HomeInstanceResponse; +import com.genius.gitget.challenge.instance.facade.InstanceHomeFacade; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.instance.service.InstanceHomeService; +import com.genius.gitget.challenge.instance.service.InstanceRecommendationService; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,9 +24,11 @@ @SpringBootTest @Transactional -class InstanceHomeServiceTest { +class InstanceHomeFacadeTest { + @Autowired + InstanceHomeFacade instanceHomeFacade; @Autowired - InstanceHomeService instanceHomeService; + InstanceRecommendationService instanceRecommendationService; @Autowired TopicRepository topicRepository; @Autowired @@ -44,7 +47,8 @@ public void should_getSuggestions_when_passUserTags() { User user = User.builder().tags("BE,React").build(); //when - Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); + Slice recommendations = instanceHomeFacade.recommendInstances(user, + pageRequest); //then assertThat(recommendations.getContent().size()).isEqualTo(4); @@ -70,7 +74,8 @@ public void should_hasNextIsTrue_when_instanceSizeOverThanPageSize() { User user = User.builder().tags("BE").build(); //when - Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); + Slice recommendations = instanceHomeFacade.recommendInstances(user, + pageRequest); //then assertThat(recommendations.getContent().size()).isEqualTo(2); @@ -92,7 +97,8 @@ public void should_hasNextIsTrue_when_instanceSizeLessThanPageSize() { User user = User.builder().tags("BE").build(); //when - Slice recommendations = instanceHomeService.getRecommendations(user, pageRequest); + Slice recommendations = instanceHomeFacade.recommendInstances(user, + pageRequest); //then assertThat(recommendations.getContent().size()).isEqualTo(3); diff --git a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java index 74493ecb..b7faa0be 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/repository/InstanceSearchRepositoryTest.java @@ -4,18 +4,20 @@ import static com.genius.gitget.challenge.instance.domain.Progress.DONE; import static com.genius.gitget.challenge.instance.domain.Progress.PREACTIVITY; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.facade.InstanceFacade; import com.genius.gitget.challenge.instance.service.InstanceSearchService; import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.util.file.FileTestUtil; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import java.io.IOException; import java.time.LocalDateTime; +import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -44,6 +46,8 @@ public class InstanceSearchRepositoryTest { InstanceSearchService instanceSearchService; @Autowired InstanceService instanceService; + @Autowired + InstanceFacade instanceFacade; @Autowired FileTestUtil fileTestUtil; @@ -94,13 +98,32 @@ public void setup() throws IOException { Topic savedTopic = topicRepository.save(topic); - createInstance(savedTopic, instanceA, instanceA.getTitle()); - createInstance(savedTopic, instanceA, instanceA.getTitle()); - createInstance(savedTopic, instanceA, instanceA.getTitle()); - createInstance(savedTopic, instanceB, instanceB.getTitle()); - createInstance(savedTopic, instanceB, instanceB.getTitle()); - createInstance(savedTopic, instanceB, "2일 3알고리즘"); - createInstance(savedTopic, instanceC, instanceC.getTitle()); + instanceFacade.createInstance(createInstance(savedTopic, instanceA, instanceA.getTitle()), + instanceA.getStartedDate().minusDays(3).toLocalDate()); + instanceFacade.createInstance(createInstance(savedTopic, instanceB, instanceB.getTitle()), + instanceA.getStartedDate().minusDays(3).toLocalDate()); + instanceFacade.createInstance(createInstance(savedTopic, instanceB, instanceB.getTitle()), + instanceA.getStartedDate().minusDays(3).toLocalDate()); + instanceFacade.createInstance(createInstance(savedTopic, instanceA, instanceA.getTitle()), + instanceA.getStartedDate().minusDays(3).toLocalDate()); + instanceFacade.createInstance(createInstance(savedTopic, instanceB, "2일 3알고리즘"), + instanceA.getStartedDate().minusDays(3).toLocalDate()); + instanceFacade.createInstance(createInstance(savedTopic, instanceC, instanceC.getTitle()), + instanceA.getStartedDate().minusDays(3).toLocalDate()); + + } + + @Builder + private InstanceCreateRequest createInstance(Topic savedTopic, Instance instance, String title) { + return InstanceCreateRequest.builder() + .topicId(savedTopic.getId()) + .title(title) + .tags(instance.getTags()) + .description(instance.getDescription()) + .notice(instance.getNotice()) + .pointPerPerson(instance.getPointPerPerson()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()).build(); } @@ -140,7 +163,7 @@ public void setup() throws IOException { cnt++; } } - Assertions.assertThat(cnt).isEqualTo(7); + Assertions.assertThat(cnt).isEqualTo(6); } @Test @@ -182,18 +205,4 @@ public void setup() throws IOException { Assertions.assertThat(cnt).isEqualTo(4); } - - private void createInstance(Topic savedTopic, Instance instance, String title) { - instanceService.createInstance( - InstanceCreateRequest.builder() - .topicId(savedTopic.getId()) - .title(title) - .tags(instance.getTags()) - .description(instance.getDescription()) - .notice(instance.getNotice()) - .pointPerPerson(instance.getPointPerPerson()) - .startedAt(instance.getStartedDate()) - .completedAt(instance.getCompletedDate()).build(), - instance.getStartedDate().minusDays(3).toLocalDate()); - } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceFacadeTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceFacadeTest.java new file mode 100644 index 00000000..7757ebe6 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceFacadeTest.java @@ -0,0 +1,347 @@ +package com.genius.gitget.challenge.instance.service; + + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; +import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; +import com.genius.gitget.challenge.instance.facade.InstanceFacade; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.instance.util.TestDTOFactory; +import com.genius.gitget.challenge.instance.util.TestSetup; +import com.genius.gitget.global.file.domain.FileType; +import com.genius.gitget.global.file.domain.Files; +import com.genius.gitget.global.file.repository.FilesRepository; +import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import org.assertj.core.api.Assertions; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Rollback +public class InstanceFacadeTest { + @Autowired + InstanceRepository instanceRepository; + @Autowired + TopicRepository topicRepository; + @Autowired + FilesService filesService; + @Autowired + FilesRepository filesRepository; + @Autowired + InstanceFacade instanceFacade; + + private Instance instanceA; + private Topic topicA; + + @BeforeEach + public void setup() { + List topicList = TestSetup.createTopicList(); + List instanceList = TestSetup.createInstanceList(); + + topicA = topicList.get(0); + instanceA = instanceList.get(0); + + } + + private Topic getSavedTopic(String title, String tags) { + Topic topic = topicRepository.save( + Topic.builder() + .title(title) + .tags(tags) + .description("토픽 설명") + .pointPerPerson(100) + .build() + ); + return topic; + } + + private Instance getSavedInstance(String title, String tags, int participantCnt) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(100) + .certificationMethod("인증 방법") + .startedDate(now.plusDays(5)) + .completedDate(now.plusDays(10)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } + + private Files getSavedFiles(String originalFilename, String savedFilename, String fileURL, FileType fileType) { + return filesRepository.save( + Files.builder() + .originalFilename(originalFilename) + .savedFilename(savedFilename) + .fileURI(fileURL) + .fileType(fileType) + .build() + ); + } + + @Nested + @DisplayName("인스턴스 생성 메서드는") + class Describe_instance_create { + @Nested + @DisplayName("instanceCreateRequestDto가 들어오면") + class Context_with_a_instanceCreateRequestDto { + @Test + @DisplayName("인스턴스을 생성한다.") + public void it_returns_2XX_if_the_instance_was_created_successfully() { + LocalDate currentDate = instanceA.getStartedDate().minusDays(3).toLocalDate(); + Topic savedTopic = topicRepository.save(topicA); + + InstanceCreateRequest instanceCreateRequest = TestDTOFactory.getInstanceCreateRequest(savedTopic, + instanceA); + instanceFacade.createInstance(instanceCreateRequest, currentDate); + + List instanceList = instanceRepository.findAll(); + Assertions.assertThat(instanceList.size()).isEqualTo(1); + } + } + } + + @Nested + @DisplayName("Instance 수정 메서드는") + class Describe_instance_modify { + + @Nested + @DisplayName("InstanceUpdateRequest가 주어지면") + class Context_with_an_instanceUpdateRequest { + + @Test + @DisplayName("인스턴스를 수정하고 수정된 내용을 반환한다") + void it_updates_the_instance_and_returns_the_updated_content() throws Exception { + LocalDate currentDate = instanceA.getStartedDate().minusDays(3).toLocalDate(); + Topic savedTopic = topicRepository.save(topicA); + + InstanceCreateRequest instanceCreateRequest = TestDTOFactory.getInstanceCreateRequest(savedTopic, + instanceA); + Long savedInstanceId = instanceFacade.createInstance(instanceCreateRequest, currentDate); + + InstanceUpdateRequest instanceUpdateRequest = InstanceUpdateRequest.builder() + .topicId(savedTopic.getId()) + .description("이것은 수정본이지롱") + .pointPerPerson(instanceA.getPointPerPerson()) + .startedAt(instanceA.getStartedDate()) + .completedAt(instanceA.getCompletedDate()) + .build(); + + Long updatedInstanceId = instanceFacade.modifyInstance(savedInstanceId, instanceUpdateRequest); + + Optional byId = instanceRepository.findById(updatedInstanceId); + Assertions.assertThat(byId.get().getDescription()).isEqualTo("이것은 수정본이지롱"); + } + } + } + + @Nested + @DisplayName("Instance 단건 조회 메서드는") + class Describe_instance_findOne { + + @Nested + @DisplayName("Instance ID가 주어졌을 때") + class Context_with_an_instanceId { + + @Test + @DisplayName("해당 ID의 인스턴스를 반환한다") + void it_returns_the_instance_with_the_given_id() throws Exception { + LocalDate currentDate = instanceA.getStartedDate().minusDays(3).toLocalDate(); + Topic savedTopic = topicRepository.save(topicA); + + InstanceCreateRequest instanceCreateRequest = TestDTOFactory.getInstanceCreateRequest(savedTopic, + instanceA); + Long savedInstanceId = instanceFacade.createInstance(instanceCreateRequest, currentDate); + + //wen + InstanceDetailResponse instanceById = instanceFacade.findOne(savedInstanceId); + + Assertions.assertThat(instanceById.title()).isEqualTo(instanceCreateRequest.title()); + } + } + } + + @Nested + @DisplayName("Instance 리스트 조회 메서드는") + class Describe_instance_findAllInstances { + + @Nested + @DisplayName("페이지 요청이 주어졌을 때") + class Context_with_a_pageRequest { + + @Test + @DisplayName("모든 인스턴스를 페이지로 반환한다") + void it_returns_all_instances_in_a_page() { + Topic savedTopic = getSavedTopic("1일 1알고리즘", "FE, BE"); + Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + savedInstance1.setTopic(savedTopic); + savedInstance2.setTopic(savedTopic); + savedInstance3.setTopic(savedTopic); + + PageRequest pageRequest = PageRequest.of(0, 10); + Page allInstances = instanceFacade.findAllInstances(pageRequest); + + Assertions.assertThat(allInstances.getTotalElements()).isEqualTo(3); + } + } + } + + + @Nested + @DisplayName("특정 토픽에 대한 인스턴스 리스트 조회 메서드는") + class Describe_instance_getAllInstancesOfSpecificTopic { + @Nested + @DisplayName("특정 토픽 ID가 주어졌을 때") + class Context_with_a_specificTopicId { + + @Test + @DisplayName("해당 토픽1의 모든 인스턴스를 페이지로 반환한다") + void it_returns_all_instances_of_topic1_in_a_page() { + Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + savedInstance1.setTopic(savedTopic1); + savedInstance2.setTopic(savedTopic1); + savedInstance3.setTopic(savedTopic2); + + PageRequest pageRequest = PageRequest.of(0, 10); + Page allInstancesOfSpecificTopic = instanceFacade.getAllInstancesOfSpecificTopic( + pageRequest, savedTopic1.getId()); + + Assertions.assertThat(allInstancesOfSpecificTopic.getTotalElements()).isEqualTo(2); + } + + @Test + @DisplayName("해당 토픽2의 모든 인스턴스를 페이지로 반환한다") + void it_returns_all_instances_of_topic2_in_a_page() { + Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + savedInstance1.setTopic(savedTopic1); + savedInstance2.setTopic(savedTopic1); + savedInstance3.setTopic(savedTopic2); + + PageRequest pageRequest = PageRequest.of(0, 10); + Page allInstancesOfSpecificTopic = instanceFacade.getAllInstancesOfSpecificTopic( + pageRequest, savedTopic2.getId()); + + Assertions.assertThat(allInstancesOfSpecificTopic.getTotalElements()).isEqualTo(1); + } + + + @Test + @DisplayName("해당 토픽의 모든 인스턴스를 페이지로 반환한다") + void it_returns_all_instances_of_the_given_topic_in_a_page() { + Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); + Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + savedInstance1.setTopic(savedTopic1); + savedInstance2.setTopic(savedTopic1); + savedInstance3.setTopic(savedTopic2); + + PageRequest pageRequest = PageRequest.of(0, 10); + Page allInstancesOfSpecificTopic = instanceFacade.getAllInstancesOfSpecificTopic( + pageRequest, savedTopic1.getId()); + + Assertions.assertThat(allInstancesOfSpecificTopic.getTotalElements()).isEqualTo(2); + } + } + } + + @Nested + @DisplayName("Instance 삭제 메서드는") + class Describe_instance_remove { + + @Nested + @DisplayName("Instance ID가 주어졌을 때") + class Context_with_an_instanceId { + + @Test + @DisplayName("해당 인스턴스를 삭제하고, 삭제된 인스턴스를 조회할 때 예외를 던진다") + void it_removes_the_instance_and_throws_exception_when_retrieving_deleted_instance() { + Instance savedInstance = getSavedInstance("1일 1알고리즘", "FE, BE", 50); + + instanceFacade.removeInstance(savedInstance.getId()); + + assertThrows(BusinessException.class, () -> instanceFacade.findOne(savedInstance.getId())); + } + } + + @Nested + @DisplayName("인스턴스를 삭제할 때") + class Context_when_removing_an_instance { + private Topic topic; + private Instance instance1, instance2, instance3; + + @BeforeEach + public void setup() { + topic = getSavedTopic("1일 1공부", "BE, ML"); + instance1 = getSavedInstance("1일 1공부", "BE, ML", 100); + instance2 = getSavedInstance("1일 3공부", "BE, ML", 100); + instance3 = getSavedInstance("1일 3공부", "BE, ML", 100); + instance1.setTopic(topic); + instance2.setTopic(topic); + } + + @Test + @DisplayName("해당 아이디가 존재한다면 삭제할 수 있다") + void it_can_remove_if_the_instance_exists() { + Long id = instance1.getId(); + + instanceFacade.removeInstance(id); + + assertThrows(BusinessException.class, () -> { + instanceFacade.findOne(id); + }); + } + + @Test + @DisplayName("해당 아이디가 존재하지 않는다면 삭제할 수 없다") + void it_cannot_remove_if_the_instance_does_not_exist() { + Long id = instance3.getId() + 1L; + + assertThrows(BusinessException.class, () -> { + instanceFacade.removeInstance(id); + }); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceHomeFacadeTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceHomeFacadeTest.java new file mode 100644 index 00000000..97c4b869 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceHomeFacadeTest.java @@ -0,0 +1,91 @@ +package com.genius.gitget.challenge.instance.service; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; +import com.genius.gitget.challenge.instance.facade.InstanceFacade; +import com.genius.gitget.challenge.instance.facade.InstanceHomeFacade; +import com.genius.gitget.challenge.instance.util.TestDTOFactory; +import com.genius.gitget.challenge.instance.util.TestSetup; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import java.time.LocalDate; +import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Rollback +@Slf4j +public class InstanceHomeFacadeTest { + @Autowired + TopicRepository topicRepository; + @Autowired + InstanceService instanceService; + + @Autowired + InstanceFacade instanceFacade; + @Autowired + InstanceHomeFacade instanceHomeFacade; + + private Instance instanceA, instanceB, instanceC; + private Topic topicA; + + @BeforeEach + public void setup() { + topicA = TestSetup.createTopicList().get(0); + instanceA = TestSetup.createInstanceList().get(0); + instanceB = TestSetup.createInstanceList().get(1); + instanceC = TestSetup.createInstanceList().get(2); + } + + @SpringBootTest + public class InstanceFacadeTest { + + @Nested + @DisplayName("Instance 검색 메서드는") + class Describe_instance_search { + + @Nested + @DisplayName("키워드와 진행 상태가 주어졌을 때") + class Context_with_keyword_and_progress { + + @Test + @DisplayName("해당 조건에 맞는 인스턴스를 반환한다") + void it_returns_instances_matching_the_given_conditions() { + Topic savedTopic = topicRepository.save(topicA); + LocalDate currentDate = instanceA.getStartedDate().minusDays(3).toLocalDate(); + + instanceFacade.createInstance(TestDTOFactory.getInstanceCreateRequest(savedTopic, instanceA), + currentDate); + instanceFacade.createInstance(TestDTOFactory.getInstanceCreateRequest(savedTopic, instanceB), + currentDate); + instanceFacade.createInstance(TestDTOFactory.getInstanceCreateRequest(savedTopic, instanceC), + currentDate); + + int instanceCount = 0; + Page orderList = instanceHomeFacade.searchInstancesByKeywordAndProgress( + InstanceSearchRequest.builder().keyword("이펙티브").progress("preactivity").build(), + PageRequest.of(0, 3)); + + for (InstanceSearchResponse instanceSearchResponse : orderList) { + if (instanceSearchResponse.getKeyword() != null) { + instanceCount++; + } + } + Assertions.assertThat(instanceCount).isEqualTo(1); + } + } + } + } +} diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java deleted file mode 100644 index 1f94bbfb..00000000 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceSearchServiceTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.genius.gitget.challenge.instance.service; - -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; -import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.instance.repository.SearchRepository; -import java.io.IOException; -import java.time.LocalDateTime; -import lombok.extern.slf4j.Slf4j; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest -@Transactional -@Rollback -@Slf4j -public class InstanceSearchServiceTest { - @Autowired - SearchRepository searchRepository; - @Autowired - TopicRepository topicRepository; - @Autowired - InstanceRepository instanceRepository; - @Autowired - InstanceSearchService instanceSearchService; - @Autowired - InstanceService instanceService; - - - @Test - public void 인스턴스_검색() throws Exception { - //given - Topic topic = Topic.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .build(); - - Instance instance = Instance.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - Topic savedTopic = topicRepository.save(topic); - - createInstance(savedTopic, instance, instance.getTitle()); - createInstance(savedTopic, instance, instance.getTitle()); - createInstance(savedTopic, instance, "title"); - - //when - InstanceSearchRequest instanceSearchRequest = new InstanceSearchRequest("고리", "preactivity"); - - //then - Page orderList = instanceSearchService.searchInstances("고리", "preactivity", - PageRequest.of(0, 3)); - - for (InstanceSearchResponse instanceSearchResponse : orderList) { - System.out.println("instanceSearchResponse = " + instanceSearchResponse.getKeyword()); - } - - Assertions.assertThat(orderList.getTotalElements()).isEqualTo(2); - - } - - private void createInstance(Topic savedTopic, Instance instance, String title) throws IOException { - instanceService.createInstance( - InstanceCreateRequest.builder() - .topicId(savedTopic.getId()) - .title(title) - .tags(instance.getTags()) - .description(instance.getDescription()) - .notice(instance.getNotice()) - .pointPerPerson(instance.getPointPerPerson()) - .startedAt(instance.getStartedDate()) - .completedAt(instance.getCompletedDate()).build(), - instance.getCompletedDate().minusDays(3).toLocalDate()); - } -} diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java deleted file mode 100644 index b2a2a082..00000000 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceServiceTest.java +++ /dev/null @@ -1,291 +0,0 @@ -package com.genius.gitget.challenge.instance.service; - - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; -import com.genius.gitget.challenge.instance.dto.crud.InstanceDetailResponse; -import com.genius.gitget.challenge.instance.dto.crud.InstancePagingResponse; -import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.global.file.domain.FileType; -import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.repository.FilesRepository; -import com.genius.gitget.global.file.service.FilesService; -import com.genius.gitget.global.util.exception.BusinessException; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest -@Transactional -@Rollback -public class InstanceServiceTest { - @Autowired - InstanceService instanceService; - @Autowired - InstanceRepository instanceRepository; - @Autowired - TopicRepository topicRepository; - @Autowired - FilesService filesService; - @Autowired - FilesRepository filesRepository; - - private Instance instance, instance1, instance2; - private Topic topic; - private Files topicFiles; - private String fileType; - - @BeforeEach - public void setup() { - instance = Instance.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.now()) - .completedDate(LocalDateTime.now().plusDays(3)) - .build(); - - topic = Topic.builder() - .title("1일 1알고리즘") - .description("하루에 한 문제씩 문제를 해결합니다.") - .tags("BE, FE, CS") - .pointPerPerson(100) - .build(); - fileType = "instance"; - - } - - @Test - public void 인스턴스_생성() throws Exception { - //given - LocalDate currentDate = instance.getCompletedDate().minusDays(3).toLocalDate(); - Topic savedTopic = topicRepository.save(topic); - InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); - - //when - instanceService.createInstance(instanceCreateRequest, currentDate); - - //then - List all = instanceRepository.findAll(); - Assertions.assertThat(all.size()).isEqualTo(1); - } - - - @Test - public void 인스턴스_수정() throws Exception { - //given - LocalDate currentDate = instance.getCompletedDate().minusDays(3).toLocalDate(); - Topic savedTopic = topicRepository.save(topic); - - InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); - Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, currentDate); - - InstanceUpdateRequest instanceUpdateRequest = InstanceUpdateRequest.builder() - .topicId(savedTopic.getId()) - .description("이것은 수정본이지롱") - .pointPerPerson(instance.getPointPerPerson()) - .startedAt(instance.getStartedDate()) - .completedAt(instance.getCompletedDate()) - .build(); - - //when - Long updatedInstanceId = instanceService.updateInstance(savedInstanceId, instanceUpdateRequest); - - //then - Optional byId = instanceRepository.findById(updatedInstanceId); - Assertions.assertThat(byId.get().getDescription()).isEqualTo("이것은 수정본이지롱"); - } - - @Test - public void 인스턴스_단건_조회() throws Exception { - //given - LocalDate currentDate = instance.getCompletedDate().minusDays(3).toLocalDate(); - Topic savedTopic = topicRepository.save(topic); - - InstanceCreateRequest instanceCreateRequest = getInstanceCreateRequest(savedTopic, instance); - Long savedInstanceId = instanceService.createInstance(instanceCreateRequest, currentDate); - - //when - InstanceDetailResponse instanceById = instanceService.getInstanceById(savedInstanceId); - - //then - Assertions.assertThat(instanceById.title()).isEqualTo(instanceCreateRequest.title()); - } - - @Test - public void 인스턴스_리스트_조회() { - Topic savedTopic = getSavedTopic("1일 1알고리즘", "FE, BE"); - Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - - savedInstance1.setTopic(savedTopic); - savedInstance2.setTopic(savedTopic); - savedInstance3.setTopic(savedTopic); - - PageRequest pageRequest = PageRequest.of(0, 10); - Page allInstances = instanceService.getAllInstances(pageRequest); - - Assertions.assertThat(allInstances.getTotalElements()).isEqualTo(3); - } - - @Test - public void 특정_토픽에_대한_리스트_조회_1() { - Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); - Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); - Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - - savedInstance1.setTopic(savedTopic1); - savedInstance2.setTopic(savedTopic1); - savedInstance3.setTopic(savedTopic2); - PageRequest pageRequest = PageRequest.of(0, 10); - Page allInstancesOfSpecificTopic = instanceService.getAllInstancesOfSpecificTopic( - pageRequest, savedTopic1.getId()); - - Assertions.assertThat(allInstancesOfSpecificTopic.getTotalElements()).isEqualTo(2); - - } - - @Test - public void 특정_토픽에_대한_리스트_조회_2() { - Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); - Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); - Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - - savedInstance1.setTopic(savedTopic1); - savedInstance2.setTopic(savedTopic1); - savedInstance3.setTopic(savedTopic2); - PageRequest pageRequest = PageRequest.of(0, 10); - Page allInstancesOfSpecificTopic = instanceService.getAllInstancesOfSpecificTopic( - pageRequest, savedTopic2.getId()); - - Assertions.assertThat(allInstancesOfSpecificTopic.getTotalElements()).isEqualTo(1); - - } - - @Test - public void 인스턴스_삭제() { - Topic savedTopic1 = getSavedTopic("1일 1알고리즘", "FE, BE"); - Topic savedTopic2 = getSavedTopic("1일 1알고리즘", "FE, BE"); - Instance savedInstance1 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - Instance savedInstance2 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - Instance savedInstance3 = getSavedInstance("1일 1알고리즘", "FE, BE", 50); - - instanceService.deleteInstance(savedInstance1.getId()); - - org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, () -> { - instanceService.getInstanceById(savedInstance1.getId()); - }); - } - - private Topic getSavedTopic(String title, String tags) { - Topic topic = topicRepository.save( - Topic.builder() - .title(title) - .tags(tags) - .description("토픽 설명") - .pointPerPerson(100) - .build() - ); - return topic; - } - - private Instance getSavedInstance(String title, String tags, int participantCnt) { - LocalDateTime now = LocalDateTime.now(); - Instance instance = instanceRepository.save( - Instance.builder() - .tags(tags) - .title(title) - .description("description") - .progress(Progress.PREACTIVITY) - .pointPerPerson(100) - .certificationMethod("인증 방법") - .startedDate(now) - .completedDate(now.plusDays(1)) - .build() - ); - instance.updateParticipantCount(participantCnt); - return instance; - } - - private InstanceCreateRequest getInstanceCreateRequest(Topic savedTopic, Instance instance) { - return InstanceCreateRequest.builder() - .topicId(savedTopic.getId()) - .title(instance.getTitle()) - .tags(instance.getTags()) - .description(instance.getDescription()) - .notice(instance.getNotice()) - .pointPerPerson(instance.getPointPerPerson()) - .startedAt(instance.getStartedDate()) - .completedAt(instance.getCompletedDate()) - .build(); - } - - private Files getSavedFiles(String originalFilename, String savedFilename, String fileURL, FileType fileType) { - return filesRepository.save( - Files.builder() - .originalFilename(originalFilename) - .savedFilename(savedFilename) - .fileURI(fileURL) - .fileType(fileType) - .build() - ); - } - - @Nested - public class 인스턴스_삭제할_때 { - private Topic topic; - private Instance instance1, instance2, instance3; - - @BeforeEach - public void setup() { - topic = getSavedTopic("1일 1공부", "BE, ML"); - instance1 = getSavedInstance("1일 1공부", "BE, ML", 100); - instance2 = getSavedInstance("1일 3공부", "BE, ML", 100); - instance3 = getSavedInstance("1일 3공부", "BE, ML", 100); - instance1.setTopic(topic); - instance2.setTopic(topic); - } - - @Test - public void 해당_아이디가_존재한다면_삭제할_수_있다() { - Long id = instance1.getId(); - instanceService.deleteInstance(id); - - assertThrows(BusinessException.class, () -> { - instanceService.getInstanceById(id); - }); - } - - @Test - public void 해당_아이디가_존재하지_않는다면_삭제할_수_없다() { - Long id = instance3.getId() + 1L; - assertThrows(BusinessException.class, () -> { - instanceService.deleteInstance(id); - }); - } - } -} diff --git a/src/test/java/com/genius/gitget/challenge/instance/util/TestDTOFactory.java b/src/test/java/com/genius/gitget/challenge/instance/util/TestDTOFactory.java new file mode 100644 index 00000000..49adbafa --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/instance/util/TestDTOFactory.java @@ -0,0 +1,55 @@ +package com.genius.gitget.challenge.instance.util; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.dto.crud.InstanceCreateRequest; +import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.dto.TopicCreateRequest; +import java.time.LocalDateTime; +import org.springframework.stereotype.Component; + +@Component +public class TestDTOFactory { + public static TopicCreateRequest createTopicCreateRequest(String title, String description, String tags, + int pointPerPerson) { + return TopicCreateRequest.builder() + .title(title) + .description(description) + .tags(tags) + .pointPerPerson(pointPerPerson) + .build(); + } + + public static InstanceCreateRequest createInstanceCreateRequest(Long topicId, String title, String description, + String tags, int pointPerPerson) { + return InstanceCreateRequest.builder() + .topicId(topicId) + .title(title) + .description(description) + .tags(tags) + .pointPerPerson(pointPerPerson) + .startedAt(LocalDateTime.now()) + .completedAt(LocalDateTime.now().plusDays(3)) + .build(); + } + + public static InstanceSearchRequest createInstanceSearchRequest(String keyword, String progress) { + return InstanceSearchRequest.builder() + .keyword(keyword) + .progress(progress) + .build(); + } + + public static InstanceCreateRequest getInstanceCreateRequest(Topic savedTopic, Instance instance) { + return InstanceCreateRequest.builder() + .topicId(savedTopic.getId()) + .title(instance.getTitle()) + .tags(instance.getTags()) + .description(instance.getDescription()) + .notice(instance.getNotice()) + .pointPerPerson(instance.getPointPerPerson()) + .startedAt(instance.getStartedDate()) + .completedAt(instance.getCompletedDate()) + .build(); + } +} diff --git a/src/test/java/com/genius/gitget/challenge/instance/util/TestSetup.java b/src/test/java/com/genius/gitget/challenge/instance/util/TestSetup.java new file mode 100644 index 00000000..43603233 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/instance/util/TestSetup.java @@ -0,0 +1,100 @@ +package com.genius.gitget.challenge.instance.util; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.topic.domain.Topic; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class TestSetup { + public static List createTopicList() { + Topic topicA = Topic.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE") + .pointPerPerson(100) + .build(); + + Topic topicB = Topic.builder() + .title("이펙티브 자바") + .description("1주일에 2개 item씩 공부합니다.") + .tags("BE") + .pointPerPerson(500) + .build(); + + Topic topicC = Topic.builder() + .title("1일 1면접 준비") + .description("1일에 면접 주제 1개씩 공부합니다.") + .tags("BE, FE, CS") + .pointPerPerson(700) + .build(); + + return List.of(topicA, topicB, topicC); + } + + public static List createInstanceList() { + Instance instanceA = Instance.builder() + .title("1일 1알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE") + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now().plusDays(10)) + .completedDate(LocalDateTime.now().plusDays(30)) + .build(); + + Instance instanceB = Instance.builder() + .title("1일 3알고리즘") + .description("하루에 한 문제씩 문제를 해결합니다.") + .tags("BE, FE") + .pointPerPerson(300) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now().plusDays(10)) + .completedDate(LocalDateTime.now().plusDays(30)) + .build(); + + Instance instanceC = Instance.builder() + .title("이펙티브 자바") + .description("1주일에 2개 item씩 공부합니다.") + .tags("BE, FE, CS") + .pointPerPerson(500) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now().plusDays(10)) + .completedDate(LocalDateTime.now().plusDays(30)) + .build(); + + Instance instanceD = Instance.builder() + .title("이펙티브 자바") + .description("1주일에 1개 item씩 공부합니다.") + .tags("BE, FE, CS") + .pointPerPerson(400) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now().plusDays(10)) + .completedDate(LocalDateTime.now().plusDays(30)) + .build(); + + Instance instanceE = Instance.builder() + .title("1일 1면접 준비") + .description("1일에 면접 주제 1개씩 공부합니다.") + .tags("BE, FE, CS") + .pointPerPerson(700) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now().plusDays(10)) + .completedDate(LocalDateTime.now().plusDays(30)) + .build(); + + Instance instanceF = Instance.builder() + .title("3일 1면접 준비") + .description("1일에 면접 주제 3개씩 공부합니다.") + .tags("BE, FE, CS") + .pointPerPerson(1000) + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now().plusDays(10)) + .completedDate(LocalDateTime.now().plusDays(30)) + .build(); + + return List.of(instanceA, instanceB, instanceC, instanceD, instanceE, instanceF); + } +} diff --git a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java similarity index 99% rename from src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java rename to src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java index d9713ddb..9dc272d2 100644 --- a/src/test/java/com/genius/gitget/admin/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.admin.topic.controller; +package com.genius.gitget.topic.controller; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -8,10 +8,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.util.TokenTestUtil; import com.genius.gitget.util.WithMockCustomUser; import org.assertj.core.api.Assertions; diff --git a/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java b/src/test/java/com/genius/gitget/topic/repository/TopicRepositoryTest.java similarity index 98% rename from src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java rename to src/test/java/com/genius/gitget/topic/repository/TopicRepositoryTest.java index 4babf210..899f220a 100644 --- a/src/test/java/com/genius/gitget/admin/topic/repository/TopicRepositoryTest.java +++ b/src/test/java/com/genius/gitget/topic/repository/TopicRepositoryTest.java @@ -1,10 +1,9 @@ -package com.genius.gitget.admin.topic.repository; +package com.genius.gitget.topic.repository; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import jakarta.transaction.Transactional; import java.util.Optional; import org.junit.jupiter.api.Assertions; diff --git a/src/test/java/com/genius/gitget/admin/topic/service/TopicFacadeTest.java b/src/test/java/com/genius/gitget/topic/service/TopicFacadeTest.java similarity index 97% rename from src/test/java/com/genius/gitget/admin/topic/service/TopicFacadeTest.java rename to src/test/java/com/genius/gitget/topic/service/TopicFacadeTest.java index d1723c54..63e68b63 100644 --- a/src/test/java/com/genius/gitget/admin/topic/service/TopicFacadeTest.java +++ b/src/test/java/com/genius/gitget/topic/service/TopicFacadeTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.admin.topic.service; +package com.genius.gitget.topic.service; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,7 +7,7 @@ import com.genius.gitget.topic.dto.TopicCreateRequest; import com.genius.gitget.topic.dto.TopicDetailResponse; import com.genius.gitget.topic.dto.TopicUpdateRequest; -import com.genius.gitget.topic.serviceFacade.TopicFacade; +import com.genius.gitget.topic.facade.TopicFacade; import jakarta.transaction.Transactional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; From 0ae019e62c358d0bb27dcf260210f6041f23ce74 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:33:36 +0900 Subject: [PATCH 209/234] =?UTF-8?q?[REFACTOR]=20Item=20&=20Orders=EC=97=90?= =?UTF-8?q?=20Facade=20pattern=20=EC=A0=81=EC=9A=A9=20(#229)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Item & Orders쪽에 Facade pattern 적ㅇ용 - ItemService를 StoreFacade로 변경 - 파사드패턴에 인터페이스 적용 - ItemProvider는 ItemService로, OrdersProvider는 OrdersService로 이름 변경 * refactor: Orders 엔티티의 정적 팩토리 메서드 리팩토링 - Orders의 정적 팩토리 메서드에서 User, Item을 받아서 연관관계 설정하는 로직 추가 * feat: Payment에 정적 팩토리 메서드 추가 * refactor: StoreFacadeImpl 리팩토링 진행 중 - 백업용(StoreFacadeImpl2) 클래스 생성 - 리팩토링용(StoreFacadeImpl) 클래스 생성 * feat: StoreFacade 구현체 로직 구현 - StoreFacade 구현체(StoreFacadeService)의 오버라이딩 메서드 로직 구현 - 컨트롤러의 이름을 StoreController로 변경 - OrdersService에 아이템 사용 시 Order 객체의 상태를 변경하는 메서드 추가 * refactor: 응답 DTO의 이름 변경 - 응답 DTO의 이름을 ItemUseResponse에서 OrderResponse로 변경 * test: DCI 패턴 적용 및 Factory 클래스 작성 - StoreFacadeTest에 DCI 패턴 적용 - Store(Item, Orders), Instance, Participant, Certification에 대해 Factory 클래스 작성 * test: StoreFacadeTest에 DCI 패턴 적용한 테스트 코드 작성 - StoreFacadeTest에 DCI 패턴 적용한 테스트 코드 작성 - InstanceFactory, ParticipantFactory에 context에 따라 필요한 엔티티를 반환할 수 있도록 메서드 추가 * test: 사용하지 않는 테스트 클래스 삭제 * refactor: 코드리뷰 피드백 반영 - 생성자 대신 정적 팩토리 메서드 사용으로 변경 - 필요없는 주석 제거 * refactor: StoreFacade의 몇 메서드의 파라미터 변경 - useItem, orderItem에서 item의 id가 아닌 identifier를 받게끔 변경 - 위 변경 사항에 따라 테스트 코드들도 변경 --- .../dto/CertificationRequest.java | 4 + .../myChallenge/dto/ActivatedResponse.java | 4 +- .../myChallenge/dto/DoneResponse.java | 4 +- .../myChallenge/dto/RewardRequest.java | 4 + .../service/MyChallengeService.java | 16 +- .../gitget/challenge/user/domain/User.java | 8 + .../challenge/user/service/UserService.java | 8 +- .../global/util/exception/ErrorCode.java | 2 +- .../profile/service/ProfileService.java | 10 +- ...emController.java => StoreController.java} | 31 +- .../gitget/store/item/domain/Orders.java | 13 +- ...temUseResponse.java => OrderResponse.java} | 6 +- .../gitget/store/item/facade/StoreFacade.java | 27 + .../store/item/facade/StoreFacadeService.java | 174 ++++++ .../store/item/service/ItemProvider.java | 32 - .../store/item/service/ItemService.java | 195 +----- ...OrdersProvider.java => OrdersService.java} | 23 +- .../gitget/store/payment/domain/Payment.java | 11 + .../CertificationControllerTest.java | 4 +- .../service/CertificationServiceTest.java | 2 +- .../InstanceHomeControllerTest.java | 8 +- .../controller/InstanceControllerTest.java | 8 +- .../item/service/ItemServiceTest.java | 565 ------------------ .../likes/controller/LikesControllerTest.java | 8 +- .../service/MyChallengeServiceTest.java | 12 +- .../user/service/UserServiceTest.java | 2 +- .../security/config/SecurityConfigTest.java | 4 +- .../controller/AuthControllerTest.java | 2 +- .../controller/PaymentControllerTest.java | 6 +- .../payment/service/PaymentServiceTest.java | 10 +- .../controller/ProfileControllerTest.java | 8 +- .../gitget/store/facade/StoreFacadeTest.java | 467 +++++++++++++++ .../service/ItemServiceTest.java} | 16 +- .../service/OrdersServiceTest.java} | 28 +- .../topic/controller/TopicControllerTest.java | 4 +- .../certification/CertificationFactory.java | 22 + .../gitget/util/instance/InstanceFactory.java | 40 ++ .../util/participant/ParticipantFactory.java | 56 ++ .../util/{ => security}/TokenTestUtil.java | 2 +- .../{ => security}/WithMockCustomUser.java | 2 +- ...hMockCustomUserSecurityContextFactory.java | 2 +- .../gitget/util/store/StoreFactory.java | 25 + 42 files changed, 975 insertions(+), 900 deletions(-) rename src/main/java/com/genius/gitget/store/item/controller/{ItemController.java => StoreController.java} (71%) rename src/main/java/com/genius/gitget/store/item/dto/{ItemUseResponse.java => OrderResponse.java} (61%) create mode 100644 src/main/java/com/genius/gitget/store/item/facade/StoreFacade.java create mode 100644 src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java delete mode 100644 src/main/java/com/genius/gitget/store/item/service/ItemProvider.java rename src/main/java/com/genius/gitget/store/item/service/{OrdersProvider.java => OrdersService.java} (81%) delete mode 100644 src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java create mode 100644 src/test/java/com/genius/gitget/store/facade/StoreFacadeTest.java rename src/test/java/com/genius/gitget/{challenge/item/service/ItemProviderTest.java => store/service/ItemServiceTest.java} (88%) rename src/test/java/com/genius/gitget/{challenge/item/service/OrdersProviderTest.java => store/service/OrdersServiceTest.java} (86%) create mode 100644 src/test/java/com/genius/gitget/util/certification/CertificationFactory.java create mode 100644 src/test/java/com/genius/gitget/util/instance/InstanceFactory.java create mode 100644 src/test/java/com/genius/gitget/util/participant/ParticipantFactory.java rename src/test/java/com/genius/gitget/util/{ => security}/TokenTestUtil.java (98%) rename src/test/java/com/genius/gitget/util/{ => security}/WithMockCustomUser.java (94%) rename src/test/java/com/genius/gitget/util/{ => security}/WithMockCustomUserSecurityContextFactory.java (98%) create mode 100644 src/test/java/com/genius/gitget/util/store/StoreFactory.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationRequest.java b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationRequest.java index 08fdc9de..39381728 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationRequest.java +++ b/src/main/java/com/genius/gitget/challenge/certification/dto/CertificationRequest.java @@ -8,4 +8,8 @@ public record CertificationRequest( Long instanceId, LocalDate targetDate ) { + + public static CertificationRequest of(Long instanceId, LocalDate targetDate) { + return new CertificationRequest(instanceId, targetDate); + } } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java index 185d9545..e1c01068 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java @@ -3,14 +3,14 @@ import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.store.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.OrderResponse; import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -public class ActivatedResponse extends ItemUseResponse { +public class ActivatedResponse extends OrderResponse { private Long instanceId; private String title; private int pointPerPerson; diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java index ac8a5788..6184667c 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java @@ -5,14 +5,14 @@ import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.domain.RewardStatus; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.store.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.OrderResponse; import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter -public class DoneResponse extends ItemUseResponse { +public class DoneResponse extends OrderResponse { private Long instanceId; private String title; private int pointPerPerson; diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java index 6f82b5e4..5c13009e 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java @@ -8,4 +8,8 @@ public record RewardRequest( Long instanceId, LocalDate targetDate ) { + + public static RewardRequest of(User user, Long instanceId, LocalDate targetDate) { + return new RewardRequest(user, instanceId, targetDate); + } } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index a9467d9c..82495468 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -26,8 +26,8 @@ import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.service.ItemProvider; -import com.genius.gitget.store.item.service.OrdersProvider; +import com.genius.gitget.store.item.service.ItemService; +import com.genius.gitget.store.item.service.OrdersService; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -43,8 +43,8 @@ public class MyChallengeService { private final FilesService filesService; private final ParticipantProvider participantProvider; private final CertificationProvider certificationProvider; - private final ItemProvider itemProvider; - private final OrdersProvider ordersProvider; + private final ItemService itemService; + private final OrdersService ordersService; public List getPreActivityInstances(User user, LocalDate targetDate) { @@ -79,8 +79,8 @@ public List getDoneInstances(User user, LocalDate targetDate) { // 포인트를 아직 수령하지 않았을 때 if (participant.getRewardStatus() == NO) { - Item item = itemProvider.findAllByCategory(POINT_MULTIPLIER).get(0); - int numOfPassItem = ordersProvider.countNumOfItem(user, item.getId()); + Item item = itemService.findAllByCategory(POINT_MULTIPLIER).get(0); + int numOfPassItem = ordersService.countNumOfItem(user, item.getId()); DoneResponse doneResponse = DoneResponse.createNotRewarded( instance, participant, numOfPassItem, fileResponse); doneResponse.setItemId(item.getId()); @@ -118,8 +118,8 @@ public List getActivatedInstances(User user, LocalDate target .orElse(getDummyCertification()); //TODO: 로직 수정 필요 - Item item = itemProvider.findAllByCategory(CERTIFICATION_PASSER).get(0); - int numOfPassItem = ordersProvider.countNumOfItem(user, item.getId()); + Item item = itemService.findAllByCategory(CERTIFICATION_PASSER).get(0); + int numOfPassItem = ordersService.countNumOfItem(user, item.getId()); ActivatedResponse activatedResponse = ActivatedResponse.create( instance, certification.getCertificationStatus(), diff --git a/src/main/java/com/genius/gitget/challenge/user/domain/User.java b/src/main/java/com/genius/gitget/challenge/user/domain/User.java index b339a91d..b7367354 100644 --- a/src/main/java/com/genius/gitget/challenge/user/domain/User.java +++ b/src/main/java/com/genius/gitget/challenge/user/domain/User.java @@ -6,6 +6,8 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.domain.BaseTimeEntity; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.domain.Orders; import com.genius.gitget.store.payment.domain.Payment; import jakarta.persistence.CascadeType; @@ -111,6 +113,12 @@ public void updateGithubPersonalToken(String encryptedToken) { this.githubToken = encryptedToken; } + public void hasEnoughPoint(int cost) { + if (this.point < cost) { + throw new BusinessException(ErrorCode.NOT_ENOUGH_POINT); + } + } + public long updatePoints(Long amount) { this.point += amount; return this.point; diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 5fee73c8..40192a2c 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -16,7 +16,7 @@ import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.service.OrdersProvider; +import com.genius.gitget.store.item.service.OrdersService; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -30,7 +30,7 @@ @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; - private final OrdersProvider ordersProvider; + private final OrdersService ordersService; private final FilesService filesService; private final EncryptUtil encryptUtil; @@ -98,12 +98,12 @@ public void isAlreadyRegistered(User user) { public AuthResponse getUserAuthInfo(String identifier) { User user = userRepository.findByIdentifier(identifier) .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); - Item usingFrame = ordersProvider.getUsingFrameItem(user.getId()); + Item usingFrame = ordersService.getUsingFrameItem(user.getId()); return new AuthResponse(user.getRole(), usingFrame.getId()); } public UserProfileInfo getUserProfileInfo(User user) { - Long frameId = ordersProvider.getUsingFrameItem(user.getId()).getId(); + Long frameId = ordersService.getUsingFrameItem(user.getId()).getId(); FileResponse fileResponse = filesService.convertToFileResponse(user.getFiles()); return UserProfileInfo.createByEntity(user, frameId, fileResponse); diff --git a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java index 50631286..c1c2df38 100644 --- a/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java +++ b/src/main/java/com/genius/gitget/global/util/exception/ErrorCode.java @@ -74,7 +74,7 @@ public enum ErrorCode { NOT_ENOUGH_POINT(HttpStatus.BAD_REQUEST, "사용자의 보유 포인트가 충분하지 않습니다."), ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "아이템 정보를 찾을 수 없습니다."), - USER_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자의 아이템 보유 정보를 찾을 수 없습니다."), + ORDERS_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자의 아이템 보유 정보를 찾을 수 없습니다."), HAS_NO_ITEM(HttpStatus.NOT_FOUND, "해당 아이템을 보유하고 있지 않습니다."), CAN_NOT_USE_PASS_ITEM(HttpStatus.BAD_REQUEST, "인증 패스 아이템을 사용할 수 없는 조건입니다."), ITEM_CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "해당 카테고리에 맞는 아이템을 찾을 수 없습니다."), diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileService.java index cd977705..9e90f9dc 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileService.java @@ -6,8 +6,6 @@ import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; import static com.genius.gitget.challenge.participant.domain.JoinStatus.YES; -import com.genius.gitget.signout.Signout; -import com.genius.gitget.signout.SignoutRepository; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.user.domain.User; @@ -23,7 +21,9 @@ import com.genius.gitget.profile.dto.UserInterestResponse; import com.genius.gitget.profile.dto.UserInterestUpdateRequest; import com.genius.gitget.profile.dto.UserPointResponse; -import com.genius.gitget.store.item.service.OrdersProvider; +import com.genius.gitget.signout.Signout; +import com.genius.gitget.signout.SignoutRepository; +import com.genius.gitget.store.item.service.OrdersService; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -41,7 +41,7 @@ public class ProfileService { private final UserRepository userRepository; private final FilesService filesService; private final SignoutRepository signoutRepository; - private final OrdersProvider ordersProvider; + private final OrdersService ordersService; // 포인트 조회 public UserPointResponse getUserPoint(User user) { @@ -54,7 +54,7 @@ public UserPointResponse getUserPoint(User user) { // 사용자 정보 조회 public UserInformationResponse getUserInformation(Long userId) { User findUser = getUserById(userId); - Long frameId = ordersProvider.getUsingFrameItem(userId).getId(); + Long frameId = ordersService.getUsingFrameItem(userId).getId(); FileResponse fileResponse = filesService.convertToFileResponse(findUser.getFiles()); return UserInformationResponse.createByEntity(findUser, frameId, fileResponse); diff --git a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java b/src/main/java/com/genius/gitget/store/item/controller/StoreController.java similarity index 71% rename from src/main/java/com/genius/gitget/store/item/controller/ItemController.java rename to src/main/java/com/genius/gitget/store/item/controller/StoreController.java index 563f0954..5d58c671 100644 --- a/src/main/java/com/genius/gitget/store/item/controller/ItemController.java +++ b/src/main/java/com/genius/gitget/store/item/controller/StoreController.java @@ -7,13 +7,11 @@ import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.ListResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; -import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.dto.ItemResponse; -import com.genius.gitget.store.item.dto.ItemUseResponse; +import com.genius.gitget.store.item.dto.OrderResponse; import com.genius.gitget.store.item.dto.ProfileResponse; -import com.genius.gitget.store.item.service.ItemProvider; -import com.genius.gitget.store.item.service.ItemService; +import com.genius.gitget.store.item.facade.StoreFacade; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; @@ -29,22 +27,17 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api") -public class ItemController { - private final ItemService itemService; - private final ItemProvider itemProvider; +public class StoreController { + private final StoreFacade storeFacade; +// private final ItemService itemService; @GetMapping("/items") public ResponseEntity> getItemList( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestParam String category ) { - List itemResponses; - if (category.trim().equalsIgnoreCase("all")) { - itemResponses = itemService.getAllItems(userPrincipal.getUser()); - } else { - ItemCategory itemCategory = ItemCategory.findCategory(category); - itemResponses = itemService.getItemsByCategory(userPrincipal.getUser(), itemCategory); - } + ItemCategory itemCategory = ItemCategory.findCategory(category); + List itemResponses = storeFacade.getItemsByCategory(userPrincipal.getUser(), itemCategory); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemResponses) @@ -56,8 +49,7 @@ public ResponseEntity> purchaseItem( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable int identifier ) { - Item item = itemProvider.findByIdentifier(identifier); - ItemResponse itemResponse = itemService.orderItem(userPrincipal.getUser(), item.getId()); + ItemResponse itemResponse = storeFacade.orderItem(userPrincipal.getUser(), identifier); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemResponse) @@ -70,12 +62,11 @@ public ResponseEntity useItem( @PathVariable int identifier, @RequestParam(required = false) Long instanceId ) { - Item item = itemProvider.findByIdentifier(identifier); - ItemUseResponse itemUseResponse = itemService.useItem(userPrincipal.getUser(), item.getId(), + OrderResponse orderResponse = storeFacade.useItem(userPrincipal.getUser(), identifier, instanceId, DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( - new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemUseResponse) + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), orderResponse) ); } @@ -83,7 +74,7 @@ public ResponseEntity useItem( public ResponseEntity> unmountItem( @AuthenticationPrincipal UserPrincipal userPrincipal ) { - List profileResponses = itemService.unmountFrame(userPrincipal.getUser()); + List profileResponses = storeFacade.unmountFrame(userPrincipal.getUser()); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), profileResponses) diff --git a/src/main/java/com/genius/gitget/store/item/domain/Orders.java b/src/main/java/com/genius/gitget/store/item/domain/Orders.java index 90525c0b..b759960f 100644 --- a/src/main/java/com/genius/gitget/store/item/domain/Orders.java +++ b/src/main/java/com/genius/gitget/store/item/domain/Orders.java @@ -44,7 +44,18 @@ private Orders(int count, EquipStatus equipStatus) { this.equipStatus = equipStatus; } - public static Orders createDefault(int count, ItemCategory itemCategory) { + public static Orders of(User user, Item item) { + Orders orders; + if (item.getItemCategory() == ItemCategory.PROFILE_FRAME) { + orders = new Orders(0, EquipStatus.AVAILABLE); + } else { + orders = new Orders(0, EquipStatus.UNAVAILABLE); + } + orders.setUserAndItem(user, item); + return orders; + } + + public static Orders of(int count, ItemCategory itemCategory) { if (itemCategory == ItemCategory.PROFILE_FRAME) { return new Orders(count, EquipStatus.AVAILABLE); } diff --git a/src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java b/src/main/java/com/genius/gitget/store/item/dto/OrderResponse.java similarity index 61% rename from src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java rename to src/main/java/com/genius/gitget/store/item/dto/OrderResponse.java index c981e890..73c923d4 100644 --- a/src/main/java/com/genius/gitget/store/item/dto/ItemUseResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/OrderResponse.java @@ -5,13 +5,13 @@ @Getter @Setter -public class ItemUseResponse { +public class OrderResponse { Long itemId; - public ItemUseResponse() { + public OrderResponse() { } - public ItemUseResponse(Long itemId) { + public OrderResponse(Long itemId) { this.itemId = itemId; } } diff --git a/src/main/java/com/genius/gitget/store/item/facade/StoreFacade.java b/src/main/java/com/genius/gitget/store/item/facade/StoreFacade.java new file mode 100644 index 00000000..5402f8f8 --- /dev/null +++ b/src/main/java/com/genius/gitget/store/item/facade/StoreFacade.java @@ -0,0 +1,27 @@ +package com.genius.gitget.store.item.facade; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.dto.OrderResponse; +import com.genius.gitget.store.item.dto.ProfileResponse; +import java.time.LocalDate; +import java.util.List; + +public interface StoreFacade { + + List getItemsByCategory(User user, ItemCategory itemCategory); + + ItemResponse orderItem(User user, int itemIdentifier); + + OrderResponse useItem(User user, int itemIdentifier, Long instanceId, LocalDate currentDate); + + OrderResponse useFrameItem(Long userId, Orders orders); + + OrderResponse usePasserItem(Orders orders, Long instanceId, LocalDate currentDate); + + OrderResponse useMultiplierItem(Orders orders, Long instanceId, LocalDate currentDate); + + List unmountFrame(User user); +} diff --git a/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java b/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java new file mode 100644 index 00000000..eb9c9a76 --- /dev/null +++ b/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java @@ -0,0 +1,174 @@ +package com.genius.gitget.store.item.facade; + +import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import com.genius.gitget.challenge.certification.service.CertificationService; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; +import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; +import com.genius.gitget.challenge.myChallenge.service.MyChallengeService; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.dto.OrderResponse; +import com.genius.gitget.store.item.dto.ProfileResponse; +import com.genius.gitget.store.item.service.ItemService; +import com.genius.gitget.store.item.service.OrdersService; +import com.genius.gitget.store.payment.domain.Payment; +import com.genius.gitget.store.payment.repository.PaymentRepository; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class StoreFacadeService implements StoreFacade { + private final ItemService itemService; + private final OrdersService ordersService; + + private final UserService userService; + + private final CertificationService certificationService; + + //TODO: 책임이 분명한 Service를 만들어서 적용하기(MyChallenge 진행 시) + private final MyChallengeService myChallengeService; + + private final PaymentRepository paymentRepository; + + + @Override + public List getItemsByCategory(User user, ItemCategory itemCategory) { + List itemResponses = new ArrayList<>(); + List items = itemService.findAllByCategory(itemCategory); + for (Item item : items) { + int numOfItem = ordersService.countNumOfItem(user, item.getId()); + ItemResponse itemResponse = getItemResponse(user, item, numOfItem); + itemResponses.add(itemResponse); + } + + return itemResponses; + } + + private ItemResponse getItemResponse(User user, Item item, int numOfItem) { + if (item.getItemCategory() == ItemCategory.PROFILE_FRAME) { + EquipStatus equipStatus = ordersService.getEquipStatus(user.getId(), item.getId()); + return ProfileResponse.create(item, numOfItem, equipStatus.getTag()); + } + return ItemResponse.create(item, numOfItem); + } + + @Override + public ItemResponse orderItem(User user, int identifier) { + User persistUser = userService.findUserById(user.getId()); + Item item = itemService.findByIdentifier(identifier); + + persistUser.hasEnoughPoint(item.getCost()); + + paymentRepository.save(Payment.create(user, item)); + + Orders orders = ordersService.findOrSave(user, item); + int numOfItem = orders.purchase(); + persistUser.updatePoints((long) item.getCost() * -1); + + return getItemResponse(persistUser, item, numOfItem); + } + + @Override + public OrderResponse useItem(User user, int identifier, Long instanceId, LocalDate currentDate) { + Item item = itemService.findByIdentifier(identifier); + Orders orders = ordersService.findByOrderInfo(user.getId(), item.getId()); + + if (!orders.hasItem()) { + throw new BusinessException(ErrorCode.HAS_NO_ITEM); + } + + switch (item.getItemCategory()) { + case PROFILE_FRAME -> { + return useFrameItem(user.getId(), orders); + } + case CERTIFICATION_PASSER -> { + return usePasserItem(orders, instanceId, currentDate); + } + case POINT_MULTIPLIER -> { + return useMultiplierItem(orders, instanceId, currentDate); + } + } + throw new BusinessException(ErrorCode.ORDERS_NOT_FOUND); + } + + @Override + public OrderResponse useFrameItem(Long userId, Orders orders) { + validateFrameEquip(userId, orders); + orders.updateEquipStatus(EquipStatus.IN_USE); + + return new OrderResponse(orders.getItem().getId()); + } + + private void validateFrameEquip(Long userId, Orders orders) { + List allUsingFrames = ordersService.findAllUsingFrames(userId); + if (!allUsingFrames.isEmpty()) { + throw new BusinessException(ErrorCode.TOO_MANY_USING_FRAME); + } + if (!orders.hasItem()) { + throw new BusinessException(ErrorCode.HAS_NO_ITEM); + } + if (orders.getEquipStatus() != EquipStatus.AVAILABLE) { + throw new BusinessException(ErrorCode.INVALID_EQUIP_CONDITION); + } + } + + @Override + public OrderResponse usePasserItem(Orders orders, Long instanceId, LocalDate currentDate) { + Long userId = orders.getUser().getId(); + Long itemId = orders.getItem().getId(); + ActivatedResponse activatedResponse = certificationService.passCertification( + userId, + CertificationRequest.of(instanceId, currentDate)); + activatedResponse.setItemId(itemId); + ordersService.useItem(orders); + return activatedResponse; + } + + @Override + public OrderResponse useMultiplierItem(Orders orders, Long instanceId, LocalDate currentDate) { + User user = orders.getUser(); + DoneResponse doneResponse = myChallengeService.getRewards( + RewardRequest.of(user, instanceId, currentDate), true + ); + doneResponse.setItemId(orders.getItem().getId()); + ordersService.useItem(orders); + return doneResponse; + } + + @Override + public List unmountFrame(User user) { + List profileResponses = new ArrayList<>(); + List frameOrders = ordersService.findAllUsingFrames(user.getId()); + + for (Orders frameOrder : frameOrders) { + validateUnmountCondition(frameOrder); + frameOrder.updateEquipStatus(EquipStatus.AVAILABLE); + profileResponses.add(ProfileResponse.createByEntity(frameOrder)); + } + + return profileResponses; + } + + private void validateUnmountCondition(Orders orders) { + if (orders.getItem().getItemCategory() != ItemCategory.PROFILE_FRAME) { + throw new BusinessException(ErrorCode.ITEM_NOT_FOUND); + } + if (orders.getEquipStatus() != EquipStatus.IN_USE) { + throw new BusinessException(ErrorCode.IN_USE_FRAME_NOT_FOUND); + } + } +} diff --git a/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java b/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java deleted file mode 100644 index e85e3607..00000000 --- a/src/main/java/com/genius/gitget/store/item/service/ItemProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.genius.gitget.store.item.service; - -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.repository.ItemRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class ItemProvider { - private final ItemRepository itemRepository; - - public Item findById(Long itemId) { - return itemRepository.findById(itemId) - .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); - } - - public Item findByIdentifier(int identifier) { - return itemRepository.findByIdentifier(identifier) - .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); - } - - public List findAllByCategory(ItemCategory itemCategory) { - return itemRepository.findAllByCategory(itemCategory); - } -} diff --git a/src/main/java/com/genius/gitget/store/item/service/ItemService.java b/src/main/java/com/genius/gitget/store/item/service/ItemService.java index 39428a77..1d25130f 100644 --- a/src/main/java/com/genius/gitget/store/item/service/ItemService.java +++ b/src/main/java/com/genius/gitget/store/item/service/ItemService.java @@ -1,27 +1,10 @@ package com.genius.gitget.store.item.service; -import com.genius.gitget.challenge.certification.dto.CertificationRequest; -import com.genius.gitget.challenge.certification.service.CertificationService; -import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; -import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; -import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; -import com.genius.gitget.challenge.myChallenge.service.MyChallengeService; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; -import com.genius.gitget.store.item.domain.EquipStatus; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.dto.ItemResponse; -import com.genius.gitget.store.item.dto.ItemUseResponse; -import com.genius.gitget.store.item.dto.ProfileResponse; -import com.genius.gitget.store.payment.domain.OrderType; -import com.genius.gitget.store.payment.domain.Payment; -import com.genius.gitget.store.payment.repository.PaymentRepository; -import java.time.LocalDate; -import java.util.ArrayList; +import com.genius.gitget.store.item.repository.ItemRepository; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -31,177 +14,19 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class ItemService { - private final UserService userService; - private final ItemProvider itemProvider; - private final OrdersProvider ordersProvider; + private final ItemRepository itemRepository; - //TODO: Service를 의존하는게 맘에 들지 않는다 provider만을 의존하게 할 순 없을까? - private final CertificationService certificationService; - private final MyChallengeService myChallengeService; - - private final PaymentRepository paymentRepository; - - public List getAllItems(User user) { - List itemResponses = new ArrayList<>(); - for (ItemCategory itemCategory : ItemCategory.values()) { - itemResponses.addAll(getItemsByCategory(user, itemCategory)); - } - return itemResponses; - } - - public List getItemsByCategory(User user, ItemCategory itemCategory) { - List itemResponses = new ArrayList<>(); - List items = itemProvider.findAllByCategory(itemCategory); - - for (Item item : items) { - int numOfItem = ordersProvider.countNumOfItem(user, item.getId()); - ItemResponse itemResponse = getItemResponse(user, item, numOfItem); - itemResponses.add(itemResponse); - } - - return itemResponses; - } - - @Transactional - public ItemResponse orderItem(User user, Long itemId) { - User persistUser = userService.findUserById(user.getId()); - Item item = itemProvider.findById(itemId); - - validateUserPoint(persistUser.getPoint(), item.getCost()); - - paymentRepository.save(getPayment(user, item)); - - Orders orders = ordersProvider.findOptionalByOrderInfo(persistUser.getId(), itemId) - .orElseGet(() -> createNew(persistUser, item)); - int numOfItem = orders.purchase(); - persistUser.updatePoints((long) item.getCost() * -1); - - return getItemResponse(persistUser, item, numOfItem); - } - - private void validateUserPoint(long userPoint, int itemCost) { - if (userPoint < itemCost) { - throw new BusinessException(ErrorCode.NOT_ENOUGH_POINT); - } - } - - private Payment getPayment(User user, Item item) { - return Payment.builder() - .user(user) - .orderType(OrderType.ITEM) - .isSuccess(true) - .pointAmount(Long.parseLong(String.valueOf(item.getCost()))) - .orderName(item.getName()) - .build(); - } - - private Orders createNew(User user, Item item) { - Orders orders = Orders.createDefault(0, item.getItemCategory()); - orders.setUserAndItem(user, item); - return ordersProvider.save(orders); - } - - @Transactional - public List unmountFrame(User user) { - List profileResponses = new ArrayList<>(); - List frameOrders = ordersProvider.findAllUsingFrames(user.getId()); - - for (Orders frameOrder : frameOrders) { - validateUnmountCondition(frameOrder); - frameOrder.updateEquipStatus(EquipStatus.AVAILABLE); - profileResponses.add(ProfileResponse.createByEntity(frameOrder)); - } - - return profileResponses; - } - - private void validateUnmountCondition(Orders orders) { - if (orders.getItem().getItemCategory() != ItemCategory.PROFILE_FRAME) { - throw new BusinessException(ErrorCode.ITEM_NOT_FOUND); - } - if (orders.getEquipStatus() != EquipStatus.IN_USE) { - throw new BusinessException(ErrorCode.IN_USE_FRAME_NOT_FOUND); - } - } - - @Transactional - public ItemUseResponse useItem(User user, Long itemId, Long instanceId, LocalDate currentDate) { - Item item = itemProvider.findById(itemId); - Orders orders = ordersProvider.findByOrderInfo(user.getId(), itemId); - - if (!orders.hasItem()) { - throw new BusinessException(ErrorCode.HAS_NO_ITEM); - } - - switch (item.getItemCategory()) { - case PROFILE_FRAME -> { - return useProfileFrameItem(user.getId(), orders); - } - case CERTIFICATION_PASSER -> { - return usePasserItem(orders, instanceId, currentDate); - } - case POINT_MULTIPLIER -> { - return usePointMultiplierItem(orders, instanceId, currentDate); - } - } - throw new BusinessException(ErrorCode.USER_ITEM_NOT_FOUND); - } - - private ItemUseResponse useProfileFrameItem(Long userId, Orders orders) { - validateFrameEquip(userId, orders); - orders.updateEquipStatus(EquipStatus.IN_USE); - - Item item = orders.getItem(); - return new ItemUseResponse(item.getId()); - } - - private void validateFrameEquip(Long userId, Orders orders) { - List allUsingFrames = ordersProvider.findAllUsingFrames(userId); - if (!allUsingFrames.isEmpty()) { - throw new BusinessException(ErrorCode.TOO_MANY_USING_FRAME); - } - - if (!orders.hasItem()) { - throw new BusinessException(ErrorCode.HAS_NO_ITEM); - } - if (orders.getEquipStatus() != EquipStatus.AVAILABLE) { - throw new BusinessException(ErrorCode.INVALID_EQUIP_CONDITION); - } - } - - private ItemUseResponse usePasserItem(Orders orders, Long instanceId, LocalDate currentDate) { - Long userId = orders.getUser().getId(); - Long itemId = orders.getItem().getId(); - ActivatedResponse activatedResponse = certificationService.passCertification( - userId, - new CertificationRequest(instanceId, currentDate)); - activatedResponse.setItemId(itemId); - useItem(orders); - return activatedResponse; - } - - private ItemUseResponse usePointMultiplierItem(Orders orders, Long instanceId, LocalDate currentDate) { - User user = orders.getUser(); - DoneResponse doneResponse = myChallengeService.getRewards( - new RewardRequest(user, instanceId, currentDate), true - ); - doneResponse.setItemId(orders.getItem().getId()); - useItem(orders); - return doneResponse; + public Item findById(Long itemId) { + return itemRepository.findById(itemId) + .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); } - private void useItem(Orders orders) { - orders.useItem(); - if (!orders.hasItem()) { - ordersProvider.delete(orders); - } + public Item findByIdentifier(int identifier) { + return itemRepository.findByIdentifier(identifier) + .orElseThrow(() -> new BusinessException(ErrorCode.ITEM_NOT_FOUND)); } - private ItemResponse getItemResponse(User user, Item item, int numOfItem) { - if (item.getItemCategory() == ItemCategory.PROFILE_FRAME) { - EquipStatus equipStatus = ordersProvider.getEquipStatus(user.getId(), item.getId()); - return ProfileResponse.create(item, numOfItem, equipStatus.getTag()); - } - return ItemResponse.create(item, numOfItem); + public List findAllByCategory(ItemCategory itemCategory) { + return itemRepository.findAllByCategory(itemCategory); } } diff --git a/src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java b/src/main/java/com/genius/gitget/store/item/service/OrdersService.java similarity index 81% rename from src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java rename to src/main/java/com/genius/gitget/store/item/service/OrdersService.java index a09918ba..5f05f9b3 100644 --- a/src/main/java/com/genius/gitget/store/item/service/OrdersProvider.java +++ b/src/main/java/com/genius/gitget/store/item/service/OrdersService.java @@ -1,6 +1,6 @@ package com.genius.gitget.store.item.service; -import static com.genius.gitget.global.util.exception.ErrorCode.USER_ITEM_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.ORDERS_NOT_FOUND; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.util.exception.BusinessException; @@ -19,7 +19,7 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class OrdersProvider { +public class OrdersService { private final OrdersRepository ordersRepository; @@ -39,17 +39,17 @@ public Optional findOptionalByOrderInfo(Long userId, Long itemId) { public Orders findByOrderInfo(Long userId, Long itemId) { return ordersRepository.findByOrderInfo(userId, itemId) - .orElseThrow(() -> new BusinessException(USER_ITEM_NOT_FOUND)); + .orElseThrow(() -> new BusinessException(ORDERS_NOT_FOUND)); } - - public List findAllByCategory(Long userId, ItemCategory itemCategory) { - return ordersRepository.findAllByCategory(userId, itemCategory); + public Orders findOrSave(User user, Item item) { + return ordersRepository.findByOrderInfo(user.getId(), item.getId()) + .orElseGet(() -> ordersRepository.save(Orders.of(user, item))); } public List findAllUsingFrames(Long userId) { - return findAllByCategory(userId, ItemCategory.PROFILE_FRAME) - .stream() + List frames = ordersRepository.findAllByCategory(userId, ItemCategory.PROFILE_FRAME); + return frames.stream() .filter(frameOrder -> frameOrder.getEquipStatus() == EquipStatus.IN_USE) .toList(); } @@ -76,6 +76,13 @@ public Item getUsingFrameItem(Long userId) { return usingFrames.get(0).getItem(); } + public void useItem(Orders orders) { + orders.useItem(); + if (!orders.hasItem()) { + delete(orders); + } + } + public int countNumOfItem(User user, Long itemId) { Optional optionalUserItem = ordersRepository.findByOrderInfo(user.getId(), itemId); return optionalUserItem.map(Orders::getCount) diff --git a/src/main/java/com/genius/gitget/store/payment/domain/Payment.java b/src/main/java/com/genius/gitget/store/payment/domain/Payment.java index 994871f3..f8b7ee6f 100644 --- a/src/main/java/com/genius/gitget/store/payment/domain/Payment.java +++ b/src/main/java/com/genius/gitget/store/payment/domain/Payment.java @@ -2,6 +2,7 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.util.domain.BaseTimeEntity; +import com.genius.gitget.store.item.domain.Item; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -70,6 +71,16 @@ public Payment(String orderId, String paymentKey, Long amount, Long pointAmount, this.orderType = orderType; } + public static Payment create(User user, Item item) { + return Payment.builder() + .user(user) + .orderType(OrderType.ITEM) + .isSuccess(true) + .pointAmount(Long.parseLong(String.valueOf(item.getCost()))) + .orderName(item.getName()) + .build(); + } + public void setPaymentSuccessStatus(String paymentKey, boolean isSuccess) { this.paymentKey = paymentKey; this.isSuccess = isSuccess; diff --git a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java index a87c8088..6ba5624c 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java @@ -13,8 +13,8 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.util.security.TokenTestUtil; +import com.genius.gitget.util.security.WithMockCustomUser; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index 842ac522..2814b344 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -709,7 +709,7 @@ private Orders getSavedOrder(User user, ItemCategory itemCategory, int count) { Item item = itemRepository.save(Item.builder() .itemCategory(itemCategory) .build()); - Orders orders = Orders.createDefault(count, itemCategory); + Orders orders = Orders.of(count, itemCategory); orders.setItem(item); orders.setUser(user); return ordersRepository.save(orders); diff --git a/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java b/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java index 73d1918f..22f67a3e 100644 --- a/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/home/controller/InstanceHomeControllerTest.java @@ -5,13 +5,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import com.genius.gitget.util.security.TokenTestUtil; +import com.genius.gitget.util.security.WithMockCustomUser; import java.time.LocalDateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java index 4a35f7b3..59a209eb 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java @@ -8,15 +8,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.global.file.service.FilesService; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import com.genius.gitget.util.security.TokenTestUtil; +import com.genius.gitget.util.security.WithMockCustomUser; import java.time.LocalDateTime; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java b/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java deleted file mode 100644 index 11d199f3..00000000 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemServiceTest.java +++ /dev/null @@ -1,565 +0,0 @@ -package com.genius.gitget.challenge.item.service; - -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.genius.gitget.challenge.certification.domain.CertificateStatus; -import com.genius.gitget.challenge.certification.domain.Certification; -import com.genius.gitget.challenge.certification.repository.CertificationRepository; -import com.genius.gitget.challenge.certification.util.DateUtil; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.participant.domain.JoinResult; -import com.genius.gitget.challenge.participant.domain.JoinStatus; -import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.repository.ParticipantRepository; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; -import com.genius.gitget.store.item.domain.EquipStatus; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.dto.ItemResponse; -import com.genius.gitget.store.item.dto.ItemUseResponse; -import com.genius.gitget.store.item.dto.ProfileResponse; -import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.store.item.repository.OrdersRepository; -import com.genius.gitget.store.item.service.ItemService; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.EnumSource.Mode; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@SpringBootTest -@Transactional -class ItemServiceTest { - @Autowired - private ItemService itemService; - @Autowired - private UserRepository userRepository; - @Autowired - private ItemRepository itemRepository; - @Autowired - private OrdersRepository ordersRepository; - @Autowired - private InstanceRepository instanceRepository; - @Autowired - private ParticipantRepository participantRepository; - @Autowired - private CertificationRepository certificationRepository; - - @Test - @DisplayName("데이터베이스에 저장되어 있는 모든 아이템 정보들을 받아올 수 있다.") - public void should_getAllItems_when_itemsSaved() { - //given - User user = getSavedUser(); - - //when - List items = itemService.getAllItems(user); - - //then - assertThat(items.size()).isGreaterThan(1); - } - - @ParameterizedTest - @DisplayName("카테고리에 해당하는 아이템들을 받아올 수 있다.") - @EnumSource(ItemCategory.class) - public void should_getItems_when_passCategory(ItemCategory itemCategory) { - //given - User user = getSavedUser(); - Item item = getSavedItem(itemCategory); - Orders orders = getSavedOrder(user, item, itemCategory, 1); - - //when - List itemResponses = itemService.getItemsByCategory(user, itemCategory); - - //then - for (ItemResponse itemResponse : itemResponses) { - assertThat(itemResponse.getName()).contains(itemCategory.getName()); - assertThat(itemResponse.getDetails()).isNotBlank(); - } - } - - @ParameterizedTest - @DisplayName("사용자의 포인트가 충분할 때, itemId(PK)를 전달하여 아이템을 구매할 수 있다.") - @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) - public void should_purchaseItem_when_passPK(ItemCategory itemCategory) { - //given - User user = getSavedUser(); - Item item = getSavedItem(itemCategory); - - user.updatePoints(1000L); - - //when - ItemResponse itemResponse = itemService.orderItem(user, item.getId()); - - //then - assertThat(itemResponse.getItemId()).isEqualTo(item.getIdentifier()); - assertThat(itemResponse.getName()).isEqualTo(item.getName()); - assertThat(itemResponse.getCost()).isEqualTo(item.getCost()); - assertThat(itemResponse.getCount()).isEqualTo(1); - } - - @ParameterizedTest - @DisplayName("사용자의 포인트가 충분하지 않을 때, 아이템 구매를 시도하면 예외가 발생해야 한다.") - @EnumSource(ItemCategory.class) - public void should_throwException_when_pointNotEnough(ItemCategory itemCategory) { - //given - User user = getSavedUser(); - Item item = getSavedItem(itemCategory); - - //when & then - assertThatThrownBy(() -> itemService.orderItem(user, item.getId())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.NOT_ENOUGH_POINT.getMessage()); - } - - @Test - @DisplayName("UserItem 정보는 있으나 아이템의 개수가 0 이하일 때 인증 패스를 요청하면 예외가 발생해야 한다.") - public void should_throwException_when_outOfStock() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - Orders orders = getSavedOrder(user, item, ItemCategory.CERTIFICATION_PASSER, 0); - - instance.updateProgress(Progress.ACTIVITY); - - //when && then - assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.HAS_NO_ITEM.getMessage()); - } - - @Test - @DisplayName("UserItem 정보가 DB에 존재하지 않을 때 인증 패스를 요청하면 예외가 발생해야 한다.") - public void should_throwException_when_userItemInfoNotExist() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - - //when - getSavedCertification(NOT_YET, currentDate, participant); - - //then - assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.USER_ITEM_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("프로필 프레임을 구매했는데 장착하지 않은 경우에는 프레임을 사용할 수 있다.") - public void should_useFrameItem_when_availableToEquip() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); - - //when - orders.updateEquipStatus(EquipStatus.AVAILABLE); - ItemUseResponse itemUseResponse = itemService.useItem(user, item.getId(), instance.getId(), currentDate); - - //then - assertThat(orders.getEquipStatus()).isEqualTo(EquipStatus.IN_USE); - } - - @Test - @DisplayName("프로필 프레임을 재구매시도할 경우 예외가 발생해야 한다.") - public void should_throwException_when_tryOrderFrameAgain() { - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - - user.updatePoints(1000L); - - // when - itemService.orderItem(user, item.getId()); - - //when & then - assertThatThrownBy(() -> itemService.orderItem(user, item.getId())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.ALREADY_PURCHASED.getMessage()); - } - - @Test - @DisplayName("프로필 프레임을 사용하려 할 때, 해당 프레임에 대한 수량이 0이라면 예외가 발생해야 한다.") - public void should_throwException_when_dontHaveItem() { - //given - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 0); - - //when && then - assertThatThrownBy(() -> itemService.useItem(user, item.getId(), 0L, LocalDate.now())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.HAS_NO_ITEM.getMessage()); - } - - @Test - @DisplayName("프로필 프레임을 사용하려 할 때, 프레임의 장착 상태가 Unavailable인 경우 장착 가능 상태가 아니라는 예외가 발생한다.") - public void should_throwException_when_notAvailable() { - //given - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - Orders orders = getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 3); - - orders.updateEquipStatus(EquipStatus.UNAVAILABLE); - - //when && then - assertThatThrownBy(() -> itemService.useItem(user, item.getId(), 0L, LocalDate.now())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.INVALID_EQUIP_CONDITION.getMessage()); - } - - @Test - @DisplayName("프로필 프레임을 사용하려할 때, 장착 중인 프레임이 있는 경우 장착 해제를 먼저 실행하라는 예외가 발생한다.") - public void should_throwException_when_inUseItemExist() { - //given - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - Orders orders = getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 3); - - orders.updateEquipStatus(EquipStatus.IN_USE); - - //when && then - assertThatThrownBy(() -> itemService.useItem(user, item.getId(), 0L, LocalDate.now())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.TOO_MANY_USING_FRAME.getMessage()); - } - - @Test - @DisplayName("인증 패스 아이템을 보유하고 있을 떄, 인증을 시도했으나 실패했을 때 아이템을 사용할 수 있다.") - public void should_usePasserItem_when_ableToUsePassItem() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); - - //when - instance.updateProgress(Progress.ACTIVITY); - Certification certification = getSavedCertification(NOT_YET, currentDate, participant); - ItemUseResponse itemUseResponse = itemService.useItem(user, item.getId(), instance.getId(), currentDate); - - //then - assertThat(orders.getCount()).isEqualTo(0); - assertThat(certification.getCertificationStatus()).isEqualTo(PASSED); - } - - @Test - @DisplayName("인증 패스 아이템을 보유하고 있고, 인증을 아직 시도하지 않았을 때 아이템을 사용할 수 있다.") - public void should_usePasserItem_when_useItemNotYet() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); - - //when - instance.updateProgress(Progress.ACTIVITY); - - //then - ItemUseResponse itemUseResponse = itemService.useItem(user, item.getId(), instance.getId(), currentDate); - - //then - Optional certification = certificationRepository.findByDate(currentDate, participant.getId()); - assertThat(orders.getCount()).isEqualTo(0); - assertThat(certification).isPresent(); - assertThat(certification.get().getCertificationStatus()).isEqualTo(PASSED); - } - - @Test - @DisplayName("인증 패스 아이템을 보유하고 있으나, 인스턴스의 상태가 ACTIVITY가 아니라면 사용 시도 시 예외가 발생해야 한다.") - public void should_throwException_when_ProgressIsNotActivity() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); - - //when & then - assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.NOT_ACTIVITY_INSTANCE.getMessage()); - } - - @ParameterizedTest - @DisplayName("인증 패스 아이템을 보유하고 있으나, 인증 상태가 PASSED 혹은 CERTIFICATED라면 예외가 발생해야 한다.") - @EnumSource(mode = Mode.INCLUDE, names = {"CERTIFICATED", "PASSED"}) - public void should_throwException_when_notNOT_YET(CertificateStatus certificateStatus) { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - getSavedOrder(user, item, item.getItemCategory(), 1); - - //when - instance.updateProgress(Progress.ACTIVITY); - getSavedCertification(certificateStatus, currentDate, participant); - - //then - assertThatThrownBy(() -> itemService.useItem(user, item.getId(), instance.getId(), currentDate)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.CAN_NOT_USE_PASS_ITEM.getMessage()); - } - - @Test - @DisplayName("인스턴스의 상태가 DONE이고 2배 획득 아이템을 보유하고 있을 때, 포인트 보상을 2배로 받을 수 있다.") - public void should_getRewardTwice_when_conditionMatches() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(currentDate, currentDate.plusDays(1)); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.POINT_MULTIPLIER); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); - - //when - Long previousPoint = user.getPoint(); - instance.updateProgress(Progress.DONE); - participant.updateJoinResult(JoinResult.SUCCESS); - getSavedCertification(CERTIFICATED, currentDate, participant); - getSavedCertification(CERTIFICATED, currentDate.plusDays(1), participant); - itemService.useItem(user, item.getId(), instance.getId(), currentDate.plusDays(1)); - Long afterRewards = user.getPoint(); - - //then - assertThat(afterRewards - previousPoint).isEqualTo(instance.getPointPerPerson() * 2L); - assertThat(orders.getCount()).isEqualTo(0); - } - - @Test - @DisplayName("포인트 2배 획득 아이템을 사용해서 아이템의 개수가 0이 되면 Orders 정보를 DB에서 삭제된다.") - public void should_deleteOrderInfo_when_countZeroAfterUseItem() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(currentDate, currentDate.plusDays(1)); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.POINT_MULTIPLIER); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); - - //when - instance.updateProgress(Progress.DONE); - participant.updateJoinResult(JoinResult.SUCCESS); - getSavedCertification(CERTIFICATED, currentDate, participant); - getSavedCertification(CERTIFICATED, currentDate.plusDays(1), participant); - itemService.useItem(user, item.getId(), instance.getId(), currentDate.plusDays(1)); - - //then - assertThat(ordersRepository.findById(orders.getId())).isNotPresent(); - } - - @Test - @DisplayName("인증 패스 아이템을 사용해서 아이템의 개수가 0이 되면 Orders 정보를 DB에서 삭제된다.") - public void should_deleteOrderInfo_when_passItemCountIsZero() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(currentDate, currentDate.plusDays(1)); - Participant participant = getSavedParticipant(user, instance); - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); - - //when - instance.updateProgress(Progress.ACTIVITY); - itemService.useItem(user, item.getId(), instance.getId(), currentDate.plusDays(1)); - - //then - assertThat(ordersRepository.findById(orders.getId())).isNotPresent(); - } - - @Test - @DisplayName("사용자가 특정 프로필 프레임을 장착하고 있을 떄, 장착 해제할 수 있다.") - public void should_unmountFrame_when_mountAlready() { - //given - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - getSavedOrder(user, item, ItemCategory.PROFILE_FRAME, 1); - - //when - itemService.useItem(user, item.getId(), 0L, LocalDate.now()); - ProfileResponse profileResponse = itemService.unmountFrame(user).get(0); - - //then - assertThat(profileResponse.getItemId()).isEqualTo(item.getIdentifier()); - assertThat(profileResponse.getCost()).isEqualTo(item.getCost()); - assertThat(profileResponse.getItemCategory()).isEqualTo(ItemCategory.PROFILE_FRAME); - assertThat(profileResponse.getEquipStatus()).isEqualTo(EquipStatus.AVAILABLE.getTag()); - } - - @ParameterizedTest - @DisplayName("사용자가 아이템 장착 해제를 요청했을 때, 프로필 프레임이 아니라면 응답 데이터의 크기가 0이다.") - @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) - public void should_throwException_when_categoryIsNotFrame(ItemCategory itemCategory) { - //given - User user = getSavedUser(); - Item item = getSavedItem(itemCategory); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 1); - - //when - List profileResponses = itemService.unmountFrame(user); - - //when & then - assertThat(profileResponses.size()).isEqualTo(0); - } - - @Test - @DisplayName("사용자가 아이템 장착 해제를 요청했을 때, 사용 상태가 IN_USE가 아니라면 반환데이터의 크기가 0이다.") - public void should_throwException_when_equipStatusIsNotIS_USE() { - //given - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - getSavedOrder(user, item, item.getItemCategory(), 1); - - // when - List profileResponses = itemService.unmountFrame(user); - - //when & then - assertThat(profileResponses.size()).isEqualTo(0); - } - - private User getSavedUser() { - return userRepository.save( - User.builder() - .role(Role.USER) - .nickname("nickname") - .providerInfo(ProviderInfo.GITHUB) - .identifier("identifier") - .build() - ); - } - - private Item getSavedItem(ItemCategory itemCategory) { - return itemRepository.save(Item.builder() - .identifier(10) - .itemCategory(itemCategory) - .cost(100) - .name(itemCategory.getName()) - .details("details") - .build()); - } - - private Orders getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { - Orders orders = Orders.createDefault(count, itemCategory); - orders.setItem(item); - orders.setUser(user); - return ordersRepository.save(orders); - } - - private Instance getSavedInstance() { - return instanceRepository.save( - Instance.builder() - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) - .completedDate(LocalDateTime.of(2024, 3, 29, 0, 0)) - .build() - ); - } - - private Instance getSavedInstance(LocalDate startDate, LocalDate completeDate) { - return instanceRepository.save( - Instance.builder() - .progress(Progress.PREACTIVITY) - .startedDate(startDate.atTime(0, 0)) - .completedDate(completeDate.atTime(0, 0)) - .build() - ); - } - - private Participant getSavedParticipant(User user, Instance instance) { - Participant participant = participantRepository.save( - Participant.builder() - .joinResult(JoinResult.PROCESSING) - .joinStatus(JoinStatus.YES) - .build() - ); - participant.setUserAndInstance(user, instance); - participant.updateRepository("targetRepo"); - - return participant; - } - - private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, - Participant participant) { - int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); - Certification certification = Certification.builder() - .certificationStatus(status) - .currentAttempt(attempt) - .certificatedAt(certificatedAt) - .certificationLinks("certificationLink") - .build(); - certification.setParticipant(participant); - return certificationRepository.save(certification); - } - - @Nested - class 유저_포인트가_충분할_때 { - - @ParameterizedTest - @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) - public void 아이템을_구매할_수_있다_1(ItemCategory itemCategory) { - User user = getSavedUser(); - Item item = getSavedItem(itemCategory); - getSavedOrder(user, item, itemCategory, 0); - user.updatePoints(1000L); - - ItemResponse itemResponse = itemService.orderItem(user, item.getId()); - - assertThat(itemResponse.getItemCategory()).isEqualTo(itemCategory); - } - - @ParameterizedTest - @EnumSource(mode = Mode.EXCLUDE, names = {"PROFILE_FRAME"}) - public void 아이템을_구매할_수_있다_2(ItemCategory itemCategory) { - User user = getSavedUser(); - Item item = getSavedItem(itemCategory); - getSavedOrder(user, item, itemCategory, 0); - user.updatePoints(1000L); - - ItemResponse itemResponse1 = itemService.orderItem(user, item.getId()); - - assertThat(user.getPoint()).isEqualTo(900L); - assertThat(itemResponse1.getCount()).isEqualTo(1); - - ItemResponse itemResponse2 = itemService.orderItem(user, item.getId()); - - assertThat(user.getPoint()).isEqualTo(800L); - assertThat(itemResponse2.getCount()).isEqualTo(2); - } - } -} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java index 24267550..8a3929d1 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java @@ -9,8 +9,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; @@ -23,8 +21,10 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import com.genius.gitget.util.security.TokenTestUtil; +import com.genius.gitget.util.security.WithMockCustomUser; import java.time.LocalDateTime; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java index 3ec0df6a..45411551 100644 --- a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java @@ -11,11 +11,6 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.store.item.repository.OrdersRepository; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; @@ -28,6 +23,11 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -256,7 +256,7 @@ private Item getSavedItem(ItemCategory itemCategory) { } private Orders getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { - Orders orders = Orders.createDefault(count, itemCategory); + Orders orders = Orders.of(count, itemCategory); orders.setItem(item); orders.setUser(user); return ordersRepository.save(orders); diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java index d90d857c..ea738091 100644 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java @@ -271,7 +271,7 @@ private Item getSavedItem(ItemCategory itemCategory) { } private Orders getSavedOrders(User user, Item item) { - Orders orders = Orders.createDefault(1, item.getItemCategory()); + Orders orders = Orders.of(1, item.getItemCategory()); orders.setUser(user); orders.setItem(item); return ordersRepository.save(orders); diff --git a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java index c1ad59d9..412d66cf 100644 --- a/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java +++ b/src/test/java/com/genius/gitget/global/security/config/SecurityConfigTest.java @@ -5,8 +5,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.util.security.TokenTestUtil; +import com.genius.gitget.util.security.WithMockCustomUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java b/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java index f03a1890..b2474504 100644 --- a/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java +++ b/src/test/java/com/genius/gitget/global/security/controller/AuthControllerTest.java @@ -2,7 +2,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import com.genius.gitget.util.TokenTestUtil; +import com.genius.gitget.util.security.TokenTestUtil; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java index 1fcf0b4f..fa7b9f97 100644 --- a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java +++ b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java @@ -7,10 +7,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.global.file.service.FilesService; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.topic.repository.TopicRepository; +import com.genius.gitget.util.security.TokenTestUtil; +import com.genius.gitget.util.security.WithMockCustomUser; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java index cec5771c..68f70e9f 100644 --- a/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java +++ b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java @@ -4,7 +4,6 @@ import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; import static org.assertj.core.api.Assertions.assertThat; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.likes.repository.LikesRepository; import com.genius.gitget.challenge.likes.service.LikesService; @@ -18,15 +17,16 @@ import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.domain.Orders; import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.facade.StoreFacade; import com.genius.gitget.store.item.repository.ItemRepository; import com.genius.gitget.store.item.repository.OrdersRepository; -import com.genius.gitget.store.item.service.ItemService; import com.genius.gitget.store.payment.domain.Payment; import com.genius.gitget.store.payment.dto.PaymentDetailsResponse; import com.genius.gitget.store.payment.dto.PaymentRequest; import com.genius.gitget.store.payment.dto.PaymentResponse; import com.genius.gitget.store.payment.repository.PaymentRepository; import com.genius.gitget.store.payment.service.PaymentService; +import com.genius.gitget.topic.repository.TopicRepository; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; @@ -63,7 +63,7 @@ public class PaymentServiceTest { @Autowired private PaymentRepository paymentRepository; @Autowired - private ItemService itemService; + private StoreFacade storeFacade; @Autowired private ItemRepository itemRepository; @Autowired @@ -89,7 +89,7 @@ private User getSavedUser() { getSavedOrder(user, item, itemCategory, 0); user.updatePoints(1000L); - ItemResponse itemResponse = itemService.orderItem(user, item.getId()); + ItemResponse itemResponse = storeFacade.orderItem(user, item.getIdentifier()); assertThat(itemResponse.getItemCategory()).isEqualTo(itemCategory); Page paymentDetails = paymentService.getPaymentDetails(user, PageRequest.of(0, 10)); @@ -115,7 +115,7 @@ private Item getSavedItem(ItemCategory itemCategory) { } private Orders getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { - Orders orders = Orders.createDefault(count, itemCategory); + Orders orders = Orders.of(count, itemCategory); orders.setItem(item); orders.setUser(user); return ordersRepository.save(orders); diff --git a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java index dba3d842..fbfa2751 100644 --- a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java +++ b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java @@ -8,8 +8,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; @@ -21,8 +19,10 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import com.genius.gitget.util.security.TokenTestUtil; +import com.genius.gitget.util.security.WithMockCustomUser; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/test/java/com/genius/gitget/store/facade/StoreFacadeTest.java b/src/test/java/com/genius/gitget/store/facade/StoreFacadeTest.java new file mode 100644 index 00000000..8e61297b --- /dev/null +++ b/src/test/java/com/genius/gitget/store/facade/StoreFacadeTest.java @@ -0,0 +1,467 @@ +package com.genius.gitget.store.facade; + +import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REWARDED; +import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_GET_REWARDS; +import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_USE_PASS_ITEM; +import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; +import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; +import static com.genius.gitget.store.item.domain.ItemCategory.PROFILE_FRAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.domain.RewardStatus; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.dto.ItemResponse; +import com.genius.gitget.store.item.dto.ProfileResponse; +import com.genius.gitget.store.item.facade.StoreFacade; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; +import com.genius.gitget.util.certification.CertificationFactory; +import com.genius.gitget.util.instance.InstanceFactory; +import com.genius.gitget.util.participant.ParticipantFactory; +import com.genius.gitget.util.store.StoreFactory; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +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 org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class StoreFacadeTest { + private User user; + private LocalDate currentDate = LocalDate.now(); + + @Autowired + private StoreFacade storeFacade; + @Autowired + private UserRepository userRepository; + @Autowired + private ItemRepository itemRepository; + @Autowired + private OrdersRepository ordersRepository; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private CertificationRepository certificationRepository; + + + @BeforeEach + void setup() { + user = userRepository.save( + User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("identifier") + .build() + ); + + } + + @Nested + @DisplayName("아이템 목록 조회 시") + class describe_get_item_list { + @Nested + @DisplayName("카테고리 별로 조회를 하면") + class context_inquiry_by_category { + @ParameterizedTest + @DisplayName("카테고리에 해당하는 아이템들을 받아올 수 있다.") + @EnumSource(ItemCategory.class) + public void it_returns_item_list(ItemCategory itemCategory) { + Item item = itemRepository.save(StoreFactory.createItem(itemCategory)); + ordersRepository.save(StoreFactory.createOrders(user, item, itemCategory, 1)); + + List itemResponses = storeFacade.getItemsByCategory(user, itemCategory); + + for (ItemResponse itemResponse : itemResponses) { + assertThat(itemResponse.getName()).contains(itemCategory.getName()); + assertThat(itemResponse.getDetails()).isNotBlank(); + } + } + } + } + + @Nested + @DisplayName("아이템 구매 시") + class describe_purchase_item { + @Nested + @DisplayName("사용자의 포인트가 충분하다면") + class context_user_have_enough_point { + @ParameterizedTest + @DisplayName("아이템을 구매할 수 있다.") + @EnumSource(ItemCategory.class) + public void it_returns_200(ItemCategory itemCategory) { + Item item = itemRepository.save(StoreFactory.createItem(itemCategory)); + user.updatePoints(1000L); + + ItemResponse itemResponse = storeFacade.orderItem(user, item.getIdentifier()); + + assertThat(itemResponse.getItemId()).isEqualTo(item.getIdentifier()); + assertThat(itemResponse.getName()).isEqualTo(item.getName()); + assertThat(itemResponse.getCost()).isEqualTo(item.getCost()); + assertThat(itemResponse.getCount()).isEqualTo(1); + } + } + + @Nested + @DisplayName("사용자의 포인트가 충분하지 않다면") + class context_user_have_not_enough_point { + @ParameterizedTest + @DisplayName("NOT_ENOUGH_POINT 예외가 발생한다.") + @EnumSource(ItemCategory.class) + public void it_throws_NOT_ENOUGH_POINT_exception(ItemCategory itemCategory) { + Item item = itemRepository.save(StoreFactory.createItem(itemCategory)); + + assertThatThrownBy(() -> storeFacade.orderItem(user, item.getIdentifier())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.NOT_ENOUGH_POINT.getMessage()); + } + } + } + + @Nested + @DisplayName("아이템 사용 시") + class describe_use_item { + Item item; + Instance instance; + + @BeforeEach + void setup() { + instance = instanceRepository.save(InstanceFactory.createPreActivity(10)); + } + + @Nested + @DisplayName("카테고리에 상관없이 Orders의 정보를 DB에서 조회했을 때") + class context_inquiry_orders { + @ParameterizedTest + @DisplayName("정보는 존재하지만 count가 0개 이하일 때, HAS_NO_ITEM 예외를 발생한다") + @EnumSource(ItemCategory.class) + public void it_throws_HAS_NO_ITEM_exception(ItemCategory itemCategory) { + item = itemRepository.save(StoreFactory.createItem(itemCategory)); + ordersRepository.save(StoreFactory.createOrders(user, item, itemCategory, 0)); + + assertThatThrownBy(() -> storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.HAS_NO_ITEM.getMessage()); + } + + @ParameterizedTest + @DisplayName("정보가 존재하지 않을 때 ORDERS_NOT_FOUND 예외가 발생한다.") + @EnumSource(ItemCategory.class) + public void it_throws_ORDERS_NOT_FOUND_exception(ItemCategory itemCategory) { + item = itemRepository.save(StoreFactory.createItem(itemCategory)); + + assertThatThrownBy(() -> storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.ORDERS_NOT_FOUND.getMessage()); + } + } + } + + @Nested + @DisplayName("인증 패스 아이템 사용 시") + class describe_use_certification_pass_item { + Instance instance; + Participant participant; + Item item; + Orders orders; + + @BeforeEach + void setup() { + item = itemRepository.save(StoreFactory.createItem(CERTIFICATION_PASSER)); + orders = ordersRepository.save(StoreFactory.createOrders(user, item, CERTIFICATION_PASSER, 5)); + } + + @Nested + @DisplayName("아이템을 가지고 있고, 인증 상태를 조회했을 때") + class context_has_item_and_check_certification_status { + @BeforeEach + void setup() { + instance = instanceRepository.save(InstanceFactory.createActivity(10)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + } + + @Test + @DisplayName("인증 정보가 존재하지 않는 경우 아이템을 사용할 수 있다.") + public void it_returns_200_when_certification_not_exist() { + int holding = orders.getCount(); + storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate); + assertThat(orders.getCount()).isEqualTo(holding - 1); + } + + @Test + @DisplayName("인증 정보는 있으나 NOT_YET인 경우 아이템을 사용할 수 있다.") + public void it_returns_200_when_certification_is_NOT_YET() { + int holding = orders.getCount(); + certificationRepository.save( + CertificationFactory.create(CertificateStatus.NOT_YET, currentDate, participant) + ); + storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate); + + assertThat(orders.getCount()).isEqualTo(holding - 1); + } + + @ParameterizedTest + @DisplayName("인증 정보는 있으나 CERTIFICATED 혹은 PASSED 라면 예외가 발생한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"CERTIFICATED", "PASSED"}) + public void it_throws_exception_status_is_CERTIFICATED_or_PASSED(CertificateStatus certificateStatus) { + certificationRepository.save( + CertificationFactory.create(certificateStatus, currentDate, participant) + ); + assertThatThrownBy(() -> storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_USE_PASS_ITEM.getMessage()); + } + } + + @Nested + @DisplayName("아이템을 가지고 있고, 인스턴스 상태를 조회했을 때") + class context_has_item_and_check_instance_status { + @Test + @DisplayName("인스턴스의 상태가 ACTIVITY가 아니라면 NOT_ACTIVITY_INSTANCE 예외가 발생한다.") + public void it_throws_exception_when_instance_not_ACTIVITY() { + instance = instanceRepository.save(InstanceFactory.createPreActivity(10)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + assertThatThrownBy(() -> storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.NOT_ACTIVITY_INSTANCE.getMessage()); + } + + @Test + @DisplayName("인스턴스의 상태가 ACTIVITY라면 아이템을 사용할 수 있다.") + public void it_returns_200_instance_status_is_ACTIVITY() { + int holding = orders.getCount(); + instance = instanceRepository.save(InstanceFactory.createActivity(10)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate); + + assertThat(orders.getCount()).isEqualTo(holding - 1); + } + } + } + + @Nested + @DisplayName("포인트 2배 획득 아이템 사용 시") + class describe_use_point_multiplier_item { + Instance instance; + Participant participant; + Item item; + Orders orders; + + @BeforeEach + void setup() { + item = itemRepository.save(StoreFactory.createItem(POINT_MULTIPLIER)); + orders = ordersRepository.save(StoreFactory.createOrders(user, item, POINT_MULTIPLIER, 5)); + } + + @Nested + @DisplayName("아이템을 가지고 있고, 인스턴스의 상태를 조회했을 때") + class context_has_item_and_check_instance_status { + @Test + @DisplayName("인스턴스의 상태가 DONE이 아니라면 예외가 발생한다.") + public void it_throws_exception_when_instance_not_DONE() { + instance = instanceRepository.save(InstanceFactory.createActivity(10)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + + assertThatThrownBy(() -> storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_GET_REWARDS.getMessage()); + } + + @Test + @DisplayName("인스턴스의 상태가 DONE이라면 아이템을 사용할 수 있다.") + public void it_returns_200_when_instance_is_DONE() { + int holding = orders.getCount(); + instance = instanceRepository.save(InstanceFactory.createDone(10)); + participant = participantRepository.save( + ParticipantFactory.createByRewardStatus(user, instance, RewardStatus.NO)); + + storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate); + + assertThat(orders.getCount()).isEqualTo(holding - 1); + } + } + + @Nested + @DisplayName("사용 가능 여부를 확인했을 때") + class context_check_valid_to_use_item { + @BeforeEach + void setup() { + instance = instanceRepository.save(InstanceFactory.createDone(10)); + } + + @ParameterizedTest + @DisplayName("participant의 JoinResult가 SUCCESS가 아니라면 CAN_NOT_GET_REWARDS 예외가 발생한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"PROCESSING", "FAIL"}) + public void it_throws_exception_when_JoinResult_not_SUCCESS(JoinResult joinResult) { + participant = participantRepository.save( + ParticipantFactory.createByJoinResult(user, instance, joinResult)); + assertThatThrownBy(() -> storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_GET_REWARDS.getMessage()); + } + + @Test + @DisplayName("participant의 RewardStatus가 YES라면 ALREADY_REWARDED 예외가 발생한다.") + public void it_throws_exception_when_RewardStatus_is_YES() { + participant = participantRepository.save( + ParticipantFactory.createByRewardStatus(user, instance, RewardStatus.YES) + ); + assertThatThrownBy(() -> storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ALREADY_REWARDED.getMessage()); + } + } + + @Nested + @DisplayName("아이템을 사용해서 아이템의 개수가 0이 되었을 때") + class context_item_count_is_zero { + @BeforeEach + void setup() { + instance = instanceRepository.save(InstanceFactory.createDone(10)); + participant = participantRepository.save( + ParticipantFactory.createByRewardStatus(user, instance, RewardStatus.NO)); + } + + @Test + @DisplayName("Orders 정보가 DB에서 삭제된다.") + public void it_delete_Orders_from_DB() { + int holding = 1; + orders = ordersRepository.save(StoreFactory.createOrders(user, item, POINT_MULTIPLIER, holding)); + storeFacade.useMultiplierItem(orders, instance.getId(), currentDate); + + Optional optionalOrders = ordersRepository.findById(orders.getId()); + assertThat(optionalOrders).isNotPresent(); + } + } + } + + @Nested + @DisplayName("프로필 아이템 사용 시") + class describe_use_profile_item { + Item item; + Orders orders; + + @BeforeEach + void setup() { + item = itemRepository.save(StoreFactory.createItem(PROFILE_FRAME)); + orders = ordersRepository.save(StoreFactory.createOrders(user, item, PROFILE_FRAME, 2)); + } + + @Nested + @DisplayName("사용 가능 여부를 확인했을 때") + class context_check_valid_to_use_item { + @Test + @DisplayName("기존에 사용 중인 프로필 아이템이 있는 경우 TOO_MANY_USING_FRAME 예외가 발생한다.") + public void it_throws_exception_already_using_frame_exist() { + orders.updateEquipStatus(EquipStatus.IN_USE); + assertThatThrownBy(() -> storeFacade.useFrameItem(user.getId(), orders)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.TOO_MANY_USING_FRAME.getMessage()); + } + + @Test + @DisplayName("EquipStatus가 UNAVAILABLE인 경우 INVALID_EQUIP_CONDITION 예외가 발생한다.") + public void it_throws_exception_when_equipStatus_is_unavailable() { + orders.updateEquipStatus(EquipStatus.UNAVAILABLE); + assertThatThrownBy(() -> storeFacade.useFrameItem(user.getId(), orders)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.INVALID_EQUIP_CONDITION.getMessage()); + } + + @Test + @DisplayName("EquipStatus가 IN_USE인 경우 INVALID_EQUIP_CONDITION 예외가 발생한다.") + public void it_throws_exception_when_equipStatus_is_in_use() { + orders.updateEquipStatus(EquipStatus.IN_USE); + assertThatThrownBy(() -> storeFacade.useFrameItem(user.getId(), orders)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.TOO_MANY_USING_FRAME.getMessage()); + } + + @Test + @DisplayName("조건에 부합한다면 프로필 아이템을 사용할 수 있다.") + public void it_returns_200_when_condition_match() { + storeFacade.useFrameItem(user.getId(), orders); + assertThat(orders.getEquipStatus()).isEqualTo(EquipStatus.IN_USE); + } + } + } + + @Nested + @DisplayName("아이템 장착 해제 요청 시") + class describe_unmount_item { + Item item; + Orders orders; + + @Nested + @DisplayName("프로필 아이템이 아니라면") + class context_not_profile_item { + @ParameterizedTest + @DisplayName("응답 데이터가 포함되지 않는다.") + @EnumSource(mode = Mode.INCLUDE, names = {"POINT_MULTIPLIER", "CERTIFICATION_PASSER"}) + public void it_not_contain_response_data(ItemCategory itemCategory) { + item = itemRepository.save(StoreFactory.createItem(itemCategory)); + ordersRepository.save(StoreFactory.createOrders(user, item, itemCategory, 2)); + + List profileResponses = storeFacade.unmountFrame(user); + assertThat(profileResponses.size()).isEqualTo(0); + } + } + + @Nested + @DisplayName("프로필 아이템인 경우") + class context_profile_item { + @Test + @DisplayName("EquipStatus가 IN_USE라면 응답 데이터가 포함된다.") + public void it_contains_response_data_equipStatus_is_IN_USE() { + item = itemRepository.save(StoreFactory.createItem(PROFILE_FRAME)); + orders = ordersRepository.save(StoreFactory.createOrders(user, item, PROFILE_FRAME, 2)); + orders.updateEquipStatus(EquipStatus.IN_USE); + + List profileResponses = storeFacade.unmountFrame(user); + assertThat(profileResponses.size()).isEqualTo(1); + } + + @ParameterizedTest + @DisplayName("EquipStatus가 IN_USE가 아니라면 응답 데이터가 포함되지 않는다.") + @EnumSource(mode = Mode.INCLUDE, names = {"UNAVAILABLE", "AVAILABLE"}) + public void it_not_contains_response_data_equipStatus_not_IN_USE(EquipStatus equipStatus) { + item = itemRepository.save(StoreFactory.createItem(PROFILE_FRAME)); + orders = ordersRepository.save(StoreFactory.createOrders(user, item, PROFILE_FRAME, 2)); + orders.updateEquipStatus(equipStatus); + + List profileResponses = storeFacade.unmountFrame(user); + assertThat(profileResponses.size()).isEqualTo(0); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java b/src/test/java/com/genius/gitget/store/service/ItemServiceTest.java similarity index 88% rename from src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java rename to src/test/java/com/genius/gitget/store/service/ItemServiceTest.java index 4cfd292a..ad98031c 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/ItemProviderTest.java +++ b/src/test/java/com/genius/gitget/store/service/ItemServiceTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.item.service; +package com.genius.gitget.store.service; import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; import static org.assertj.core.api.Assertions.assertThat; @@ -9,7 +9,7 @@ import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.domain.ItemCategory; import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.store.item.service.ItemProvider; +import com.genius.gitget.store.item.service.ItemService; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; @@ -24,11 +24,11 @@ @Slf4j @SpringBootTest @Transactional -class ItemProviderTest { +class ItemServiceTest { @Autowired ItemRepository itemRepository; @Autowired - ItemProvider itemProvider; + ItemService itemService; @ParameterizedTest @DisplayName("DB에 저장되어 있는 아이템을 카테고리 별로 받아올 수 있다.") @@ -38,7 +38,7 @@ public void should_findItems_when_passCategory(ItemCategory itemCategory) { Item item = getSavedItem(10, itemCategory); //when - List items = itemProvider.findAllByCategory(itemCategory); + List items = itemService.findAllByCategory(itemCategory); //then assertThat(items.size()).isEqualTo(2); @@ -53,7 +53,7 @@ public void should_findItem_when_passPK() { Item item = getSavedItem(10, CERTIFICATION_PASSER); //when - Item foundItem = itemProvider.findById(item.getId()); + Item foundItem = itemService.findById(item.getId()); //then assertThat(item.getId()).isEqualTo(foundItem.getId()); @@ -64,7 +64,7 @@ public void should_findItem_when_passPK() { @Test @DisplayName("PK를 통해 아이템을 조회하려고 했을 때, 존재하지 않으면 예외를 발생시켜야 한다.") public void should_throwException_when_pkNotExist() { - assertThatThrownBy(() -> itemProvider.findById(0L)) + assertThatThrownBy(() -> itemService.findById(0L)) .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.ITEM_NOT_FOUND.getMessage()); } @@ -77,7 +77,7 @@ public void should_findItem_by_identifier() { Item item = getSavedItem(identifier, CERTIFICATION_PASSER); //when - Item byIdentifier = itemProvider.findByIdentifier(identifier); + Item byIdentifier = itemService.findByIdentifier(identifier); //then assertThat(item.getId()).isEqualTo(byIdentifier.getId()); diff --git a/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java b/src/test/java/com/genius/gitget/store/service/OrdersServiceTest.java similarity index 86% rename from src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java rename to src/test/java/com/genius/gitget/store/service/OrdersServiceTest.java index 907d37f7..28d8abbb 100644 --- a/src/test/java/com/genius/gitget/challenge/item/service/OrdersProviderTest.java +++ b/src/test/java/com/genius/gitget/store/service/OrdersServiceTest.java @@ -1,4 +1,4 @@ -package com.genius.gitget.challenge.item.service; +package com.genius.gitget.store.service; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -15,7 +15,7 @@ import com.genius.gitget.store.item.domain.Orders; import com.genius.gitget.store.item.repository.ItemRepository; import com.genius.gitget.store.item.repository.OrdersRepository; -import com.genius.gitget.store.item.service.OrdersProvider; +import com.genius.gitget.store.item.service.OrdersService; import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; @@ -27,7 +27,7 @@ @Slf4j @SpringBootTest @Transactional -class OrdersProviderTest { +class OrdersServiceTest { @Autowired private UserRepository userRepository; @Autowired @@ -35,7 +35,7 @@ class OrdersProviderTest { @Autowired private OrdersRepository ordersRepository; @Autowired - private OrdersProvider ordersProvider; + private OrdersService ordersService; @Test @DisplayName("사용자가 특정 아이템을 보유하고 있을 때, 보유하고 있는 아이템의 개수를 반환받을 수 있다.") @@ -46,7 +46,7 @@ public void should_returnItemCount_when_haveItem() { getSavedOrder(user, item, 1); //when - int numOfItem = ordersProvider.countNumOfItem(user, item.getId()); + int numOfItem = ordersService.countNumOfItem(user, item.getId()); //then assertThat(numOfItem).isEqualTo(1); @@ -60,7 +60,7 @@ public void should_returnZero_when_dataNotSaved() { Item item = getSavedItem(ItemCategory.PROFILE_FRAME); //when - int numOfItem = ordersProvider.countNumOfItem(user, item.getId()); + int numOfItem = ordersService.countNumOfItem(user, item.getId()); //then assertThat(numOfItem).isEqualTo(0); @@ -75,7 +75,7 @@ public void should_getOrder_when_ordered() { Orders orders = getSavedOrder(user, item, 1); //when - Optional optionalOrders = ordersProvider.findOptionalByOrderInfo(user.getId(), item.getId()); + Optional optionalOrders = ordersService.findOptionalByOrderInfo(user.getId(), item.getId()); //then assertThat(optionalOrders).isPresent(); @@ -90,7 +90,7 @@ public void should_returnOptional_when_notOrdered() { Item item = getSavedItem(ItemCategory.PROFILE_FRAME); //when - Optional optionalOrders = ordersProvider.findOptionalByOrderInfo(user.getId(), item.getId()); + Optional optionalOrders = ordersService.findOptionalByOrderInfo(user.getId(), item.getId()); //then assertThat(optionalOrders).isNotPresent(); @@ -105,7 +105,7 @@ public void should_getEquipStatus_when_ordered() { getSavedOrder(user, item, 1); //when - EquipStatus equipStatus = ordersProvider.getEquipStatus(user.getId(), item.getId()); + EquipStatus equipStatus = ordersService.getEquipStatus(user.getId(), item.getId()); //then assertThat(equipStatus).isEqualTo(EquipStatus.AVAILABLE); @@ -119,7 +119,7 @@ public void should_returnUnavailable_when_notOrdered() { Item item = getSavedItem(ItemCategory.PROFILE_FRAME); //when - EquipStatus equipStatus = ordersProvider.getEquipStatus(user.getId(), item.getId()); + EquipStatus equipStatus = ordersService.getEquipStatus(user.getId(), item.getId()); //then assertThat(equipStatus).isEqualTo(EquipStatus.UNAVAILABLE); @@ -134,7 +134,7 @@ public void should_returnPK_when_equipOneFrame() { Orders orders = getSavedOrder(user, item, 1); //when - Item usingFrame = ordersProvider.getUsingFrameItem(user.getId()); + Item usingFrame = ordersService.getUsingFrameItem(user.getId()); //then assertThat(item.getItemCategory()).isEqualTo(usingFrame.getItemCategory()); @@ -153,7 +153,7 @@ public void should_throwException_when_numOfFrameMoreThanTwo() { orders2.updateEquipStatus(EquipStatus.IN_USE); //when & then - assertThatThrownBy(() -> ordersProvider.getUsingFrameItem(user.getId())) + assertThatThrownBy(() -> ordersService.getUsingFrameItem(user.getId())) .isInstanceOf(BusinessException.class) .hasMessageContaining(ErrorCode.TOO_MANY_USING_FRAME.getMessage()); } @@ -166,7 +166,7 @@ public void should_returnDummy_when_notEquipped() { Item item = getSavedItem(ItemCategory.PROFILE_FRAME); //when - Item usingFrame = ordersProvider.getUsingFrameItem(user.getId()); + Item usingFrame = ordersService.getUsingFrameItem(user.getId()); //then assertThat(usingFrame.getId()).isNull(); @@ -195,7 +195,7 @@ private Item getSavedItem(ItemCategory itemCategory) { } private Orders getSavedOrder(User user, Item item, int count) { - Orders orders = Orders.createDefault(count, item.getItemCategory()); + Orders orders = Orders.of(count, item.getItemCategory()); orders.setUser(user); orders.setItem(item); return ordersRepository.save(orders); diff --git a/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java index 9dc272d2..3f4193df 100644 --- a/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java @@ -12,8 +12,8 @@ import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.repository.TopicRepository; -import com.genius.gitget.util.TokenTestUtil; -import com.genius.gitget.util.WithMockCustomUser; +import com.genius.gitget.util.security.TokenTestUtil; +import com.genius.gitget.util.security.WithMockCustomUser; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/genius/gitget/util/certification/CertificationFactory.java b/src/test/java/com/genius/gitget/util/certification/CertificationFactory.java new file mode 100644 index 00000000..e68b9dd7 --- /dev/null +++ b/src/test/java/com/genius/gitget/util/certification/CertificationFactory.java @@ -0,0 +1,22 @@ +package com.genius.gitget.util.certification; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.participant.domain.Participant; +import java.time.LocalDate; + +public class CertificationFactory { + public static Certification create(CertificateStatus status, LocalDate certificatedAt, + Participant participant) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); + Certification certification = Certification.builder() + .certificationStatus(status) + .currentAttempt(attempt) + .certificatedAt(certificatedAt) + .certificationLinks("certificationLink") + .build(); + certification.setParticipant(participant); + return certification; + } +} diff --git a/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java b/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java new file mode 100644 index 00000000..1a6de597 --- /dev/null +++ b/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java @@ -0,0 +1,40 @@ +package com.genius.gitget.util.instance; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import java.time.LocalDateTime; + +public class InstanceFactory { + /** + * LocalDate.now()를 기준으로 PREACTIVITY(시작 전) 인스턴스 생성 후 반환 + */ + public static Instance createPreActivity(int duration) { + return Instance.builder() + .progress(Progress.PREACTIVITY) + .startedDate(LocalDateTime.now().plusDays(1)) + .completedDate(LocalDateTime.now().plusDays(duration + 1)) + .build(); + } + + /** + * LocalDate.now()를 기준으로 진행 중인 인스턴스 생성 후 반환 + */ + public static Instance createActivity(int duration) { + return Instance.builder() + .progress(Progress.ACTIVITY) + .startedDate(LocalDateTime.now()) + .completedDate(LocalDateTime.now().plusDays(duration)) + .build(); + } + + /** + * LocalDate.now()를 기준으로 완료된 인스턴스 생성 후 반환 + */ + public static Instance createDone(int duration) { + return Instance.builder() + .progress(Progress.ACTIVITY) + .startedDate(LocalDateTime.now().minusDays(duration - 1)) + .completedDate(LocalDateTime.now().minusDays(1)) + .build(); + } +} diff --git a/src/test/java/com/genius/gitget/util/participant/ParticipantFactory.java b/src/test/java/com/genius/gitget/util/participant/ParticipantFactory.java new file mode 100644 index 00000000..02b583a0 --- /dev/null +++ b/src/test/java/com/genius/gitget/util/participant/ParticipantFactory.java @@ -0,0 +1,56 @@ +package com.genius.gitget.util.participant; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.participant.domain.JoinResult; +import com.genius.gitget.challenge.participant.domain.JoinStatus; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.domain.RewardStatus; +import com.genius.gitget.challenge.user.domain.User; + +public class ParticipantFactory { + /** + * 진행 중인 참여 정보 엔티티 만들어서 반환 + * user, instance를 받아서 연관관계 설정 후 반환 + */ + public static Participant createProcessing(User user, Instance instance) { + Participant participant = Participant.builder() + .joinResult(JoinResult.PROCESSING) + .joinStatus(JoinStatus.YES) + .build(); + participant.setUserAndInstance(user, instance); + participant.updateRepository("targetRepo"); + + return participant; + } + + /** + * 참여 정보에 대해 JoinResult(참여 결과 - 시작전, 진행중, 실패, 성공) 설정 후 반환 + * user, instance를 받아서 연관관계 설정 후 반환 + */ + public static Participant createByJoinResult(User user, Instance instance, JoinResult joinResult) { + Participant participant = Participant.builder() + .joinResult(joinResult) + .joinStatus(JoinStatus.YES) + .build(); + participant.setUserAndInstance(user, instance); + participant.updateRepository("targetRepo"); + + return participant; + } + + /** + * 챌린지가 끝난 참여 정보에 대해, RewardStatus(보상 수령 상태)에 대한 값을 설정 후 반환 + * user, instance를 받아서 연관관계 설정 후 반환 + */ + public static Participant createByRewardStatus(User user, Instance instance, RewardStatus rewardStatus) { + Participant participant = Participant.builder() + .joinResult(JoinResult.SUCCESS) + .joinStatus(JoinStatus.YES) + .rewardStatus(rewardStatus) + .build(); + participant.setUserAndInstance(user, instance); + participant.updateRepository("targetRepo"); + + return participant; + } +} diff --git a/src/test/java/com/genius/gitget/util/TokenTestUtil.java b/src/test/java/com/genius/gitget/util/security/TokenTestUtil.java similarity index 98% rename from src/test/java/com/genius/gitget/util/TokenTestUtil.java rename to src/test/java/com/genius/gitget/util/security/TokenTestUtil.java index 44d30ade..966c3195 100644 --- a/src/test/java/com/genius/gitget/util/TokenTestUtil.java +++ b/src/test/java/com/genius/gitget/util/security/TokenTestUtil.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util; +package com.genius.gitget.util.security; import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_PREFIX; import static com.genius.gitget.global.security.constants.JwtRule.REFRESH_PREFIX; diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUser.java b/src/test/java/com/genius/gitget/util/security/WithMockCustomUser.java similarity index 94% rename from src/test/java/com/genius/gitget/util/WithMockCustomUser.java rename to src/test/java/com/genius/gitget/util/security/WithMockCustomUser.java index e01e948f..33f906fa 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUser.java +++ b/src/test/java/com/genius/gitget/util/security/WithMockCustomUser.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util; +package com.genius.gitget.util.security; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.global.security.constants.ProviderInfo; diff --git a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/security/WithMockCustomUserSecurityContextFactory.java similarity index 98% rename from src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java rename to src/test/java/com/genius/gitget/util/security/WithMockCustomUserSecurityContextFactory.java index 6b76ae5c..6e49bc3e 100644 --- a/src/test/java/com/genius/gitget/util/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/com/genius/gitget/util/security/WithMockCustomUserSecurityContextFactory.java @@ -1,4 +1,4 @@ -package com.genius.gitget.util; +package com.genius.gitget.util.security; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; diff --git a/src/test/java/com/genius/gitget/util/store/StoreFactory.java b/src/test/java/com/genius/gitget/util/store/StoreFactory.java new file mode 100644 index 00000000..a038ec68 --- /dev/null +++ b/src/test/java/com/genius/gitget/util/store/StoreFactory.java @@ -0,0 +1,25 @@ +package com.genius.gitget.util.store; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.ItemCategory; +import com.genius.gitget.store.item.domain.Orders; + +public class StoreFactory { + public static Item createItem(ItemCategory itemCategory) { + return Item.builder() + .identifier(10) + .itemCategory(itemCategory) + .cost(100) + .name(itemCategory.getName()) + .details("details") + .build(); + } + + public static Orders createOrders(User user, Item item, ItemCategory itemCategory, int count) { + Orders orders = Orders.of(count, itemCategory); + orders.setItem(item); + orders.setUser(user); + return orders; + } +} From 1659a2c1ddf1c8ce358f86ec6a61b25565b94e6a Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Wed, 31 Jul 2024 14:48:27 +0900 Subject: [PATCH 210/234] =?UTF-8?q?feat:=20LikesController=20Facade=20&=20?= =?UTF-8?q?DCI=20=ED=8C=A8=ED=84=B4=20=EB=8F=84=EC=9E=85=20(#231)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../likes/controller/LikesController.java | 10 +- .../gitget/challenge/likes/domain/Likes.java | 2 +- .../challenge/likes/facade/LikesFacade.java | 15 ++ .../likes/facade/LikesFacadeService.java | 69 ++++++ .../challenge/likes/service/LikesService.java | 54 ++--- .../service/InstanceDetailServiceTest.java | 6 +- .../likes/controller/LikesControllerTest.java | 3 +- .../likes/service/LikesFacadeTest.java | 214 ++++++++++++++++++ .../likes/service/LikesServiceTest.java | 196 ---------------- 9 files changed, 321 insertions(+), 248 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/likes/facade/LikesFacade.java create mode 100644 src/main/java/com/genius/gitget/challenge/likes/facade/LikesFacadeService.java create mode 100644 src/test/java/com/genius/gitget/challenge/likes/service/LikesFacadeTest.java delete mode 100644 src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java diff --git a/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java b/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java index ce8a35d2..95fb667a 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java +++ b/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java @@ -3,7 +3,7 @@ import com.genius.gitget.challenge.likes.dto.UserLikesAddRequest; import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; import com.genius.gitget.challenge.likes.dto.UserLikesResponse; -import com.genius.gitget.challenge.likes.service.LikesService; +import com.genius.gitget.challenge.likes.facade.LikesFacade; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; @@ -29,7 +29,7 @@ @Slf4j @RequestMapping("/api/profile") public class LikesController { - private final LikesService likesService; + private final LikesFacade likesFacade; // 좋아요 목록 조회 @GetMapping("/likes") @@ -38,7 +38,7 @@ public ResponseEntity> getLikesListOfUser( @AuthenticationPrincipal UserPrincipal userPrincipal) { PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); - Page likesResponses = likesService.getLikesList(userPrincipal.getUser(), pageRequest); + Page likesResponses = likesFacade.getLikesList(userPrincipal.getUser(), pageRequest); return ResponseEntity.ok().body( new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), likesResponses) @@ -50,7 +50,7 @@ public ResponseEntity> getLikesListOfUser( public ResponseEntity> addLikes( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody UserLikesAddRequest userLikesAddRequest) { - UserLikesAddResponse userLikesAddResponse = likesService.addLikes(userPrincipal.getUser(), + UserLikesAddResponse userLikesAddResponse = likesFacade.addLikes(userPrincipal.getUser(), userLikesAddRequest.getIdentifier(), userLikesAddRequest.getInstanceId()); return ResponseEntity.ok().body( @@ -63,7 +63,7 @@ public ResponseEntity> addLikes( @DeleteMapping("/likes/{likesId}") public ResponseEntity deleteLikes(@AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable(value = "likesId") Long likesId) { - likesService.deleteLikes(userPrincipal.getUser(), likesId); + likesFacade.deleteLikes(userPrincipal.getUser(), likesId); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) ); diff --git a/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java b/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java index 0da8384a..16e44dff 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java +++ b/src/main/java/com/genius/gitget/challenge/likes/domain/Likes.java @@ -44,7 +44,7 @@ public class Likes { @CreatedDate @Column(name = "liked_at") - private LocalDateTime likedAt; // 찜하기 누른 시각 + private LocalDateTime likedAt; @Builder public Likes(User user, Instance instance) { diff --git a/src/main/java/com/genius/gitget/challenge/likes/facade/LikesFacade.java b/src/main/java/com/genius/gitget/challenge/likes/facade/LikesFacade.java new file mode 100644 index 00000000..418f978e --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/likes/facade/LikesFacade.java @@ -0,0 +1,15 @@ +package com.genius.gitget.challenge.likes.facade; + +import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; +import com.genius.gitget.challenge.likes.dto.UserLikesResponse; +import com.genius.gitget.challenge.user.domain.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface LikesFacade { + Page getLikesList(User user, Pageable pageable); + + UserLikesAddResponse addLikes(User user, String identifier, Long instanceId); + + void deleteLikes(User user, Long likesId); +} diff --git a/src/main/java/com/genius/gitget/challenge/likes/facade/LikesFacadeService.java b/src/main/java/com/genius/gitget/challenge/likes/facade/LikesFacadeService.java new file mode 100644 index 00000000..6861a363 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/likes/facade/LikesFacadeService.java @@ -0,0 +1,69 @@ +package com.genius.gitget.challenge.likes.facade; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.likes.domain.Likes; +import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; +import com.genius.gitget.challenge.likes.dto.UserLikesResponse; +import com.genius.gitget.challenge.likes.service.LikesService; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; +import java.util.LinkedList; +import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; + +@Component +public class LikesFacadeService implements LikesFacade { + + LikesService likesService; + FilesService filesService; + + public LikesFacadeService(LikesService likesService, FilesService filesService) { + this.likesService = likesService; + this.filesService = filesService; + } + + @Override + public Page getLikesList(User user, Pageable pageable) { + LinkedList userLikesResponses = new LinkedList<>(); + + List likesList = likesService.getLikesList(user); + + for (Likes like : likesList) { + Instance instance = like.getInstance(); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + UserLikesResponse userLikesResponse = getUserLikesResponse(like, instance, fileResponse); + userLikesResponses.addFirst(userLikesResponse); + } + + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), userLikesResponses.size()); + return new PageImpl<>(userLikesResponses.stream().toList().subList(start, end), pageable, + userLikesResponses.size()); + } + + @Override + public UserLikesAddResponse addLikes(User user, String identifier, Long instanceId) { + Long id = likesService.addLikes(user, identifier, instanceId); + return UserLikesAddResponse.builder() + .likesId(id).build(); + } + + @Override + public void deleteLikes(User user, Long likesId) { + likesService.deleteLikes(likesId); + } + + private UserLikesResponse getUserLikesResponse(Likes like, Instance instance, FileResponse fileResponse) { + return UserLikesResponse.builder() + .likesId(like.getId()) + .instanceId(instance.getId()) + .title(instance.getTitle()) + .pointPerPerson(instance.getPointPerPerson()) + .fileResponse(fileResponse) + .build(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java index f9a40e73..6f456b34 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java +++ b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java @@ -3,24 +3,15 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.likes.domain.Likes; -import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; -import com.genius.gitget.challenge.likes.dto.UserLikesResponse; import com.genius.gitget.challenge.likes.repository.LikesRepository; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,45 +20,24 @@ @Slf4j @Service public class LikesService { - private final FilesService filesService; private final UserRepository userRepository; private final InstanceRepository instanceRepository; private final LikesRepository likesRepository; - public Page getLikesList(User user, Pageable pageable) { + public List getLikesList(User user) { List userList = verifyUser(user); List likes = new ArrayList<>(); - for (User userObject : userList) { - if (userObject.getIdentifier().equals(user.getIdentifier())) { - likes = userObject.getLikesList(); + for (User userData : userList) { + if (userData.getIdentifier().equals(user.getIdentifier())) { + likes = userData.getLikesList(); } } - - Deque userLikesResponses = new ArrayDeque<>(); - for (Likes like : likes) { - Instance instance = like.getInstance(); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - - UserLikesResponse userLikesResponse = UserLikesResponse.builder() - .likesId(like.getId()) - .instanceId(instance.getId()) - .title(instance.getTitle()) - .pointPerPerson(instance.getPointPerPerson()) - .fileResponse(fileResponse) - .build(); - - userLikesResponses.addFirst(userLikesResponse); - } - - int start = (int) pageable.getOffset(); - int end = Math.min((start + pageable.getPageSize()), userLikesResponses.size()); - return new PageImpl<>(userLikesResponses.stream().toList().subList(start, end), pageable, - userLikesResponses.size()); + return likes; } @Transactional - public UserLikesAddResponse addLikes(User user, String identifier, Long instanceId) { + public Long addLikes(User user, String identifier, Long instanceId) { User comparedUser = compareToUserIdentifier(user, identifier); List userList = verifyUser(comparedUser); User findUser = null; @@ -79,14 +49,16 @@ public UserLikesAddResponse addLikes(User user, String identifier, Long instance } Instance findInstance = verifyInstance(instanceId); - Likes likes = new Likes(findUser, findInstance); - Long id = likesRepository.save(likes).getId(); - return UserLikesAddResponse.builder() - .likesId(id).build(); + Likes likes = Likes.builder() + .instance(findInstance) + .user(findUser) + .build(); + + return likesRepository.save(likes).getId(); } @Transactional - public void deleteLikes(User user, Long likesId) { + public void deleteLikes(Long likesId) { Likes findLikes = likesRepository.findById(likesId) .orElseThrow(() -> new BusinessException(ErrorCode.LIKES_NOT_FOUND)); diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 03366e0b..2a3fa6a2 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -15,7 +15,7 @@ import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; -import com.genius.gitget.challenge.likes.service.LikesService; +import com.genius.gitget.challenge.likes.facade.LikesFacade; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; @@ -45,7 +45,7 @@ class InstanceDetailServiceTest { @Autowired InstanceDetailService instanceDetailService; @Autowired - LikesService likesService; + LikesFacade likesFacade; @Autowired ParticipantProvider participantProvider; @Autowired @@ -321,7 +321,7 @@ public void should_returnLikesData_when_userPushLikes() { Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); //when - UserLikesAddResponse userLikesAddResponse = likesService.addLikes(savedUser, savedUser.getIdentifier(), + UserLikesAddResponse userLikesAddResponse = likesFacade.addLikes(savedUser, savedUser.getIdentifier(), savedInstance.getId()); InstanceResponse instanceResponse = instanceDetailService.getInstanceDetailInformation(savedUser, savedInstance.getId()); diff --git a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java index 8a3929d1..9e106dc4 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java @@ -41,7 +41,7 @@ @SpringBootTest @Transactional public class LikesControllerTest { - private static Topic savedTopic1, savedTopic2; + private static Topic savedTopic1; private static Instance savedInstance1, savedInstance2; MockMvc mockMvc; @@ -72,7 +72,6 @@ public void setup() { .build(); savedTopic1 = getSavedTopic(); - savedTopic2 = getSavedTopic(); savedInstance1 = getSavedInstance("title1", "FE", 50, 1000); savedInstance2 = getSavedInstance("title2", "BE, CS", 50, 1000); diff --git a/src/test/java/com/genius/gitget/challenge/likes/service/LikesFacadeTest.java b/src/test/java/com/genius/gitget/challenge/likes/service/LikesFacadeTest.java new file mode 100644 index 00000000..976f06cf --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/likes/service/LikesFacadeTest.java @@ -0,0 +1,214 @@ +package com.genius.gitget.challenge.likes.service; + +import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.likes.domain.Likes; +import com.genius.gitget.challenge.likes.dto.UserLikesResponse; +import com.genius.gitget.challenge.likes.facade.LikesFacade; +import com.genius.gitget.challenge.likes.repository.LikesRepository; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.constants.ProviderInfo; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; +import java.time.LocalDateTime; +import java.util.List; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +class LikesFacadeTest { + private User userA; + private Topic topicA; + private Instance instanceA, instanceB, instanceC; + + @Autowired + private UserRepository userRepository; + + @Autowired + private InstanceRepository instanceRepository; + + @Autowired + private TopicRepository topicRepository; + + @Autowired + private LikesRepository likesRepository; + + @Autowired + private LikesFacade likesFacade; + + + @BeforeEach + void setup() { + userA = saveUser("test@gmail.com", GITHUB, "kimdozzi"); + + topicA = saveTopic("1일 1커밋", "BE"); + + instanceA = saveInstance("1일 1커밋", "BE", 50, 100); + instanceB = saveInstance("1일 1커밋", "BE", 50, 150); + instanceC = saveInstance("1일 1알고리즘", "CS,BE,FE", 50, 200); + + associateInstancesWithTopic(topicA, instanceA, instanceB, instanceC); + + saveLikes(userA, instanceA, instanceB, instanceC); + } + + @Nested + @DisplayName("좋아요 추가 메서드는") + class Describe_add_likes { + @Nested + @DisplayName("유저와 인스턴스가 주어지면") + class Context_with_a_user_and_instance { + @Test + @DisplayName("유저의 좋아요 목록에 추가된다") + void it_adds_the_instance_to_user_likes() { + likesFacade.addLikes(userA, "test@gmail.com", instanceA.getId()); + + List allLikes = likesRepository.findAll(); + long count = allLikes.stream() + .filter(like -> like.getUser().getIdentifier().equals("test@gmail.com") && like.getInstance() + .getTitle() + .equals("1일 1커밋")).count(); + + assertThat(count).isEqualTo(allLikes.size() - 1); + } + } + } + + @Nested + @DisplayName("좋아요 삭제 메서드는") + class Describe_delete_likes { + @Nested + @DisplayName("유저와 좋아요 ID가 주어지면") + class Context_with_a_user_and_likes_id { + @Test + @DisplayName("유저의 좋아요 목록에서 삭제된다") + void it_removes_the_instance_from_user_likes() { + List likesList = likesRepository.findAll(); + Long likesId = likesList.get(0).getId(); + + likesFacade.deleteLikes(userA, likesId); + + assertThrows(BusinessException.class, + () -> likesRepository.findById(likesId) + .orElseThrow(() -> new BusinessException(ErrorCode.LIKES_NOT_FOUND))); + + List allLikes = likesRepository.findAll(); + + assertThat(allLikes.size()).isEqualTo(2); + } + } + } + + @Nested + @DisplayName("유저의 좋아요 목록 조회 메서드는") + class Describe_get_likes_list { + + @Nested + @DisplayName("모든 좋아요 목록을 조회할 때") + class Context_when_retrieving_all_likes { + @Test + @DisplayName("유저의 좋아요 목록을 반환한다") + void it_returns_all_likes() { + List allLikes = likesRepository.findAll(); + + for (int i = 0; i < allLikes.size(); i++) { + if (i <= 1) { + assertThat(allLikes.get(i).getInstance().getTitle()).isEqualTo("1일 1커밋"); + } else { + assertThat(allLikes.get(i).getInstance().getTitle()).isEqualTo("1일 1알고리즘"); + } + } + assertThat(allLikes.size()).isEqualTo(3); + } + } + + @Nested + @DisplayName("페이징된 좋아요 목록을 조회할 때") + class Context_when_retrieving_paginated_likes { + @Test + @DisplayName("유저의 페이징된 좋아요 목록을 반환한다") + void it_returns_paginated_likes() { + PageRequest pageRequest = PageRequest.of(0, 5); + Page likesResponses = likesFacade.getLikesList(userA, pageRequest); + + assertThat(likesResponses.getContent().size()).isEqualTo(3); + assertThat(likesResponses.getContent().get(2).getTitle()).isEqualTo("1일 1커밋"); + assertThat(likesResponses.getContent().get(2).getPointPerPerson()).isEqualTo(100); + assertThat(likesResponses.getContent().get(1).getTitle()).isEqualTo("1일 1커밋"); + assertThat(likesResponses.getContent().get(1).getPointPerPerson()).isEqualTo(150); + assertThat(likesResponses.getContent().get(0).getTitle()).isEqualTo("1일 1알고리즘"); + assertThat(likesResponses.getContent().get(0).getPointPerPerson()).isEqualTo(200); + } + } + } + + private User saveUser(String identifier, ProviderInfo providerInfo, String nickname) { + return userRepository.save( + User.builder() + .identifier(identifier) + .providerInfo(providerInfo) + .role(Role.ADMIN) + .nickname(nickname) + .build() + ); + } + + private Topic saveTopic(String title, String tags) { + return topicRepository.save( + Topic.builder() + .title(title) + .tags(tags) + .description("토픽 설명") + .pointPerPerson(100) + .build() + ); + } + + private Instance saveInstance(String title, String tags, int participantCnt, int pointPerPerson) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(pointPerPerson) + .certificationMethod("인증 방법") + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } + + + private void associateInstancesWithTopic(Topic topic, Instance... instances) { + for (Instance instance : instances) { + instance.setTopic(topic); + } + } + + private void saveLikes(User user, Instance... instances) { + for (Instance instance : instances) { + likesRepository.save(new Likes(user, instance)); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java b/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java deleted file mode 100644 index 52880407..00000000 --- a/src/test/java/com/genius/gitget/challenge/likes/service/LikesServiceTest.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.genius.gitget.challenge.likes.service; - -import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; -import static org.assertj.core.api.Assertions.assertThat; - -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.likes.domain.Likes; -import com.genius.gitget.challenge.likes.dto.UserLikesResponse; -import com.genius.gitget.challenge.likes.repository.LikesRepository; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.domain.FileType; -import com.genius.gitget.global.file.domain.Files; -import com.genius.gitget.global.file.repository.FilesRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; -import java.time.LocalDateTime; -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; - -@SpringBootTest -@Transactional -@Rollback -class LikesServiceTest { - static User user1; - static Topic topic1; - static Instance instance1, instance2, instance3; - static Files files1, files2, files3, files4; - @Autowired - UserRepository userRepository; - @Autowired - InstanceRepository instanceRepository; - @Autowired - TopicRepository topicRepository; - @Autowired - LikesRepository likesRepository; - @Autowired - LikesService likesService; - @Autowired - FilesRepository filesRepository; - - @BeforeEach - void setup() { - files1 = getSavedFiles("originalFileName", "savedFileName", "fileURL", FileType.INSTANCE); - files2 = getSavedFiles("originalFileName", "savedFileName", "fileURL", FileType.TOPIC); - - user1 = getSavedUser("neo5188@gmail.com", GITHUB, "kimdozzi"); - - topic1 = getSavedTopic("1일 1커밋", "BE"); - - instance1 = getSavedInstance("1일 1커밋", "BE", 50, 100); - instance2 = getSavedInstance("1일 1커밋", "BE", 50, 150); - instance3 = getSavedInstance("1일 1알고리즘", "CS,BE,FE", 50, 200); - - //== 연관관계 ==// - instance1.setTopic(topic1); - instance2.setTopic(topic1); - instance3.setTopic(topic1); - - Likes likes1 = new Likes(user1, instance1); - Likes likes2 = new Likes(user1, instance2); - Likes likes3 = new Likes(user1, instance3); - likesRepository.save(likes1); - likesRepository.save(likes2); - likesRepository.save(likes3); - } - - - @Test - void 유저_좋아요_목록_추가() { - List all = likesRepository.findAll(); - int cnt = 0; - for (Likes likes : all) { - if (likes.getUser().getIdentifier().equals("neo5188@gmail.com") && likes.getInstance().getTitle() - .equals("1일 1커밋")) { - cnt++; - } - } - Assertions.assertThat(all.size() - 1).isEqualTo(cnt); - } - - @Test - void 유저_좋아요_목록_삭제() { - List likes = likesRepository.findAll(); - Long likesId = likes.get(0).getId(); - - likesService.deleteLikes(user1, likesId); - org.junit.jupiter.api.Assertions.assertThrows(BusinessException.class, - () -> likesRepository.findById(likesId) - .orElseThrow(() -> new BusinessException(ErrorCode.LIKES_NOT_FOUND))); - - List all = likesRepository.findAll(); - - Assertions.assertThat(all.size()).isEqualTo(2); - } - - @Test - void 유저는_좋아요목록을_조회할_수_있다1() { - List all = likesRepository.findAll(); - - for (int i = 0; i < all.size(); i++) { - if (i <= 1) { - assertThat(all.get(i).getInstance().getTitle()).isEqualTo("1일 1커밋"); - } else { - assertThat(all.get(i).getInstance().getTitle()).isEqualTo("1일 1알고리즘"); - } - } - assertThat(all.size()).isEqualTo(3); - } - - @Test - void 유저는_좋아요목록을_조회할_수_있다2() { - - PageRequest pageRequest = PageRequest.of(0, 5); - Page likesResponses = likesService.getLikesList(user1, pageRequest); - for (UserLikesResponse likesResponse : likesResponses) { - System.out.println(likesResponse.getInstanceId() + " " + likesResponse.getTitle() + " " - + likesResponse.getPointPerPerson()); - } - assertThat(likesResponses.getContent().size()).isEqualTo(3); - assertThat(likesResponses.getContent().get(2).getTitle()).isEqualTo("1일 1커밋"); - assertThat(likesResponses.getContent().get(2).getPointPerPerson()).isEqualTo(100); - - assertThat(likesResponses.getContent().get(1).getTitle()).isEqualTo("1일 1커밋"); - assertThat(likesResponses.getContent().get(1).getPointPerPerson()).isEqualTo(150); - - assertThat(likesResponses.getContent().get(0).getTitle()).isEqualTo("1일 1알고리즘"); - assertThat(likesResponses.getContent().get(0).getPointPerPerson()).isEqualTo(200); - } - - - private User getSavedUser(String identifier, ProviderInfo providerInfo, String nickname) { - return userRepository.save( - User.builder() - .identifier(identifier) - .providerInfo(providerInfo) - .role(Role.ADMIN) - .nickname(nickname) - .build() - ); - } - - private Topic getSavedTopic(String title, String tags) { - return topicRepository.save( - Topic.builder() - .title(title) - .tags(tags) - .description("토픽 설명") - .pointPerPerson(100) - .build() - ); - } - - private Instance getSavedInstance(String title, String tags, int participantCnt, int pointPerPerson) { - LocalDateTime now = LocalDateTime.now(); - Instance instance = instanceRepository.save( - Instance.builder() - .tags(tags) - .title(title) - .description("description") - .progress(Progress.PREACTIVITY) - .pointPerPerson(pointPerPerson) - .certificationMethod("인증 방법") - .startedDate(now) - .completedDate(now.plusDays(1)) - .build() - ); - instance.updateParticipantCount(participantCnt); - return instance; - } - - private Files getSavedFiles(String originalFilename, String savedFilename, String fileURL, FileType fileType) { - return filesRepository.save( - Files.builder() - .originalFilename(originalFilename) - .savedFilename(savedFilename) - .fileURI(fileURL) - .fileType(fileType) - .build() - ); - } -} \ No newline at end of file From 58c8425185e65d39721d537d5f03fa8e7f3ea737 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:00:10 +0900 Subject: [PATCH 211/234] =?UTF-8?q?[FIX]=20=EC=8B=A4=ED=8C=A8=ED=95=9C=20?= =?UTF-8?q?=EC=B1=8C=EB=A6=B0=EC=A7=80=EC=97=90=20=EB=8C=80=ED=95=B4=20?= =?UTF-8?q?=EB=8B=AC=EC=84=B1=EC=9C=A8=EC=9D=B4=20=EB=82=98=EC=98=A4?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=ED=94=BD?= =?UTF-8?q?=EC=8A=A4=20(#232)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 실패한 챌린지에 대해 달성율이 나오지 않는 버그 픽스 - 끝까지 참여는 했지만, 달성율이 목표치에 도달하지 않아 실패한 챌린지에 대해 달성율이 나오지 않는 버그 픽스 - 응답 DTO에 해당 데이터 추가 * test: 버그가 발생한 상황에 대한 테스트 코드 추가 --- .../myChallenge/dto/DoneResponse.java | 4 ++- .../service/MyChallengeService.java | 4 +-- .../service/MyChallengeServiceTest.java | 28 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java index 6184667c..449960ab 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/DoneResponse.java @@ -40,7 +40,8 @@ public DoneResponse(Long instanceId, String title, int pointPerPerson, JoinResul public static DoneResponse createNotRewarded(Instance instance, Participant participant, - int numOfPointItem, FileResponse fileResponse) { + int numOfPointItem, double achievementRate, + FileResponse fileResponse) { return DoneResponse.builder() .title(instance.getTitle()) .instanceId(instance.getId()) @@ -48,6 +49,7 @@ public static DoneResponse createNotRewarded(Instance instance, .joinResult(participant.getJoinResult()) .canGetReward(canGetReward(participant)) .numOfPointItem(numOfPointItem) + .achievementRate(achievementRate) .fileResponse(fileResponse) .build(); } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java index 82495468..325ff858 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java @@ -76,20 +76,20 @@ public List getDoneInstances(User user, LocalDate targetDate) { for (Participant participant : participants) { Instance instance = participant.getInstance(); FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + double achievementRate = getAchievementRate(instance, participant.getId(), targetDate); // 포인트를 아직 수령하지 않았을 때 if (participant.getRewardStatus() == NO) { Item item = itemService.findAllByCategory(POINT_MULTIPLIER).get(0); int numOfPassItem = ordersService.countNumOfItem(user, item.getId()); DoneResponse doneResponse = DoneResponse.createNotRewarded( - instance, participant, numOfPassItem, fileResponse); + instance, participant, numOfPassItem, achievementRate, fileResponse); doneResponse.setItemId(item.getId()); done.add(doneResponse); continue; } // 포인트를 수령했을 때 - double achievementRate = getAchievementRate(instance, participant.getId(), targetDate); DoneResponse doneResponse = DoneResponse.createRewarded( instance, participant, achievementRate, fileResponse); done.add(doneResponse); diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java index 45411551..ba67aeb6 100644 --- a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.myChallenge.service; +import static com.genius.gitget.challenge.participant.domain.JoinResult.FAIL; import static com.genius.gitget.challenge.participant.domain.JoinResult.PROCESSING; import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; import static org.assertj.core.api.Assertions.assertThat; @@ -182,6 +183,33 @@ public void should_returnRewardInfo_when_alreadyRewarded() { assertThat(doneResponses.get(0).isCanGetReward()).isTrue(); } + @Test + @DisplayName("챌린지는 종료되었으나 실패한 챌린지에 대해, 정보를 전달해야 한다.") + public void should_returnInfo_when_failedChallenge() { + //given + LocalDate targetDate = LocalDate.of(2024, 2, 14); + User user = getSavedUser(); + Instance instance = getSavedInstance(Progress.DONE); + getSavedParticipant(user, instance, FAIL); + + //when + List doneInstances = myChallengeService.getDoneInstances(user, targetDate); + DoneResponse doneResponse = doneInstances.get(0); + + //then + assertThat(doneInstances.size()).isEqualTo(1); + + assertThat(doneResponse.getInstanceId()).isEqualTo(instance.getId()); + assertThat(doneResponse.getTitle()).isEqualTo(instance.getTitle()); + assertThat(doneResponse.getInstanceId()).isEqualTo(instance.getId()); + assertThat(doneResponse.getRewardedPoints()).isZero(); + assertThat(doneResponse.getJoinResult()).isEqualTo(FAIL); + assertThat(doneResponse.getFileResponse()).isNotNull(); + assertThat(doneResponse.isCanGetReward()).isFalse(); + assertThat(doneResponse.getAchievementRate()).isEqualTo(0.0); + assertThat(doneResponse.getNumOfPointItem()).isEqualTo(0); + } + private User getSavedUser() { return userRepository.save( From 4ee60d5f2b8fadf283bd9ca64d711a9fced1e8c4 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:40:17 +0900 Subject: [PATCH 212/234] =?UTF-8?q?[REFACTOR]=20MyChallengeController?= =?UTF-8?q?=EC=97=90=20Facade=20&=20DCI=20=ED=8C=A8=ED=84=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20(#235)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 아이템 구매가 안되는 버그 픽스 - LazyInitialization으로 인해 아이템 구매가 안되는 버그 픽스 - Transactional 어노테이션 적용 및 영속화된 엔티티 전달을 통해 버그 픿흐 * refactor: DTO에 정적 팩토리 메서드 적용 - PreActivityResponse에 정적 팩토리 메서드 적용 - ActivityResponse 정적 팩토리 메서드 네이밍 of로 변경 * refactor: MyChallenge 클래스에 Facade 패턴 적용 - MyChallengeService를 Facade 클래스로 변경 - Facade 인터페이스 선언 및 구현 * feat: 트랜잭션 어노테이션 추가 - OrderService에 트랜잭션 어노테이션 추가 * feat: myChallengeFacade 적용 및 코드 리팩토링 - Participant에 보상 컨디션 확인하는 메서드 추가 - Certification에 더미 데이터 생성하는 정적 팩토리 메서드 추가 - StoreFacade, MyChallengeFacade 코드 리팩토링 * refactor: Certification 생성 코드 리팩토링 - Certification이 없을 때 더미 데이터를 생성하는 코드를 CertificationProvider로 변경 * fix: 인증 조건을 검증하는 코드 누락 수정 * refactor: 보상 받는 로직 통일을 위해 리팩토링 - ParticipantProvider에 보상 받는 메서드를 추가하고, 이를 호출하도록 리팩토링 (로직의 통일성을 위해) * refactor: 클래스명 변경 - ParticipantProvider에서 ProviderService로 클래스명 변경 * test: 테스트 코드에 DCI 패턴 적용 * chore: 필요 없는 부분 삭제 --- .../controller/CertificationController.java | 10 +- .../certification/domain/Certification.java | 10 + .../service/CertificationProvider.java | 15 + .../service/CertificationService.java | 16 +- .../service/InstanceDetailService.java | 14 +- .../controller/MyChallengeController.java | 15 +- .../myChallenge/dto/ActivatedResponse.java | 4 +- .../myChallenge/dto/PreActivityResponse.java | 12 + .../myChallenge/dto/RewardRequest.java | 7 +- .../myChallenge/facade/MyChallengeFacade.java | 19 ++ .../MyChallengeFacadeService.java} | 128 +++----- .../participant/domain/Participant.java | 14 + ...tProvider.java => ParticipantService.java} | 12 +- .../store/item/facade/StoreFacadeService.java | 44 ++- .../store/item/service/OrdersService.java | 11 + .../service/InstanceDetailServiceTest.java | 6 +- .../service/MyChallengeFacadeTest.java | 287 +++++++++++++++++ .../service/MyChallengeServiceTest.java | 292 ------------------ ...rTest.java => ParticipantServiceTest.java} | 16 +- .../gitget/store/facade/StoreFacadeTest.java | 12 +- .../certification/CertificationFactory.java | 36 +++ .../gitget/util/instance/InstanceFactory.java | 2 +- .../util/participant/ParticipantFactory.java | 32 +- .../genius/gitget/util/user/UserFactory.java | 30 ++ 24 files changed, 590 insertions(+), 454 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacade.java rename src/main/java/com/genius/gitget/challenge/myChallenge/{service/MyChallengeService.java => facade/MyChallengeFacadeService.java} (58%) rename src/main/java/com/genius/gitget/challenge/participant/service/{ParticipantProvider.java => ParticipantService.java} (90%) create mode 100644 src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeFacadeTest.java delete mode 100644 src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java rename src/test/java/com/genius/gitget/challenge/participant/service/{ParticipantProviderTest.java => ParticipantServiceTest.java} (91%) create mode 100644 src/test/java/com/genius/gitget/util/user/UserFactory.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index 81330437..d276dd1d 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -14,7 +14,7 @@ import com.genius.gitget.challenge.instance.service.InstanceProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.participant.service.ParticipantService; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.domain.UserPrincipal; @@ -45,7 +45,7 @@ public class CertificationController { private final UserService userService; private final CertificationService certificationService; private final InstanceProvider instanceProvider; - private final ParticipantProvider participantProvider; + private final ParticipantService participantService; @GetMapping("/{instanceId}") @@ -92,7 +92,7 @@ public ResponseEntity> getWeekCertification( @PathVariable Long instanceId ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); - Participant participant = participantProvider.findByJoinInfo(userPrincipal.getUser().getId(), instanceId); + Participant participant = participantService.findByJoinInfo(userPrincipal.getUser().getId(), instanceId); WeekResponse weekResponse = certificationService.getMyWeekCertifications(participant.getId(), kstDate); return ResponseEntity.ok().body( @@ -123,7 +123,7 @@ public ResponseEntity> getTotalCertifications( ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); User user = userService.findUserById(userId); - Participant participant = participantProvider.findByJoinInfo(user.getId(), instanceId); + Participant participant = participantService.findByJoinInfo(user.getId(), instanceId); TotalResponse totalResponse = certificationService.getTotalCertification( participant.getId(), kstDate); @@ -140,7 +140,7 @@ public ResponseEntity> getCertification LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); Instance instance = instanceProvider.findById(instanceId); - Participant participant = participantProvider.findByJoinInfo( + Participant participant = participantService.findByJoinInfo( userPrincipal.getUser().getId(), instanceId); diff --git a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java index a184182a..c60e9dab 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java +++ b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.certification.domain; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; import com.genius.gitget.challenge.participant.domain.Participant; @@ -64,6 +65,15 @@ public static Certification createPassed(LocalDate certificatedAt) { .build(); } + public static Certification createDummy(LocalDate certificatedAt) { + return Certification.builder() + .currentAttempt(0) + .certificationStatus(NOT_YET) + .certificatedAt(certificatedAt) + .certificationLinks(null) + .build(); + } + //=== 비지니스 로직 ===// public void update(LocalDate certificatedAt, CertificateStatus status, String certificationLinks) { this.certificatedAt = certificatedAt; diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java index 34ec34ad..b9e40b0b 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java @@ -7,6 +7,7 @@ import com.genius.gitget.challenge.certification.domain.Certification; import com.genius.gitget.challenge.certification.repository.CertificationRepository; import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.participant.domain.Participant; import java.time.LocalDate; import java.util.List; @@ -50,6 +51,12 @@ targetDate, getCertificateStatus(pullRequests), getPrLinks(pullRequests) return certification; } + @Transactional + public Certification findOrGetDummy(LocalDate targetDate, Long participantId) { + return findByDate(targetDate, participantId) + .orElse(Certification.createDummy(targetDate)); + } + @Transactional public Certification createCertification(Participant participant, LocalDate targetDate, @@ -83,4 +90,12 @@ private CertificateStatus getCertificateStatus(List pullRequests) { } return CERTIFICATED; } + + public double getAchievementRate(Instance instance, Long participantId, LocalDate targetDate) { + int totalAttempt = instance.getTotalAttempt(); + int successCount = countByStatus(participantId, CERTIFICATED, targetDate); + + double successPercent = (double) successCount / (double) totalAttempt * 100; + return Math.round(successPercent * 100 / 100.0); + } } diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index d0e6cb40..b3c1453b 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -17,7 +17,7 @@ import com.genius.gitget.challenge.instance.service.InstanceProvider; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.participant.service.ParticipantService; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.UserProfileInfo; import com.genius.gitget.challenge.user.service.UserService; @@ -49,18 +49,18 @@ public class CertificationService { private final FilesService filesService; private final GithubProvider githubProvider; private final CertificationProvider certificationProvider; - private final ParticipantProvider participantProvider; + private final ParticipantService participantService; private final InstanceProvider instanceProvider; public WeekResponse getMyWeekCertifications(Long participantId, LocalDate currentDate) { - Participant participant = participantProvider.findById(participantId); + Participant participant = participantService.findById(participantId); return getWeekResponse(participant, currentDate); } public Slice getOthersWeekCertifications(Long userId, Long instanceId, LocalDate currentDate, Pageable pageable) { - Slice participants = participantProvider.findAllByInstanceId(userId, instanceId, pageable); + Slice participants = participantService.findAllByInstanceId(userId, instanceId, pageable); return participants.map( participant -> getWeekResponse(participant, currentDate) ); @@ -107,7 +107,7 @@ private List getWeekCertifications(List ce } public TotalResponse getTotalCertification(Long participantId, LocalDate currentDate) { - Instance instance = participantProvider.getInstanceById(participantId); + Instance instance = participantService.getInstanceById(participantId); LocalDate startDate = instance.getStartedDate().toLocalDate(); int totalAttempts = instance.getTotalAttempt(); @@ -156,7 +156,7 @@ private List getTotalCertifications(List c @Transactional public ActivatedResponse passCertification(Long userId, CertificationRequest certificationRequest) { Instance instance = instanceProvider.findById(certificationRequest.instanceId()); - Participant participant = participantProvider.findByJoinInfo(userId, instance.getId()); + Participant participant = participantService.findByJoinInfo(userId, instance.getId()); LocalDate targetDate = certificationRequest.targetDate(); Optional optionalCertification = certificationProvider.findByDate(targetDate, @@ -179,7 +179,7 @@ public ActivatedResponse passCertification(Long userId, CertificationRequest cer FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); //TODO: pass 했기 때문에 pass item이 필요없어 numOfPassItem을 0으로 전달하는 것 같음. but, 가독성이 떨어지기 때문에 수정 필요 - return ActivatedResponse.create(instance, certification.getCertificationStatus(), + return ActivatedResponse.of(instance, certification.getCertificationStatus(), 0, participant.getRepositoryName(), fileResponse); } @@ -193,7 +193,7 @@ private void validatePassCondition(Optional optional) { public CertificationResponse updateCertification(User user, CertificationRequest certificationRequest) { GitHub gitHub = githubProvider.getGithubConnection(user); Instance instance = instanceProvider.findById(certificationRequest.instanceId()); - Participant participant = participantProvider.findByJoinInfo(user.getId(), instance.getId()); + Participant participant = participantService.findByJoinInfo(user.getId(), instance.getId()); String repositoryName = participant.getRepositoryName(); LocalDate targetDate = certificationRequest.targetDate(); diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index 5d331418..a03b3878 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -14,7 +14,7 @@ import com.genius.gitget.challenge.likes.repository.LikesRepository; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.participant.service.ParticipantService; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.file.dto.FileResponse; @@ -36,7 +36,7 @@ public class InstanceDetailService { private final UserService userService; private final FilesService filesService; private final InstanceProvider instanceProvider; - private final ParticipantProvider participantProvider; + private final ParticipantService participantService; private final GithubProvider githubProvider; private final LikesRepository likesRepository; @@ -46,7 +46,7 @@ public InstanceResponse getInstanceDetailInformation(User user, Long instanceId) FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); LikesInfo likesInfo = getLikesInfo(user.getId(), instance); - if (participantProvider.hasJoinedParticipant(user.getId(), instanceId)) { + if (participantService.hasJoinedParticipant(user.getId(), instanceId)) { return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.YES, fileResponse); } @@ -77,7 +77,7 @@ public JoinResponse joinNewChallenge(User user, JoinRequest joinRequest) { instance.updateParticipantCount(1); Participant participant = Participant.createDefaultParticipant(repository); participant.setUserAndInstance(persistUser, instance); - return JoinResponse.createJoinResponse(participantProvider.save(participant)); + return JoinResponse.createJoinResponse(participantService.save(participant)); } private void validateJoinDate(Instance instance, LocalDate todayDate) { @@ -90,7 +90,7 @@ private void validateJoinDate(Instance instance, LocalDate todayDate) { } private void validateInstanceCondition(User user, Instance instance) { - boolean isParticipated = participantProvider.hasJoinedParticipant(user.getId(), instance.getId()); + boolean isParticipated = participantService.hasJoinedParticipant(user.getId(), instance.getId()); if ((instance.getProgress() == Progress.PREACTIVITY) && !isParticipated) { return; } @@ -106,7 +106,7 @@ private void validateGithub(User user, String repository) { @Transactional public JoinResponse quitChallenge(User user, Long instanceId) { Instance instance = instanceProvider.findById(instanceId); - Participant participant = participantProvider.findByJoinInfo(user.getId(), instanceId); + Participant participant = participantService.findByJoinInfo(user.getId(), instanceId); if (instance.getProgress() == Progress.DONE) { throw new BusinessException(CAN_NOT_QUIT_INSTANCE); @@ -114,7 +114,7 @@ public JoinResponse quitChallenge(User user, Long instanceId) { if (instance.getProgress() == Progress.PREACTIVITY) { instance.updateParticipantCount(-1); - participantProvider.delete(participant); + participantService.delete(participant); return JoinResponse.createQuitResponse(); } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java index ef3a2d72..22f17812 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java @@ -7,7 +7,7 @@ import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; -import com.genius.gitget.challenge.myChallenge.service.MyChallengeService; +import com.genius.gitget.challenge.myChallenge.facade.MyChallengeFacade; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.ListResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -28,14 +28,13 @@ @RequiredArgsConstructor @CrossOrigin public class MyChallengeController { - private final MyChallengeService myChallengeService; - + private final MyChallengeFacade myChallengeFacade; @GetMapping("/my/pre-activity") public ResponseEntity> getPreActivityChallenges( @AuthenticationPrincipal UserPrincipal userPrincipal ) { - List preActivityInstances = myChallengeService.getPreActivityInstances( + List preActivityInstances = myChallengeFacade.getPreActivityInstances( userPrincipal.getUser(), DateUtil.convertToKST(LocalDateTime.now())); @@ -49,7 +48,7 @@ public ResponseEntity> getPreActivityChallenge public ResponseEntity> getActivatedChallenges( @AuthenticationPrincipal UserPrincipal userPrincipal ) { - List activatedInstances = myChallengeService.getActivatedInstances( + List activatedInstances = myChallengeFacade.getActivatedInstances( userPrincipal.getUser(), DateUtil.convertToKST(LocalDateTime.now())); @@ -62,7 +61,7 @@ public ResponseEntity> getActivatedChallenges( public ResponseEntity> getDoneChallenges( @AuthenticationPrincipal UserPrincipal userPrincipal ) { - List doneInstances = myChallengeService.getDoneInstances( + List doneInstances = myChallengeFacade.getDoneInstances( userPrincipal.getUser(), DateUtil.convertToKST(LocalDateTime.now())); @@ -77,8 +76,8 @@ public ResponseEntity> getRewards( @PathVariable Long instanceId ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); - RewardRequest rewardRequest = new RewardRequest(userPrincipal.getUser(), instanceId, kstDate); - DoneResponse doneResponse = myChallengeService.getRewards(rewardRequest, false); + RewardRequest rewardRequest = new RewardRequest(userPrincipal.getUser().getId(), instanceId, kstDate); + DoneResponse doneResponse = myChallengeFacade.getRewards(rewardRequest); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), doneResponse) diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java index e1c01068..5ee50427 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/ActivatedResponse.java @@ -34,8 +34,8 @@ public ActivatedResponse(Long instanceId, String title, int pointPerPerson, Stri this.fileResponse = fileResponse; } - public static ActivatedResponse create(Instance instance, CertificateStatus certificateStatus, - int numOfPassItem, String repository, FileResponse fileResponse) { + public static ActivatedResponse of(Instance instance, CertificateStatus certificateStatus, + int numOfPassItem, String repository, FileResponse fileResponse) { boolean canUseItem = checkItemCondition(certificateStatus, numOfPassItem); return ActivatedResponse.builder() diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java index a00f16c3..9ed51312 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/PreActivityResponse.java @@ -1,5 +1,6 @@ package com.genius.gitget.challenge.myChallenge.dto; +import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.global.file.dto.FileResponse; import lombok.Builder; @@ -12,4 +13,15 @@ public record PreActivityResponse( int remainDays, FileResponse fileResponse ) { + + public static PreActivityResponse of(Instance instance, int remainDays, FileResponse fileResponse) { + return PreActivityResponse.builder() + .instanceId(instance.getId()) + .title(instance.getTitle()) + .participantCount(instance.getParticipantCount()) + .pointPerPerson(instance.getPointPerPerson()) + .remainDays(remainDays) + .fileResponse(fileResponse) + .build(); + } } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java index 5c13009e..d2f08b8c 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/dto/RewardRequest.java @@ -1,15 +1,14 @@ package com.genius.gitget.challenge.myChallenge.dto; -import com.genius.gitget.challenge.user.domain.User; import java.time.LocalDate; public record RewardRequest( - User user, + Long userId, Long instanceId, LocalDate targetDate ) { - public static RewardRequest of(User user, Long instanceId, LocalDate targetDate) { - return new RewardRequest(user, instanceId, targetDate); + public static RewardRequest of(Long userId, Long instanceId, LocalDate targetDate) { + return new RewardRequest(userId, instanceId, targetDate); } } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacade.java b/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacade.java new file mode 100644 index 00000000..135153e9 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacade.java @@ -0,0 +1,19 @@ +package com.genius.gitget.challenge.myChallenge.facade; + +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; +import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; +import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; +import com.genius.gitget.challenge.user.domain.User; +import java.time.LocalDate; +import java.util.List; + +public interface MyChallengeFacade { + List getPreActivityInstances(User user, LocalDate targetDate); + + List getActivatedInstances(User user, LocalDate targetDate); + + List getDoneInstances(User user, LocalDate targetDate); + + DoneResponse getRewards(RewardRequest rewardRequest); +} diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java similarity index 58% rename from src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java rename to src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java index 325ff858..97b41591 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java @@ -1,13 +1,9 @@ -package com.genius.gitget.challenge.myChallenge.service; +package com.genius.gitget.challenge.myChallenge.facade; -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; -import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; import static com.genius.gitget.challenge.participant.domain.RewardStatus.NO; -import static com.genius.gitget.challenge.participant.domain.RewardStatus.YES; import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; -import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.certification.domain.Certification; import com.genius.gitget.challenge.certification.service.CertificationProvider; import com.genius.gitget.challenge.certification.util.DateUtil; @@ -18,13 +14,10 @@ import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.participant.service.ParticipantService; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.service.ItemService; import com.genius.gitget.store.item.service.OrdersService; @@ -38,45 +31,64 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class MyChallengeService { - private final UserService userService; +public class MyChallengeFacadeService implements MyChallengeFacade { private final FilesService filesService; - private final ParticipantProvider participantProvider; + private final ParticipantService participantService; private final CertificationProvider certificationProvider; private final ItemService itemService; private final OrdersService ordersService; + @Override public List getPreActivityInstances(User user, LocalDate targetDate) { List preActivity = new ArrayList<>(); - List participants = participantProvider.findJoinedByProgress(user.getId(), Progress.PREACTIVITY); + List participants = participantService.findJoinedByProgress(user.getId(), Progress.PREACTIVITY); for (Participant participant : participants) { Instance instance = participant.getInstance(); FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + int remainDays = DateUtil.getRemainDaysToStart(participant.getStartedDate(), targetDate); - PreActivityResponse preActivityResponse = PreActivityResponse.builder() - .instanceId(instance.getId()) - .title(instance.getTitle()) - .participantCount(instance.getParticipantCount()) - .pointPerPerson(instance.getPointPerPerson()) - .remainDays(DateUtil.getRemainDaysToStart(participant.getStartedDate(), targetDate)) - .fileResponse(fileResponse) - .build(); + PreActivityResponse preActivityResponse = PreActivityResponse.of(instance, remainDays, fileResponse); preActivity.add(preActivityResponse); } return preActivity; } + @Override + public List getActivatedInstances(User user, LocalDate targetDate) { + List activated = new ArrayList<>(); + List participants = participantService.findJoinedByProgress(user.getId(), Progress.ACTIVITY); + + for (Participant participant : participants) { + Instance instance = participant.getInstance(); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + Certification certification = certificationProvider.findOrGetDummy(targetDate, participant.getId()); + + Item item = itemService.findAllByCategory(CERTIFICATION_PASSER).get(0); + int numOfPassItem = ordersService.countNumOfItem(user, item.getId()); + + ActivatedResponse activatedResponse = ActivatedResponse.of( + instance, certification.getCertificationStatus(), + numOfPassItem, participant.getRepositoryName(), fileResponse + ); + activatedResponse.setItemId(item.getId()); + activated.add(activatedResponse); + } + return activated; + } + + @Override public List getDoneInstances(User user, LocalDate targetDate) { List done = new ArrayList<>(); - List participants = participantProvider.findDoneInstances(user.getId()); + List participants = participantService.findDoneInstances(user.getId()); for (Participant participant : participants) { Instance instance = participant.getInstance(); FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - double achievementRate = getAchievementRate(instance, participant.getId(), targetDate); + double achievementRate = certificationProvider.getAchievementRate(instance, participant.getId(), + targetDate); // 포인트를 아직 수령하지 않았을 때 if (participant.getRewardStatus() == NO) { @@ -98,76 +110,22 @@ public List getDoneInstances(User user, LocalDate targetDate) { return done; } - private double getAchievementRate(Instance instance, Long participantId, LocalDate targetDate) { - int totalAttempt = instance.getTotalAttempt(); - int successCount = certificationProvider.countByStatus(participantId, CERTIFICATED, - targetDate); - - double successPercent = (double) successCount / (double) totalAttempt * 100; - return Math.round(successPercent * 100 / 100.0); - } - - public List getActivatedInstances(User user, LocalDate targetDate) { - List activated = new ArrayList<>(); - List participants = participantProvider.findJoinedByProgress(user.getId(), Progress.ACTIVITY); - - for (Participant participant : participants) { - Instance instance = participant.getInstance(); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - Certification certification = certificationProvider.findByDate(targetDate, participant.getId()) - .orElse(getDummyCertification()); - - //TODO: 로직 수정 필요 - Item item = itemService.findAllByCategory(CERTIFICATION_PASSER).get(0); - int numOfPassItem = ordersService.countNumOfItem(user, item.getId()); - - ActivatedResponse activatedResponse = ActivatedResponse.create( - instance, certification.getCertificationStatus(), - numOfPassItem, participant.getRepositoryName(), fileResponse - ); - activatedResponse.setItemId(item.getId()); - activated.add(activatedResponse); - } - return activated; - } - - private Certification getDummyCertification() { - return Certification.builder() - .currentAttempt(0) - .certificationStatus(CertificateStatus.NOT_YET) - .certificatedAt(null) - .certificationLinks(null) - .build(); - } - + @Override @Transactional - public DoneResponse getRewards(RewardRequest rewardRequest, boolean useItem) { - User user = userService.findUserById(rewardRequest.user().getId()); - Participant participant = participantProvider.findByJoinInfo(user.getId(), rewardRequest.instanceId()); + public DoneResponse getRewards(RewardRequest rewardRequest) { + Participant participant = participantService.findByJoinInfo( + rewardRequest.userId(), rewardRequest.instanceId() + ); Instance instance = participant.getInstance(); FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - validRewardCondition(participant); - int rewardPoints = instance.getPointPerPerson(); - if (useItem) { - rewardPoints *= 2; - } + participantService.getRewards(participant, rewardPoints); - user.updatePoints((long) rewardPoints); - double achievementRate = getAchievementRate(instance, participant.getId(), rewardRequest.targetDate()); + double achievementRate = certificationProvider.getAchievementRate(instance, participant.getId(), + rewardRequest.targetDate()); - participant.getRewards(rewardPoints); return DoneResponse.createRewarded(instance, participant, achievementRate, fileResponse); } - - private void validRewardCondition(Participant participant) { - if (participant.getJoinResult() != SUCCESS) { - throw new BusinessException(ErrorCode.CAN_NOT_GET_REWARDS); - } - if (participant.getRewardStatus() == YES) { - throw new BusinessException(ErrorCode.ALREADY_REWARDED); - } - } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java index 02504576..09cfba9f 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java +++ b/src/main/java/com/genius/gitget/challenge/participant/domain/Participant.java @@ -1,8 +1,13 @@ package com.genius.gitget.challenge.participant.domain; +import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; +import static com.genius.gitget.challenge.participant.domain.RewardStatus.YES; + import com.genius.gitget.challenge.certification.domain.Certification; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -108,6 +113,15 @@ public LocalDate getStartedDate() { return this.getInstance().getStartedDate().toLocalDate(); } + public void validateRewardCondition() { + if (this.getJoinResult() != SUCCESS) { + throw new BusinessException(ErrorCode.CAN_NOT_GET_REWARDS); + } + if (this.getRewardStatus() == YES) { + throw new BusinessException(ErrorCode.ALREADY_REWARDED); + } + } + /*== 연관관계 편의 메서드 ==*/ public void setUserAndInstance(User user, Instance instance) { diff --git a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantService.java similarity index 90% rename from src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java rename to src/main/java/com/genius/gitget/challenge/participant/service/ParticipantService.java index a8241f21..e33c23fe 100644 --- a/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantProvider.java +++ b/src/main/java/com/genius/gitget/challenge/participant/service/ParticipantService.java @@ -7,6 +7,7 @@ import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.util.exception.BusinessException; import java.util.ArrayList; import java.util.List; @@ -22,7 +23,7 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class ParticipantProvider { +public class ParticipantService { private final ParticipantRepository participantRepository; @Transactional @@ -81,4 +82,13 @@ public boolean hasJoinedParticipant(Long userId, Long instanceId) { .map(participant -> participant.getJoinStatus() != JoinStatus.NO) .orElse(false); } + + @Transactional + public void getRewards(Participant participant, int rewardPoints) { + User user = participant.getUser(); + + participant.validateRewardCondition(); + user.updatePoints((long) rewardPoints); + participant.getRewards(rewardPoints); + } } diff --git a/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java b/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java index eb9c9a76..f68a1909 100644 --- a/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java +++ b/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java @@ -2,10 +2,12 @@ import com.genius.gitget.challenge.certification.dto.CertificationRequest; import com.genius.gitget.challenge.certification.service.CertificationService; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.service.InstanceService; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; -import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; -import com.genius.gitget.challenge.myChallenge.service.MyChallengeService; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.service.ParticipantService; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.util.exception.BusinessException; @@ -36,12 +38,11 @@ public class StoreFacadeService implements StoreFacade { private final OrdersService ordersService; private final UserService userService; + private final InstanceService instanceService; + private final ParticipantService participantService; + // TODO: CertificationProvider에만 의존하도록 변경(파사드 패턴 적용 시) private final CertificationService certificationService; - - //TODO: 책임이 분명한 Service를 만들어서 적용하기(MyChallenge 진행 시) - private final MyChallengeService myChallengeService; - private final PaymentRepository paymentRepository; @@ -67,15 +68,16 @@ private ItemResponse getItemResponse(User user, Item item, int numOfItem) { } @Override + @Transactional public ItemResponse orderItem(User user, int identifier) { User persistUser = userService.findUserById(user.getId()); Item item = itemService.findByIdentifier(identifier); persistUser.hasEnoughPoint(item.getCost()); - paymentRepository.save(Payment.create(user, item)); + paymentRepository.save(Payment.create(persistUser, item)); - Orders orders = ordersService.findOrSave(user, item); + Orders orders = ordersService.findOrSave(persistUser, item); int numOfItem = orders.purchase(); persistUser.updatePoints((long) item.getCost() * -1); @@ -83,6 +85,7 @@ public ItemResponse orderItem(User user, int identifier) { } @Override + @Transactional public OrderResponse useItem(User user, int identifier, Long instanceId, LocalDate currentDate) { Item item = itemService.findByIdentifier(identifier); Orders orders = ordersService.findByOrderInfo(user.getId(), item.getId()); @@ -141,12 +144,16 @@ public OrderResponse usePasserItem(Orders orders, Long instanceId, LocalDate cur @Override public OrderResponse useMultiplierItem(Orders orders, Long instanceId, LocalDate currentDate) { User user = orders.getUser(); - DoneResponse doneResponse = myChallengeService.getRewards( - RewardRequest.of(user, instanceId, currentDate), true - ); - doneResponse.setItemId(orders.getItem().getId()); + Instance instance = instanceService.findInstanceById(instanceId); + Participant participant = participantService.findByJoinInfo(user.getId(), instanceId); + + int rewardPoints = instance.getPointPerPerson() * 2; + participantService.getRewards(participant, rewardPoints); ordersService.useItem(orders); - return doneResponse; + + return DoneResponse.builder() + .rewardedPoints(rewardPoints) + .build(); } @Override @@ -155,20 +162,11 @@ public List unmountFrame(User user) { List frameOrders = ordersService.findAllUsingFrames(user.getId()); for (Orders frameOrder : frameOrders) { - validateUnmountCondition(frameOrder); + ordersService.validateUnmountCondition(frameOrder); frameOrder.updateEquipStatus(EquipStatus.AVAILABLE); profileResponses.add(ProfileResponse.createByEntity(frameOrder)); } return profileResponses; } - - private void validateUnmountCondition(Orders orders) { - if (orders.getItem().getItemCategory() != ItemCategory.PROFILE_FRAME) { - throw new BusinessException(ErrorCode.ITEM_NOT_FOUND); - } - if (orders.getEquipStatus() != EquipStatus.IN_USE) { - throw new BusinessException(ErrorCode.IN_USE_FRAME_NOT_FOUND); - } - } } diff --git a/src/main/java/com/genius/gitget/store/item/service/OrdersService.java b/src/main/java/com/genius/gitget/store/item/service/OrdersService.java index 5f05f9b3..26457721 100644 --- a/src/main/java/com/genius/gitget/store/item/service/OrdersService.java +++ b/src/main/java/com/genius/gitget/store/item/service/OrdersService.java @@ -42,6 +42,7 @@ public Orders findByOrderInfo(Long userId, Long itemId) { .orElseThrow(() -> new BusinessException(ORDERS_NOT_FOUND)); } + @Transactional public Orders findOrSave(User user, Item item) { return ordersRepository.findByOrderInfo(user.getId(), item.getId()) .orElseGet(() -> ordersRepository.save(Orders.of(user, item))); @@ -76,6 +77,7 @@ public Item getUsingFrameItem(Long userId) { return usingFrames.get(0).getItem(); } + @Transactional public void useItem(Orders orders) { orders.useItem(); if (!orders.hasItem()) { @@ -88,4 +90,13 @@ public int countNumOfItem(User user, Long itemId) { return optionalUserItem.map(Orders::getCount) .orElse(0); } + + public void validateUnmountCondition(Orders orders) { + if (orders.getItem().getItemCategory() != ItemCategory.PROFILE_FRAME) { + throw new BusinessException(ErrorCode.ITEM_NOT_FOUND); + } + if (orders.getEquipStatus() != EquipStatus.IN_USE) { + throw new BusinessException(ErrorCode.IN_USE_FRAME_NOT_FOUND); + } + } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 2a3fa6a2..38a34262 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -20,7 +20,7 @@ import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.repository.ParticipantRepository; -import com.genius.gitget.challenge.participant.service.ParticipantProvider; +import com.genius.gitget.challenge.participant.service.ParticipantService; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; @@ -47,7 +47,7 @@ class InstanceDetailServiceTest { @Autowired LikesFacade likesFacade; @Autowired - ParticipantProvider participantProvider; + ParticipantService participantService; @Autowired GithubService githubService; @Autowired @@ -202,7 +202,7 @@ public void should_changeParticipantInfo_when_requestQuitInstance() { instanceDetailService.joinNewChallenge(savedUser, joinRequest); savedInstance.updateProgress(Progress.ACTIVITY); instanceDetailService.quitChallenge(savedUser, savedInstance.getId()); - Participant participant = participantProvider.findByJoinInfo(savedUser.getId(), + Participant participant = participantService.findByJoinInfo(savedUser.getId(), savedInstance.getId()); //then diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeFacadeTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeFacadeTest.java new file mode 100644 index 00000000..d75cda1a --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeFacadeTest.java @@ -0,0 +1,287 @@ +package com.genius.gitget.challenge.myChallenge.service; + +import static com.genius.gitget.challenge.participant.domain.JoinResult.FAIL; +import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; +import static com.genius.gitget.challenge.participant.domain.RewardStatus.NO; +import static com.genius.gitget.challenge.participant.domain.RewardStatus.YES; +import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; +import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; +import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; +import com.genius.gitget.challenge.myChallenge.facade.MyChallengeFacade; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; +import com.genius.gitget.util.certification.CertificationFactory; +import com.genius.gitget.util.instance.InstanceFactory; +import com.genius.gitget.util.participant.ParticipantFactory; +import com.genius.gitget.util.store.StoreFactory; +import com.genius.gitget.util.user.UserFactory; +import java.time.LocalDate; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +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 org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +class MyChallengeFacadeTest { + private LocalDate localDate = LocalDate.now(); + private User user; + private Instance instance1; + private Instance instance2; + private Instance instance3; + + @Autowired + private MyChallengeFacade myChallengeFacade; + @Autowired + private UserRepository userRepository; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private CertificationRepository certificationRepository; + @Autowired + private ItemRepository itemRepository; + @Autowired + private OrdersRepository ordersRepository; + + @BeforeEach + void setup() { + user = userRepository.save(UserFactory.createUser()); + } + + + @Nested + @DisplayName("시작 전 인스턴스들을 조회할 때") + class context_inquiry_preActivity_instances { + @BeforeEach + void setup() { + instance1 = instanceRepository.save(InstanceFactory.createPreActivity(10)); + instance2 = instanceRepository.save(InstanceFactory.createPreActivity(10)); + instance3 = instanceRepository.save(InstanceFactory.createPreActivity(10)); + } + + @Nested + @DisplayName("참여한 인스턴스 중 시작 전인 인스턴스들이 있을 때") + class describe_joined_preActivity_instance { + @BeforeEach + void setup() { + participantRepository.save(ParticipantFactory.createPreActivity(user, instance1)); + participantRepository.save(ParticipantFactory.createPreActivity(user, instance2)); + participantRepository.save(ParticipantFactory.createPreActivity(user, instance3)); + } + + @Test + @DisplayName("인스턴스 목록들을 조회할 수 있다.") + public void it_returns_instance_list() { + List preActivityInstances = myChallengeFacade.getPreActivityInstances(user, + localDate); + assertThat(preActivityInstances.size()).isEqualTo(3); + } + } + } + + @Nested + @DisplayName("진행 중인 인스턴스 조회 시") + class context_inquiry_activity_instances { + Participant participant; + + @BeforeEach + void setup() { + instance1 = instanceRepository.save(InstanceFactory.createActivity(10)); + participant = participantRepository.save(ParticipantFactory.createPreActivity(user, instance1)); + } + + @Nested + @DisplayName("패스 아이템 사용 여부 조회할 때") + class describe_check_pass_item { + Item item; + Orders orders; + + @BeforeEach + void setup() { + item = itemRepository.findAllByCategory(CERTIFICATION_PASSER).get(0); + orders = ordersRepository.save(StoreFactory.createOrders(user, item, CERTIFICATION_PASSER, 3)); + } + + @Test + @DisplayName("인증 정보가 DB에 저장되어 있지 않다면 사용 가능 여부가 true여야 한다.") + public void it_return_true_when_certification_not_saved_DB() { + List activatedInstances = myChallengeFacade.getActivatedInstances(user, localDate); + for (ActivatedResponse activatedInstance : activatedInstances) { + assertThat(activatedInstance.isCanUsePassItem()).isTrue(); + } + } + + @Test + @DisplayName("인증 정보가 NOT_YET 이라면 사용 가능 여부가 true여야 한다.") + public void it_return_true_when_certification_NOT_YET() { + certificationRepository.save(CertificationFactory.createNotYet(participant, localDate)); + + List activatedInstances = myChallengeFacade.getActivatedInstances(user, localDate); + for (ActivatedResponse activatedInstance : activatedInstances) { + assertThat(activatedInstance.isCanUsePassItem()).isTrue(); + } + } + + @ParameterizedTest + @DisplayName("인증 정보가 PASSED 혹은 CERTIFICATED라면 사용 가능 여부가 false여야 한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"PASSED", "CERTIFICATED"}) + public void it_return_false_when_certification_PASSED_or_CERTIFICATED(CertificateStatus certificateStatus) { + certificationRepository.save(CertificationFactory.create(certificateStatus, localDate, participant)); + + List activatedInstances = myChallengeFacade.getActivatedInstances(user, localDate); + for (ActivatedResponse activatedInstance : activatedInstances) { + assertThat(activatedInstance.isCanUsePassItem()).isFalse(); + } + } + } + } + + @Nested + @DisplayName("완료된 인스턴스 조회 시") + class context_inquiry_done_instances { + Participant participant; + + @BeforeEach + void setup() { + instance1 = instanceRepository.save(InstanceFactory.createDone(10)); + } + + @Nested + @DisplayName("아직 보상받지 않은 인스턴스가 있을 때") + class describe_not_rewarded_challenge_exist { + @BeforeEach + void setup() { + participant = participantRepository.save( + ParticipantFactory.createByRewardStatus(user, instance1, SUCCESS, NO)); + } + + @Test + @DisplayName("실패한 챌린지라면 획득 포인트는 0이어야하고, 달성률 정보를 전달해야 한다.") + public void it_returns_achievementRate() { + List doneInstances = myChallengeFacade.getDoneInstances(user, localDate); + for (DoneResponse doneInstance : doneInstances) { + assertThat(doneInstance.getRewardedPoints()).isEqualTo(0); + assertThat(doneInstance.getAchievementRate()).isEqualTo(0.0); + } + } + + @Test + @DisplayName("성공한 챌린지라면 보상 가능 여부가 true어야 한다.") + public void it_return_true_when_success_instances() { + List doneInstances = myChallengeFacade.getDoneInstances(user, localDate); + for (DoneResponse doneInstance : doneInstances) { + assertThat(doneInstance.isCanGetReward()).isTrue(); + } + } + } + + @Nested + @DisplayName("보상이 완료된 인스턴스가 있을 때") + class describe_rewarded_challenge_exist { + @BeforeEach + void setup() { + participant = participantRepository.save( + ParticipantFactory.createByRewardStatus(user, instance1, SUCCESS, YES)); + } + + @Test + @DisplayName("획득 포인트와 달성률에 대한 정보를 전달해야 한다.") + public void it_returns_point_and_achievementRate() { + List doneInstances = myChallengeFacade.getDoneInstances(user, localDate); + DoneResponse doneResponse = doneInstances.get(0); + assertThat(doneResponse.getRewardedPoints()).isEqualTo(participant.getRewardPoints()); + } + } + } + + @Nested + @DisplayName("챌린지 보상을 받을 때") + class context_ { + Participant participant; + + @BeforeEach + void setup() { + instance1 = instanceRepository.save(InstanceFactory.createDone(10)); + } + + @Nested + @DisplayName("보상을 받을 수 있는 조건인지 확인했을 때") + class describe_validate_reward_condition { + @Test + @DisplayName("JoinResult가 SUCCESS가 아니라면 CAN_NOT_GET_REWARDS 예외가 발생해야 한다.") + public void it_throws_CAN_NOT_GET_REWARDS_exception() { + participant = participantRepository.save( + ParticipantFactory.createByRewardStatus(user, instance1, FAIL, NO)); + assertThatThrownBy(() -> myChallengeFacade.getRewards( + RewardRequest.of(user.getId(), instance1.getId(), localDate))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.CAN_NOT_GET_REWARDS.getMessage()); + } + + @Test + @DisplayName("RewardStatus가 YES라면 ALREADY_REWARDED 예외가 발생해야 한다.") + public void it_throws_ALREADY_REWARDED_exception() { + participant = participantRepository.save( + ParticipantFactory.createByRewardStatus(user, instance1, SUCCESS, YES) + ); + assertThatThrownBy(() -> myChallengeFacade.getRewards( + RewardRequest.of(user.getId(), instance1.getId(), localDate) + )) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.ALREADY_REWARDED.getMessage()); + } + } + + @Nested + @DisplayName("보상을 받을 수 있고, 보상을 받았을 때") + class describe_get_rewards { + @BeforeEach + void setup() { + participant = participantRepository.save( + ParticipantFactory.createByRewardStatus(user, instance1, SUCCESS, NO) + ); + myChallengeFacade.getRewards(RewardRequest.of(user.getId(), instance1.getId(), localDate)); + } + + @Test + @DisplayName("participant의 RewardStatus가 YES가 되어야 한다.") + public void it_change_rewardStatus_YES() { + assertThat(participant.getRewardStatus()).isEqualTo(YES); + } + + @Test + @DisplayName("user의 point의 값이 갱신되어야 한다.") + public void it_change_user_point() { + assertThat(user.getPoint()).isEqualTo(participant.getRewardPoints()); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java b/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java deleted file mode 100644 index ba67aeb6..00000000 --- a/src/test/java/com/genius/gitget/challenge/myChallenge/service/MyChallengeServiceTest.java +++ /dev/null @@ -1,292 +0,0 @@ -package com.genius.gitget.challenge.myChallenge.service; - -import static com.genius.gitget.challenge.participant.domain.JoinResult.FAIL; -import static com.genius.gitget.challenge.participant.domain.JoinResult.PROCESSING; -import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; -import static org.assertj.core.api.Assertions.assertThat; - -import com.genius.gitget.challenge.certification.domain.CertificateStatus; -import com.genius.gitget.challenge.certification.domain.Certification; -import com.genius.gitget.challenge.certification.repository.CertificationRepository; -import com.genius.gitget.challenge.certification.util.DateUtil; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; -import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; -import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; -import com.genius.gitget.challenge.participant.domain.JoinResult; -import com.genius.gitget.challenge.participant.domain.JoinStatus; -import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.domain.RewardStatus; -import com.genius.gitget.challenge.participant.repository.ParticipantRepository; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.store.item.repository.OrdersRepository; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.EnumSource.Mode; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@SpringBootTest -@Transactional -class MyChallengeServiceTest { - @Autowired - private MyChallengeService myChallengeService; - @Autowired - private UserRepository userRepository; - @Autowired - private InstanceRepository instanceRepository; - @Autowired - private ParticipantRepository participantRepository; - @Autowired - private ItemRepository itemRepository; - @Autowired - private OrdersRepository ordersRepository; - @Autowired - private CertificationRepository certificationRepository; - - @Test - @DisplayName("사용자가 참여한 챌린지들 중, 시작 전인 챌린지들을 받아올 수 있다.") - public void should_getPreActivities_when_userJoinChallenges() { - //given - LocalDate targetDate = LocalDate.of(2024, 2, 14); - User user = getSavedUser(); - Instance instance1 = getSavedInstance(Progress.PREACTIVITY); - Instance instance2 = getSavedInstance(Progress.PREACTIVITY); - Instance instance3 = getSavedInstance(Progress.PREACTIVITY); - Participant participant1 = getSavedParticipant(user, instance1, PROCESSING); - Participant participant2 = getSavedParticipant(user, instance2, PROCESSING); - Participant participant3 = getSavedParticipant(user, instance3, PROCESSING); - - //when - List instances = myChallengeService.getPreActivityInstances(user, targetDate); - - //then - assertThat(instances.size()).isEqualTo(3); - assertThat(instances.get(0).instanceId()).isEqualTo(instance1.getId()); - - assertThat(instances.get(0).fileResponse()).isNotNull(); - assertThat(instances.get(1).instanceId()).isEqualTo(instance2.getId()); - assertThat(instances.get(1).fileResponse()).isNotNull(); - assertThat(instances.get(2).instanceId()).isEqualTo(instance3.getId()); - assertThat(instances.get(2).fileResponse()).isNotNull(); - } - - @Test - @DisplayName("진행 중인 챌린지 목록 조회 시, 패스 아이템을 사용할 수 있는 조건이라면 아이템 사용 가능하다는 데이터를 반환한다.") - public void should_getActivatedList_when_userJoinChallenges() { - //given - LocalDate targetDate = LocalDate.of(2024, 2, 14); - User user = getSavedUser(); - Instance instance1 = getSavedInstance(Progress.ACTIVITY); - Instance instance2 = getSavedInstance(Progress.ACTIVITY); - Participant participant1 = getSavedParticipant(user, instance1, PROCESSING); - Participant participant2 = getSavedParticipant(user, instance2, PROCESSING); - - Item item = itemRepository.findAllByCategory(ItemCategory.CERTIFICATION_PASSER).get(0); - Orders orders = getSavedOrder(user, item, item.getItemCategory(), 3); - - //when - getSavedCertification(CertificateStatus.NOT_YET, targetDate, participant2); - List activatedResponses = myChallengeService.getActivatedInstances(user, targetDate); - - //then - assertThat(activatedResponses.size()).isEqualTo(2); - assertThat(activatedResponses.get(0).getNumOfPassItem()).isEqualTo(3); - assertThat(activatedResponses.get(1).getNumOfPassItem()).isEqualTo(3); - } - - @ParameterizedTest - @DisplayName("진행 중인 챌린지 목록 조회 시, NOT_YET을 제외한 챌린지들은 아이템 사용 가능 여부가 false여야 한다.") - @EnumSource(mode = Mode.INCLUDE, names = {"CERTIFICATED", "PASSED"}) - public void should_canUserPassItemIsFalse_when_AlreadyCertificated(CertificateStatus certificateStatus) { - //given - LocalDate targetDate = LocalDate.of(2024, 2, 14); - User user = getSavedUser(); - Instance instance1 = getSavedInstance(Progress.ACTIVITY); - Participant participant1 = getSavedParticipant(user, instance1, PROCESSING); - - Item item = getSavedItem(ItemCategory.CERTIFICATION_PASSER); - getSavedOrder(user, item, item.getItemCategory(), 3); - - //when - getSavedCertification(certificateStatus, targetDate, participant1); - List instances = myChallengeService.getActivatedInstances(user, targetDate); - - //then - assertThat(instances.size()).isEqualTo(1); - assertThat(instances.get(0).getCertificateStatus()).isEqualTo(certificateStatus.getTag()); - assertThat(instances.get(0).getNumOfPassItem()).isEqualTo(0); - assertThat(instances.get(0).getPointPerPerson()).isEqualTo(instance1.getPointPerPerson()); - } - - @Test - @DisplayName("완료된 챌린지 목록 조회 시, 포인트를 아직 수령하지 않은 챌린지에 대해서는 보상 가능 정보를 전달해야 한다.") - public void should_returnTrue_when_ableToReward() { - //given - LocalDateTime targetDate = LocalDateTime.of(2024, 2, 14, 0, 0); - User user = getSavedUser(); - Instance instance = getSavedInstance(Progress.DONE, targetDate, targetDate.plusDays(1)); - Participant participant = getSavedParticipant(user, instance, SUCCESS); - Item item = itemRepository.findAllByCategory(ItemCategory.POINT_MULTIPLIER).get(0); - getSavedOrder(user, item, item.getItemCategory(), 3); - - //when - getSavedCertification(CertificateStatus.PASSED, targetDate.toLocalDate(), participant); - getSavedCertification(CertificateStatus.CERTIFICATED, targetDate.plusDays(1).toLocalDate(), participant); - List doneResponses = myChallengeService.getDoneInstances(user, targetDate.toLocalDate()); - - //then - DoneResponse doneResponse = doneResponses.get(0); - assertThat(doneResponses.size()).isEqualTo(1); - assertThat(doneResponse.getTitle()).isEqualTo(instance.getTitle()); - assertThat(doneResponse.getInstanceId()).isEqualTo(instance.getId()); - assertThat(doneResponse.getRewardedPoints()).isZero(); - assertThat(doneResponse.getJoinResult()).isEqualTo(SUCCESS); - assertThat(doneResponse.getFileResponse()).isNotNull(); - assertThat(doneResponse.isCanGetReward()).isTrue(); - assertThat(doneResponse.getNumOfPointItem()).isEqualTo(3); - } - - @Test - @DisplayName("완료된 챌린지 목록 조회 시, 포인트를 수령한 챌린지에 대해서는 포인트 수령 정보를 전달해야 한다.") - public void should_returnRewardInfo_when_alreadyRewarded() { - LocalDate targetDate = LocalDate.of(2024, 2, 14); - User user = getSavedUser(); - Instance instance = getSavedInstance(Progress.DONE); - getSavedParticipant(user, instance, SUCCESS); - - Item item = getSavedItem(ItemCategory.POINT_MULTIPLIER); - getSavedOrder(user, item, item.getItemCategory(), 3); - - //when - List doneResponses = myChallengeService.getDoneInstances(user, targetDate); - - //then - assertThat(doneResponses.size()).isEqualTo(1); - assertThat(doneResponses.get(0).isCanGetReward()).isTrue(); - } - - @Test - @DisplayName("챌린지는 종료되었으나 실패한 챌린지에 대해, 정보를 전달해야 한다.") - public void should_returnInfo_when_failedChallenge() { - //given - LocalDate targetDate = LocalDate.of(2024, 2, 14); - User user = getSavedUser(); - Instance instance = getSavedInstance(Progress.DONE); - getSavedParticipant(user, instance, FAIL); - - //when - List doneInstances = myChallengeService.getDoneInstances(user, targetDate); - DoneResponse doneResponse = doneInstances.get(0); - - //then - assertThat(doneInstances.size()).isEqualTo(1); - - assertThat(doneResponse.getInstanceId()).isEqualTo(instance.getId()); - assertThat(doneResponse.getTitle()).isEqualTo(instance.getTitle()); - assertThat(doneResponse.getInstanceId()).isEqualTo(instance.getId()); - assertThat(doneResponse.getRewardedPoints()).isZero(); - assertThat(doneResponse.getJoinResult()).isEqualTo(FAIL); - assertThat(doneResponse.getFileResponse()).isNotNull(); - assertThat(doneResponse.isCanGetReward()).isFalse(); - assertThat(doneResponse.getAchievementRate()).isEqualTo(0.0); - assertThat(doneResponse.getNumOfPointItem()).isEqualTo(0); - } - - - private User getSavedUser() { - return userRepository.save( - User.builder() - .role(Role.USER) - .nickname("nickname") - .providerInfo(ProviderInfo.GITHUB) - .identifier("githubId") - .information("information") - .tags("BE,FE") - .build() - ); - } - - private Instance getSavedInstance(Progress progress) { - return instanceRepository.save( - Instance.builder() - .progress(progress) - .pointPerPerson(100) - .title("title") - .startedDate(LocalDateTime.of(2024, 2, 1, 11, 3)) - .completedDate(LocalDateTime.of(2024, 3, 29, 23, 59)) - .build() - ); - } - - private Instance getSavedInstance(Progress progress, LocalDateTime startedDate, LocalDateTime completedDate) { - return instanceRepository.save( - Instance.builder() - .progress(progress) - .pointPerPerson(100) - .title("title") - .startedDate(startedDate) - .completedDate(completedDate) - .build() - ); - } - - private Participant getSavedParticipant(User user, Instance instance, JoinResult joinResult) { - Participant participant = participantRepository.save( - Participant.builder() - .joinResult(joinResult) - .joinStatus(JoinStatus.YES) - .rewardStatus(RewardStatus.NO) - .build() - ); - participant.setUserAndInstance(user, instance); - instance.updateParticipantCount(1); - return participant; - } - - - private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, - Participant participant) { - int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); - Certification certification = Certification.builder() - .certificationStatus(status) - .currentAttempt(attempt) - .certificatedAt(certificatedAt) - .certificationLinks("certificationLink") - .build(); - certification.setParticipant(participant); - return certificationRepository.save(certification); - } - - private Item getSavedItem(ItemCategory itemCategory) { - return itemRepository.save(Item.builder() - .itemCategory(itemCategory) - .cost(100) - .name(itemCategory.getName()) - .build()); - } - - private Orders getSavedOrder(User user, Item item, ItemCategory itemCategory, int count) { - Orders orders = Orders.of(count, itemCategory); - orders.setItem(item); - orders.setUser(user); - return ordersRepository.save(orders); - } -} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java b/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantServiceTest.java similarity index 91% rename from src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java rename to src/test/java/com/genius/gitget/challenge/participant/service/ParticipantServiceTest.java index 821817ba..d1d9a38a 100644 --- a/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantProviderTest.java +++ b/src/test/java/com/genius/gitget/challenge/participant/service/ParticipantServiceTest.java @@ -24,9 +24,9 @@ @SpringBootTest @Transactional -class ParticipantProviderTest { +class ParticipantServiceTest { @Autowired - ParticipantProvider participantProvider; + ParticipantService participantService; @Autowired UserRepository userRepository; @Autowired @@ -44,7 +44,7 @@ public void should_getParticipantInfo_when_passUserIdAndInstanceId() { getSavedParticipant(savedUser, savedInstance); //when - Participant participant = participantProvider.findByJoinInfo(savedUser.getId(), + Participant participant = participantService.findByJoinInfo(savedUser.getId(), savedInstance.getId()); //then @@ -61,7 +61,7 @@ public void should_getParticipant_when_passPK() { Participant participant = getSavedParticipant(savedUser, savedInstance); //when - Participant foundParticipant = participantProvider.findById(participant.getId()); + Participant foundParticipant = participantService.findById(participant.getId()); //then assertThat(foundParticipant.getId()).isEqualTo(participant.getId()); @@ -83,7 +83,7 @@ public void should_returnList_when_passProgress() { Participant participant3 = getSavedParticipant(user, instance3); //when - List participants = participantProvider.findJoinedByProgress(user.getId(), PREACTIVITY); + List participants = participantService.findJoinedByProgress(user.getId(), PREACTIVITY); //then assertThat(participants.size()).isEqualTo(2); @@ -102,7 +102,7 @@ public void should_return_activity_participants() { Participant participant3 = getSavedParticipant(user, instance3); //when - List participants = participantProvider.findJoinedByProgress(user.getId(), ACTIVITY); + List participants = participantService.findJoinedByProgress(user.getId(), ACTIVITY); //then assertThat(participants.size()).isEqualTo(1); @@ -121,7 +121,7 @@ public void should_return_quit_instances_when_activity() { Participant participant1 = getSavedParticipant(user, instance3, JoinStatus.NO); //when - List participants = participantProvider.findDoneInstances(user.getId()); + List participants = participantService.findDoneInstances(user.getId()); //then assertThat(participants.size()).isEqualTo(1); @@ -140,7 +140,7 @@ public void should_return_success_instances() { Participant participant1 = getSavedParticipant(user, instance3); //when - List participants = participantProvider.findDoneInstances(user.getId()); + List participants = participantService.findDoneInstances(user.getId()); //then assertThat(participants.size()).isEqualTo(2); diff --git a/src/test/java/com/genius/gitget/store/facade/StoreFacadeTest.java b/src/test/java/com/genius/gitget/store/facade/StoreFacadeTest.java index 8e61297b..d19c83fb 100644 --- a/src/test/java/com/genius/gitget/store/facade/StoreFacadeTest.java +++ b/src/test/java/com/genius/gitget/store/facade/StoreFacadeTest.java @@ -1,5 +1,8 @@ package com.genius.gitget.store.facade; +import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; +import static com.genius.gitget.challenge.participant.domain.RewardStatus.NO; +import static com.genius.gitget.challenge.participant.domain.RewardStatus.YES; import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REWARDED; import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_GET_REWARDS; import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_USE_PASS_ITEM; @@ -15,7 +18,6 @@ import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.domain.RewardStatus; import com.genius.gitget.challenge.participant.repository.ParticipantRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; @@ -223,7 +225,7 @@ public void it_returns_200_when_certification_not_exist() { public void it_returns_200_when_certification_is_NOT_YET() { int holding = orders.getCount(); certificationRepository.save( - CertificationFactory.create(CertificateStatus.NOT_YET, currentDate, participant) + CertificationFactory.createNotYet(participant, currentDate) ); storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate); @@ -303,7 +305,7 @@ public void it_returns_200_when_instance_is_DONE() { int holding = orders.getCount(); instance = instanceRepository.save(InstanceFactory.createDone(10)); participant = participantRepository.save( - ParticipantFactory.createByRewardStatus(user, instance, RewardStatus.NO)); + ParticipantFactory.createByRewardStatus(user, instance, SUCCESS, NO)); storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate); @@ -334,7 +336,7 @@ public void it_throws_exception_when_JoinResult_not_SUCCESS(JoinResult joinResul @DisplayName("participant의 RewardStatus가 YES라면 ALREADY_REWARDED 예외가 발생한다.") public void it_throws_exception_when_RewardStatus_is_YES() { participant = participantRepository.save( - ParticipantFactory.createByRewardStatus(user, instance, RewardStatus.YES) + ParticipantFactory.createByRewardStatus(user, instance, SUCCESS, YES) ); assertThatThrownBy(() -> storeFacade.useItem(user, item.getIdentifier(), instance.getId(), currentDate)) .isInstanceOf(BusinessException.class) @@ -349,7 +351,7 @@ class context_item_count_is_zero { void setup() { instance = instanceRepository.save(InstanceFactory.createDone(10)); participant = participantRepository.save( - ParticipantFactory.createByRewardStatus(user, instance, RewardStatus.NO)); + ParticipantFactory.createByRewardStatus(user, instance, SUCCESS, NO)); } @Test diff --git a/src/test/java/com/genius/gitget/util/certification/CertificationFactory.java b/src/test/java/com/genius/gitget/util/certification/CertificationFactory.java index e68b9dd7..572a3717 100644 --- a/src/test/java/com/genius/gitget/util/certification/CertificationFactory.java +++ b/src/test/java/com/genius/gitget/util/certification/CertificationFactory.java @@ -19,4 +19,40 @@ public static Certification create(CertificateStatus status, LocalDate certifica certification.setParticipant(participant); return certification; } + + public static Certification createNotYet(Participant participant, LocalDate certificatedAt) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); + Certification certification = Certification.builder() + .certificationStatus(CertificateStatus.NOT_YET) + .currentAttempt(attempt) + .certificatedAt(certificatedAt) + .certificationLinks(null) + .build(); + certification.setParticipant(participant); + return certification; + } + + public static Certification createCertificated(Participant participant, LocalDate certificatedAt) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); + Certification certification = Certification.builder() + .certificationStatus(CertificateStatus.CERTIFICATED) + .currentAttempt(attempt) + .certificatedAt(certificatedAt) + .certificationLinks("certificationLink") + .build(); + certification.setParticipant(participant); + return certification; + } + + public static Certification createPassed(Participant participant, LocalDate certificatedAt) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); + Certification certification = Certification.builder() + .certificationStatus(CertificateStatus.PASSED) + .currentAttempt(attempt) + .certificatedAt(certificatedAt) + .certificationLinks(null) + .build(); + certification.setParticipant(participant); + return certification; + } } diff --git a/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java b/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java index 1a6de597..8adc887e 100644 --- a/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java +++ b/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java @@ -32,7 +32,7 @@ public static Instance createActivity(int duration) { */ public static Instance createDone(int duration) { return Instance.builder() - .progress(Progress.ACTIVITY) + .progress(Progress.DONE) .startedDate(LocalDateTime.now().minusDays(duration - 1)) .completedDate(LocalDateTime.now().minusDays(1)) .build(); diff --git a/src/test/java/com/genius/gitget/util/participant/ParticipantFactory.java b/src/test/java/com/genius/gitget/util/participant/ParticipantFactory.java index 02b583a0..ba66082f 100644 --- a/src/test/java/com/genius/gitget/util/participant/ParticipantFactory.java +++ b/src/test/java/com/genius/gitget/util/participant/ParticipantFactory.java @@ -8,6 +8,21 @@ import com.genius.gitget.challenge.user.domain.User; public class ParticipantFactory { + /** + * 시작 전인 참여 정보 엔티티 만들어서 반환 + * user, instance를 받아서 연관관계 설정 후 반환 + */ + public static Participant createPreActivity(User user, Instance instance) { + Participant participant = Participant.builder() + .joinResult(JoinResult.READY) + .joinStatus(JoinStatus.YES) + .build(); + participant.setUserAndInstance(user, instance); + participant.updateRepository("targetRepo"); + + return participant; + } + /** * 진행 중인 참여 정보 엔티티 만들어서 반환 * user, instance를 받아서 연관관계 설정 후 반환 @@ -42,9 +57,10 @@ public static Participant createByJoinResult(User user, Instance instance, JoinR * 챌린지가 끝난 참여 정보에 대해, RewardStatus(보상 수령 상태)에 대한 값을 설정 후 반환 * user, instance를 받아서 연관관계 설정 후 반환 */ - public static Participant createByRewardStatus(User user, Instance instance, RewardStatus rewardStatus) { + public static Participant createByRewardStatus(User user, Instance instance, JoinResult joinResult, + RewardStatus rewardStatus) { Participant participant = Participant.builder() - .joinResult(JoinResult.SUCCESS) + .joinResult(joinResult) .joinStatus(JoinStatus.YES) .rewardStatus(rewardStatus) .build(); @@ -53,4 +69,16 @@ public static Participant createByRewardStatus(User user, Instance instance, Rew return participant; } + + public static Participant createQuit(User user, Instance instance, JoinResult joinResult) { + Participant participant = Participant.builder() + .joinResult(joinResult) + .joinStatus(JoinStatus.NO) + .rewardStatus(RewardStatus.NO) + .build(); + participant.setUserAndInstance(user, instance); + participant.updateRepository("targetRepo"); + + return participant; + } } diff --git a/src/test/java/com/genius/gitget/util/user/UserFactory.java b/src/test/java/com/genius/gitget/util/user/UserFactory.java new file mode 100644 index 00000000..7111e8da --- /dev/null +++ b/src/test/java/com/genius/gitget/util/user/UserFactory.java @@ -0,0 +1,30 @@ +package com.genius.gitget.util.user; + +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.security.constants.ProviderInfo; + +public class UserFactory { + + public static User createUser() { + return User.builder() + .role(Role.USER) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("githubId") + .information("information") + .tags("BE,FE") + .build(); + } + + public static User createAdmin() { + return User.builder() + .role(Role.ADMIN) + .nickname("nickname") + .providerInfo(ProviderInfo.GITHUB) + .identifier("githubId") + .information("information") + .tags("BE,FE") + .build(); + } +} From 909ce59233954463d8fbfc523a6db57faad36a53 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:06:44 +0900 Subject: [PATCH 213/234] =?UTF-8?q?[REFACTOR]=20CertificationController=20?= =?UTF-8?q?&=20GithubController=20Facade=20&DCI=20=ED=8C=A8=ED=84=B4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: CertificationController에 파사드 패턴 적용 - 파사드 패턴 적용에 따라 인터페이스 추가 및 구현체 적용 - 이름 변경 * refactor: GithubController에 파사드 패턴 적용 - 파사드 패턴 적용에 따른 인터페이스 및 구현체 적용 - 이름 변경 * refactor: GithubProvider에서 GithubService로 이름 변경 * refactor: 파사드 패턴에 따라 의존성 변경 - InstanceProvider 의존에서 InstanceService 의존으로 변경 * feat: Certification & Github에 파사드 패턴 적용 - Facade에서 Service만 의존하도록 리팩토링 * test: 테스트코드에 DCI 패턴 적용 중 * test: DCI 패턴 적용 중 * test: CertificationFacade 테스트 코드에 DCI 패턴 적용 * refactor: CertificationFacade 코드 리팩토링 - 깃허브와 관련된 부분은 GithubService로 이동 - 도메인에 해당하는 비지니스 로직 이동 * test: GithubFacade 테스트 코드에 DCI 패턴 적용 --- .../controller/CertificationController.java | 18 +- .../controller/GithubController.java | 14 +- .../certification/domain/Certification.java | 27 +- .../facade/CertificationFacade.java | 33 + .../certification/facade/GithubFacade.java | 21 + .../service/CertificationFacadeService.java | 265 +++++++ .../service/CertificationProvider.java | 101 --- .../service/CertificationService.java | 308 ++------ .../service/GithubFacadeService.java | 87 +++ .../certification/service/GithubProvider.java | 134 ---- .../certification/service/GithubService.java | 148 ++-- .../challenge/instance/domain/Instance.java | 14 + .../service/InstanceDetailService.java | 10 +- .../facade/MyChallengeFacadeService.java | 12 +- .../schedule/service/ProgressService.java | 8 +- .../gitget/store/item/dto/OrderResponse.java | 4 + .../store/item/facade/StoreFacadeService.java | 23 +- .../CertificationControllerTest.java | 10 +- .../service/CertificationFacadeTest.java | 656 +++++++++++++++++ .../service/CertificationProviderTest.java | 195 ----- .../service/CertificationServiceTest.java | 686 ++---------------- .../service/GithubFacadeTest.java | 229 ++++++ .../service/GithubProviderTest.java | 152 ---- .../service/GithubServiceTest.java | 241 ++---- .../service/InstanceDetailServiceTest.java | 6 +- .../instance/service/ProgressServiceTest.java | 8 +- .../gitget/util/instance/InstanceFactory.java | 9 + .../genius/gitget/util/user/UserFactory.java | 10 + 28 files changed, 1705 insertions(+), 1724 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/certification/facade/CertificationFacade.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/facade/GithubFacade.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java delete mode 100644 src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java create mode 100644 src/main/java/com/genius/gitget/challenge/certification/service/GithubFacadeService.java delete mode 100644 src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/service/CertificationFacadeTest.java delete mode 100644 src/test/java/com/genius/gitget/challenge/certification/service/CertificationProviderTest.java create mode 100644 src/test/java/com/genius/gitget/challenge/certification/service/GithubFacadeTest.java delete mode 100644 src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index d276dd1d..4c5eff36 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -8,7 +8,7 @@ import com.genius.gitget.challenge.certification.dto.InstancePreviewResponse; import com.genius.gitget.challenge.certification.dto.TotalResponse; import com.genius.gitget.challenge.certification.dto.WeekResponse; -import com.genius.gitget.challenge.certification.service.CertificationService; +import com.genius.gitget.challenge.certification.facade.CertificationFacade; import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.service.InstanceProvider; @@ -43,7 +43,7 @@ @RequestMapping("/api/certification") public class CertificationController { private final UserService userService; - private final CertificationService certificationService; + private final CertificationFacade certificationFacade; private final InstanceProvider instanceProvider; private final ParticipantService participantService; @@ -52,7 +52,7 @@ public class CertificationController { public ResponseEntity> getInstanceInformation( @PathVariable Long instanceId ) { - InstancePreviewResponse instancePreviewResponse = certificationService.getInstancePreview(instanceId); + InstancePreviewResponse instancePreviewResponse = certificationFacade.getInstancePreview(instanceId); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instancePreviewResponse) @@ -64,7 +64,7 @@ public ResponseEntity> certificateByGithub @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody CertificationRequest certificationRequest ) { - CertificationResponse certificationResponse = certificationService.updateCertification( + CertificationResponse certificationResponse = certificationFacade.updateCertification( userPrincipal.getUser(), certificationRequest); return ResponseEntity.ok().body( @@ -78,7 +78,7 @@ public ResponseEntity> passCertification( @RequestBody CertificationRequest certificationRequest ) { User user = userPrincipal.getUser(); - ActivatedResponse activatedResponse = certificationService.passCertification( + ActivatedResponse activatedResponse = certificationFacade.passCertification( user.getId(), certificationRequest); return ResponseEntity.ok().body( @@ -93,7 +93,7 @@ public ResponseEntity> getWeekCertification( ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); Participant participant = participantService.findByJoinInfo(userPrincipal.getUser().getId(), instanceId); - WeekResponse weekResponse = certificationService.getMyWeekCertifications(participant.getId(), kstDate); + WeekResponse weekResponse = certificationFacade.getMyWeekCertifications(participant.getId(), kstDate); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), weekResponse) @@ -108,7 +108,7 @@ public ResponseEntity> getAllUserWeekCertification ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); User user = userPrincipal.getUser(); - Slice certifications = certificationService.getOthersWeekCertifications( + Slice certifications = certificationFacade.getOthersWeekCertifications( user.getId(), instanceId, kstDate, pageable); return ResponseEntity.ok().body( @@ -124,7 +124,7 @@ public ResponseEntity> getTotalCertifications( LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); User user = userService.findUserById(userId); Participant participant = participantService.findByJoinInfo(user.getId(), instanceId); - TotalResponse totalResponse = certificationService.getTotalCertification( + TotalResponse totalResponse = certificationFacade.getTotalCertification( participant.getId(), kstDate); return ResponseEntity.ok().body( @@ -144,7 +144,7 @@ public ResponseEntity> getCertification userPrincipal.getUser().getId(), instanceId); - CertificationInformation certificationInformation = certificationService.getCertificationInformation( + CertificationInformation certificationInformation = certificationFacade.getCertificationInformation( instance, participant, kstDate); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java index 42f353f1..385bdd41 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java @@ -4,7 +4,7 @@ import com.genius.gitget.challenge.certification.dto.github.GithubTokenRequest; import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; -import com.genius.gitget.challenge.certification.service.GithubService; +import com.genius.gitget.challenge.certification.facade.GithubFacade; import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.CommonResponse; @@ -27,14 +27,14 @@ @RequiredArgsConstructor @RequestMapping("/api/certification") public class GithubController { - private final GithubService githubService; + private final GithubFacade githubFacade; @PostMapping("/register/token") public ResponseEntity registerGithubToken( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody GithubTokenRequest githubTokenRequest ) { - githubService.registerGithubPersonalToken(userPrincipal.getUser(), githubTokenRequest.githubToken()); + githubFacade.registerGithubPersonalToken(userPrincipal.getUser(), githubTokenRequest.githubToken()); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) @@ -45,7 +45,7 @@ public ResponseEntity registerGithubToken( public ResponseEntity> getPublicRepositories( @AuthenticationPrincipal UserPrincipal userPrincipal ) { - List repositories = githubService.getPublicRepositories(userPrincipal.getUser()); + List repositories = githubFacade.getPublicRepositories(userPrincipal.getUser()); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), repositories) @@ -56,7 +56,7 @@ public ResponseEntity> getPublicRepositories( public ResponseEntity verifyGithubToken( @AuthenticationPrincipal UserPrincipal userPrincipal ) { - githubService.verifyGithubToken(userPrincipal.getUser()); + githubFacade.verifyGithubToken(userPrincipal.getUser()); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) @@ -69,7 +69,7 @@ public ResponseEntity verifyRepository( @RequestParam String repo ) { - githubService.verifyRepository(userPrincipal.getUser(), repo); + githubFacade.verifyRepository(userPrincipal.getUser(), repo); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) @@ -82,7 +82,7 @@ public ResponseEntity> verifyPullRequest( @RequestParam String repo ) { - List pullRequestResponses = githubService.verifyPullRequest( + List pullRequestResponses = githubFacade.verifyPullRequest( userPrincipal.getUser(), repo, DateUtil.convertToKST(LocalDateTime.now()) ); diff --git a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java index c60e9dab..f6375952 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java +++ b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java @@ -5,6 +5,8 @@ import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.global.util.domain.BaseTimeEntity; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -57,22 +59,15 @@ public Certification(int currentAttempt, LocalDate certificatedAt, String certif this.certificationStatus = certificationStatus; } - public static Certification createPassed(LocalDate certificatedAt) { + public static Certification of(CertificateStatus status, int currentAttempt, LocalDate certificatedAt) { return Certification.builder() .certificatedAt(certificatedAt) - .certificationStatus(PASSED) + .currentAttempt(currentAttempt) + .certificationStatus(status) .certificationLinks("") .build(); } - public static Certification createDummy(LocalDate certificatedAt) { - return Certification.builder() - .currentAttempt(0) - .certificationStatus(NOT_YET) - .certificatedAt(certificatedAt) - .certificationLinks(null) - .build(); - } //=== 비지니스 로직 ===// public void update(LocalDate certificatedAt, CertificateStatus status, String certificationLinks) { @@ -87,6 +82,18 @@ public void updateToPass(LocalDate certificatedAt) { this.certificationLinks = ""; } + public void validatePassCondition() { + if (this.certificationStatus != NOT_YET) { + throw new BusinessException(ErrorCode.CAN_NOT_USE_PASS_ITEM); + } + } + + public void validateCertificateCondition() { + if (this.getCertificationStatus() == PASSED) { + throw new BusinessException(ErrorCode.ALREADY_PASSED_CERTIFICATION); + } + } + //=== 연관관계 편의 메서드 ===// public void setParticipant(Participant participant) { diff --git a/src/main/java/com/genius/gitget/challenge/certification/facade/CertificationFacade.java b/src/main/java/com/genius/gitget/challenge/certification/facade/CertificationFacade.java new file mode 100644 index 00000000..7227afdf --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/facade/CertificationFacade.java @@ -0,0 +1,33 @@ +package com.genius.gitget.challenge.certification.facade; + +import com.genius.gitget.challenge.certification.dto.CertificationInformation; +import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import com.genius.gitget.challenge.certification.dto.CertificationResponse; +import com.genius.gitget.challenge.certification.dto.InstancePreviewResponse; +import com.genius.gitget.challenge.certification.dto.TotalResponse; +import com.genius.gitget.challenge.certification.dto.WeekResponse; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.user.domain.User; +import java.time.LocalDate; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +public interface CertificationFacade { + WeekResponse getMyWeekCertifications(Long participantId, LocalDate currentDate); + + Slice getOthersWeekCertifications(Long userId, Long instanceId, + LocalDate currentDate, Pageable pageable); + + TotalResponse getTotalCertification(Long participantId, LocalDate currentDate); + + ActivatedResponse passCertification(Long userId, CertificationRequest certificationRequest); + + CertificationResponse updateCertification(User user, CertificationRequest certificationRequest); + + CertificationInformation getCertificationInformation(Instance instance, Participant participant, + LocalDate currentDate); + + InstancePreviewResponse getInstancePreview(Long instanceId); +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/facade/GithubFacade.java b/src/main/java/com/genius/gitget/challenge/certification/facade/GithubFacade.java new file mode 100644 index 00000000..dfe15b7b --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/facade/GithubFacade.java @@ -0,0 +1,21 @@ +package com.genius.gitget.challenge.certification.facade; + +import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; +import com.genius.gitget.challenge.user.domain.User; +import java.time.LocalDate; +import java.util.List; + +public interface GithubFacade { + void registerGithubPersonalToken(User user, String githubToken); + + void verifyGithubToken(User user); + + void verifyRepository(User user, String repository); + + List getPublicRepositories(User user); + + List verifyPullRequest(User user, String repositoryName, LocalDate targetDate); + + List getPullRequestListByDate(User user, String repositoryName, + LocalDate targetDate); +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java new file mode 100644 index 00000000..8583ab85 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java @@ -0,0 +1,265 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; + +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.dto.CertificationInformation; +import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import com.genius.gitget.challenge.certification.dto.CertificationResponse; +import com.genius.gitget.challenge.certification.dto.InstancePreviewResponse; +import com.genius.gitget.challenge.certification.dto.TotalResponse; +import com.genius.gitget.challenge.certification.dto.WeekResponse; +import com.genius.gitget.challenge.certification.facade.CertificationFacade; +import com.genius.gitget.challenge.certification.util.DateUtil; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.service.InstanceService; +import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.service.ParticipantService; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.UserProfileInfo; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.file.dto.FileResponse; +import com.genius.gitget.global.file.service.FilesService; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GitHub; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class CertificationFacadeService implements CertificationFacade { + private final FilesService filesService; + + private final UserService userService; + private final InstanceService instanceService; + private final ParticipantService participantService; + private final GithubService githubService; + private final CertificationService certificationService; + + @Override + public WeekResponse getMyWeekCertifications(Long participantId, LocalDate currentDate) { + Participant participant = participantService.findById(participantId); + return getWeekResponse(participant, currentDate); + } + + @Override + public Slice getOthersWeekCertifications(Long userId, Long instanceId, + LocalDate currentDate, Pageable pageable) { + Slice participants = participantService.findAllByInstanceId(userId, instanceId, pageable); + return participants.map( + participant -> getWeekResponse(participant, currentDate) + ); + } + + private WeekResponse getWeekResponse(Participant participant, LocalDate currentDate) { + Instance instance = participant.getInstance(); + LocalDate instanceStartDate = instance.getStartedDate().toLocalDate(); + LocalDate weekStartDate = DateUtil.getWeekStartDate(instanceStartDate, currentDate); + + UserProfileInfo userProfileInfo = userService.getUserProfileInfo(participant.getUser()); + + if (!instance.isActivatedInstance()) { + return WeekResponse.create(userProfileInfo, new ArrayList<>()); + } + + List certifications = certificationService.findByDuration( + weekStartDate, currentDate, participant.getId()); + + List weekCertifications = getWeekCertifications( + certifications, instanceStartDate, currentDate); + + return WeekResponse.create(userProfileInfo, weekCertifications); + } + + private List getWeekCertifications(List certifications, + LocalDate startDate, LocalDate currentDate) { + List results = new ArrayList<>(); + Map weekMap = new HashMap<>(); + for (Certification certification : certifications) { + weekMap.put(certification.getCertificatedAt(), certification); + } + + LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate).minusDays(1); + while (weekStartDate.isBefore(currentDate)) { + weekStartDate = weekStartDate.plusDays(1); + if (weekMap.containsKey(weekStartDate)) { + results.add(CertificationResponse.createExist(weekMap.get(weekStartDate))); + continue; + } + results.add(CertificationResponse.createNonExist(0, weekStartDate)); + } + return results; + } + + @Override + public TotalResponse getTotalCertification(Long participantId, LocalDate currentDate) { + Instance instance = participantService.getInstanceById(participantId); + LocalDate startDate = instance.getStartedDate().toLocalDate(); + + int totalAttempts = instance.getTotalAttempt(); + int curAttempt = DateUtil.getAttemptCount(startDate, currentDate); + + if (instance.getProgress() == Progress.DONE) { + currentDate = instance.getCompletedDate().toLocalDate(); + curAttempt = totalAttempts; + } + + List certifications = certificationService.findByDuration( + startDate, currentDate, participantId); + + List totalCertifications = getTotalCertifications( + certifications, curAttempt, startDate); + + return TotalResponse.builder() + .totalAttempts(totalAttempts) + .certifications(totalCertifications) + .build(); + } + + private List getTotalCertifications(List certifications, + int curAttempt, LocalDate startedDate) { + List result = new ArrayList<>(); + Map totalMap = new HashMap<>(); + + for (Certification certification : certifications) { + totalMap.put(certification.getCurrentAttempt(), certification); + } + + startedDate = startedDate.minusDays(1); + + for (int cur = 1; cur <= curAttempt; cur++) { + startedDate = startedDate.plusDays(1); + if (totalMap.containsKey(cur)) { + result.add(CertificationResponse.createExist(totalMap.get(cur))); + continue; + } + result.add(CertificationResponse.createNonExist(cur, startedDate)); + } + + return result; + } + + @Override + @Transactional + public ActivatedResponse passCertification(Long userId, CertificationRequest certificationRequest) { + Instance instance = instanceService.findInstanceById(certificationRequest.instanceId()); + Participant participant = participantService.findByJoinInfo(userId, instance.getId()); + LocalDate targetDate = certificationRequest.targetDate(); + + Certification certification = certificationService.findOrSave(participant, NOT_YET, targetDate); + + instance.validateCertificateCondition(targetDate); + certification.validatePassCondition(); + + certification.updateToPass(targetDate); + + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + return ActivatedResponse.of(instance, certification.getCertificationStatus(), + 0, participant.getRepositoryName(), fileResponse); + } + + @Override + @Transactional + public CertificationResponse updateCertification(User user, CertificationRequest certificationRequest) { + GitHub gitHub = githubService.getGithubConnection(user); + Instance instance = instanceService.findInstanceById(certificationRequest.instanceId()); + Participant participant = participantService.findByJoinInfo(user.getId(), instance.getId()); + + String repositoryName = participant.getRepositoryName(); + LocalDate targetDate = certificationRequest.targetDate(); + + instance.validateCertificateCondition(targetDate); + + List filteredPullRequests = githubService.filterValidPR( + githubService.getPullRequestByDate(gitHub, repositoryName, targetDate), + instance.getPrTemplate(targetDate) + ); + + certificationService.findOrSave(participant, NOT_YET, targetDate); + + Certification certification = certificationService.findByDate(targetDate, participant.getId()) + .map(updated -> { + updated.validateCertificateCondition(); + return certificationService.update(updated, targetDate, filteredPullRequests); + }) + .orElseGet( + () -> certificationService.createCertificated(participant, targetDate, filteredPullRequests) + ); + + return CertificationResponse.createExist(certification); + } + + @Override + public InstancePreviewResponse getInstancePreview(Long instanceId) { + Instance instance = instanceService.findInstanceById(instanceId); + FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + return InstancePreviewResponse.createByEntity(instance, fileResponse); + } + + @Override + @Transactional + public CertificationInformation getCertificationInformation(Instance instance, Participant participant, + LocalDate currentDate) { + int successCount = 0; + int failureCount = 0; + int remainCount = 0; + + int totalAttempt = instance.getTotalAttempt(); + int currentAttempt = 0; + + switch (instance.getProgress()) { + case PREACTIVITY -> { + remainCount = instance.getTotalAttempt(); + } + case ACTIVITY -> { + currentAttempt = DateUtil.getAttemptCount(instance.getStartedDate().toLocalDate(), currentDate); + successCount = calculateSuccess(participant.getId(), currentDate); + failureCount = currentAttempt - successCount; + remainCount = totalAttempt - currentAttempt; + + } + case DONE -> { + currentAttempt = totalAttempt; + successCount = calculateSuccess(participant.getId(), instance.getCompletedDate().toLocalDate()); + failureCount = totalAttempt - successCount; + } + } + + return CertificationInformation.builder() + .prTemplate(instance.getPrTemplate(currentDate)) + .repository(participant.getRepositoryName()) + .successPercent(getSuccessPercent(successCount, currentAttempt)) + .totalAttempt(totalAttempt) + .currentAttempt(currentAttempt) + .pointPerPerson(instance.getPointPerPerson()) + .successCount(successCount) + .failureCount(failureCount) + .remainCount(remainCount) + .build(); + } + + private int calculateSuccess(Long participantId, LocalDate currentDate) { + int certificated = certificationService.countByStatus(participantId, CERTIFICATED, currentDate); + int passed = certificationService.countByStatus(participantId, PASSED, currentDate); + return certificated + passed; + } + + private double getSuccessPercent(int successCount, int currentAttempt) { + double successPercent = (double) successCount / (double) currentAttempt * 100; + return Math.round(successPercent * 100 / 100.0); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java deleted file mode 100644 index b9e40b0b..00000000 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.genius.gitget.challenge.certification.service; - -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; - -import com.genius.gitget.challenge.certification.domain.CertificateStatus; -import com.genius.gitget.challenge.certification.domain.Certification; -import com.genius.gitget.challenge.certification.repository.CertificationRepository; -import com.genius.gitget.challenge.certification.util.DateUtil; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.participant.domain.Participant; -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class CertificationProvider { - private final CertificationRepository certificationRepository; - - - public List findByDuration(LocalDate startDate, LocalDate endDate, Long participantId) { - return certificationRepository.findByDuration(startDate, endDate, participantId); - } - - public Optional findByDate(LocalDate targetDate, Long participantId) { - return certificationRepository.findByDate(targetDate, participantId); - } - - public int countByStatus(Long participantId, CertificateStatus certificateStatus, - LocalDate targetDate) { - return certificationRepository.findByStatus(participantId, certificateStatus, targetDate).size(); - } - - @Transactional - public Certification save(Certification certification) { - return certificationRepository.save(certification); - } - - @Transactional - public Certification update(Certification certification, - LocalDate targetDate, - List pullRequests) { - certification.update( - targetDate, getCertificateStatus(pullRequests), getPrLinks(pullRequests) - ); - return certification; - } - - @Transactional - public Certification findOrGetDummy(LocalDate targetDate, Long participantId) { - return findByDate(targetDate, participantId) - .orElse(Certification.createDummy(targetDate)); - } - - @Transactional - public Certification createCertification(Participant participant, - LocalDate targetDate, - List pullRequests) { - int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), targetDate); - - Certification certification = Certification.builder() - .currentAttempt(attempt) - .certificatedAt(targetDate) - .certificationStatus(getCertificateStatus(pullRequests)) - .certificationLinks(getPrLinks(pullRequests)) - .build(); - - certification.setParticipant(participant); - - return certificationRepository.save(certification); - } - - private String getPrLinks(List pullRequests) { - StringBuilder prLinkBuilder = new StringBuilder(); - for (String pullRequest : pullRequests) { - prLinkBuilder.append(pullRequest); - prLinkBuilder.append(","); - } - return prLinkBuilder.toString(); - } - - private CertificateStatus getCertificateStatus(List pullRequests) { - if (pullRequests.isEmpty()) { - return NOT_YET; - } - return CERTIFICATED; - } - - public double getAchievementRate(Instance instance, Long participantId, LocalDate targetDate) { - int totalAttempt = instance.getTotalAttempt(); - int successCount = countByStatus(participantId, CERTIFICATED, targetDate); - - double successPercent = (double) successCount / (double) totalAttempt * 100; - return Math.round(successPercent * 100 / 100.0); - } -} diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java index b3c1453b..b60400a4 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationService.java @@ -2,304 +2,106 @@ import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; +import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.certification.domain.Certification; -import com.genius.gitget.challenge.certification.dto.CertificationInformation; -import com.genius.gitget.challenge.certification.dto.CertificationRequest; -import com.genius.gitget.challenge.certification.dto.CertificationResponse; -import com.genius.gitget.challenge.certification.dto.InstancePreviewResponse; -import com.genius.gitget.challenge.certification.dto.TotalResponse; -import com.genius.gitget.challenge.certification.dto.WeekResponse; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.service.InstanceProvider; -import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.service.ParticipantService; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.dto.UserProfileInfo; -import com.genius.gitget.challenge.user.service.UserService; -import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.kohsuke.github.GHPullRequest; -import org.kohsuke.github.GitHub; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class CertificationService { - private final UserService userService; - private final FilesService filesService; - private final GithubProvider githubProvider; - private final CertificationProvider certificationProvider; - private final ParticipantService participantService; - private final InstanceProvider instanceProvider; + private final CertificationRepository certificationRepository; - public WeekResponse getMyWeekCertifications(Long participantId, LocalDate currentDate) { - Participant participant = participantService.findById(participantId); - return getWeekResponse(participant, currentDate); + public List findByDuration(LocalDate startDate, LocalDate endDate, Long participantId) { + return certificationRepository.findByDuration(startDate, endDate, participantId); } - public Slice getOthersWeekCertifications(Long userId, Long instanceId, - LocalDate currentDate, Pageable pageable) { - Slice participants = participantService.findAllByInstanceId(userId, instanceId, pageable); - return participants.map( - participant -> getWeekResponse(participant, currentDate) - ); - } - - private WeekResponse getWeekResponse(Participant participant, LocalDate currentDate) { - Instance instance = participant.getInstance(); - LocalDate instanceStartDate = instance.getStartedDate().toLocalDate(); - LocalDate weekStartDate = DateUtil.getWeekStartDate(instanceStartDate, currentDate); - - UserProfileInfo userProfileInfo = userService.getUserProfileInfo(participant.getUser()); - - if (!instance.isActivatedInstance()) { - return WeekResponse.create(userProfileInfo, new ArrayList<>()); - } - - List certifications = certificationProvider.findByDuration( - weekStartDate, currentDate, participant.getId()); - - List weekCertifications = getWeekCertifications( - certifications, instanceStartDate, currentDate); - - return WeekResponse.create(userProfileInfo, weekCertifications); + public Optional findByDate(LocalDate targetDate, Long participantId) { + return certificationRepository.findByDate(targetDate, participantId); } - private List getWeekCertifications(List certifications, - LocalDate startDate, LocalDate currentDate) { - List results = new ArrayList<>(); - Map weekMap = new HashMap<>(); - for (Certification certification : certifications) { - weekMap.put(certification.getCertificatedAt(), certification); - } - - LocalDate weekStartDate = DateUtil.getWeekStartDate(startDate, currentDate).minusDays(1); - while (weekStartDate.isBefore(currentDate)) { - weekStartDate = weekStartDate.plusDays(1); - if (weekMap.containsKey(weekStartDate)) { - results.add(CertificationResponse.createExist(weekMap.get(weekStartDate))); - continue; - } - results.add(CertificationResponse.createNonExist(0, weekStartDate)); - } - return results; + public int countByStatus(Long participantId, CertificateStatus certificateStatus, + LocalDate targetDate) { + return certificationRepository.findByStatus(participantId, certificateStatus, targetDate).size(); } - public TotalResponse getTotalCertification(Long participantId, LocalDate currentDate) { - Instance instance = participantService.getInstanceById(participantId); - LocalDate startDate = instance.getStartedDate().toLocalDate(); - - int totalAttempts = instance.getTotalAttempt(); - int curAttempt = DateUtil.getAttemptCount(startDate, currentDate); - - if (instance.getProgress() == Progress.DONE) { - currentDate = instance.getCompletedDate().toLocalDate(); - curAttempt = totalAttempts; - } - - List certifications = certificationProvider.findByDuration( - startDate, currentDate, participantId); - - List totalCertifications = getTotalCertifications( - certifications, curAttempt, startDate); - - return TotalResponse.builder() - .totalAttempts(totalAttempts) - .certifications(totalCertifications) - .build(); + @Transactional + public Certification save(Certification certification) { + return certificationRepository.save(certification); } - private List getTotalCertifications(List certifications, - int curAttempt, LocalDate startedDate) { - List result = new ArrayList<>(); - Map totalMap = new HashMap<>(); - - for (Certification certification : certifications) { - totalMap.put(certification.getCurrentAttempt(), certification); - } - - startedDate = startedDate.minusDays(1); - - for (int cur = 1; cur <= curAttempt; cur++) { - startedDate = startedDate.plusDays(1); - if (totalMap.containsKey(cur)) { - result.add(CertificationResponse.createExist(totalMap.get(cur))); - continue; - } - result.add(CertificationResponse.createNonExist(cur, startedDate)); - } - - return result; + @Transactional + public Certification update(Certification certification, + LocalDate targetDate, + List pullRequests) { + certification.update( + targetDate, getCertificateStatus(pullRequests), getPrLinks(pullRequests) + ); + return certification; } @Transactional - public ActivatedResponse passCertification(Long userId, CertificationRequest certificationRequest) { - Instance instance = instanceProvider.findById(certificationRequest.instanceId()); - Participant participant = participantService.findByJoinInfo(userId, instance.getId()); - LocalDate targetDate = certificationRequest.targetDate(); + public Certification findOrSave(Participant participant, CertificateStatus status, LocalDate targetDate) { + int currentAttempt = DateUtil.getAttemptCount(participant.getStartedDate(), targetDate); - Optional optionalCertification = certificationProvider.findByDate(targetDate, - participant.getId()); - - validCertificationCondition(instance, targetDate); - validatePassCondition(optionalCertification); - - Certification certification = optionalCertification - .map(passed -> { - passed.updateToPass(targetDate); - return passed; - }) + return findByDate(targetDate, participant.getId()) .orElseGet(() -> { - Certification passed = Certification.createPassed(targetDate); - passed.setParticipant(participant); - certificationProvider.save(passed); - return passed; + Certification certification = Certification.of(status, currentAttempt, targetDate); + certification.setParticipant(participant); + return certificationRepository.save(certification); }); - - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - //TODO: pass 했기 때문에 pass item이 필요없어 numOfPassItem을 0으로 전달하는 것 같음. but, 가독성이 떨어지기 때문에 수정 필요 - return ActivatedResponse.of(instance, certification.getCertificationStatus(), - 0, participant.getRepositoryName(), fileResponse); - } - - private void validatePassCondition(Optional optional) { - if (optional.isPresent() && optional.get().getCertificationStatus() != NOT_YET) { - throw new BusinessException(ErrorCode.CAN_NOT_USE_PASS_ITEM); - } } @Transactional - public CertificationResponse updateCertification(User user, CertificationRequest certificationRequest) { - GitHub gitHub = githubProvider.getGithubConnection(user); - Instance instance = instanceProvider.findById(certificationRequest.instanceId()); - Participant participant = participantService.findByJoinInfo(user.getId(), instance.getId()); - - String repositoryName = participant.getRepositoryName(); - LocalDate targetDate = certificationRequest.targetDate(); - - validCertificationCondition(instance, targetDate); - - List filteredPullRequests = filterValidPR( - githubProvider.getPullRequestByDate(gitHub, repositoryName, targetDate), - instance.getPrTemplate(targetDate) - ); - - Certification certification = certificationProvider.findByDate(targetDate, participant.getId()) - .map(updated -> { - if (updated.getCertificationStatus() == PASSED) { - throw new BusinessException(ErrorCode.ALREADY_PASSED_CERTIFICATION); - } - return certificationProvider.update(updated, targetDate, filteredPullRequests); - }) - .orElseGet( - () -> certificationProvider.createCertification(participant, targetDate, filteredPullRequests) - ); + public Certification createCertificated(Participant participant, + LocalDate targetDate, + List pullRequests) { + int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), targetDate); + + Certification certification = Certification.builder() + .currentAttempt(attempt) + .certificatedAt(targetDate) + .certificationStatus(getCertificateStatus(pullRequests)) + .certificationLinks(getPrLinks(pullRequests)) + .build(); - return CertificationResponse.createExist(certification); - } + certification.setParticipant(participant); - private List filterValidPR(List ghPullRequests, String prTemplate) { - return ghPullRequests.stream() - .filter(ghPullRequest -> { - if (ghPullRequest.getBody() == null) { - return false; - } - return ghPullRequest.getBody().contains(prTemplate); - }) - .map(ghPullRequest -> ghPullRequest.getHtmlUrl().toString()) - .toList(); + return certificationRepository.save(certification); } - private void validCertificationCondition(Instance instance, LocalDate targetDate) { - if (instance.getProgress() != Progress.ACTIVITY) { - throw new BusinessException(ErrorCode.NOT_ACTIVITY_INSTANCE); + private String getPrLinks(List pullRequests) { + StringBuilder prLinkBuilder = new StringBuilder(); + for (String pullRequest : pullRequests) { + prLinkBuilder.append(pullRequest); + prLinkBuilder.append(","); } - - LocalDate startedDate = instance.getStartedDate().toLocalDate().minusDays(1); - LocalDate completedDate = instance.getCompletedDate().toLocalDate().plusDays(1); - - boolean isValidPeriod = targetDate.isAfter(startedDate) && targetDate.isBefore(completedDate); - if (!isValidPeriod) { - throw new BusinessException(ErrorCode.NOT_CERTIFICATE_PERIOD); - } - } - - public InstancePreviewResponse getInstancePreview(Long instanceId) { - Instance instance = instanceProvider.findById(instanceId); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - return InstancePreviewResponse.createByEntity(instance, fileResponse); + return prLinkBuilder.toString(); } - @Transactional - public CertificationInformation getCertificationInformation(Instance instance, Participant participant, - LocalDate currentDate) { - int successCount = 0; - int failureCount = 0; - int remainCount = 0; - - int totalAttempt = instance.getTotalAttempt(); - int currentAttempt = 0; - - switch (instance.getProgress()) { - case PREACTIVITY -> { - remainCount = instance.getTotalAttempt(); - } - case ACTIVITY -> { - currentAttempt = DateUtil.getAttemptCount(instance.getStartedDate().toLocalDate(), currentDate); - successCount = calculateSuccess(participant.getId(), currentDate); - failureCount = currentAttempt - successCount; - remainCount = totalAttempt - currentAttempt; - - } - case DONE -> { - currentAttempt = totalAttempt; - successCount = calculateSuccess(participant.getId(), instance.getCompletedDate().toLocalDate()); - failureCount = totalAttempt - successCount; - } + private CertificateStatus getCertificateStatus(List pullRequests) { + if (pullRequests.isEmpty()) { + return NOT_YET; } - - return CertificationInformation.builder() - .prTemplate(instance.getPrTemplate(currentDate)) - .repository(participant.getRepositoryName()) - .successPercent(getSuccessPercent(successCount, currentAttempt)) - .totalAttempt(totalAttempt) - .currentAttempt(currentAttempt) - .pointPerPerson(instance.getPointPerPerson()) - .successCount(successCount) - .failureCount(failureCount) - .remainCount(remainCount) - .build(); + return CERTIFICATED; } - private int calculateSuccess(Long participantId, LocalDate currentDate) { - int certificated = certificationProvider.countByStatus(participantId, CERTIFICATED, currentDate); - int passed = certificationProvider.countByStatus(participantId, PASSED, currentDate); - return certificated + passed; - } + public double getAchievementRate(Instance instance, Long participantId, LocalDate targetDate) { + int totalAttempt = instance.getTotalAttempt(); + int successCount = countByStatus(participantId, CERTIFICATED, targetDate); - private double getSuccessPercent(int successCount, int currentAttempt) { - double successPercent = (double) successCount / (double) currentAttempt * 100; + double successPercent = (double) successCount / (double) totalAttempt * 100; return Math.round(successPercent * 100 / 100.0); } -} \ No newline at end of file +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/GithubFacadeService.java b/src/main/java/com/genius/gitget/challenge/certification/service/GithubFacadeService.java new file mode 100644 index 00000000..350f1e15 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/certification/service/GithubFacadeService.java @@ -0,0 +1,87 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_PR_NOT_FOUND; + +import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; +import com.genius.gitget.challenge.certification.facade.GithubFacade; +import com.genius.gitget.challenge.certification.util.EncryptUtil; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.util.exception.BusinessException; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class GithubFacadeService implements GithubFacade { + private final UserService userService; + private final GithubService githubService; + private final EncryptUtil encryptUtil; + + @Override + @Transactional + public void registerGithubPersonalToken(User user, String githubToken) { + GitHub gitHub = githubService.getGithubConnection(githubToken); + githubService.validateGithubConnection(gitHub, user.getIdentifier()); + + String encryptedToken = encryptUtil.encrypt(githubToken); + user.updateGithubPersonalToken(encryptedToken); + userService.save(user); + } + + @Override + public void verifyGithubToken(User user) { + String githubToken = encryptUtil.decrypt(user.getGithubToken()); + + GitHub gitHub = githubService.getGithubConnection(githubToken); + githubService.validateGithubConnection(gitHub, user.getIdentifier()); + } + + @Override + @Transactional + public void verifyRepository(User user, String repository) { + GitHub gitHub = githubService.getGithubConnection(user); + + String repositoryFullName = githubService.getRepoFullName(gitHub, repository); + githubService.validateGithubRepository(gitHub, repositoryFullName); + } + + @Override + public List getPublicRepositories(User user) { + GitHub gitHub = githubService.getGithubConnection(user); + List repositoryList = githubService.getRepositoryList(gitHub); + return repositoryList.stream() + .map(GHRepository::getName) + .toList(); + } + + @Override + public List verifyPullRequest(User user, String repositoryName, LocalDate targetDate) { + List responses = getPullRequestListByDate(user, repositoryName, targetDate); + + if (responses.isEmpty()) { + throw new BusinessException(GITHUB_PR_NOT_FOUND); + } + return responses; + } + + @Override + public List getPullRequestListByDate(User user, String repositoryName, LocalDate targetDate) { + GitHub gitHub = githubService.getGithubConnection(user); + + List pullRequest = githubService.getPullRequestByDate(gitHub, repositoryName, targetDate); + + return pullRequest.stream() + .map(PullRequestResponse::create) + .toList(); + } +} diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java b/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java deleted file mode 100644 index 32f5e465..00000000 --- a/src/main/java/com/genius/gitget/challenge/certification/service/GithubProvider.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.genius.gitget.challenge.certification.service; - -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_CONNECTION_FAILED; -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_ID_INCORRECT; -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_REPOSITORY_INCORRECT; - -import com.genius.gitget.challenge.certification.util.DateUtil; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.service.UserService; -import com.genius.gitget.global.util.exception.BusinessException; -import java.io.IOException; -import java.time.LocalDate; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.kohsuke.github.GHDirection; -import org.kohsuke.github.GHFileNotFoundException; -import org.kohsuke.github.GHPullRequest; -import org.kohsuke.github.GHPullRequestSearchBuilder; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHRepositorySearchBuilder; -import org.kohsuke.github.GHRepositorySearchBuilder.Sort; -import org.kohsuke.github.GHUser; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.GitHubBuilder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class GithubProvider { - private final UserService userService; - private final int PAGE_SIZE = 10; - - public GitHub getGithubConnection(String githubToken) { - try { - GitHub gitHub = new GitHubBuilder().withOAuthToken(githubToken).build(); - gitHub.checkApiUrlValidity(); - return gitHub; - } catch (IOException e) { - throw new BusinessException(GITHUB_CONNECTION_FAILED); - } - } - - public GitHub getGithubConnection(User user) { - try { - String githubToken = userService.getGithubToken(user); - GitHub gitHub = new GitHubBuilder().withOAuthToken(githubToken).build(); - gitHub.checkApiUrlValidity(); - return gitHub; - } catch (IOException e) { - throw new BusinessException(GITHUB_CONNECTION_FAILED); - } - } - - public void validateGithubConnection(GitHub gitHub, String githubId) { - try { - String accountId = gitHub.getMyself().getLogin(); - validateGithubAccount(githubId, accountId); - } catch (IOException e) { - throw new BusinessException(GITHUB_CONNECTION_FAILED); - } - } - - private void validateGithubAccount(String githubId, String accountId) { - if (!githubId.equals(accountId)) { - throw new BusinessException(GITHUB_ID_INCORRECT); - } - } - - public void validateGithubRepository(GitHub gitHub, String repositoryFullName) { - try { - gitHub.getRepository(repositoryFullName); - } catch (GHFileNotFoundException e) { - throw new BusinessException(GITHUB_REPOSITORY_INCORRECT); - } catch (IllegalArgumentException | IOException e) { - throw new BusinessException(e); - } - } - - public List getRepositoryList(GitHub gitHub) { - try { - GHRepositorySearchBuilder builder = gitHub.searchRepositories() - .user(getGHUser(gitHub).getLogin()) - .sort(Sort.UPDATED) - .order(GHDirection.DESC); - return builder.list().iterator().nextPage(); - } catch (IOException e) { - throw new BusinessException(e); - } - } - - public List getPullRequestByDate(GitHub gitHub, String repositoryName, - LocalDate kstDate) { - try { - GHRepository repository = gitHub.getRepository(getRepoFullName(gitHub, repositoryName)); - GHPullRequestSearchBuilder prSearchBuilder = gitHub.searchPullRequests() - .repo(repository) - .author(getGHUser(gitHub)) - .created(kstDate.minusDays(1), kstDate); - - return prSearchBuilder.list().iterator().nextPage().stream() - .filter(pr -> isEqualToKST(pr, kstDate)) - .toList(); - - } catch (GHFileNotFoundException e) { - throw new BusinessException(GITHUB_REPOSITORY_INCORRECT); - } catch (IOException e) { - throw new BusinessException(e); - } - } - - private boolean isEqualToKST(GHPullRequest ghPullRequest, LocalDate targetDate) { - try { - LocalDate kst = DateUtil.convertToKST(ghPullRequest.getCreatedAt()); - return kst.isEqual(targetDate); - } catch (IOException e) { - throw new BusinessException(e); - } - } - - private GHUser getGHUser(GitHub gitHub) throws IOException { - String accountId = gitHub.getMyself().getLogin(); - return gitHub.getUser(accountId); - } - - public String getRepoFullName(GitHub gitHub, String repositoryName) { - try { - return gitHub.getMyself().getLogin() + "/" + repositoryName; - } catch (IOException e) { - throw new BusinessException(e); - } - } -} diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/GithubService.java b/src/main/java/com/genius/gitget/challenge/certification/service/GithubService.java index 94dfded1..0575656b 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/GithubService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/GithubService.java @@ -1,82 +1,146 @@ package com.genius.gitget.challenge.certification.service; -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_PR_NOT_FOUND; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_CONNECTION_FAILED; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_ID_INCORRECT; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_REPOSITORY_INCORRECT; -import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; -import com.genius.gitget.challenge.certification.util.EncryptUtil; +import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.util.exception.BusinessException; +import java.io.IOException; import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GHDirection; +import org.kohsuke.github.GHFileNotFoundException; import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHPullRequestSearchBuilder; import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHRepositorySearchBuilder; +import org.kohsuke.github.GHRepositorySearchBuilder.Sort; +import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class GithubService { private final UserService userService; - private final GithubProvider githubProvider; - private final EncryptUtil encryptUtil; - @Transactional - public void registerGithubPersonalToken(User user, String githubToken) { - GitHub gitHub = githubProvider.getGithubConnection(githubToken); - githubProvider.validateGithubConnection(gitHub, user.getIdentifier()); - String encryptedToken = encryptUtil.encrypt(githubToken); - user.updateGithubPersonalToken(encryptedToken); - userService.save(user); + public GitHub getGithubConnection(String githubToken) { + try { + GitHub gitHub = new GitHubBuilder().withOAuthToken(githubToken).build(); + gitHub.checkApiUrlValidity(); + return gitHub; + } catch (IOException e) { + throw new BusinessException(GITHUB_CONNECTION_FAILED); + } } - public void verifyGithubToken(User user) { - String githubToken = encryptUtil.decrypt(user.getGithubToken()); + public GitHub getGithubConnection(User user) { + try { + String githubToken = userService.getGithubToken(user); + GitHub gitHub = new GitHubBuilder().withOAuthToken(githubToken).build(); + gitHub.checkApiUrlValidity(); + return gitHub; + } catch (IOException e) { + throw new BusinessException(GITHUB_CONNECTION_FAILED); + } + } - GitHub gitHub = githubProvider.getGithubConnection(githubToken); - githubProvider.validateGithubConnection(gitHub, user.getIdentifier()); + public void validateGithubConnection(GitHub gitHub, String githubId) { + try { + String accountId = gitHub.getMyself().getLogin(); + validateGithubAccount(githubId, accountId); + } catch (IOException e) { + throw new BusinessException(GITHUB_CONNECTION_FAILED); + } } - @Transactional - public void verifyRepository(User user, String repository) { - GitHub gitHub = githubProvider.getGithubConnection(user); + private void validateGithubAccount(String githubId, String accountId) { + if (!githubId.equals(accountId)) { + throw new BusinessException(GITHUB_ID_INCORRECT); + } + } - String repositoryFullName = githubProvider.getRepoFullName(gitHub, repository); - githubProvider.validateGithubRepository(gitHub, repositoryFullName); + public void validateGithubRepository(GitHub gitHub, String repositoryFullName) { + try { + gitHub.getRepository(repositoryFullName); + } catch (GHFileNotFoundException e) { + throw new BusinessException(GITHUB_REPOSITORY_INCORRECT); + } catch (IllegalArgumentException | IOException e) { + throw new BusinessException(e); + } } - public List getPublicRepositories(User user) { - GitHub gitHub = githubProvider.getGithubConnection(user); - List repositoryList = githubProvider.getRepositoryList(gitHub); - return repositoryList.stream() - .map(GHRepository::getName) - .toList(); + public List getRepositoryList(GitHub gitHub) { + try { + GHRepositorySearchBuilder builder = gitHub.searchRepositories() + .user(getGHUser(gitHub).getLogin()) + .sort(Sort.UPDATED) + .order(GHDirection.DESC); + return builder.list().iterator().nextPage(); + } catch (IOException e) { + throw new BusinessException(e); + } } - //TODO: PR이 날라온 브랜치의 이름이 정해진 규칙에 맞는지 여부 확인 필요 - public List verifyPullRequest(User user, String repositoryName, LocalDate targetDate) { - List responses = getPullRequestListByDate(user, repositoryName, targetDate); + public List getPullRequestByDate(GitHub gitHub, String repositoryName, + LocalDate kstDate) { + try { + GHRepository repository = gitHub.getRepository(getRepoFullName(gitHub, repositoryName)); + GHPullRequestSearchBuilder prSearchBuilder = gitHub.searchPullRequests() + .repo(repository) + .author(getGHUser(gitHub)) + .created(kstDate.minusDays(1), kstDate); - if (responses.isEmpty()) { - throw new BusinessException(GITHUB_PR_NOT_FOUND); + return prSearchBuilder.list().iterator().nextPage().stream() + .filter(pr -> isEqualToKST(pr, kstDate)) + .toList(); + + } catch (GHFileNotFoundException e) { + throw new BusinessException(GITHUB_REPOSITORY_INCORRECT); + } catch (IOException e) { + throw new BusinessException(e); } - return responses; } - public List getPullRequestListByDate(User user, String repositoryName, - LocalDate targetDate) { - GitHub gitHub = githubProvider.getGithubConnection(user); - - List pullRequest = githubProvider.getPullRequestByDate(gitHub, repositoryName, targetDate); + private boolean isEqualToKST(GHPullRequest ghPullRequest, LocalDate targetDate) { + try { + LocalDate kst = DateUtil.convertToKST(ghPullRequest.getCreatedAt()); + return kst.isEqual(targetDate); + } catch (IOException e) { + throw new BusinessException(e); + } + } - return pullRequest.stream() - .map(PullRequestResponse::create) + public List filterValidPR(List ghPullRequests, String prTemplate) { + return ghPullRequests.stream() + .filter(ghPullRequest -> { + if (ghPullRequest.getBody() == null) { + return false; + } + return ghPullRequest.getBody().contains(prTemplate); + }) + .map(ghPullRequest -> ghPullRequest.getHtmlUrl().toString()) .toList(); } + + private GHUser getGHUser(GitHub gitHub) throws IOException { + String accountId = gitHub.getMyself().getLogin(); + return gitHub.getUser(accountId); + } + + public String getRepoFullName(GitHub gitHub, String repositoryName) { + try { + return gitHub.getMyself().getLogin() + "/" + repositoryName; + } catch (IOException e) { + throw new BusinessException(e); + } + } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java index be16bf0c..b3647531 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java +++ b/src/main/java/com/genius/gitget/challenge/instance/domain/Instance.java @@ -183,4 +183,18 @@ public String getPrTemplate(LocalDate currentDate) { String today = currentDate.toString().replace("-", ""); return "GITGET-" + instanceUUID + "-" + today; } + + public void validateCertificateCondition(LocalDate targetDate) { + if (this.getProgress() != Progress.ACTIVITY) { + throw new BusinessException(ErrorCode.NOT_ACTIVITY_INSTANCE); + } + + LocalDate startedDate = this.getStartedDate().toLocalDate().minusDays(1); + LocalDate completedDate = this.getCompletedDate().toLocalDate().plusDays(1); + + boolean isValidPeriod = targetDate.isAfter(startedDate) && targetDate.isBefore(completedDate); + if (!isValidPeriod) { + throw new BusinessException(ErrorCode.NOT_CERTIFICATE_PERIOD); + } + } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java index a03b3878..f620a615 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java @@ -3,7 +3,7 @@ import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_JOIN_INSTANCE; import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_QUIT_INSTANCE; -import com.genius.gitget.challenge.certification.service.GithubProvider; +import com.genius.gitget.challenge.certification.service.GithubService; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; @@ -37,7 +37,7 @@ public class InstanceDetailService { private final FilesService filesService; private final InstanceProvider instanceProvider; private final ParticipantService participantService; - private final GithubProvider githubProvider; + private final GithubService githubService; private final LikesRepository likesRepository; @@ -98,9 +98,9 @@ private void validateInstanceCondition(User user, Instance instance) { } private void validateGithub(User user, String repository) { - GitHub gitHub = githubProvider.getGithubConnection(user); - String repositoryFullName = githubProvider.getRepoFullName(gitHub, repository); - githubProvider.validateGithubRepository(gitHub, repositoryFullName); + GitHub gitHub = githubService.getGithubConnection(user); + String repositoryFullName = githubService.getRepoFullName(gitHub, repository); + githubService.validateGithubRepository(gitHub, repositoryFullName); } @Transactional diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java index 97b41591..cc4ef7a7 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java @@ -1,11 +1,12 @@ package com.genius.gitget.challenge.myChallenge.facade; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; import static com.genius.gitget.challenge.participant.domain.RewardStatus.NO; import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; import static com.genius.gitget.store.item.domain.ItemCategory.POINT_MULTIPLIER; import com.genius.gitget.challenge.certification.domain.Certification; -import com.genius.gitget.challenge.certification.service.CertificationProvider; +import com.genius.gitget.challenge.certification.service.CertificationService; import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; @@ -34,7 +35,7 @@ public class MyChallengeFacadeService implements MyChallengeFacade { private final FilesService filesService; private final ParticipantService participantService; - private final CertificationProvider certificationProvider; + private final CertificationService certificationService; private final ItemService itemService; private final OrdersService ordersService; @@ -64,7 +65,8 @@ public List getActivatedInstances(User user, LocalDate target for (Participant participant : participants) { Instance instance = participant.getInstance(); FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - Certification certification = certificationProvider.findOrGetDummy(targetDate, participant.getId()); + + Certification certification = certificationService.findOrSave(participant, NOT_YET, targetDate); Item item = itemService.findAllByCategory(CERTIFICATION_PASSER).get(0); int numOfPassItem = ordersService.countNumOfItem(user, item.getId()); @@ -87,7 +89,7 @@ public List getDoneInstances(User user, LocalDate targetDate) { for (Participant participant : participants) { Instance instance = participant.getInstance(); FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - double achievementRate = certificationProvider.getAchievementRate(instance, participant.getId(), + double achievementRate = certificationService.getAchievementRate(instance, participant.getId(), targetDate); // 포인트를 아직 수령하지 않았을 때 @@ -123,7 +125,7 @@ public DoneResponse getRewards(RewardRequest rewardRequest) { int rewardPoints = instance.getPointPerPerson(); participantService.getRewards(participant, rewardPoints); - double achievementRate = certificationProvider.getAchievementRate(instance, participant.getId(), + double achievementRate = certificationService.getAchievementRate(instance, participant.getId(), rewardRequest.targetDate()); return DoneResponse.createRewarded(instance, participant, achievementRate, fileResponse); diff --git a/src/main/java/com/genius/gitget/schedule/service/ProgressService.java b/src/main/java/com/genius/gitget/schedule/service/ProgressService.java index 1158d3dd..21752b0d 100644 --- a/src/main/java/com/genius/gitget/schedule/service/ProgressService.java +++ b/src/main/java/com/genius/gitget/schedule/service/ProgressService.java @@ -3,7 +3,7 @@ import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; -import com.genius.gitget.challenge.certification.service.CertificationProvider; +import com.genius.gitget.challenge.certification.service.CertificationService; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.service.InstanceProvider; @@ -23,7 +23,7 @@ @RequiredArgsConstructor public class ProgressService { private final InstanceProvider instanceProvider; - private final CertificationProvider certificationProvider; + private final CertificationService certificationService; private final double SUCCESS_THRESHOLD = 85; @Transactional @@ -86,8 +86,8 @@ private JoinResult getJoinResult(int totalAttempt, int successAttempt) { } private int getSuccessAttempt(Long participantId, LocalDate currentDate) { - int certificated = certificationProvider.countByStatus(participantId, CERTIFICATED, currentDate); - int passed = certificationProvider.countByStatus(participantId, PASSED, currentDate); + int certificated = certificationService.countByStatus(participantId, CERTIFICATED, currentDate); + int passed = certificationService.countByStatus(participantId, PASSED, currentDate); return certificated + passed; } diff --git a/src/main/java/com/genius/gitget/store/item/dto/OrderResponse.java b/src/main/java/com/genius/gitget/store/item/dto/OrderResponse.java index 73c923d4..3b498822 100644 --- a/src/main/java/com/genius/gitget/store/item/dto/OrderResponse.java +++ b/src/main/java/com/genius/gitget/store/item/dto/OrderResponse.java @@ -14,4 +14,8 @@ public OrderResponse() { public OrderResponse(Long itemId) { this.itemId = itemId; } + + public static OrderResponse of(Long itemId) { + return new OrderResponse(itemId); + } } diff --git a/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java b/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java index f68a1909..2c76d748 100644 --- a/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java +++ b/src/main/java/com/genius/gitget/store/item/facade/StoreFacadeService.java @@ -1,10 +1,11 @@ package com.genius.gitget.store.item.facade; -import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; + +import com.genius.gitget.challenge.certification.domain.Certification; import com.genius.gitget.challenge.certification.service.CertificationService; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.service.InstanceService; -import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.myChallenge.dto.DoneResponse; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantService; @@ -41,7 +42,6 @@ public class StoreFacadeService implements StoreFacade { private final InstanceService instanceService; private final ParticipantService participantService; - // TODO: CertificationProvider에만 의존하도록 변경(파사드 패턴 적용 시) private final CertificationService certificationService; private final PaymentRepository paymentRepository; @@ -133,12 +133,19 @@ private void validateFrameEquip(Long userId, Orders orders) { public OrderResponse usePasserItem(Orders orders, Long instanceId, LocalDate currentDate) { Long userId = orders.getUser().getId(); Long itemId = orders.getItem().getId(); - ActivatedResponse activatedResponse = certificationService.passCertification( - userId, - CertificationRequest.of(instanceId, currentDate)); - activatedResponse.setItemId(itemId); + + Instance instance = instanceService.findInstanceById(instanceId); + Participant participant = participantService.findByJoinInfo(userId, instanceId); + + Certification certification = certificationService.findOrSave(participant, NOT_YET, currentDate); + + instance.validateCertificateCondition(currentDate); + certification.validatePassCondition(); + + certification.updateToPass(currentDate); + ordersService.useItem(orders); - return activatedResponse; + return OrderResponse.of(itemId); } @Override diff --git a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java index 6ba5624c..5ab111a9 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/controller/CertificationControllerTest.java @@ -5,8 +5,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.genius.gitget.challenge.certification.service.CertificationService; -import com.genius.gitget.challenge.certification.service.GithubService; +import com.genius.gitget.challenge.certification.facade.CertificationFacade; +import com.genius.gitget.challenge.certification.facade.GithubFacade; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; @@ -38,9 +38,9 @@ class CertificationControllerTest { @Autowired TokenTestUtil tokenTestUtil; @Autowired - CertificationService certificationService; + CertificationFacade certificationFacade; @Autowired - GithubService githubService; + GithubFacade githubFacade; @Autowired InstanceRepository instanceRepository; @Autowired @@ -121,7 +121,7 @@ public void should_saveToken_when_repositoryValid() throws Exception { //when User user = userRepository.findByIdentifier(githubId).get(); - githubService.registerGithubPersonalToken(user, githubToken); + githubFacade.registerGithubPersonalToken(user, githubToken); //then mockMvc.perform(get("/api/certification/verify/repository?repo=" + targetRepo) diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationFacadeTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationFacadeTest.java new file mode 100644 index 00000000..f6a74183 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationFacadeTest.java @@ -0,0 +1,656 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; +import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; +import static com.genius.gitget.challenge.instance.domain.Progress.ACTIVITY; +import static com.genius.gitget.challenge.instance.domain.Progress.DONE; +import static com.genius.gitget.challenge.participant.domain.JoinResult.SUCCESS; +import static com.genius.gitget.challenge.user.domain.Role.USER; +import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_PASSED_CERTIFICATION; +import static com.genius.gitget.global.util.exception.ErrorCode.CAN_NOT_USE_PASS_ITEM; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_ACTIVITY_INSTANCE; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_CERTIFICATE_PERIOD; +import static com.genius.gitget.store.item.domain.ItemCategory.CERTIFICATION_PASSER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.dto.CertificationInformation; +import com.genius.gitget.challenge.certification.dto.CertificationRequest; +import com.genius.gitget.challenge.certification.dto.CertificationResponse; +import com.genius.gitget.challenge.certification.dto.TotalResponse; +import com.genius.gitget.challenge.certification.dto.WeekResponse; +import com.genius.gitget.challenge.certification.facade.CertificationFacade; +import com.genius.gitget.challenge.certification.facade.GithubFacade; +import com.genius.gitget.challenge.certification.repository.CertificationRepository; +import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.domain.Progress; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; +import com.genius.gitget.challenge.participant.domain.Participant; +import com.genius.gitget.challenge.participant.repository.ParticipantRepository; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; +import com.genius.gitget.util.certification.CertificationFactory; +import com.genius.gitget.util.instance.InstanceFactory; +import com.genius.gitget.util.participant.ParticipantFactory; +import com.genius.gitget.util.store.StoreFactory; +import com.genius.gitget.util.user.UserFactory; +import java.time.LocalDate; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +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 org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +@ActiveProfiles({"github"}) +class CertificationFacadeTest { + @Autowired + private CertificationFacade certificationFacade; + @Autowired + private GithubFacade githubFacade; + @Autowired + private UserRepository userRepository; + @Autowired + private InstanceRepository instanceRepository; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private CertificationRepository certificationRepository; + @Autowired + private ItemRepository itemRepository; + @Autowired + private OrdersRepository ordersRepository; + + @Value("${github.yeon-personalKey}") + private String githubToken; + + @Value("${github.yeon-githubId}") + private String githubId; + + @Value("${github.yeon-repository}") + private String targetRepo; + + private LocalDate currentDate; + private User user; + private Instance instance; + private Participant participant; + + @BeforeEach + void setup() { + user = userRepository.save(UserFactory.createByInfo(githubId, USER)); + githubFacade.registerGithubPersonalToken(user, githubToken); + } + + @Nested + @DisplayName("한 주 간의 인증 내역 조회 시") + class context_inquiry_week_certifications { + @Nested + @DisplayName("인스턴스가 아직 시작하지 않았고, 본인의 정보 조회 시") + class describe_instance_preActivity_inquiry_mine { + @BeforeEach + void setup() { + currentDate = LocalDate.now(); + instance = instanceRepository.save(InstanceFactory.createPreActivity(10)); + participant = participantRepository.save(ParticipantFactory.createPreActivity(user, instance)); + } + + @Test + @DisplayName("반환한 데이터의 개수가 0개여야 한다.") + public void it_return_nothing() { + WeekResponse weekResponses = certificationFacade.getMyWeekCertifications(participant.getId(), + currentDate); + + assertThat(weekResponses.certifications().size()).isEqualTo(0); + } + } + + @Nested + @DisplayName("인스턴스가 진행 중이고, 본인의 정보를 조회할 때") + class describe_instance_activity_inquiry_mine { + @BeforeEach + void setup() { + currentDate = LocalDate.of(2024, 8, 13); + + instance = instanceRepository.save(InstanceFactory.createByInfo(currentDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + } + + @Test + @DisplayName("챌린지의 시작일자가 월요일이 아니고 첫째주일 때, 시작일부터 현재 일자까지의 인증 내역을 반환해야 한다.") + public void it_return_current_certifications() { + int passedDays = 3; + + WeekResponse weekResponses = certificationFacade.getMyWeekCertifications(participant.getId(), + currentDate.plusDays(passedDays)); + + assertThat(weekResponses.certifications().size()).isEqualTo(passedDays + 1); + } + } + + @Nested + @DisplayName("다른 사람들의 한 주간 인증 내역 조회 시") + class describe_inquiry_others { + User other; + + @BeforeEach + void setup() { + other = userRepository.save(UserFactory.createByInfo("identifier2", USER)); + instance = instanceRepository.save(InstanceFactory.createActivity(10)); + participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + participantRepository.save(ParticipantFactory.createProcessing(other, instance)); + } + + + @Test + @DisplayName("본인의 값을 제외하고 반환받아야 한다.") + public void it_return_except_mine() { + currentDate = LocalDate.now(); + + Slice weekResponses = certificationFacade.getOthersWeekCertifications( + user.getId(), instance.getId(), currentDate, + PageRequest.of(0, 10)); + + assertThat(weekResponses.getContent().size()).isEqualTo(1); + } + } + } + + @Nested + @DisplayName("인증 내역 전체 조회 시") + class context_inquiry_whole_certification { + @Nested + @DisplayName("인스턴스의 상태가 PREACTIVITY일 때") + class describe_instance_is_preActivity { + @BeforeEach + void setup() { + currentDate = LocalDate.now(); + instance = instanceRepository.save(InstanceFactory.createPreActivity(10)); + participant = participantRepository.save(ParticipantFactory.createPreActivity(user, instance)); + } + + @Test + @DisplayName("반환하는 데이터의 개수는 0개여야 한다.") + public void it_return_nothing() { + TotalResponse totalResponse = certificationFacade.getTotalCertification(participant.getId(), + currentDate); + + assertThat(totalResponse.certifications().size()).isEqualTo(0); + } + } + + @Nested + @DisplayName("인스턴스의 상태가 ACTIVITY일 떄") + class describe_instance_is_ACTIVITY { + @BeforeEach + void setup() { + LocalDate startedDate = LocalDate.of(2024, 8, 5); + currentDate = LocalDate.of(2024, 8, 13); + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + } + + @Test + @DisplayName("반환하는 데이터의 개수는 시작일자부터 현재일자까지의 일차와 같아야 한다.") + public void it_returns_data_size_current_attempt() { + TotalResponse totalResponse = certificationFacade.getTotalCertification(participant.getId(), + currentDate); + + assertThat(totalResponse.certifications().size()).isEqualTo(9); + } + } + + @Nested + @DisplayName("인스턴스의 상태가 DONE일 때") + class describe_instance_is_Done { + @BeforeEach + void setup() { + LocalDate startedDate = LocalDate.of(2024, 8, 5); + currentDate = LocalDate.of(2024, 8, 20); + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, DONE)); + participant = participantRepository.save( + ParticipantFactory.createByJoinResult(user, instance, SUCCESS)); + } + + @Test + @DisplayName("반환하는 데이터의 개수는 인스턴스의 전체 일차와 같아야 한다.") + public void it_returns_data_size_total_attempt() { + TotalResponse totalResponse = certificationFacade.getTotalCertification(participant.getId(), + currentDate); + + int totalAttempt = instance.getTotalAttempt(); + + assertThat(totalResponse.totalAttempts()).isEqualTo(totalAttempt); + assertThat(totalResponse.certifications().size()).isEqualTo(totalAttempt); + } + } + } + + @Nested + @DisplayName("인증 갱신 시도 시") + class context_try_update_certification { + LocalDate startedDate; + + @Nested + @DisplayName("인스턴스의 인증 가능 조건 확인 시") + class describe_validate_certification_instance_condition { + @BeforeEach + void setup() { + currentDate = LocalDate.of(2024, 2, 5); + startedDate = currentDate.minusDays(5); + + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + instance.setInstanceUUID("instanceUUID"); + participant.updateRepository(targetRepo); + } + + @Test + @DisplayName("인스턴스의 상태가 ACTIVITY이고, 인스턴스 진행일 사이라면 예외가 발생하지 않는다.") + public void it_not_throw_exception_when_condition_valid() { + assertThatNoException().isThrownBy(() -> { + certificationFacade.updateCertification(user, + CertificationRequest.of(instance.getId(), currentDate)); + }); + } + + @ParameterizedTest + @DisplayName("인스턴스의 상태가 ACTIVITY가 아니라면, NOT_ACTIVITY_INSTANCE 예외가 발생한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"PREACTIVITY", "DONE"}) + public void it_throws_NOT_ACTIVITY_INSTANCE_exception(Progress progress) { + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, progress)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + participant.updateRepository(targetRepo); + + assertThatThrownBy(() -> certificationFacade.updateCertification(user, + CertificationRequest.of(instance.getId(), currentDate))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(NOT_ACTIVITY_INSTANCE.getMessage()); + } + + @Test + @DisplayName("현재 일자가 인스턴스 진행 일 사이가 아니라면 NOT_CERTIFICATE_PERIOD 예외가 발생한다.") + public void it_throws_NOT_CERTIFICATE_PERIOD_exception() { + currentDate = startedDate.minusDays(1); + + assertThatThrownBy(() -> certificationFacade.updateCertification(user, + CertificationRequest.of(instance.getId(), currentDate))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(NOT_CERTIFICATE_PERIOD.getMessage()); + } + } + + @Nested + @DisplayName("인증에 사용할 PR 확인 시") + class describe_check_pr { + CertificationRequest certificationRequest; + + @Test + @DisplayName("PR의 body가 null이거나 empty하다면 인증 결과가 NOT_YET으로 유지된다.") + public void it_does_not_contain_PR_body_empty() { + currentDate = LocalDate.of(2024, 2, 25); + startedDate = currentDate.minusDays(10); + + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + instance.setInstanceUUID("instanceUUID"); + participant.updateRepository(targetRepo); + + certificationRequest = CertificationRequest.of(instance.getId(), currentDate); + CertificationResponse certificationResponse = certificationFacade.updateCertification(user, + certificationRequest); + + assertThat(certificationResponse.certificateStatus()).isEqualTo(NOT_YET); + } + + @Test + @DisplayName("PR의 body에 PR Template가 없다면 인증 결과가 NOT_YET 으로 유지된다.") + public void it_does_not_contain_PR_template_not_exist() { + currentDate = LocalDate.of(2024, 3, 12); + startedDate = currentDate.minusDays(10); + + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + instance.setInstanceUUID("instanceUUID"); + participant.updateRepository(targetRepo); + + certificationRequest = CertificationRequest.of(instance.getId(), currentDate); + CertificationResponse certificationResponse = certificationFacade.updateCertification(user, + certificationRequest); + + assertThat(certificationResponse.certificateStatus()).isEqualTo(NOT_YET); + } + + @Test + @DisplayName("PR 인증 조건에 부합한다면 인증 결과를 CERTIFICATED 로 갱신한다.") + public void it_returns_pr_link_when_pr_valid() { + currentDate = LocalDate.of(2024, 8, 11); + startedDate = currentDate.minusDays(10); + + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + instance.setInstanceUUID("instanceUUID"); + participant.updateRepository(targetRepo); + + certificationRequest = CertificationRequest.of(instance.getId(), currentDate); + CertificationResponse certificationResponse = certificationFacade.updateCertification(user, + certificationRequest); + + assertThat(certificationResponse.certificateStatus()).isEqualTo(CertificateStatus.CERTIFICATED); + } + } + + @Nested + @DisplayName("인증 객체 확인 시") + class describe_check_certification_object { + Certification certification; + + @BeforeEach + void setup() { + currentDate = LocalDate.of(2024, 8, 11); + startedDate = currentDate.minusDays(10); + + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + instance.setInstanceUUID("instanceUUID"); + participant.updateRepository(targetRepo); + } + + @Test + @DisplayName("인증 객체의 상태가 PASSED라면 ALREADY_PASSED_CERTIFICATION 예외가 발생한다.") + public void it_throws_ALREADY_PASSED_CERTIFICATION_exception() { + certification = certificationRepository.save( + CertificationFactory.createPassed(participant, currentDate)); + + assertThatThrownBy(() -> certificationFacade.updateCertification(user, + CertificationRequest.of(instance.getId(), currentDate))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ALREADY_PASSED_CERTIFICATION.getMessage()); + } + + @Test + @DisplayName("인증 상태가 NOT_YET이라면 상태가 CERTIFICATED로 갱신된다.") + public void it_update_to_CERTIFICATED() { + certification = certificationRepository.save( + CertificationFactory.createNotYet(participant, currentDate) + ); + + CertificationResponse certificationResponse = certificationFacade.updateCertification(user, + CertificationRequest.of(instance.getId(), currentDate)); + + assertThat(certificationResponse.certificateStatus()).isEqualTo(CertificateStatus.CERTIFICATED); + assertThat(certificationResponse.certificatedAt()).isEqualTo(currentDate); + } + + @Test + @DisplayName("인증 상태가 CERTIFICATED라면 certificationLinks의 내용이 갱신된다.") + public void it_update_certificationLinks() { + certification = certificationRepository.save( + CertificationFactory.createCertificated(participant, currentDate) + ); + + CertificationResponse certificationResponse = certificationFacade.updateCertification(user, + CertificationRequest.of(instance.getId(), currentDate)); + + assertThat(certificationResponse.certificateStatus()).isEqualTo(CertificateStatus.CERTIFICATED); + assertThat(certificationResponse.prLinks()).isNotEmpty(); + assertThat(certificationResponse.prCount()).isNotZero(); + } + } + } + + @Nested + @DisplayName("인증 패스 시도 시") + class context_try_pass_certification { + LocalDate startedDate; + Item item; + Orders orders; + Certification certification; + CertificationRequest certificationRequest; + + @BeforeEach + void setup() { + currentDate = LocalDate.of(2024, 2, 5); + startedDate = currentDate.minusDays(5); + + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + instance.setInstanceUUID("instanceUUID"); + participant.updateRepository(targetRepo); + + item = itemRepository.save(StoreFactory.createItem(CERTIFICATION_PASSER)); + orders = ordersRepository.save(StoreFactory.createOrders(user, item, CERTIFICATION_PASSER, 3)); + + certificationRequest = CertificationRequest.of(instance.getId(), currentDate); + } + + @Nested + @DisplayName("인스턴스의 인증 가능 조건 확인 시") + class describe_validate_instance_certification_condition { + @Test + @DisplayName("인스턴스의 상태가 ACTIVITY이고, 진행일에 해당한다면 인증 패스 처리가 된다.") + public void it_pass_certification_when_condition_valid() { + instance = instanceRepository.save(InstanceFactory.createByInfo(currentDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + participant.updateRepository(targetRepo); + + assertThatNoException().isThrownBy(() -> { + certificationFacade.passCertification(user.getId(), certificationRequest); + }); + } + + @ParameterizedTest + @DisplayName("인스턴스의 상태가 ACTIVITY가 아니라면, NOT_ACTIVITY_INSTANCE 예외가 발생한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"PREACTIVITY", "DONE"}) + public void it_throws_NOT_ACTIVITY_INSTANCE_exception(Progress progress) { + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, progress)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + participant.updateRepository(targetRepo); + + assertThatThrownBy(() -> certificationFacade.passCertification(user.getId(), + CertificationRequest.of(instance.getId(), currentDate))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(NOT_ACTIVITY_INSTANCE.getMessage()); + } + + @Test + @DisplayName("현재 일자가 인스턴스 진행일 사이가 아니라면, NOT_CERTIFICATE_PERIOD 예외가 발생한다.") + public void it_throws_NOT_CERTIFICATE_PERIOD_exception() { + currentDate = startedDate.minusDays(1); + + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + participant.updateRepository(targetRepo); + + assertThatThrownBy(() -> certificationFacade.passCertification(user.getId(), + CertificationRequest.of(instance.getId(), currentDate))) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(NOT_CERTIFICATE_PERIOD.getMessage()); + } + } + + @Nested + @DisplayName("인증 객체의 인증 가능 조건 확인 시") + class describe_validate_certification_condition { + @ParameterizedTest + @DisplayName("인증의 상태가 NOT_YET이 아니라면 CAN_NOT_USE_PASS_ITEM 예외가 발생한다.") + @EnumSource(mode = Mode.INCLUDE, names = {"CERTIFICATED", "PASSED"}) + public void it_throws_exception_when_certificateStatus_not_NOT_YET(CertificateStatus status) { + certification = certificationRepository.save( + CertificationFactory.create(status, currentDate, participant)); + + assertThatThrownBy(() -> certificationFacade.passCertification(user.getId(), certificationRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_USE_PASS_ITEM.getMessage()); + } + + @Test + @DisplayName("인증의 상태가 NOT_YET이라면 예외가 발생하지 않는다.") + public void it_not_throws_exception_when_certificateStatus_is_NOT_YET() { + certification = certificationRepository.save( + CertificationFactory.create(NOT_YET, currentDate, participant) + ); + + assertThatNoException().isThrownBy(() -> { + certificationFacade.passCertification(user.getId(), certificationRequest); + }); + } + } + + @Nested + @DisplayName("인증 객체의 존재 여부 확인 시") + class describe_check_certification_exist { + @Test + @DisplayName("인증 객체가 존재하지 않았다면 인증 객체를 새로 저장한다.") + public void it_save_new_object_when_not_exist() { + Optional beforePassed = certificationRepository.findByDate(currentDate, + participant.getId()); + + certificationFacade.passCertification(user.getId(), certificationRequest); + Optional afterPassed = certificationRepository.findByDate(currentDate, + participant.getId()); + + assertThat(beforePassed).isNotPresent(); + assertThat(afterPassed).isPresent(); + } + + @Test + @DisplayName("인증 객체가 존재했다면 PASSED로 상태를 업데이트한다.") + public void it_update_certificateStatus_to_PASSED() { + certification = certificationRepository.save( + CertificationFactory.createNotYet(participant, currentDate) + ); + + certificationFacade.passCertification(user.getId(), certificationRequest); + Optional afterPassed = certificationRepository.findByDate(currentDate, + participant.getId()); + + assertThat(afterPassed).isPresent(); + assertThat(afterPassed.get().getCertificationStatus()).isEqualTo(PASSED); + } + } + } + + @Nested + @DisplayName("인증 관련 정보 조회 시") + class context_inquiry_certification_information { + LocalDate startedDate; + + @Nested + @DisplayName("인스턴스의 상태가 모두 PREACTIVITY라면") + class describe_instance_is_all_preActivity { + @BeforeEach + void setup() { + currentDate = LocalDate.now(); + instance = instanceRepository.save(InstanceFactory.createPreActivity(10)); + participant = participantRepository.save(ParticipantFactory.createPreActivity(user, instance)); + } + + @Test + @DisplayName("성공/실패의 값은 모두 0이고, 남은일자는 전체 회차여야 한다.") + public void it_returns_correct_values() { + CertificationInformation information = certificationFacade.getCertificationInformation(instance, + participant, currentDate); + + assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); + assertThat(information.currentAttempt()).isZero(); + assertThat(information.successCount()).isZero(); + assertThat(information.failureCount()).isZero(); + assertThat(information.remainCount()).isEqualTo(instance.getTotalAttempt()); + } + } + + @Nested + @DisplayName("인스턴스의 상태가 모두 ACTIVITY라면") + class describe_instance_is_all_activity { + @BeforeEach + void setup() { + startedDate = LocalDate.of(2024, 8, 10); + currentDate = LocalDate.of(2024, 8, 15); + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, ACTIVITY)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + + certificationRepository.save(CertificationFactory.create(NOT_YET, startedDate, participant)); + certificationRepository.save( + CertificationFactory.create(CERTIFICATED, startedDate.plusDays(1), participant)); + certificationRepository.save( + CertificationFactory.create(CERTIFICATED, startedDate.plusDays(2), participant)); + certificationRepository.save( + CertificationFactory.create(PASSED, startedDate.plusDays(3), participant)); + certificationRepository.save( + CertificationFactory.create(NOT_YET, startedDate.plusDays(4), participant)); + } + + @Test + @DisplayName("성공/실패/남을 일자가 올바르게 나와야 한다.") + public void it_returns_correct_values() { + CertificationInformation information = certificationFacade.getCertificationInformation(instance, + participant, currentDate); + + assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); + assertThat(information.successCount()).isEqualTo(3); + assertThat(information.failureCount()).isEqualTo(3); + assertThat(information.currentAttempt()).isEqualTo(6); + assertThat(information.remainCount()).isEqualTo(instance.getTotalAttempt() - 6); + } + } + + @Nested + @DisplayName("인스턴스의 상태가 모두 DONE이라면") + class describe_instance_is_all_DONE { + @BeforeEach + void setup() { + startedDate = LocalDate.of(2024, 8, 10); + currentDate = LocalDate.of(2024, 8, 15); + instance = instanceRepository.save(InstanceFactory.createByInfo(startedDate, DONE)); + participant = participantRepository.save(ParticipantFactory.createProcessing(user, instance)); + + certificationRepository.save(CertificationFactory.create(NOT_YET, startedDate, participant)); + certificationRepository.save( + CertificationFactory.create(CERTIFICATED, startedDate.plusDays(1), participant)); + certificationRepository.save( + CertificationFactory.create(CERTIFICATED, startedDate.plusDays(2), participant)); + certificationRepository.save( + CertificationFactory.create(PASSED, startedDate.plusDays(3), participant)); + certificationRepository.save( + CertificationFactory.create(NOT_YET, startedDate.plusDays(4), participant)); + } + + @Test + @DisplayName("성공/실패/남은일자가 올바르게 나와야 한다.") + public void it_returns_correct_values() { + CertificationInformation information = certificationFacade.getCertificationInformation(instance, + participant, currentDate); + + int totalAttempt = instance.getTotalAttempt(); + + assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); + assertThat(information.successCount()).isEqualTo(3); + assertThat(information.failureCount()).isEqualTo(totalAttempt - 3); + assertThat(information.currentAttempt()).isEqualTo(totalAttempt); + assertThat(information.remainCount()).isEqualTo(0); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationProviderTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationProviderTest.java deleted file mode 100644 index 69d7f6e8..00000000 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationProviderTest.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.genius.gitget.challenge.certification.service; - -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; -import static org.assertj.core.api.Assertions.assertThat; - -import com.genius.gitget.challenge.certification.domain.CertificateStatus; -import com.genius.gitget.challenge.certification.domain.Certification; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.repository.ParticipantRepository; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@SpringBootTest -@Transactional -class CertificationProviderTest { - @Autowired - private CertificationProvider certificationProvider; - @Autowired - private ParticipantRepository participantRepository; - @Autowired - private InstanceRepository instanceRepository; - @Autowired - private UserRepository userRepository; - - @Test - @DisplayName("DB에서 특정 기간 내의 인증 객체 리스트들을 받아올 수 있다.") - public void should_returnList_when_passDuration() { - //given - LocalDate startDate = LocalDate.of(2024, 2, 1); - LocalDate endDate = LocalDate.of(2024, 2, 5); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - - getSavedCertification(startDate, CERTIFICATED, "link1", participant); - getSavedCertification(startDate.plusDays(1), CERTIFICATED, "link1", participant); - getSavedCertification(endDate.minusDays(1), NOT_YET, null, participant); - getSavedCertification(endDate.minusDays(2), CERTIFICATED, "link1", participant); - - //when - List certifications = certificationProvider.findByDuration(startDate, endDate, - participant.getId()); - - //then - assertThat(certifications.size()).isEqualTo(4); - } - - @Test - @DisplayName("특정 일자에 저장된 인증 객체를 받아올 수 있다.") - public void should_getCertification_when_passDate() { - //given - LocalDate targetDate = LocalDate.of(2024, 2, 1); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - - //when - getSavedCertification(targetDate, CERTIFICATED, "link1", participant); - Optional byDate = certificationProvider.findByDate(targetDate, participant.getId()); - - //then - assertThat(byDate).isPresent(); - } - - @Test - @DisplayName("특정 기간 이내에 특정 인증 상태인 인증 객체의 개수를 받아올 수 있다.") - public void should_count_when_passStatus() { - //given - LocalDate startDate = LocalDate.of(2024, 2, 1); - LocalDate endDate = LocalDate.of(2024, 2, 5); - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - - getSavedCertification(startDate, CERTIFICATED, "link1", participant); - getSavedCertification(startDate.plusDays(1), CERTIFICATED, "link1", participant); - getSavedCertification(endDate.minusDays(1), NOT_YET, null, participant); - getSavedCertification(endDate.minusDays(2), CERTIFICATED, "link1", participant); - - //when - int certificated = certificationProvider.countByStatus(participant.getId(), CERTIFICATED, - endDate); - - //then - assertThat(certificated).isEqualTo(3); - } - - @Test - @DisplayName("사용자가 인증을 생성/갱신할 수 있다.") - public void should_renewCertification() { - //given - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - LocalDate targetDate = LocalDate.of(2024, 2, 1); - List pullRequests = List.of("pr link1", "pr link2"); - - //when - Certification certification = certificationProvider.createCertification(participant, targetDate, - pullRequests); - - //then - assertThat(certification.getCertificatedAt()).isEqualTo(targetDate); - } - - @Test - @DisplayName("인증과 관련된 정보를 전달했을 때, 객체의 정보를 업데이트할 수 있다.") - public void should_update_when_passInfo() { - //given - User user = getSavedUser(); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - LocalDate targetDate = LocalDate.of(2024, 2, 1); - Certification certification = getSavedCertification(targetDate, NOT_YET, "", participant); - List pullRequests = List.of("pr link1", "pr link2"); - - //when - Certification updatedCertification = certificationProvider.update(certification, targetDate, pullRequests); - - //then - assertThat(updatedCertification.getId()).isEqualTo(certification.getId()); - assertThat(updatedCertification.getCertificatedAt()).isEqualTo(targetDate); - assertThat(updatedCertification.getCertificationStatus()).isEqualTo(CERTIFICATED); - assertThat(updatedCertification.getCertificationLinks()).isEqualTo("pr link1,pr link2,"); - } - - private Certification getSavedCertification(LocalDate certificatedAt, CertificateStatus status, - String link, Participant participant) { - Certification certification = certificationProvider.save( - Certification.builder() - .certificatedAt(certificatedAt) - .certificationStatus(status) - .certificationLinks(link) - .build() - ); - certification.setParticipant(participant); - return certification; - } - - private Participant getSavedParticipant(User user, Instance instance) { - Participant participant = participantRepository.save( - Participant.createDefaultParticipant("repo") - ); - participant.setUserAndInstance(user, instance); - return participant; - } - - private Participant getSavedParticipant(Instance instance) { - Participant participant = participantRepository.save( - Participant.createDefaultParticipant("repo") - ); - participant.setUserAndInstance(null, instance); - return participant; - } - - private Instance getSavedInstance() { - return instanceRepository.save( - Instance.builder() - .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) - .pointPerPerson(100) - .progress(Progress.PREACTIVITY) - .build() - ); - } - - private User getSavedUser() { - return userRepository.save( - User.builder() - .role(Role.USER) - .nickname("nickname") - .providerInfo(ProviderInfo.GITHUB) - .identifier("githubId") - .information("information") - .tags("BE,FE") - .build() - ); - } -} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java index 2814b344..e92cbfc5 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/CertificationServiceTest.java @@ -2,716 +2,186 @@ import static com.genius.gitget.challenge.certification.domain.CertificateStatus.CERTIFICATED; import static com.genius.gitget.challenge.certification.domain.CertificateStatus.NOT_YET; -import static com.genius.gitget.challenge.certification.domain.CertificateStatus.PASSED; -import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_PASSED_CERTIFICATION; -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; -import static com.genius.gitget.global.util.exception.ErrorCode.NOT_CERTIFICATE_PERIOD; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.certification.domain.Certification; -import com.genius.gitget.challenge.certification.dto.CertificationInformation; -import com.genius.gitget.challenge.certification.dto.CertificationRequest; -import com.genius.gitget.challenge.certification.dto.CertificationResponse; -import com.genius.gitget.challenge.certification.dto.InstancePreviewResponse; -import com.genius.gitget.challenge.certification.dto.TotalResponse; -import com.genius.gitget.challenge.certification.dto.WeekResponse; -import com.genius.gitget.challenge.certification.repository.CertificationRepository; -import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; -import com.genius.gitget.challenge.participant.domain.JoinResult; -import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.repository.ParticipantRepository; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.store.item.repository.OrdersRepository; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.EnumSource.Mode; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Slice; -import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; @Slf4j @SpringBootTest @Transactional -@ActiveProfiles({"github"}) class CertificationServiceTest { @Autowired private CertificationService certificationService; @Autowired - private GithubService githubService; - @Autowired - private UserRepository userRepository; - @Autowired - private InstanceRepository instanceRepository; - @Autowired private ParticipantRepository participantRepository; @Autowired - private CertificationRepository certificationRepository; - @Autowired - private ItemRepository itemRepository; + private InstanceRepository instanceRepository; @Autowired - private OrdersRepository ordersRepository; - - @Value("${github.yeon-personalKey}") - private String personalKey; - - @Value("${github.yeon-githubId}") - private String githubId; - - @Value("${github.yeon-repository}") - private String targetRepo; - - - @Test - @DisplayName("사용자가 연결한 레포지토리에 특정 날짜의 PR이 있으면 인증으로 간주한다") - public void should_certificate_when_prExist() { - //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - getSavedParticipant(user, instance); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 2, 5); - - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(targetDate) - .build(); - instance.updateProgress(Progress.ACTIVITY); - - //when - CertificationResponse certificationResponse = certificationService.updateCertification(user, - certificationRequest); - Certification certification = certificationRepository.findById(certificationResponse.certificationId()) - .get(); - - //then - assertThat(certification.getId()).isEqualTo(certificationResponse.certificationId()); - assertThat(certificationResponse.certificateStatus()).isEqualTo(CERTIFICATED); - assertThat(certificationResponse.certificatedAt()).isEqualTo(targetDate); - assertThat(certificationResponse.prCount()).isEqualTo(1); - } - - @Test - @DisplayName("기존에 저장되어있는 인증 내역이 있더라도, 인증 시도를 다시 하면 최신의 내용으로 갱신된다.") - public void should_updateCertification_when_certificateOnce() { - //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 2, 5); - - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(targetDate) - .build(); - instance.updateProgress(Progress.ACTIVITY); - - //when - Certification certification = getSavedCertification(CERTIFICATED, targetDate, participant); - CertificationResponse certificationResponse = certificationService.updateCertification(user, - certificationRequest); - - //then - assertThat(certificationResponse.certificatedAt()).isEqualTo(targetDate); - assertThat(certificationResponse.certificationId()).isEqualTo(certification.getId()); - assertThat(certificationResponse.certificateStatus()).isEqualTo(certification.getCertificationStatus()); - } - - @Test - @DisplayName("인증을 시도한 날짜가 챌린지의 진행 기간과 겹치지 않는다면 예외를 발생한다.") - public void should_throwException_when_progressIsNotActivity() { - //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - getSavedParticipant(user, instance); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 12, 6); - - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(targetDate) - .build(); - instance.updateProgress(Progress.ACTIVITY); - - //when && then - assertThatThrownBy(() -> certificationService.updateCertification(user, certificationRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(NOT_CERTIFICATE_PERIOD.getMessage()); - } - - @Test - @DisplayName("패스를 완료했을 때, 인증 갱신을 요청한다면 예외가 발생한다.") - public void should_throwException_when_passedAlready() { - //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 2, 6); - - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(targetDate) - .build(); - instance.updateProgress(Progress.ACTIVITY); - - //when - getSavedCertification(PASSED, targetDate, participant); - - //then - assertThatThrownBy(() -> certificationService.updateCertification(user, certificationRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ALREADY_PASSED_CERTIFICATION.getMessage()); - } - - @Test - @DisplayName("사용자가 연결한 레포지토리에 특정 날짜의 PR이 존재하지 않으면 인증이 아직 안된 것으로 간주한다.") - public void should_notCertificate_when_prNotExist() { - //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - getSavedParticipant(user, instance); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 2, 6); - - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(targetDate) - .build(); - instance.updateProgress(Progress.ACTIVITY); - - //when - CertificationResponse certificationResponse = certificationService.updateCertification(user, - certificationRequest); - Certification certification = certificationRepository.findById(certificationResponse.certificationId()) - .get(); - - //then - assertThat(certification.getId()).isEqualTo(certificationResponse.certificationId()); - assertThat(certificationResponse.certificateStatus()).isEqualTo(CertificateStatus.NOT_YET); - assertThat(certificationResponse.certificatedAt()).isEqualTo(targetDate); - assertThat(certificationResponse.prCount()).isEqualTo(0); - } - - @Test - @DisplayName("github를 통해 public repository 정보들을 받아올 수 있다.") - public void should_returnRepositoryList_when_passGitHubToken() { - //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - githubService.registerGithubPersonalToken(user, personalKey); - - //when - List repositoryList = githubService.getPublicRepositories(user); - - //then - assertThat(repositoryList.size()).isGreaterThan(0); - } - - @Test - @DisplayName("repository 정보를 불러올 때 github token이 제대로 설정되어있지 않다면 예외를 발생해야 한다.") - public void should_throwException_when_loadRepository() { - //given - User user = getSavedUser(githubId); - - //when & then - assertThatThrownBy(() -> githubService.getPublicRepositories(user)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("챌린지의 시작일자가 월요일이 아니고 첫째 주 일 때, 챌린지 시작일부터 현재일자까지의 인증 내역을 반환해야 한다.") - public void should_returnCertifications_when_passDuration() { - //given - LocalDate currentDate = LocalDate.of(2024, 2, 3); - LocalDate startDate = LocalDate.of(2024, 2, 1); - LocalDate endDate = LocalDate.of(2024, 2, 4); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(getSavedUser(githubId), instance); - - //when - instance.updateProgress(Progress.ACTIVITY); - getSavedCertification(NOT_YET, startDate, participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); - getSavedCertification(CERTIFICATED, endDate.minusDays(1), participant); - getSavedCertification(CERTIFICATED, endDate, participant); - - WeekResponse weekCertification = certificationService.getMyWeekCertifications( - participant.getId(), currentDate); - - //then - List certifications = weekCertification.certifications(); - assertThat(certifications.size()).isEqualTo(3); - assertThat(certifications.get(0).certificateStatus()).isEqualTo(NOT_YET); - assertThat(certifications.get(1).certificateStatus()).isEqualTo(CERTIFICATED); - assertThat(certifications.get(2).certificateStatus()).isEqualTo(CERTIFICATED); - } - - @Test - @DisplayName("특정 사용자가 일주일 간 인증한 현황들을 받아올 때, 더미 데이터를 포함하여 연속적인 데이터로 받아올 수 있다.") - public void should_returnList_when_dataIsNotContinuous() { - //given - LocalDate startDate = LocalDate.of(2024, 2, 1); - LocalDate endDate = LocalDate.of(2024, 2, 29); - LocalDate currentDate = LocalDate.of(2024, 2, 8); - - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(getSavedUser(githubId), instance); - - //when - instance.updateProgress(Progress.ACTIVITY); - getSavedCertification(NOT_YET, startDate, participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); - getSavedCertification(CERTIFICATED, currentDate, participant); - - WeekResponse weekCertification = certificationService.getMyWeekCertifications( - participant.getId(), currentDate); - - //then - List certifications = weekCertification.certifications(); - assertThat(certifications.size()).isEqualTo(4); - assertThat(certifications.get(0).certificateStatus()).isEqualTo(CERTIFICATED); - assertThat(certifications.get(1).certificateStatus()).isEqualTo(NOT_YET); - assertThat(certifications.get(2).certificateStatus()).isEqualTo(NOT_YET); - assertThat(certifications.get(3).certificateStatus()).isEqualTo(CERTIFICATED); - } + private UserRepository userRepository; @Test - @DisplayName("현재 일자까지의 인증 현황들을 받아올 수 있다.") - public void should_returnList_when_passDate() { + @DisplayName("DB에서 특정 기간 내의 인증 객체 리스트들을 받아올 수 있다.") + public void should_returnList_when_passDuration() { //given LocalDate startDate = LocalDate.of(2024, 2, 1); - LocalDate endDate = LocalDate.of(2024, 2, 29); - LocalDate currentDate = LocalDate.of(2024, 2, 8); - - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(getSavedUser(githubId), instance); - - //when - getSavedCertification(NOT_YET, startDate, participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(6), participant); - - TotalResponse totalResponse = certificationService.getTotalCertification(participant.getId(), - currentDate); - - //then - assertThat(totalResponse.certifications().size()).isEqualTo(8); - assertThat(totalResponse.totalAttempts()).isEqualTo(instance.getTotalAttempt()); - } - - @Test - @DisplayName("아직 시작하지 않은 챌린지에 대해 전체 인증 조회를 했을 때, 데이터의 개수는 0개여야 한다.") - public void should_return0_when_preActivityInstance() { - //given - LocalDate startDate = LocalDate.of(2024, 3, 10); - LocalDate endDate = LocalDate.of(2024, 3, 20); - LocalDate currentDate = LocalDate.of(2024, 2, 20); - Instance instance = getSavedInstance(startDate, endDate); - Participant participant = getSavedParticipant(getSavedUser(githubId), instance); - - //when - TotalResponse totalCertification = certificationService.getTotalCertification(participant.getId(), currentDate); - - //then - assertThat(totalCertification.totalAttempts()).isEqualTo(DateUtil.getAttemptCount(startDate, endDate)); - assertThat(totalCertification.certifications().size()).isEqualTo(0); - } - - @Test - @DisplayName("진행 중인 챌린지의 전체 인증 조회를 했을 때, 시작일자부터 오늘일자까지의 데이터를 전달해야 한다.") - public void should_returnEmptyList_when_activityInstance() { - //given - LocalDate startDate = LocalDate.of(2024, 3, 10); - LocalDate endDate = LocalDate.of(2024, 3, 30); - LocalDate currentDate = LocalDate.of(2024, 3, 20); - Instance instance = getSavedInstance(startDate, endDate); - Participant participant = getSavedParticipant(getSavedUser(githubId), instance); - - //when - instance.updateProgress(Progress.ACTIVITY); - getSavedCertification(PASSED, currentDate, participant); - TotalResponse totalCertification = certificationService.getTotalCertification(participant.getId(), currentDate); - - //then - assertThat(totalCertification.totalAttempts()).isEqualTo(DateUtil.getAttemptCount(startDate, endDate)); - assertThat(totalCertification.certifications().size()).isEqualTo(11); - } - - @Test - @DisplayName("완료된 챌린지의 전체 인증 조회를 했을 때, 챌린지의 시작일자부터 완료일자까지의 데이터를 전달해야 한다.") - public void should_returnPeriod_when_doneInstance() { - //given - LocalDate startDate = LocalDate.of(2024, 3, 10); - LocalDate endDate = LocalDate.of(2024, 3, 30); - LocalDate currentDate = LocalDate.of(2024, 4, 20); - Instance instance = getSavedInstance(startDate, endDate); - Participant participant = getSavedParticipant(getSavedUser(githubId), instance); - - //when - instance.updateProgress(Progress.DONE); - TotalResponse totalCertification = certificationService.getTotalCertification(participant.getId(), currentDate); - - //then - int totalAttempt = DateUtil.getAttemptCount(startDate, endDate); - assertThat(totalCertification.totalAttempts()).isEqualTo(totalAttempt); - assertThat(totalCertification.certifications().size()).isEqualTo(totalAttempt); - } - - @Test - @DisplayName("사용자가 참여한 챌린지에 대한 상세 정보를 받을 수 있다.") - public void should_returnDetailInfo_when_participate() { - //given - User user = getSavedUser(githubId); + LocalDate endDate = LocalDate.of(2024, 2, 5); + User user = getSavedUser(); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - //when - InstancePreviewResponse instancePreviewResponse = certificationService.getInstancePreview(instance.getId()); - - //then - assertThat(instancePreviewResponse.instanceId()).isEqualTo(instance.getId()); - } - - @Test - @DisplayName("사용자가 참여한 챌린지가 아직 시작 전이라면, 성공/실패의 값이 모두 0이어야한다.") - public void should_getInformation_when_progressIsPreActivity() { - //given - LocalDate targetDate = LocalDate.of(2024, 2, 8); - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); + getSavedCertification(startDate, CERTIFICATED, "link1", participant); + getSavedCertification(startDate.plusDays(1), CERTIFICATED, "link1", participant); + getSavedCertification(endDate.minusDays(1), NOT_YET, null, participant); + getSavedCertification(endDate.minusDays(2), CERTIFICATED, "link1", participant); //when - CertificationInformation information = certificationService.getCertificationInformation(instance, - participant, targetDate); + List certifications = certificationService.findByDuration(startDate, endDate, + participant.getId()); //then - assertThat(information.prTemplate()).isEqualTo(instance.getPrTemplate(targetDate)); - assertThat(information.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(information.remainCount()).isEqualTo(information.totalAttempt()); - assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); - assertThat(information.currentAttempt()).isEqualTo(0); - assertThat(information.successCount()).isEqualTo(0); - assertThat(information.failureCount()).isEqualTo(0); - assertThat(information.remainCount()).isEqualTo(instance.getTotalAttempt()); + assertThat(certifications.size()).isEqualTo(4); } @Test - @DisplayName("사용자가 참여한 챌린지가 진행 중이라면, 성공/실패/남은 일자의 값의 제대로 나와야 한다.") - public void should_getInformation_when_progressIsActivity() { + @DisplayName("특정 일자에 저장된 인증 객체를 받아올 수 있다.") + public void should_getCertification_when_passDate() { //given - LocalDate startDate = LocalDate.of(2024, 2, 1); - LocalDate targetDate = LocalDate.of(2024, 2, 8); - User user = getSavedUser(githubId); + LocalDate targetDate = LocalDate.of(2024, 2, 1); + User user = getSavedUser(); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); //when - instance.updateProgress(Progress.ACTIVITY); - getSavedCertification(NOT_YET, startDate, participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); - getSavedCertification(PASSED, startDate.plusDays(6), participant); - CertificationInformation information = certificationService.getCertificationInformation(instance, - participant, targetDate); + getSavedCertification(targetDate, CERTIFICATED, "link1", participant); + Optional byDate = certificationService.findByDate(targetDate, participant.getId()); //then - assertThat(information.repository()).isEqualTo(participant.getRepositoryName()); - assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); - assertThat(information.currentAttempt()).isEqualTo(8); - assertThat(information.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(information.successCount()).isEqualTo(3); - assertThat(information.failureCount()).isEqualTo(information.currentAttempt() - 3); - assertThat(information.remainCount()).isEqualTo(instance.getTotalAttempt() - 8); + assertThat(byDate).isPresent(); } @Test - @DisplayName("사용자가 참여한 챌린지가 완료이라면, 성공/실패/남은 일자의 값의 제대로 나와야 한다.") - public void should_getInformation_when_progressIsDone() { + @DisplayName("특정 기간 이내에 특정 인증 상태인 인증 객체의 개수를 받아올 수 있다.") + public void should_count_when_passStatus() { //given LocalDate startDate = LocalDate.of(2024, 2, 1); - LocalDate targetDate = LocalDate.of(2024, 2, 8); - User user = getSavedUser(githubId); + LocalDate endDate = LocalDate.of(2024, 2, 5); + User user = getSavedUser(); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - //when - instance.updateProgress(Progress.DONE); - getSavedCertification(NOT_YET, startDate, participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(1), participant); - getSavedCertification(CERTIFICATED, startDate.plusDays(4), participant); - getSavedCertification(PASSED, startDate.plusDays(6), participant); - CertificationInformation information = certificationService.getCertificationInformation(instance, - participant, targetDate); - - //then - assertThat(information.repository()).isEqualTo(participant.getRepositoryName()); - assertThat(information.totalAttempt()).isEqualTo(instance.getTotalAttempt()); - assertThat(information.currentAttempt()).isEqualTo(instance.getTotalAttempt()); - assertThat(information.pointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(information.successCount()).isEqualTo(3); - assertThat(information.failureCount()).isEqualTo(information.totalAttempt() - 3); - assertThat(information.remainCount()).isEqualTo(0); - } - - @Test - @DisplayName("챌린지에 참여한 모든 사용자들의 일주일 간 인증 현황을 받아올 수 있다. 단, 본인의 값을 제외한다.") - public void should_getWeekCertification_aboutAllParticipants() { - //given - PageRequest pageRequest = PageRequest.of(0, 10); - LocalDate currentDate = LocalDate.of(2024, 3, 6); - User user1 = getSavedUser(githubId, "nickname1"); - User user2 = getSavedUser(githubId, "nickname2"); - Instance instance = getSavedInstance(); - Participant participant1 = getSavedParticipant(user1, instance); - Participant participant2 = getSavedParticipant(user2, instance); + getSavedCertification(startDate, CERTIFICATED, "link1", participant); + getSavedCertification(startDate.plusDays(1), CERTIFICATED, "link1", participant); + getSavedCertification(endDate.minusDays(1), NOT_YET, null, participant); + getSavedCertification(endDate.minusDays(2), CERTIFICATED, "link1", participant); //when - instance.updateProgress(Progress.ACTIVITY); - Slice certification = certificationService.getOthersWeekCertifications( - user1.getId(), instance.getId(), currentDate, pageRequest); + int certificated = certificationService.countByStatus(participant.getId(), CERTIFICATED, + endDate); //then - assertThat(certification.getContent().size()).isEqualTo(1); - assertThat(certification.getContent().get(0).certifications().size()).isEqualTo(3); + assertThat(certificated).isEqualTo(3); } @Test - @DisplayName("아직 인증을 하지 않았을 때 해당 일자의 인증을 패스할 수 있다.") - public void should_passCertification_when_conditionIsValid() { + @DisplayName("사용자가 인증을 생성/갱신할 수 있다.") + public void should_renewCertification() { //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(githubId); + User user = getSavedUser(); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(currentDate) - .build(); + LocalDate targetDate = LocalDate.of(2024, 2, 1); + List pullRequests = List.of("pr link1", "pr link2"); //when - instance.updateProgress(Progress.ACTIVITY); - getSavedCertification(NOT_YET, currentDate, participant); - getSavedCertification(CERTIFICATED, currentDate.plusDays(1), participant); - getSavedCertification(CERTIFICATED, currentDate.plusDays(4), participant); - getSavedCertification(CERTIFICATED, currentDate.plusDays(6), participant); - - ActivatedResponse activatedResponse = certificationService.passCertification( - user.getId(), - certificationRequest); + Certification certification = certificationService.createCertificated(participant, targetDate, + pullRequests); //then - assertThat(activatedResponse.getInstanceId()).isEqualTo(instance.getId()); - assertThat(activatedResponse.getTitle()).isEqualTo(instance.getTitle()); - assertThat(activatedResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(activatedResponse.getRepository()).isEqualTo(participant.getRepositoryName()); - assertThat(activatedResponse.getCertificateStatus()).isEqualTo(PASSED.getTag()); - assertThat(activatedResponse.getNumOfPassItem()).isEqualTo(0); - assertThat(activatedResponse.isCanUsePassItem()).isFalse(); - assertThat(activatedResponse.getFileResponse()).isNotNull(); - } - - @ParameterizedTest - @DisplayName("패스 아이템은 있으나, 챌린지가 인증 필요 상태가 아니라면 예외가 발생해야 한다.") - @EnumSource(mode = Mode.INCLUDE, names = {"CERTIFICATED", "PASSED"}) - public void should_throwException_when_challengeIsNotNOT_YET(CertificateStatus certificateStatus) { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(currentDate) - .build(); - - //when - instance.updateProgress(Progress.ACTIVITY); - getSavedCertification(certificateStatus, currentDate, participant); - - //then - assertThatThrownBy(() -> certificationService.passCertification(user.getId(), certificationRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.CAN_NOT_USE_PASS_ITEM.getMessage()); + assertThat(certification.getCertificatedAt()).isEqualTo(targetDate); } @Test - @DisplayName("패스 아이템을 사용할 수 있고, 기존에 인증을 한 차례 시도했다면 PASSED로 데이터가 덮어진다.") - public void should_overwriteData_when_certificatedBefore() { + @DisplayName("인증과 관련된 정보를 전달했을 때, 객체의 정보를 업데이트할 수 있다.") + public void should_update_when_passInfo() { //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(githubId); + User user = getSavedUser(); Instance instance = getSavedInstance(); Participant participant = getSavedParticipant(user, instance); - Orders orders = getSavedOrder(user, ItemCategory.CERTIFICATION_PASSER, 1); - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(currentDate) - .build(); + LocalDate targetDate = LocalDate.of(2024, 2, 1); + Certification certification = getSavedCertification(targetDate, NOT_YET, "", participant); + List pullRequests = List.of("pr link1", "pr link2"); //when - instance.updateProgress(Progress.ACTIVITY); - getSavedCertification(NOT_YET, currentDate, participant); - ActivatedResponse activatedResponse = certificationService.passCertification(user.getId(), - certificationRequest); + Certification updatedCertification = certificationService.update(certification, targetDate, pullRequests); //then - assertThat(activatedResponse.getInstanceId()).isEqualTo(instance.getId()); - assertThat(activatedResponse.getTitle()).isEqualTo(instance.getTitle()); - assertThat(activatedResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(activatedResponse.getRepository()).isEqualTo(participant.getRepositoryName()); - assertThat(activatedResponse.getCertificateStatus()).isEqualTo(PASSED.getTag()); - assertThat(activatedResponse.getNumOfPassItem()).isEqualTo(0); - assertThat(activatedResponse.isCanUsePassItem()).isFalse(); - assertThat(activatedResponse.getFileResponse()).isNotNull(); + assertThat(updatedCertification.getId()).isEqualTo(certification.getId()); + assertThat(updatedCertification.getCertificatedAt()).isEqualTo(targetDate); + assertThat(updatedCertification.getCertificationStatus()).isEqualTo(CERTIFICATED); + assertThat(updatedCertification.getCertificationLinks()).isEqualTo("pr link1,pr link2,"); } - @Test - @DisplayName("패스 아이템을 가지고 있고, 이전에 인증을 한차례도 시도하지 않았을 때에도 아이템 사용이 가능하다") - public void should_usePassItem_when_conditionIsValid() { - //given - LocalDate currentDate = LocalDate.of(2024, 3, 1); - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - Participant participant = getSavedParticipant(user, instance); - Orders orders = getSavedOrder(user, ItemCategory.CERTIFICATION_PASSER, 1); - CertificationRequest certificationRequest = CertificationRequest.builder() - .instanceId(instance.getId()) - .targetDate(currentDate) - .build(); - - //when - instance.updateProgress(Progress.ACTIVITY); - ActivatedResponse activatedResponse = certificationService.passCertification(user.getId(), - certificationRequest); - - //then - assertThat(activatedResponse.getInstanceId()).isEqualTo(instance.getId()); - assertThat(activatedResponse.getTitle()).isEqualTo(instance.getTitle()); - assertThat(activatedResponse.getPointPerPerson()).isEqualTo(instance.getPointPerPerson()); - assertThat(activatedResponse.getRepository()).isEqualTo(participant.getRepositoryName()); - assertThat(activatedResponse.getCertificateStatus()).isEqualTo(PASSED.getTag()); - assertThat(activatedResponse.getNumOfPassItem()).isEqualTo(0); - assertThat(activatedResponse.isCanUsePassItem()).isFalse(); - assertThat(activatedResponse.getFileResponse()).isNotNull(); + private Certification getSavedCertification(LocalDate certificatedAt, CertificateStatus status, + String link, Participant participant) { + Certification certification = certificationService.save( + Certification.builder() + .certificatedAt(certificatedAt) + .certificationStatus(status) + .certificationLinks(link) + .build() + ); + certification.setParticipant(participant); + return certification; } + private Participant getSavedParticipant(User user, Instance instance) { + Participant participant = participantRepository.save( + Participant.createDefaultParticipant("repo") + ); + participant.setUserAndInstance(user, instance); + return participant; + } - private User getSavedUser(String githubId) { - return userRepository.save( - User.builder() - .role(Role.USER) - .nickname("nickname") - .providerInfo(ProviderInfo.GITHUB) - .identifier(githubId) - .information("information") - .tags("BE,FE") + private Instance getSavedInstance() { + return instanceRepository.save( + Instance.builder() + .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) + .pointPerPerson(100) + .progress(Progress.PREACTIVITY) .build() ); } - private User getSavedUser(String githubId, String nickname) { + private User getSavedUser() { return userRepository.save( User.builder() .role(Role.USER) - .nickname(nickname) + .nickname("nickname") .providerInfo(ProviderInfo.GITHUB) - .identifier(githubId) + .identifier("githubId") .information("information") .tags("BE,FE") .build() ); } - - private Instance getSavedInstance() { - Instance instance = Instance.builder() - .progress(Progress.PREACTIVITY) - .startedDate(LocalDateTime.of(2024, 2, 1, 0, 0)) - .completedDate(LocalDateTime.of(2024, 3, 29, 0, 0)) - .build(); - instance.setInstanceUUID("instanceUUID"); - return instanceRepository.save(instance); - } - - private Instance getSavedInstance(LocalDate startedDate, LocalDate completedDate) { - Instance instance = Instance.builder() - .progress(Progress.PREACTIVITY) - .startedDate(startedDate.atTime(0, 0)) - .completedDate(completedDate.atTime(0, 0)) - .build(); - instance.setInstanceUUID("instanceUUID"); - return instanceRepository.save(instance); - } - - private Participant getSavedParticipant(User user, Instance instance) { - Participant participant = participantRepository.save( - Participant.builder() - .joinResult(JoinResult.PROCESSING) - .joinStatus(JoinStatus.YES) - .build() - ); - participant.setUserAndInstance(user, instance); - participant.updateRepository(targetRepo); - - return participant; - } - - - private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, - Participant participant) { - int attempt = DateUtil.getAttemptCount(participant.getStartedDate(), certificatedAt); - Certification certification = Certification.builder() - .certificationStatus(status) - .currentAttempt(attempt) - .certificatedAt(certificatedAt) - .certificationLinks("certificationLink") - .build(); - certification.setParticipant(participant); - return certificationRepository.save(certification); - } - - private Orders getSavedOrder(User user, ItemCategory itemCategory, int count) { - Item item = itemRepository.save(Item.builder() - .itemCategory(itemCategory) - .build()); - Orders orders = Orders.of(count, itemCategory); - orders.setItem(item); - orders.setUser(user); - return ordersRepository.save(orders); - } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/GithubFacadeTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/GithubFacadeTest.java new file mode 100644 index 00000000..a1e8fbf1 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/certification/service/GithubFacadeTest.java @@ -0,0 +1,229 @@ +package com.genius.gitget.challenge.certification.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_REPOSITORY_INCORRECT; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; +import com.genius.gitget.challenge.certification.facade.GithubFacade; +import com.genius.gitget.challenge.certification.util.EncryptUtil; +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.util.user.UserFactory; +import java.time.LocalDate; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@SpringBootTest +@Transactional +public class GithubFacadeTest { + @Autowired + private EncryptUtil encryptUtil; + @Autowired + private GithubFacade githubFacade; + @Autowired + private UserRepository userRepository; + + @Value("${github.yeon-personalKey}") + private String githubToken; + @Value("${github.yeon-githubId}") + private String githubId; + @Value("${github.yeon-repository}") + private String targetRepo; + + private User user; + + @BeforeEach + void setup() { + user = userRepository.save(UserFactory.createByInfo(githubId, Role.USER)); + } + + @Nested + @DisplayName("Github Token 등록 시") + class context_register_github_token { + @Nested + @DisplayName("Github 연결에 이상이 없다면") + class describe_connection_valid { + @Test + @DisplayName("Github token을 암호화하여 User 엔티티에 저장한다.") + public void it_save_token_to_user_entity() { + String encrypted = encryptUtil.encrypt(githubToken); + + githubFacade.registerGithubPersonalToken(user, githubToken); + + assertThat(user.getGithubToken()).isEqualTo(encrypted); + } + } + + @Nested + @DisplayName("깃허브 계정 정보와 identifier 비교 시") + class describe_mismatch_id { + @Test + @DisplayName("사용자의 identifier와 일치하지 않으면 GITHUB_ID_INCORRECT 예외가 발생한다.") + public void it_throws_GITHUB_ID_INCORRECT_exception() { + user = userRepository.save(UserFactory.createByInfo("incorrectID", Role.USER)); + + assertThatThrownBy(() -> githubFacade.registerGithubPersonalToken(user, githubToken)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.GITHUB_ID_INCORRECT.getMessage()); + } + } + } + + @Nested + @DisplayName("Repository 유효성 확인 시") + class context_register_repository { + @Nested + @DisplayName("사용자로부터 Github token을 불러올 때") + class describe_get_github_token { + @Test + @DisplayName("사용자에게 Github token이 저장되어 있지 않다면 GITHUB_TOKEN_NOT_FOUND 예외가 발생한다.") + public void it_throws_GITHUB_TOKEN_NOT_FOUND_exception() { + assertThatThrownBy(() -> githubFacade.verifyRepository(user, targetRepo)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + } + } + + @Nested + @DisplayName("repository의 이름을 전달했을 때") + class describe_pass_repository_name { + @BeforeEach + void setup() { + user.updateGithubPersonalToken(encryptUtil.encrypt(githubToken)); + } + + @Test + @DisplayName("해당 깃허브 계정에 repository가 존재한다면 예외가 발생하지 않는다.") + public void it_not_throw_exception() { + assertThatNoException().isThrownBy(() -> { + githubFacade.verifyRepository(user, targetRepo); + }); + } + + @Test + @DisplayName("해당 깃허브 계정에 Repository가 존재하지 않는다면 GITHUB_REPOSITORY_INCORRECT 예외가 발생한다.") + public void it_throw_GITHUB_REPOSITORY_INCORRECT_exception() { + String fakeRepoName = "Fake"; + + assertThatThrownBy(() -> githubFacade.verifyRepository(user, fakeRepoName)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_REPOSITORY_INCORRECT.getMessage()); + } + } + } + + @Nested + @DisplayName("특정 일자의 PR 내역 확인 시") + class context_check_PR { + LocalDate targetDate; + + @Nested + @DisplayName("조건 확인 시") + class describe_validate_condition { + @Test + @DisplayName("사용자의 Github token이 저장되어 있지 않을 때 GITHUB_TOKEN_NOT_FOUND 예외가 발생한다.") + public void it_throws_GITHUB_TOKEN_NOT_FOUND_exception() { + targetDate = LocalDate.of(2024, 1, 4); + assertThatThrownBy(() -> githubFacade.getPullRequestListByDate(user, targetRepo, targetDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("repository 이름이 유효하지 않을 때 예외가 발생한다.") + public void it_throws_GITHUB_REPOSITORY_INCORRECT_exception() { + String fakeRepo = "fake Repo"; + targetDate = LocalDate.of(2024, 2, 5); + user.updateGithubPersonalToken(encryptUtil.encrypt(githubToken)); + + assertThatThrownBy(() -> githubFacade.getPullRequestListByDate(user, fakeRepo, targetDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_REPOSITORY_INCORRECT.getMessage()); + } + } + + @Nested + @DisplayName("PR을 확인할 수 있는 유효한 조건일 때") + class describe_valid_condition { + @BeforeEach + void setup() { + user.updateGithubPersonalToken(encryptUtil.encrypt(githubToken)); + } + + @Test + @DisplayName("특정 일자에 PR이 존재하지 않는다면 빈 리스트를 반환한다.") + public void it_returns_emptyList_when_pr_not_exist() { + LocalDate targetDate = LocalDate.of(2024, 1, 4); + + List pullRequestResponses = githubFacade.getPullRequestListByDate( + user, targetRepo, targetDate); + + assertThat(pullRequestResponses.size()).isEqualTo(0); + } + + @Test + @DisplayName("특정 일자에 PR이 존재한다면 목록을 불러 올 수 있다.") + public void it_returns_pr_list() { + LocalDate targetDate = LocalDate.of(2024, 2, 5); + + List pullRequestResponses = githubFacade.getPullRequestListByDate( + user, targetRepo, targetDate); + + assertThat(pullRequestResponses.size()).isEqualTo(1); + } + } + } + + @Nested + @DisplayName("특정 레포지토리에 PR이 존재하는지 확인 시") + class context_verify_pr { + LocalDate targetDate; + + @BeforeEach + void setup() { + targetDate = LocalDate.of(2024, 3, 5); + user.updateGithubPersonalToken(encryptUtil.encrypt(githubToken)); + } + + @Nested + @DisplayName("PR 확인 시") + class describe_check_pr { + @Test + @DisplayName("존재하지 않는다면 GITHUB_PR_NOT_FOUND 예외가 발생한다.") + public void it_throws_GITHUB_PR_NOT_FOUND_exception() { + assertThatThrownBy(() -> githubFacade.verifyPullRequest(user, targetRepo, targetDate)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.GITHUB_PR_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("특정 일자에 PR이 존재한다면 결과를 List로 반환한다.") + public void it_returns_list_if_pr_exist() { + targetDate = LocalDate.of(2024, 2, 5); + + //when + List pullRequestResponses = githubFacade.verifyPullRequest(user, targetRepo, + targetDate); + + //then + assertThat(pullRequestResponses.size()).isNotZero(); + + } + } + } +} diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java deleted file mode 100644 index f8f437f4..00000000 --- a/src/test/java/com/genius/gitget/challenge/certification/service/GithubProviderTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.genius.gitget.challenge.certification.service; - -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_ID_INCORRECT; -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_REPOSITORY_INCORRECT; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.genius.gitget.global.util.exception.BusinessException; -import java.io.IOException; -import java.time.LocalDate; -import java.util.List; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.kohsuke.github.GHPullRequest; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHub; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles({"github"}) -class GithubProviderTest { - @Autowired - private GithubProvider githubProvider; - - @Value("${github.yeon-personalKey}") - private String personalKey; - - @Value("${github.yeon-githubId}") - private String githubId; - - @Value("${github.yeon-repository}") - private String repository; - - @Test - @DisplayName("정상적인 github token을 전달받았을 때, API를 통해 GitHub 객체를 반환받을 수 있다.") - public void should_returnGitHubInstance_when_passValidToken() { - //given - - //when - GitHub gitHub = githubProvider.getGithubConnection(personalKey); - - //then - assertThat(gitHub).isNotNull(); - } - - @Test - @DisplayName("github token을 전달받았을 때, 사용자가 소셜로그인할 때 사용했던 깃허브 계정 아이디와 일치한다면 연결 성공으로 간주한다.") - public void should_checkConnection_when_passPersonalToken() { - //given - GitHub gitHub = getGitHub(); - - //when - githubProvider.validateGithubConnection(gitHub, githubId); - } - - @Test - @DisplayName("github token을 전달받았을 때, 소셜로그인 깃허브 계정 아이디와 일치하지 않는다면 예외가 발생한다.") - public void should_throwException_when_idIncorrect() { - //given - GitHub gitHub = getGitHub(); - String githubId = "fake Id"; - - //when & then - assertThatThrownBy(() -> githubProvider.validateGithubConnection(gitHub, githubId)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(GITHUB_ID_INCORRECT.getMessage()); - } - - @Test - @DisplayName("특정 사용자의 특정 repository가 연결됨을 검증할 수 있다.") - public void should_findRepository_when_passToken() throws IOException { - // given - GitHub gitHub = getGitHub(); - - //when & then - githubProvider.validateGithubRepository(gitHub, githubId + "/" + repository); - } - - @Test - @DisplayName("전달받은 Repository명이 명확하지 않는다면 예외가 발생한다.") - public void should_throwException_when_repositoryNameInvalid() { - //given - GitHub gitHub = getGitHub(); - String repositoryName = "fake repository"; - - //when & then - assertThatThrownBy(() -> githubProvider.validateGithubRepository(gitHub, repositoryName)) - .isInstanceOf(BusinessException.class); - } - - @Test - @DisplayName("해당 레포지토리에 있는 PR을 확인할 수 있다.") - public void should_checkPR_when_validRepo() { - //given - GitHub gitHub = getGitHub(); - LocalDate createdAt = LocalDate.of(2024, 2, 5); - - //when - List pullRequest = githubProvider.getPullRequestByDate(gitHub, repository, createdAt); - - //then - assertThat(pullRequest.size()).isEqualTo(1); - } - - @Test - @DisplayName("특정 레포지토리에 연결이 되지 않으면 예외를 발생한다.") - public void should_throwException_when_repoConnectionInvalid() { - //given - GitHub gitHub = getGitHub(); - String repositoryName = "Fake"; - LocalDate createdAt = LocalDate.of(2024, 2, 5); - - //when & then - assertThatThrownBy(() -> githubProvider.getPullRequestByDate(gitHub, repositoryName, createdAt)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(GITHUB_REPOSITORY_INCORRECT.getMessage()); - } - - @Test - @DisplayName("사용자가 가지고 있는 레포지토리 리스트들을 반환할 수 있다.") - public void should_returnRepositories() { - //given - GitHub gitHub = getGitHub(); - - //when - List repositoryList = githubProvider.getRepositoryList(gitHub); - - //then - assertThat(repositoryList.size()).isGreaterThan(0); - } - - @Test - @DisplayName("Pr 인증을 시도 했을 때, KST 기준으로 생성된 PR 리스트를 불러올 수 있다.") - public void should_searchPR_when_tryToCertificate() { - //given - GitHub gitHub = getGitHub(); - LocalDate kstDate = LocalDate.of(2024, 2, 25); - - //when - List pullRequests = githubProvider.getPullRequestByDate(gitHub, repository, kstDate); - - //then - assertThat(pullRequests.size()).isEqualTo(2); - } - - private GitHub getGitHub() { - return githubProvider.getGithubConnection(personalKey); - } -} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java b/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java index 45d72a60..2e67c65c 100644 --- a/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/certification/service/GithubServiceTest.java @@ -1,269 +1,152 @@ package com.genius.gitget.challenge.certification.service; +import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_ID_INCORRECT; import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_REPOSITORY_INCORRECT; -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.genius.gitget.challenge.certification.domain.CertificateStatus; -import com.genius.gitget.challenge.certification.domain.Certification; -import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; -import com.genius.gitget.challenge.certification.repository.CertificationRepository; -import com.genius.gitget.challenge.certification.util.EncryptUtil; -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.participant.domain.JoinResult; -import com.genius.gitget.challenge.participant.domain.JoinStatus; -import com.genius.gitget.challenge.participant.domain.Participant; -import com.genius.gitget.challenge.participant.repository.ParticipantRepository; -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import java.io.IOException; import java.time.LocalDate; import java.util.List; -import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.test.context.ActiveProfiles; -@Slf4j @SpringBootTest -@Transactional +@ActiveProfiles({"github"}) class GithubServiceTest { - @Autowired - private EncryptUtil encryptUtil; @Autowired private GithubService githubService; - @Autowired - private UserRepository userRepository; - @Autowired - private InstanceRepository instanceRepository; - @Autowired - private ParticipantRepository participantRepository; - @Autowired - private CertificationRepository certificationRepository; @Value("${github.yeon-personalKey}") private String personalKey; + @Value("${github.yeon-githubId}") private String githubId; + @Value("${github.yeon-repository}") - private String targetRepo; + private String repository; @Test - @DisplayName("Github personal access token 연결에 이상이 없다면, 암호화하여 User 엔티티에 저장한다.") - public void should_updateTokenInfo_when_tokenValid() { + @DisplayName("정상적인 github token을 전달받았을 때, API를 통해 GitHub 객체를 반환받을 수 있다.") + public void should_returnGitHubInstance_when_passValidToken() { //given - User user = getSavedUser(githubId); - String encrypted = encryptUtil.encrypt(personalKey); //when - githubService.registerGithubPersonalToken(user, personalKey); - User updatedUser = userRepository.findByIdentifier(githubId).get(); + GitHub gitHub = githubService.getGithubConnection(personalKey); //then - assertThat(updatedUser.getGithubToken()).isEqualTo(encrypted); + assertThat(gitHub).isNotNull(); } @Test - @DisplayName("Github personal access token과 소셜로그인 시의 계정이 일치하지 않으면 예외를 발생시킨다.") - public void should_throwException_when_accountIncorrect() { + @DisplayName("github token을 전달받았을 때, 사용자가 소셜로그인할 때 사용했던 깃허브 계정 아이디와 일치한다면 연결 성공으로 간주한다.") + public void should_checkConnection_when_passPersonalToken() { //given - User user = getSavedUser("incorrect Id"); - encryptUtil.encrypt(personalKey); + GitHub gitHub = getGitHub(); - //when & then - assertThatThrownBy(() -> githubService.registerGithubPersonalToken(user, personalKey)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.GITHUB_ID_INCORRECT.getMessage()); + //when + githubService.validateGithubConnection(gitHub, githubId); } @Test - @DisplayName("repository 이름을 전달했을 때, 해당 깃허브 계정에 레포지토리가 있어야 한다.") - public void should_repositoryExist_when_passRepositoryName() { + @DisplayName("github token을 전달받았을 때, 소셜로그인 깃허브 계정 아이디와 일치하지 않는다면 예외가 발생한다.") + public void should_throwException_when_idIncorrect() { //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - githubService.registerGithubPersonalToken(user, personalKey); - - //when - githubService.verifyRepository(user, targetRepo); + GitHub gitHub = getGitHub(); + String githubId = "fake Id"; + //when & then + assertThatThrownBy(() -> githubService.validateGithubConnection(gitHub, githubId)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(GITHUB_ID_INCORRECT.getMessage()); } @Test - @DisplayName("repository 등록 시, 해당 레포지토리가 없다면 예외가 발생해야 한다.") - public void should_throw_Exception_when_thereIsNoRepository() { - //given - String fakeRepositoryName = "Fake"; - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - githubService.registerGithubPersonalToken(user, personalKey); + @DisplayName("특정 사용자의 특정 repository가 연결됨을 검증할 수 있다.") + public void should_findRepository_when_passToken() throws IOException { + // given + GitHub gitHub = getGitHub(); //when & then - assertThatThrownBy(() -> githubService.verifyRepository(user, fakeRepositoryName)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(GITHUB_REPOSITORY_INCORRECT.getMessage()); + githubService.validateGithubRepository(gitHub, githubId + "/" + repository); } @Test - @DisplayName("repository 등록 시, 사용자에게 Github token이 저장되어있지 않다면 예외가 발생해야 한다.") - public void should_throwException_when_userDontHaveToken() { + @DisplayName("전달받은 Repository명이 명확하지 않는다면 예외가 발생한다.") + public void should_throwException_when_repositoryNameInvalid() { //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(); - Participant participant = getParticipantInfo(user, instance); + GitHub gitHub = getGitHub(); + String repositoryName = "fake repository"; //when & then - assertThatThrownBy(() -> githubService.verifyRepository(user, targetRepo)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + assertThatThrownBy(() -> githubService.validateGithubRepository(gitHub, repositoryName)) + .isInstanceOf(BusinessException.class); } @Test - @DisplayName("특정 일자에 PR이 존재하지 않는다면 빈 리스트를 반환한다.") - public void should_returnEmptyList_when_prNotExist() throws IOException { + @DisplayName("해당 레포지토리에 있는 PR을 확인할 수 있다.") + public void should_checkPR_when_validRepo() { //given - User user = getSavedUser(githubId); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 1, 4); + GitHub gitHub = getGitHub(); + LocalDate createdAt = LocalDate.of(2024, 2, 5); //when - List pullRequestResponses = githubService.getPullRequestListByDate( - user, targetRepo, targetDate); + List pullRequest = githubService.getPullRequestByDate(gitHub, repository, createdAt); //then - assertThat(pullRequestResponses.size()).isEqualTo(0); - } - - @Test - @DisplayName("사용자의 github token이 저장되어있지 않을 때 예외가 발생해야 한다.") - public void should_throwException_when_githubTokenNotSaved() { - //given - LocalDate targetDate = LocalDate.of(2024, 2, 5); - User user = getSavedUser(githubId); - - //when & then - assertThatThrownBy(() -> githubService.getPullRequestListByDate(user, targetRepo, targetDate)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); + assertThat(pullRequest.size()).isEqualTo(1); } @Test - @DisplayName("repository가 등록되어있지 않을 때 예외가 발생해야 한다.") - public void should_throwException_when_repositoryNotRegistered() { + @DisplayName("특정 레포지토리에 연결이 되지 않으면 예외를 발생한다.") + public void should_throwException_when_repoConnectionInvalid() { //given - LocalDate targetDate = LocalDate.of(2024, 2, 5); - User user = getSavedUser(githubId); - String fakeRepo = "fake Repo"; - githubService.registerGithubPersonalToken(user, personalKey); + GitHub gitHub = getGitHub(); + String repositoryName = "Fake"; + LocalDate createdAt = LocalDate.of(2024, 2, 5); //when & then - assertThatThrownBy(() -> githubService.getPullRequestListByDate(user, fakeRepo, targetDate)) + assertThatThrownBy(() -> githubService.getPullRequestByDate(gitHub, repositoryName, createdAt)) .isInstanceOf(BusinessException.class) .hasMessageContaining(GITHUB_REPOSITORY_INCORRECT.getMessage()); } @Test - @DisplayName("특정 레포지토리에 특정 날짜에 생성된 PR 목록을 불러올 수 있다.") - public void should_loadPRList_when_tryJoin() { + @DisplayName("사용자가 가지고 있는 레포지토리 리스트들을 반환할 수 있다.") + public void should_returnRepositories() { //given - User user = getSavedUser(githubId); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 2, 5); + GitHub gitHub = getGitHub(); //when - List pullRequestResponses = githubService.getPullRequestListByDate( - user, targetRepo, targetDate); + List repositoryList = githubService.getRepositoryList(gitHub); //then - assertThat(pullRequestResponses.size()).isEqualTo(1); - log.info(pullRequestResponses.get(0).toString()); + assertThat(repositoryList.size()).isGreaterThan(0); } @Test - @DisplayName("특정 일자에 특정 브랜치에 PR이 있는지 확인할 수 있다.") - public void should_checkPR_when_tryToVerify() { + @DisplayName("Pr 인증을 시도 했을 때, KST 기준으로 생성된 PR 리스트를 불러올 수 있다.") + public void should_searchPR_when_tryToCertificate() { //given - User user = getSavedUser(githubId); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 2, 5); + GitHub gitHub = getGitHub(); + LocalDate kstDate = LocalDate.of(2024, 2, 25); //when - List pullRequestResponses = githubService.verifyPullRequest(user, targetRepo, targetDate); + List pullRequests = githubService.getPullRequestByDate(gitHub, repository, kstDate); //then - assertThat(pullRequestResponses.size()).isNotZero(); - } - - @Test - @DisplayName("PR 검증을 요청했을 때, PR이 해당 브랜치에 존재하지 않는다면 예외를 발생한다.") - public void should_throwException_when_PRNotExist() { - //given - User user = getSavedUser(githubId); - githubService.registerGithubPersonalToken(user, personalKey); - - LocalDate targetDate = LocalDate.of(2024, 3, 5); - - //when & then - assertThatThrownBy(() -> githubService.verifyPullRequest(user, targetRepo, targetDate)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.GITHUB_PR_NOT_FOUND.getMessage()); - } - - - private User getSavedUser(String githubId) { - return userRepository.save( - User.builder() - .role(Role.USER) - .nickname("nickname") - .providerInfo(ProviderInfo.GITHUB) - .identifier(githubId) - .information("information") - .tags("BE,FE") - .build() - ); - } - - private Instance getSavedInstance() { - return instanceRepository.save( - Instance.builder() - .progress(Progress.PREACTIVITY) - .build() - ); - } - - private Participant getParticipantInfo(User user, Instance instance) { - Participant participant = participantRepository.save( - Participant.builder() - .joinResult(JoinResult.PROCESSING) - .joinStatus(JoinStatus.YES) - .build() - ); - participant.setUserAndInstance(user, instance); - - return participant; + assertThat(pullRequests.size()).isEqualTo(2); } - private Certification getSavedCertification(CertificateStatus status, LocalDate certificatedAt, - Participant participant) { - Certification certification = Certification.builder() - .certificationStatus(status) - .certificatedAt(certificatedAt) - .certificationLinks("certificationLink") - .build(); - certification.setParticipant(participant); - return certificationRepository.save(certification); + private GitHub getGitHub() { + return githubService.getGithubConnection(personalKey); } } \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 38a34262..82be2061 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.genius.gitget.challenge.certification.service.GithubService; +import com.genius.gitget.challenge.certification.facade.GithubFacade; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; @@ -49,7 +49,7 @@ class InstanceDetailServiceTest { @Autowired ParticipantService participantService; @Autowired - GithubService githubService; + GithubFacade githubFacade; @Autowired UserRepository userRepository; @Autowired @@ -361,7 +361,7 @@ private User getSavedUser(String githubId) { .tags("BE,FE") .build() ); - githubService.registerGithubPersonalToken(user, githubToken); + githubFacade.registerGithubPersonalToken(user, githubToken); return user; } diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java index 1f446e8c..0bd5716a 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java @@ -4,8 +4,8 @@ import com.genius.gitget.challenge.certification.domain.CertificateStatus; import com.genius.gitget.challenge.certification.domain.Certification; +import com.genius.gitget.challenge.certification.facade.GithubFacade; import com.genius.gitget.challenge.certification.repository.CertificationRepository; -import com.genius.gitget.challenge.certification.service.GithubService; import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; @@ -43,7 +43,7 @@ class ProgressServiceTest { @Autowired private ProgressService scheduleService; @Autowired - private GithubService githubService; + private GithubFacade githubFacade; @Autowired private InstanceRepository instanceRepository; @Autowired @@ -76,7 +76,7 @@ public void should_updateToActivity_when_conditionMatches() { getSavedInstance(startedDate, completedDate); getSavedInstance(startedDate, completedDate); - githubService.registerGithubPersonalToken(user, personalKey); + githubFacade.registerGithubPersonalToken(user, personalKey); instanceDetailService.joinNewChallenge( user, JoinRequest.builder() @@ -115,7 +115,7 @@ public void should_updateToActivity_when_dDay() { getSavedInstance(startedDate, completedDate); getSavedInstance(startedDate, completedDate); - githubService.registerGithubPersonalToken(user, personalKey); + githubFacade.registerGithubPersonalToken(user, personalKey); instanceDetailService.joinNewChallenge( user, JoinRequest.builder() diff --git a/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java b/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java index 8adc887e..118c559c 100644 --- a/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java +++ b/src/test/java/com/genius/gitget/util/instance/InstanceFactory.java @@ -2,9 +2,18 @@ import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; +import java.time.LocalDate; import java.time.LocalDateTime; public class InstanceFactory { + public static Instance createByInfo(LocalDate started, Progress progress) { + return Instance.builder() + .progress(progress) + .startedDate(started.atTime(0, 0)) + .completedDate(started.plusDays(10).atTime(1, 0)) + .build(); + } + /** * LocalDate.now()를 기준으로 PREACTIVITY(시작 전) 인스턴스 생성 후 반환 */ diff --git a/src/test/java/com/genius/gitget/util/user/UserFactory.java b/src/test/java/com/genius/gitget/util/user/UserFactory.java index 7111e8da..06ba2d76 100644 --- a/src/test/java/com/genius/gitget/util/user/UserFactory.java +++ b/src/test/java/com/genius/gitget/util/user/UserFactory.java @@ -6,6 +6,16 @@ public class UserFactory { + public static User createByInfo(String identifier, Role role) { + return User.builder() + .role(role) + .providerInfo(ProviderInfo.GITHUB) + .identifier(identifier) + .information("information") + .tags("BE,FE") + .build(); + } + public static User createUser() { return User.builder() .role(Role.USER) From 8eb76485e804bb6b9c551fd71c0ada81156e896f Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Wed, 14 Aug 2024 13:27:18 +0900 Subject: [PATCH 214/234] =?UTF-8?q?feat:=20ProfileController=20Facade=20&?= =?UTF-8?q?=20DCI=20=ED=8C=A8=ED=84=B4=20(#243)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ProfileService 제거 - ProfileFacade & ProfileFacadeService 구현 - 테스트 코드 DCI 패턴 도입 --- .../challenge/user/service/UserService.java | 22 ++ .../profile/controller/ProfileController.java | 20 +- .../gitget/profile/service/ProfileFacade.java | 29 +++ ...Service.java => ProfileFacadeService.java} | 92 +++----- .../profile/service/ProfileServiceTest.java | 204 +++++++++++------- 5 files changed, 222 insertions(+), 145 deletions(-) create mode 100644 src/main/java/com/genius/gitget/profile/service/ProfileFacade.java rename src/main/java/com/genius/gitget/profile/service/{ProfileService.java => ProfileFacadeService.java} (71%) diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 40192a2c..c6498440 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -15,6 +15,9 @@ import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.signout.Signout; +import com.genius.gitget.signout.SignoutRepository; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.service.OrdersService; import java.util.List; @@ -33,6 +36,7 @@ public class UserService { private final OrdersService ordersService; private final FilesService filesService; private final EncryptUtil encryptUtil; + private final SignoutRepository signoutRepository; @Value("${admin.githubId}") private List adminIds; @@ -53,6 +57,24 @@ public Long save(User user) { return userRepository.saveAndFlush(user).getId(); } + + public void delete(Long userId, String identifier, String reason) { + userRepository.deleteById(userId); + signoutRepository.save( + Signout.builder() + .identifier(identifier) + .reason(reason) + .build()); + } + + // 포인트 조회 + public Long getUserPoint(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + + return user.getPoint(); + } + @Transactional public Long signup(SignupRequest requestUser) { User user = findUserByIdentifier(requestUser.identifier()); diff --git a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java index 3b6ff110..0be708a9 100644 --- a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java +++ b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java @@ -15,7 +15,7 @@ import com.genius.gitget.profile.dto.UserInterestUpdateRequest; import com.genius.gitget.profile.dto.UserPointResponse; import com.genius.gitget.profile.dto.UserSignoutRequest; -import com.genius.gitget.profile.service.ProfileService; +import com.genius.gitget.profile.service.ProfileFacade; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -30,13 +30,13 @@ @RequiredArgsConstructor @RequestMapping("/api/profile") public class ProfileController { - private final ProfileService profileService; + private final ProfileFacade profileFacade; // 마이페이지 - 사용자 상세 정보 조회 @GetMapping public ResponseEntity> getUserDetailsInformation( @AuthenticationPrincipal UserPrincipal userPrincipal) { - UserDetailsInformationResponse userInformation = profileService.getUserDetailsInformation( + UserDetailsInformationResponse userInformation = profileFacade.getUserDetailsInformation( userPrincipal.getUser()); return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), @@ -48,7 +48,7 @@ public ResponseEntity> getUserDet @PostMapping public ResponseEntity> getUserInformation( @RequestBody UserInformationRequest userInformationRequest) { - UserInformationResponse userInformation = profileService.getUserInformation(userInformationRequest.getUserId()); + UserInformationResponse userInformation = profileFacade.getUserInformation(userInformationRequest.getUserId()); return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userInformation) @@ -61,7 +61,7 @@ public ResponseEntity> updateUserInformation( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody UserInformationUpdateRequest userInformationUpdateRequest) { - Long userId = profileService.updateUserInformation(userPrincipal.getUser(), userInformationUpdateRequest); + Long userId = profileFacade.updateUserInformation(userPrincipal.getUser(), userInformationUpdateRequest); UserIndexResponse userIndexResponse = new UserIndexResponse(userId); return ResponseEntity.ok().body( @@ -73,7 +73,7 @@ public ResponseEntity> updateUserInformation( @GetMapping("/interest") public ResponseEntity> getUserInterest( @AuthenticationPrincipal UserPrincipal userPrincipal) { - UserInterestResponse userInterest = profileService.getUserInterest(userPrincipal.getUser()); + UserInterestResponse userInterest = profileFacade.getUserInterest(userPrincipal.getUser()); return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), @@ -85,7 +85,7 @@ public ResponseEntity> getUserInterest( @PostMapping("/interest") public ResponseEntity updateUserTags(@AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody UserInterestUpdateRequest userInterestUpdateRequest) { - profileService.updateUserTags(userPrincipal.getUser(), userInterestUpdateRequest); + profileFacade.updateUserTags(userPrincipal.getUser(), userInterestUpdateRequest); return ResponseEntity.ok() .body(new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage())); @@ -96,7 +96,7 @@ public ResponseEntity updateUserTags(@AuthenticationPrincipal Us @GetMapping("/challenges") public ResponseEntity> getUserChallengeResult( @AuthenticationPrincipal UserPrincipal userPrincipal) { - UserChallengeResultResponse userChallengeResult = profileService.getUserChallengeResult( + UserChallengeResultResponse userChallengeResult = profileFacade.getUserChallengeResult( userPrincipal.getUser()); return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), @@ -108,7 +108,7 @@ public ResponseEntity> getUserChalle @DeleteMapping public ResponseEntity deleteUserInformation(@AuthenticationPrincipal UserPrincipal userPrincipal, @RequestBody UserSignoutRequest userSignoutRequest) { - profileService.deleteUserInformation(userPrincipal.getUser(), userSignoutRequest.getReason()); + profileFacade.deleteUserInformation(userPrincipal.getUser(), userSignoutRequest.getReason()); return ResponseEntity.ok() .body(new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage())); @@ -119,7 +119,7 @@ public ResponseEntity deleteUserInformation(@AuthenticationPrinc @GetMapping("/point") public ResponseEntity> getUserPoint( @AuthenticationPrincipal UserPrincipal userPrincipal) { - UserPointResponse userPoint = profileService.getUserPoint(userPrincipal.getUser()); + UserPointResponse userPoint = profileFacade.getUserPoint(userPrincipal.getUser()); return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileFacade.java b/src/main/java/com/genius/gitget/profile/service/ProfileFacade.java new file mode 100644 index 00000000..bd3ecef5 --- /dev/null +++ b/src/main/java/com/genius/gitget/profile/service/ProfileFacade.java @@ -0,0 +1,29 @@ +package com.genius.gitget.profile.service; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.profile.dto.UserChallengeResultResponse; +import com.genius.gitget.profile.dto.UserDetailsInformationResponse; +import com.genius.gitget.profile.dto.UserInformationResponse; +import com.genius.gitget.profile.dto.UserInformationUpdateRequest; +import com.genius.gitget.profile.dto.UserInterestResponse; +import com.genius.gitget.profile.dto.UserInterestUpdateRequest; +import com.genius.gitget.profile.dto.UserPointResponse; + +public interface ProfileFacade { + public UserPointResponse getUserPoint(User user); + + public UserInformationResponse getUserInformation(Long userId); + + public UserDetailsInformationResponse getUserDetailsInformation(User user); + + public Long updateUserInformation(User user, UserInformationUpdateRequest userInformationUpdateRequest); + + public void deleteUserInformation(User user, String reason); + + public void updateUserTags(User user, UserInterestUpdateRequest userInterestUpdateRequest); + + public UserInterestResponse getUserInterest(User user); + + public UserChallengeResultResponse getUserChallengeResult(User user); + +} diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileService.java b/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java similarity index 71% rename from src/main/java/com/genius/gitget/profile/service/ProfileService.java rename to src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java index 9e90f9dc..d019a424 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java @@ -9,11 +9,10 @@ import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.profile.dto.UserChallengeResultResponse; import com.genius.gitget.profile.dto.UserDetailsInformationResponse; import com.genius.gitget.profile.dto.UserInformationResponse; @@ -21,48 +20,47 @@ import com.genius.gitget.profile.dto.UserInterestResponse; import com.genius.gitget.profile.dto.UserInterestUpdateRequest; import com.genius.gitget.profile.dto.UserPointResponse; -import com.genius.gitget.signout.Signout; -import com.genius.gitget.signout.SignoutRepository; import com.genius.gitget.store.item.service.OrdersService; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Slf4j -@Transactional(readOnly = true) -public class ProfileService { - private final UserRepository userRepository; - private final FilesService filesService; - private final SignoutRepository signoutRepository; +import org.springframework.stereotype.Component; + +@Component +public class ProfileFacadeService implements ProfileFacade { + private final UserService userService; private final OrdersService ordersService; + private final FilesService filesService; - // 포인트 조회 + public ProfileFacadeService(UserService userService, OrdersService ordersService, + FilesService filesService) { + this.userService = userService; + this.ordersService = ordersService; + this.filesService = filesService; + } + + @Override public UserPointResponse getUserPoint(User user) { + Long userPoint = userService.getUserPoint(user.getId()); return UserPointResponse.builder() .identifier(user.getIdentifier()) - .point(user.getPoint()) + .point(userPoint) .build(); } - // 사용자 정보 조회 + @Override public UserInformationResponse getUserInformation(Long userId) { - User findUser = getUserById(userId); + User findUser = userService.findUserById(userId); Long frameId = ordersService.getUsingFrameItem(userId).getId(); - FileResponse fileResponse = filesService.convertToFileResponse(findUser.getFiles()); return UserInformationResponse.createByEntity(findUser, frameId, fileResponse); } - // 마이페이지 - 사용자 정보 상세 조회 + @Override public UserDetailsInformationResponse getUserDetailsInformation(User user) { - User findUser = getUserByIdentifier(user.getIdentifier()); + User findUser = userService.findUserByIdentifier(user.getIdentifier()); + int participantCount = 0; List participantInfoList = findUser.getParticipantList(); @@ -76,49 +74,39 @@ public UserDetailsInformationResponse getUserDetailsInformation(User user) { return UserDetailsInformationResponse.createByEntity(findUser, participantCount, fileResponse); } - // 마이페이지 - 사용자 정보 수정 - @Transactional + @Override public Long updateUserInformation(User user, UserInformationUpdateRequest userInformationUpdateRequest) { - User findUser = getUserByIdentifier(user.getIdentifier()); + User findUser = userService.findUserByIdentifier(user.getIdentifier()); findUser.updateUserInformation( userInformationUpdateRequest.getNickname(), userInformationUpdateRequest.getInformation()); - User updatedUser = userRepository.save(findUser); - return updatedUser.getId(); + return userService.save(findUser); } - // 마이페이지 - 회원 탈퇴 - @Transactional + @Override public void deleteUserInformation(User user, String reason) { - User findUser = getUserByIdentifier(user.getIdentifier()); + User findUser = userService.findUserByIdentifier(user.getIdentifier()); filesService.deleteFile(findUser.getFiles()); findUser.setFiles(null); - findUser.deleteLikesList(); - userRepository.deleteById(findUser.getId()); - signoutRepository.save( - Signout.builder() - .identifier(user.getIdentifier()) - .reason(reason) - .build() - ); + + userService.delete(findUser.getId(), user.getIdentifier(), reason); } - // 마이페이지 - 관심사 수정 - @Transactional + @Override public void updateUserTags(User user, UserInterestUpdateRequest userInterestUpdateRequest) { if (userInterestUpdateRequest.getTags() == null) { throw new BusinessException(); } - User findUser = getUserByIdentifier(user.getIdentifier()); + User findUser = userService.findUserByIdentifier(user.getIdentifier()); String interest = String.join(",", userInterestUpdateRequest.getTags()); findUser.updateUserTags(interest); - userRepository.save(findUser); + userService.save(findUser); } - // 관심사 조회 + @Override public UserInterestResponse getUserInterest(User user) { String tags = user.getTags(); String[] tagsList = tags.split(","); @@ -131,9 +119,9 @@ public UserInterestResponse getUserInterest(User user) { .build(); } - // 마이페이지 - 챌린지 현황 + @Override public UserChallengeResultResponse getUserChallengeResult(User user) { - User findUser = getUserByIdentifier(user.getIdentifier()); + User findUser = userService.findUserByIdentifier(user.getIdentifier()); HashMap> participantHashMap = new HashMap<>() { { put(READY, new ArrayList<>()); @@ -166,14 +154,4 @@ public UserChallengeResultResponse getUserChallengeResult(User user) { .processing(processing) .build(); } - - private User getUserByIdentifier(String identifier) { - return userRepository.findByIdentifier(identifier) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - } - - private User getUserById(Long userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - } } diff --git a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java index 6974a374..0a09c914 100644 --- a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java +++ b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java @@ -3,10 +3,6 @@ import static com.genius.gitget.global.security.constants.ProviderInfo.GITHUB; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.genius.gitget.signout.Signout; -import com.genius.gitget.signout.SignoutRepository; -import com.genius.gitget.topic.domain.Topic; -import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; @@ -26,12 +22,18 @@ import com.genius.gitget.profile.dto.UserInterestResponse; import com.genius.gitget.profile.dto.UserInterestUpdateRequest; import com.genius.gitget.profile.dto.UserPointResponse; +import com.genius.gitget.signout.Signout; +import com.genius.gitget.signout.SignoutRepository; +import com.genius.gitget.topic.domain.Topic; +import com.genius.gitget.topic.repository.TopicRepository; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.assertj.core.api.Assertions; 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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -42,9 +44,7 @@ @Transactional @Rollback public class ProfileServiceTest { - static User user1, user2; - static Topic topic1; - static Instance instance1, instance2, instance3; + @Autowired UserRepository userRepository; @Autowired @@ -56,10 +56,14 @@ public class ProfileServiceTest { @Autowired LikesService likesService; @Autowired - ProfileService profileService; + ProfileFacade profileFacade; @Autowired SignoutRepository signoutRepository; + static User user1, user2; + static Topic topic1; + static Instance instance1, instance2, instance3; + @BeforeEach void setup() { user1 = getSavedUser("neo5188@gmail.com", GITHUB, "alias1"); @@ -84,100 +88,144 @@ void setup() { likesRepository.save(likes3); } - // TODO 챌린지 현황 조회 -> 코드 병합 후 테스트할 것 + @Nested + @DisplayName("유저 상세 정보 조회") + class Describe_getUserDetailsInformation { - @Test - void 유저_상세_조회() { - UserDetailsInformationResponse userDetailsInformation = profileService.getUserDetailsInformation(user1); - Assertions.assertThat(userDetailsInformation.getIdentifier()).isEqualTo("neo5188@gmail.com"); + @Test + @DisplayName("유저의 상세 정보를 반환한다.") + void it_returns_user_details_information() { + UserDetailsInformationResponse userDetailsInformation = profileFacade.getUserDetailsInformation(user1); + Assertions.assertThat(userDetailsInformation.getIdentifier()).isEqualTo("neo5188@gmail.com"); + } } - @Test - void 유저_조회() { - List userIdList = new ArrayList<>(); - List all = userRepository.findAll(); - for (User user : all) { - Long id = user.getId(); - userIdList.add(id); - } + @Nested + @DisplayName("유저 정보 조회") + class Describe_getUserInformation { - for (int i = userIdList.size() - 1; i >= 0; i--) { - UserInformationResponse userInformation = profileService.getUserInformation(userIdList.get(i)); - Assertions.assertThat(userInformation.getNickname()).isEqualTo("alias" + (i + 1)); + @Test + @DisplayName("유저의 정보를 반환한다.") + void it_returns_user_information() { + List userIdList = new ArrayList<>(); + List all = userRepository.findAll(); + for (User user : all) { + Long id = user.getId(); + userIdList.add(id); + } + + for (int i = userIdList.size() - 1; i >= 0; i--) { + UserInformationResponse userInformation = profileFacade.getUserInformation(userIdList.get(i)); + Assertions.assertThat(userInformation.getNickname()).isEqualTo("alias" + (i + 1)); + } } } - @Test - void 유저_정보_수정() { - profileService.updateUserInformation(user1, - UserInformationUpdateRequest.builder() - .nickname("수정된 nickname") - .information("수정된 information") - .build()); + @Nested + @DisplayName("유저 정보 수정") + class Describe_updateUserInformation { + + @Test + @DisplayName("유저의 정보를 수정한다.") + void it_updates_user_information() { + profileFacade.updateUserInformation(user1, + UserInformationUpdateRequest.builder() + .nickname("수정된 nickname") + .information("수정된 information") + .build()); - User user = userRepository.findByIdentifier(user1.getIdentifier()) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - Assertions.assertThat(user.getNickname()).isEqualTo("수정된 nickname"); + Assertions.assertThat(user.getNickname()).isEqualTo("수정된 nickname"); + } } - @Test - void 유저_관심사_조회() { - UserInterestResponse userInterest = profileService.getUserInterest(user1); - List tags = userInterest.getTags(); - String join = String.join(",", tags); - Assertions.assertThat(join).isEqualTo("BE,FE"); + @Nested + @DisplayName("유저 관심사 조회") + class Describe_getUserInterest { + + @Test + @DisplayName("유저의 관심사를 반환한다.") + void it_returns_user_interest() { + UserInterestResponse userInterest = profileFacade.getUserInterest(user1); + List tags = userInterest.getTags(); + String join = String.join(",", tags); + Assertions.assertThat(join).isEqualTo("BE,FE"); + } } - @Test - void 유저_관심사_수정() { - profileService.updateUserTags(user1, - UserInterestUpdateRequest.builder().tags(new ArrayList<>(Arrays.asList("FE", "BE"))).build()); - User user = userRepository.findByIdentifier(user1.getIdentifier()) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - Assertions.assertThat(user.getTags()).isEqualTo("FE,BE"); + @Nested + @DisplayName("유저 관심사 수정") + class Describe_updateUserTags { + + @Test + @DisplayName("유저의 관심사를 수정한다.") + void it_updates_user_tags() { + profileFacade.updateUserTags(user1, + UserInterestUpdateRequest.builder().tags(new ArrayList<>(Arrays.asList("FE", "BE"))).build()); + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + Assertions.assertThat(user.getTags()).isEqualTo("FE,BE"); + } } + @Nested + @DisplayName("회원 탈퇴") + class Describe_deleteUserInformation { - @Test - void 회원_탈퇴() { - User user = userRepository.findByIdentifier(user1.getIdentifier()) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - String userIdentifier = user.getIdentifier(); - profileService.deleteUserInformation(user, "서비스 이용 불편"); + @Test + @DisplayName("유저의 정보를 삭제한다.") + void it_deletes_user_information() { + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + String userIdentifier = user.getIdentifier(); + profileFacade.deleteUserInformation(user, "서비스 이용 불편"); - assertThrows(BusinessException.class, - () -> userRepository.findByIdentifier(userIdentifier) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND))); + assertThrows(BusinessException.class, + () -> userRepository.findByIdentifier(userIdentifier) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND))); - Signout byIdentifier = signoutRepository.findByIdentifier(userIdentifier); + Signout byIdentifier = signoutRepository.findByIdentifier(userIdentifier); - Assertions.assertThat(byIdentifier.getReason()).isEqualTo("서비스 이용 불편"); + Assertions.assertThat(byIdentifier.getReason()).isEqualTo("서비스 이용 불편"); + } } - @Test - void 유저_포인트_조회() { - User user = userRepository.findByIdentifier(user1.getIdentifier()) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - user.updatePoints(1500L); - userRepository.save(user); - UserPointResponse userPoint = profileService.getUserPoint(user1); - Assertions.assertThat(userPoint.getPoint()).isEqualTo(1500); - } + @Nested + @DisplayName("유저 포인트 조회") + class Describe_getUserPoint { - @Test - void 챌린지_현황_조회() { - // TODO 챌린지 현황 조회 - User user = userRepository.findByIdentifier(user1.getIdentifier()) - .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); - - UserChallengeResultResponse userChallengeResult = profileService.getUserChallengeResult(user); - System.out.println(userChallengeResult.getBeforeStart()); - System.out.println(userChallengeResult.getProcessing()); - System.out.println(userChallengeResult.getFail()); - System.out.println(userChallengeResult.getSuccess()); + @Test + @DisplayName("유저의 포인트를 반환한다.") + void it_returns_user_point() { + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + user.updatePoints(1500L); + userRepository.save(user); + UserPointResponse userPoint = profileFacade.getUserPoint(user1); + Assertions.assertThat(userPoint.getPoint()).isEqualTo(1500); + } } + @Nested + @DisplayName("챌린지 현황 조회") + class Describe_getUserChallengeResult { + + @Test + @DisplayName("유저의 챌린지 현황을 반환한다.") + void it_returns_user_challenge_result() { + // TODO 챌린지 현황 조회 + User user = userRepository.findByIdentifier(user1.getIdentifier()) + .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND)); + + UserChallengeResultResponse userChallengeResult = profileFacade.getUserChallengeResult(user); + System.out.println(userChallengeResult.getBeforeStart()); + System.out.println(userChallengeResult.getProcessing()); + System.out.println(userChallengeResult.getFail()); + System.out.println(userChallengeResult.getSuccess()); + } + } private User getSavedUser(String identifier, ProviderInfo providerInfo, String nickname) { User user = userRepository.save( From 5ef6720ff1be9afd2d880c6c13be611842c43d43 Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Wed, 14 Aug 2024 13:27:30 +0900 Subject: [PATCH 215/234] =?UTF-8?q?feat:=20InstanceDetail=20Facade=20Patte?= =?UTF-8?q?rn=20=EC=A0=81=EC=9A=A9=20(#242)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InstanceDetailFacade & InstanceDetailFacadeService 생성 - InstanceDetailService 기능 상실 -> 제거 - 테스트 코드 DCI 도입 --- .../controller/CertificationController.java | 6 +- .../controller/InstanceDetailController.java | 10 +- .../service/InstanceDetailFacade.java | 14 + ....java => InstanceDetailFacadeService.java} | 68 +- .../instance/service/InstanceProvider.java | 28 - .../challenge/likes/service/LikesService.java | 12 + .../schedule/service/ProgressService.java | 10 +- .../service/InstanceDetailServiceTest.java | 686 ++++++++++-------- .../instance/service/ProgressServiceTest.java | 25 +- 9 files changed, 488 insertions(+), 371 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailFacade.java rename src/main/java/com/genius/gitget/challenge/instance/service/{InstanceDetailService.java => InstanceDetailFacadeService.java} (75%) delete mode 100644 src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index 4c5eff36..07f32680 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -11,7 +11,7 @@ import com.genius.gitget.challenge.certification.facade.CertificationFacade; import com.genius.gitget.challenge.certification.util.DateUtil; import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.service.InstanceProvider; +import com.genius.gitget.challenge.instance.service.InstanceService; import com.genius.gitget.challenge.myChallenge.dto.ActivatedResponse; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantService; @@ -43,8 +43,8 @@ @RequestMapping("/api/certification") public class CertificationController { private final UserService userService; + private final InstanceService instanceService; private final CertificationFacade certificationFacade; - private final InstanceProvider instanceProvider; private final ParticipantService participantService; @@ -139,7 +139,7 @@ public ResponseEntity> getCertification ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); - Instance instance = instanceProvider.findById(instanceId); + Instance instance = instanceService.findInstanceById(instanceId); Participant participant = participantService.findByJoinInfo( userPrincipal.getUser().getId(), instanceId); diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java index 7134a5ce..0dbaf390 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java @@ -8,7 +8,7 @@ import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; -import com.genius.gitget.challenge.instance.service.InstanceDetailService; +import com.genius.gitget.challenge.instance.service.InstanceDetailFacade; import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.time.LocalDate; @@ -30,7 +30,7 @@ @RequiredArgsConstructor @RequestMapping("/api/challenges") public class InstanceDetailController { - private final InstanceDetailService instanceDetailService; + private final InstanceDetailFacade instanceDetailFacade; @GetMapping("/{instanceId}") @@ -38,7 +38,7 @@ public ResponseEntity> getInstanceDetail( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long instanceId ) { - InstanceResponse instanceDetailInformation = instanceDetailService.getInstanceDetailInformation( + InstanceResponse instanceDetailInformation = instanceDetailFacade.getInstanceDetailInformation( userPrincipal.getUser(), instanceId); return ResponseEntity.ok().body( @@ -58,7 +58,7 @@ public ResponseEntity> joinChallenge( .repository(repo) .todayDate(kstDate) .build(); - JoinResponse joinResponse = instanceDetailService.joinNewChallenge(userPrincipal.getUser(), joinRequest); + JoinResponse joinResponse = instanceDetailFacade.joinNewChallenge(userPrincipal.getUser(), joinRequest); return ResponseEntity.ok().body( new SingleResponse<>(JOIN_SUCCESS.getStatus(), JOIN_SUCCESS.getMessage(), joinResponse) @@ -70,7 +70,7 @@ public ResponseEntity> quitChallenge( @AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long instanceId ) { - JoinResponse joinResponse = instanceDetailService.quitChallenge(userPrincipal.getUser(), instanceId); + JoinResponse joinResponse = instanceDetailFacade.quitChallenge(userPrincipal.getUser(), instanceId); return ResponseEntity.ok().body( new SingleResponse<>(QUIT_SUCCESS.getStatus(), QUIT_SUCCESS.getMessage(), joinResponse) diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailFacade.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailFacade.java new file mode 100644 index 00000000..f500cdd4 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailFacade.java @@ -0,0 +1,14 @@ +package com.genius.gitget.challenge.instance.service; + +import com.genius.gitget.challenge.instance.dto.detail.InstanceResponse; +import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; +import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; +import com.genius.gitget.challenge.user.domain.User; + +public interface InstanceDetailFacade { + InstanceResponse getInstanceDetailInformation(User user, Long instanceId); + + JoinResponse joinNewChallenge(User user, JoinRequest joinRequest); + + public JoinResponse quitChallenge(User user, Long instanceId); +} diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailFacadeService.java similarity index 75% rename from src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java rename to src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailFacadeService.java index f620a615..dbbac1f0 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceDetailFacadeService.java @@ -10,8 +10,7 @@ import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; import com.genius.gitget.challenge.instance.dto.detail.LikesInfo; -import com.genius.gitget.challenge.likes.domain.Likes; -import com.genius.gitget.challenge.likes.repository.LikesRepository; +import com.genius.gitget.challenge.likes.service.LikesService; import com.genius.gitget.challenge.participant.domain.JoinStatus; import com.genius.gitget.challenge.participant.domain.Participant; import com.genius.gitget.challenge.participant.service.ParticipantService; @@ -21,52 +20,55 @@ import com.genius.gitget.global.file.service.FilesService; import com.genius.gitget.global.util.exception.BusinessException; import java.time.LocalDate; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.kohsuke.github.GitHub; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class InstanceDetailService { - private final UserService userService; +import org.springframework.stereotype.Component; + +@Component +public class InstanceDetailFacadeService implements InstanceDetailFacade { + + private final InstanceService instanceService; private final FilesService filesService; - private final InstanceProvider instanceProvider; private final ParticipantService participantService; + private final LikesService likesService; + private final UserService userService; private final GithubService githubService; - private final LikesRepository likesRepository; + public InstanceDetailFacadeService(InstanceService instanceService, FilesService filesService, + ParticipantService participantService, LikesService likesService, + UserService userService, GithubService githubService) { + this.instanceService = instanceService; + this.filesService = filesService; + this.participantService = participantService; + this.likesService = likesService; + this.userService = userService; + this.githubService = githubService; + } + @Override public InstanceResponse getInstanceDetailInformation(User user, Long instanceId) { - Instance instance = instanceProvider.findById(instanceId); + + // 인스턴스 정보 + Instance instance = instanceService.findInstanceById(instanceId); + + // 파일 객체 생성 FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); - LikesInfo likesInfo = getLikesInfo(user.getId(), instance); - if (participantService.hasJoinedParticipant(user.getId(), instanceId)) { + // 좋아요 정보 + LikesInfo likesInfo = likesService.getLikesInfo(user.getId(), instance); + + if (participantService.hasJoinedParticipant(user.getId(), instance.getId())) { return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.YES, fileResponse); } - return InstanceResponse.createByEntity(instance, likesInfo, JoinStatus.NO, fileResponse); } - private LikesInfo getLikesInfo(Long userId, Instance instance) { - Optional optionalLikes = likesRepository.findSpecificLike(userId, instance.getId()); - if (optionalLikes.isPresent()) { - Likes likes = optionalLikes.get(); - return LikesInfo.createExist(likes.getId(), instance.getLikesCount()); - } - return LikesInfo.createNotExist(instance.getLikesCount()); - } - - @Transactional + @Override public JoinResponse joinNewChallenge(User user, JoinRequest joinRequest) { + User persistUser = userService.findUserById(user.getId()); - Instance instance = instanceProvider.findById(joinRequest.instanceId()); + + Instance instance = instanceService.findInstanceById(joinRequest.instanceId()); String repository = joinRequest.repository(); @@ -103,9 +105,9 @@ private void validateGithub(User user, String repository) { githubService.validateGithubRepository(gitHub, repositoryFullName); } - @Transactional + @Override public JoinResponse quitChallenge(User user, Long instanceId) { - Instance instance = instanceProvider.findById(instanceId); + Instance instance = instanceService.findInstanceById(instanceId); Participant participant = participantService.findByJoinInfo(user.getId(), instanceId); if (instance.getProgress() == Progress.DONE) { diff --git a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java b/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java deleted file mode 100644 index 3992c34e..00000000 --- a/src/main/java/com/genius/gitget/challenge/instance/service/InstanceProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.genius.gitget.challenge.instance.service; - -import static com.genius.gitget.global.util.exception.ErrorCode.INSTANCE_NOT_FOUND; - -import com.genius.gitget.challenge.instance.domain.Instance; -import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.global.util.exception.BusinessException; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class InstanceProvider { - private final InstanceRepository instanceRepository; - - public Instance findById(Long instanceId) { - return instanceRepository.findById(instanceId) - .orElseThrow(() -> new BusinessException(INSTANCE_NOT_FOUND)); - } - - public List findAllByProgress(Progress progress) { - return instanceRepository.findAllByProgress(progress); - } -} diff --git a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java index 6f456b34..19055f54 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java +++ b/src/main/java/com/genius/gitget/challenge/likes/service/LikesService.java @@ -1,6 +1,7 @@ package com.genius.gitget.challenge.likes.service; import com.genius.gitget.challenge.instance.domain.Instance; +import com.genius.gitget.challenge.instance.dto.detail.LikesInfo; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.likes.domain.Likes; import com.genius.gitget.challenge.likes.repository.LikesRepository; @@ -10,6 +11,7 @@ import com.genius.gitget.global.util.exception.ErrorCode; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -65,6 +67,16 @@ public void deleteLikes(Long likesId) { likesRepository.deleteById(findLikes.getId()); } + public LikesInfo getLikesInfo(Long userId, Instance instance) { + Optional optionalLikes = likesRepository.findSpecificLike(userId, instance.getId()); + if (optionalLikes.isPresent()) { + Likes likes = optionalLikes.get(); + return LikesInfo.createExist(likes.getId(), instance.getLikesCount()); + } + + return LikesInfo.createNotExist(instance.getLikesCount()); + } + private List verifyUser(User user) { return userRepository.findAllByIdentifier(user.getIdentifier()); } diff --git a/src/main/java/com/genius/gitget/schedule/service/ProgressService.java b/src/main/java/com/genius/gitget/schedule/service/ProgressService.java index 21752b0d..2aaf8147 100644 --- a/src/main/java/com/genius/gitget/schedule/service/ProgressService.java +++ b/src/main/java/com/genius/gitget/schedule/service/ProgressService.java @@ -6,7 +6,7 @@ import com.genius.gitget.challenge.certification.service.CertificationService; import com.genius.gitget.challenge.instance.domain.Instance; import com.genius.gitget.challenge.instance.domain.Progress; -import com.genius.gitget.challenge.instance.service.InstanceProvider; +import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.Participant; import java.time.LocalDate; @@ -22,13 +22,13 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class ProgressService { - private final InstanceProvider instanceProvider; private final CertificationService certificationService; + private final InstanceRepository instanceRepository; private final double SUCCESS_THRESHOLD = 85; @Transactional public void updateToActivity(LocalDate currentDate) { - List preActivities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + List preActivities = instanceRepository.findAllByProgress(Progress.PREACTIVITY); for (Instance preActivity : preActivities) { LocalDate startedDate = preActivity.getStartedDate().toLocalDate(); LocalDate completedDate = preActivity.getCompletedDate().toLocalDate(); @@ -53,8 +53,8 @@ private void updateActivityInstance(Instance preActivity) { @Transactional public void updateToDone(LocalDate currentDate) { List instances = new ArrayList<>(); - instances.addAll(instanceProvider.findAllByProgress(Progress.PREACTIVITY)); - instances.addAll(instanceProvider.findAllByProgress(Progress.ACTIVITY)); + instances.addAll(instanceRepository.findAllByProgress(Progress.PREACTIVITY)); + instances.addAll(instanceRepository.findAllByProgress(Progress.ACTIVITY)); for (Instance instance : instances) { LocalDate startedDate = instance.getStartedDate().toLocalDate(); diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java index 82be2061..39cfb550 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceDetailServiceTest.java @@ -14,7 +14,6 @@ import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; import com.genius.gitget.challenge.instance.repository.InstanceRepository; -import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; import com.genius.gitget.challenge.likes.facade.LikesFacade; import com.genius.gitget.challenge.participant.domain.JoinResult; import com.genius.gitget.challenge.participant.domain.JoinStatus; @@ -26,10 +25,11 @@ import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; import java.time.LocalDate; import java.time.LocalDateTime; +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 org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -42,8 +42,9 @@ @SpringBootTest @Transactional class InstanceDetailServiceTest { + @Autowired - InstanceDetailService instanceDetailService; + InstanceDetailFacade instanceDetailFacade; @Autowired LikesFacade likesFacade; @Autowired @@ -64,292 +65,409 @@ class InstanceDetailServiceTest { @Value("${github.yeon-repository}") private String targetRepo; - - @Test - @DisplayName("챌린지 참여에 필요한 정보를 전달했을 때, 참여 정보가 저장이 되어야 한다.") - public void should_saveParticipantInfo_when_passInfo() { - //given - User savedUser = getSavedUser(githubId); - Instance instance = getSavedInstance(Progress.PREACTIVITY); - LocalDate todayDate = LocalDate.of(2024, 1, 30); - JoinRequest joinRequest = JoinRequest.builder() - .instanceId(instance.getId()) - .repository(targetRepo) - .todayDate(todayDate) - .build(); - - //when - JoinResponse joinResponse = instanceDetailService.joinNewChallenge(savedUser, joinRequest); - - //then - assertThat(joinResponse.joinStatus()).isEqualTo(JoinStatus.YES); - assertThat(joinResponse.joinResult()).isEqualTo(JoinResult.READY); - assertThat(instance.getParticipantCount()).isEqualTo(1); - } - - @Test - @DisplayName("챌린지 참여 요청을 했을 때, 인스턴스가 존재하지 않는다면 예외가 발생한다.") - public void should_throwException_when_instanceNotExist() { - //given - User savedUser = getSavedUser(githubId); - JoinRequest joinRequest = JoinRequest.builder() - .instanceId(1L) - .repository(targetRepo) - .build(); - - //when & then - assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(savedUser, joinRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); - } - - @ParameterizedTest - @DisplayName("챌린지 참여 요청을 했을 때, 인스턴스의 상태가 시작 전이 아니라면 예외가 발생한다.") - @EnumSource(mode = Mode.INCLUDE, names = {"ACTIVITY", "DONE"}) - public void should_throwException_when_instanceProgressNotPreactivity(Progress progress) { - //given - User savedUser = getSavedUser(githubId); - Instance savedInstance = getSavedInstance(progress); - LocalDate todayDate = LocalDate.of(2024, 1, 30); - JoinRequest joinRequest = JoinRequest.builder() - .repository(targetRepo) - .instanceId(savedInstance.getId()) - .todayDate(todayDate) - .build(); - - //when & then - assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(savedUser, joinRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); - } - - @Test - @DisplayName("챌린지 참여 요청을 했을 때, 사용자가 이미 참여한 챌린지인 경우 예외가 발생한다.") - public void should_throwException_when_userAlreadyJoined() { - //given - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(Progress.PREACTIVITY); - LocalDate todayDate = LocalDate.of(2024, 1, 30); - JoinRequest joinRequest = JoinRequest.builder() - .repository(targetRepo) - .instanceId(instance.getId()) - .todayDate(todayDate) - .build(); - - //when - instanceDetailService.joinNewChallenge(user, joinRequest); - - //then - assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(user, joinRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); - } - - @Test - @DisplayName("챌린지 시작 당일에 챌린지 참여 요청을 하면 예외가 발생한다") - public void should_throwException_when_joinAtStartedDate() { - //given - LocalDate today = LocalDate.of(2024, 1, 30); - - User user = getSavedUser(githubId); - Instance instance = getSavedInstance(Progress.PREACTIVITY, today); - JoinRequest joinRequest = JoinRequest.builder() - .repository(targetRepo) - .instanceId(instance.getId()) - .todayDate(today) - .build(); - - //when - assertThatThrownBy(() -> instanceDetailService.joinNewChallenge(user, joinRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.CAN_NOT_JOIN_INSTANCE.getMessage()); - } - - @Test - @DisplayName("아직 시작하지 않은 챌린지에 대해 취소 요청을 하면 ParticipantInfo가 삭제된다.") - public void should_joinStatusIsNo_when_quitChallenge() { - //given - User savedUser = getSavedUser(githubId); - Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); - LocalDate todayDate = LocalDate.of(2024, 1, 30); - - //when - instanceDetailService.joinNewChallenge(savedUser, - new JoinRequest(savedInstance.getId(), targetRepo, todayDate)); - JoinResponse joinResponse = instanceDetailService.quitChallenge(savedUser, savedInstance.getId()); - - //then - assertThat(savedInstance.getParticipantCount()).isEqualTo(0); - assertThat(joinResponse.participantId()).isEqualTo(null); - assertThat(joinResponse.joinResult()).isEqualTo(null); - assertThat(joinResponse.joinStatus()).isEqualTo(null); - } - - @Test - @DisplayName("진행 중인 챌린지에 대해 취소 요청을 하면 인스턴스의 참여 인원 수가 줄어들고, 참여 정보가 변경된다") - public void should_changeParticipantInfo_when_requestQuitInstance() { - //given - User savedUser = getSavedUser(githubId); - Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); - LocalDate todayDate = LocalDate.of(2024, 1, 30); - JoinRequest joinRequest = JoinRequest.builder() - .instanceId(savedInstance.getId()) - .repository(targetRepo) - .todayDate(todayDate) - .build(); - - //when - instanceDetailService.joinNewChallenge(savedUser, joinRequest); - savedInstance.updateProgress(Progress.ACTIVITY); - instanceDetailService.quitChallenge(savedUser, savedInstance.getId()); - Participant participant = participantService.findByJoinInfo(savedUser.getId(), - savedInstance.getId()); - - //then - assertThat(savedInstance.getParticipantCount()).isEqualTo(0); - assertThat(participant.getJoinResult()).isEqualTo(JoinResult.FAIL); - assertThat(participant.getJoinStatus()).isEqualTo(JoinStatus.NO); - } - - @Test - @DisplayName("챌린지 취소 요청을 할 때 인스턴스가 존재하지 않으면 예외가 발생한다.") - public void should_throwException_when_instanceNotExist_quitChallenge() { - //given - User savedUser = getSavedUser(githubId); - - //when & then - assertThatThrownBy(() -> instanceDetailService.quitChallenge(savedUser, 1L)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); + @Nested + @DisplayName("챌린지 참여") + class Describe_joinChallenge { + + @Nested + @DisplayName("유효한 요청이 주어졌을 때") + class Context_with_validRequest { + + private User savedUser; + private Instance instance; + private JoinRequest joinRequest; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + instance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); + joinRequest = JoinRequest.builder() + .instanceId(instance.getId()) + .repository(targetRepo) + .todayDate(todayDate) + .build(); + } + + @Test + @DisplayName("참여 정보가 저장된다.") + void it_savesParticipantInfo() { + JoinResponse joinResponse = instanceDetailFacade.joinNewChallenge(savedUser, joinRequest); + + assertThat(joinResponse.joinStatus()).isEqualTo(JoinStatus.YES); + assertThat(joinResponse.joinResult()).isEqualTo(JoinResult.READY); + assertThat(instance.getParticipantCount()).isEqualTo(1); + } + } + + @Nested + @DisplayName("인스턴스가 존재하지 않을 때") + class Context_when_instanceNotExist { + + private User savedUser; + private JoinRequest joinRequest; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + joinRequest = JoinRequest.builder() + .instanceId(1L) // 존재하지 않는 인스턴스 ID + .repository(targetRepo) + .build(); + } + + @Test + @DisplayName("예외가 발생한다.") + void it_throwsException() { + assertThatThrownBy(() -> instanceDetailFacade.joinNewChallenge(savedUser, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); + } + } + + @Nested + @DisplayName("인스턴스의 상태가 시작 전이 아닌 경우") + class Context_when_instanceProgressNotPreactivity { + + private User savedUser; + private Instance instance; + private JoinRequest joinRequest; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + instance = getSavedInstance(Progress.ACTIVITY); + joinRequest = JoinRequest.builder() + .repository(targetRepo) + .instanceId(instance.getId()) + .todayDate(LocalDate.of(2024, 1, 30)) + .build(); + } + + @ParameterizedTest + @EnumSource(value = Progress.class, mode = Mode.INCLUDE, names = {"ACTIVITY", "DONE"}) + @DisplayName("예외가 발생한다.") + void it_throwsException(Progress progress) { + instance.updateProgress(progress); + + assertThatThrownBy(() -> instanceDetailFacade.joinNewChallenge(savedUser, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); + } + } + + @Nested + @DisplayName("이미 참여한 경우") + class Context_when_userAlreadyJoined { + + private User savedUser; + private Instance instance; + private JoinRequest joinRequest; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + instance = getSavedInstance(Progress.PREACTIVITY); + joinRequest = JoinRequest.builder() + .repository(targetRepo) + .instanceId(instance.getId()) + .todayDate(LocalDate.of(2024, 1, 30)) + .build(); + instanceDetailFacade.joinNewChallenge(savedUser, joinRequest); + } + + @Test + @DisplayName("예외가 발생한다.") + void it_throwsException() { + assertThatThrownBy(() -> instanceDetailFacade.joinNewChallenge(savedUser, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); + } + } + + @Nested + @DisplayName("챌린지 시작 당일에 참여 요청을 했을 때") + class Context_when_joinAtStartedDate { + + private User savedUser; + private Instance instance; + private JoinRequest joinRequest; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + instance = getSavedInstance(Progress.PREACTIVITY, LocalDate.of(2024, 1, 30)); + joinRequest = JoinRequest.builder() + .repository(targetRepo) + .instanceId(instance.getId()) + .todayDate(LocalDate.of(2024, 1, 30)) + .build(); + } + + @Test + @DisplayName("예외가 발생한다.") + void it_throwsException() { + assertThatThrownBy(() -> instanceDetailFacade.joinNewChallenge(savedUser, joinRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_JOIN_INSTANCE.getMessage()); + } + } } - @Test - @DisplayName("챌린지 취소 요청을 할 때 참여 정보가 존재하지 않으면 예외가 발생한다.") - public void should_throwException_when_participantInfoNotExist() { - //given - User savedUser = getSavedUser(githubId); - Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); - - //when & then - assertThatThrownBy(() -> instanceDetailService.quitChallenge(savedUser, savedInstance.getId())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(PARTICIPANT_NOT_FOUND.getMessage()); + @Nested + @DisplayName("챌린지 취소") + class Describe_quitChallenge { + + @Nested + @DisplayName("아직 시작하지 않은 챌린지에 대해 취소 요청을 했을 때") + class Context_when_quitBeforeStart { + + private User savedUser; + private Instance savedInstance; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + savedInstance = getSavedInstance(Progress.PREACTIVITY); + } + + @Test + @DisplayName("ParticipantInfo가 삭제된다.") + void it_deletesParticipantInfo() { + instanceDetailFacade.joinNewChallenge(savedUser, + new JoinRequest(savedInstance.getId(), targetRepo, LocalDate.of(2024, 1, 30))); + JoinResponse joinResponse = instanceDetailFacade.quitChallenge(savedUser, savedInstance.getId()); + + assertThat(savedInstance.getParticipantCount()).isEqualTo(0); + assertThat(joinResponse.participantId()).isNull(); + assertThat(joinResponse.joinResult()).isNull(); + assertThat(joinResponse.joinStatus()).isNull(); + } + } + + @Nested + @DisplayName("진행 중인 챌린지에 대해 취소 요청을 했을 때") + class Context_when_quitDuringActivity { + + private User savedUser; + private Instance savedInstance; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + savedInstance = getSavedInstance(Progress.PREACTIVITY); + instanceDetailFacade.joinNewChallenge(savedUser, + new JoinRequest(savedInstance.getId(), targetRepo, LocalDate.of(2024, 1, 30))); + savedInstance.updateProgress(Progress.ACTIVITY); + } + + @Test + @DisplayName("참여 인원 수가 줄어들고, 참여 정보가 변경된다.") + void it_changesParticipantInfo() { + instanceDetailFacade.quitChallenge(savedUser, savedInstance.getId()); + Participant participant = participantService.findByJoinInfo(savedUser.getId(), + savedInstance.getId()); + + assertThat(savedInstance.getParticipantCount()).isEqualTo(0); + assertThat(participant.getJoinResult()).isEqualTo(JoinResult.FAIL); + assertThat(participant.getJoinStatus()).isEqualTo(JoinStatus.NO); + } + } + + @Nested + @DisplayName("인스턴스가 존재하지 않을 때") + class Context_when_instanceNotExist { + + private User savedUser; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + } + + @Test + @DisplayName("예외가 발생한다.") + void it_throwsException() { + assertThatThrownBy(() -> instanceDetailFacade.quitChallenge(savedUser, 1L)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); + } + } + + @Nested + @DisplayName("참여 정보가 존재하지 않을 때") + class Context_when_participantInfoNotExist { + + private User savedUser; + private Instance savedInstance; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + savedInstance = getSavedInstance(Progress.PREACTIVITY); + } + + @Test + @DisplayName("예외가 발생한다.") + void it_throwsException() { + assertThatThrownBy(() -> instanceDetailFacade.quitChallenge(savedUser, savedInstance.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(PARTICIPANT_NOT_FOUND.getMessage()); + } + } + + @Nested + @DisplayName("진행 상황이 DONE인 챌린지에 대해 취소 요청을 했을 때") + class Context_when_progressIsDone { + + @Test + @DisplayName("인스턴스의 진행 상황이 DONE이면 예외가 발생한다.") + public void should_throwException_when_progressIsDONE() { + //given + User savedUser = getSavedUser(githubId); + Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); + LocalDate todayDate = LocalDate.of(2024, 1, 30); + JoinRequest joinRequest = JoinRequest.builder() + .instanceId(savedInstance.getId()) + .repository(targetRepo) + .todayDate(todayDate) + .build(); + + //when + instanceDetailFacade.joinNewChallenge(savedUser, joinRequest); + savedInstance.updateProgress(Progress.DONE); + + //then + assertThatThrownBy(() -> instanceDetailFacade.quitChallenge(savedUser, savedInstance.getId())) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(CAN_NOT_QUIT_INSTANCE.getMessage()); + } + } } - @Test - @DisplayName("챌린지 취소 요청을 할 때, 인스턴스의 진행 상황이 DONE이면 예외가 발생한다.") - public void should_throwException_when_progressIsDONE() { - //given - User savedUser = getSavedUser(githubId); - Instance savedInstance = getSavedInstance(Progress.PREACTIVITY); - LocalDate todayDate = LocalDate.of(2024, 1, 30); - JoinRequest joinRequest = JoinRequest.builder() - .instanceId(savedInstance.getId()) - .repository(targetRepo) - .todayDate(todayDate) - .build(); - - //when - instanceDetailService.joinNewChallenge(savedUser, joinRequest); - savedInstance.updateProgress(Progress.DONE); - - //then - assertThatThrownBy(() -> instanceDetailService.quitChallenge(savedUser, savedInstance.getId())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(CAN_NOT_QUIT_INSTANCE.getMessage()); - } - - @Test - @DisplayName("사용자가 참여한 인스턴스에 대해 상세 조회를 하면 상세 페이지에 필요한 데이터들을 반환해야 한다.") - public void should_returnValues_when_joinedInstance() { - //given - User savedUser = getSavedUser(githubId); - Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); - LocalDate todayDate = LocalDate.of(2024, 1, 30); - JoinRequest joinRequest = JoinRequest.builder() - .instanceId(savedInstance.getId()) - .repository(targetRepo) - .todayDate(todayDate) - .build(); - - //when - instanceDetailService.joinNewChallenge(savedUser, joinRequest); - InstanceResponse instanceResponse = instanceDetailService.getInstanceDetailInformation(savedUser, - savedInstance.getId()); - - //then - assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); - assertThat(instanceResponse.progress()).isEqualTo(Progress.PREACTIVITY); - assertThat(instanceResponse.remainDays()).isEqualTo(2); - assertThat(instanceResponse.participantCount()).isEqualTo(1); - assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); - assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); - assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.YES); - assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(0); - assertThat(instanceResponse.likesInfo().likesId()).isEqualTo(0); - assertThat(instanceResponse.likesInfo().isLiked()).isFalse(); - } - - @Test - @DisplayName("사용자가 참여하지 않은 챌린지에 대해 상세 조회를 하면 상세 페이지에 필요한 정보들을 반환할 수 있다.") - public void should_returnData_when_notJoinedInstance() { - //given - User savedUser = getSavedUser(githubId); - Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); - - //when - InstanceResponse instanceResponse = instanceDetailService.getInstanceDetailInformation(savedUser, - savedInstance.getId()); - - //then - assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); - assertThat(instanceResponse.remainDays()).isEqualTo(2); - assertThat(instanceResponse.participantCount()).isEqualTo(0); - assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); - assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); - assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.NO); - assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(0); - assertThat(instanceResponse.likesInfo().likesId()).isEqualTo(0); - assertThat(instanceResponse.likesInfo().isLiked()).isFalse(); - } - - @Test - @DisplayName("시용자가 좋아요를 한 이후, 상세 정보를 요청하면 좋아요 관련된 정보를 받을 수 있다.") - public void should_returnLikesData_when_userPushLikes() { - //given - User savedUser = getSavedUser(githubId); - Instance savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); - - //when - UserLikesAddResponse userLikesAddResponse = likesFacade.addLikes(savedUser, savedUser.getIdentifier(), - savedInstance.getId()); - InstanceResponse instanceResponse = instanceDetailService.getInstanceDetailInformation(savedUser, - savedInstance.getId()); - - //then - assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); - assertThat(instanceResponse.remainDays()).isEqualTo(2); - assertThat(instanceResponse.participantCount()).isEqualTo(0); - assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); - assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); - assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.NO); - assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(1); - assertThat(instanceResponse.likesInfo().likesId()).isEqualTo(userLikesAddResponse.getLikesId()); - assertThat(instanceResponse.likesInfo().isLiked()).isTrue(); - } - - @Test - @DisplayName("상세 정보를 요청하는 인스턴스가 존재하지 않는다면 예외가 발생해야 한다.") - public void should_throwException_when_requestDetail_instanceNotExist() { - //given - User savedUser = getSavedUser(githubId); - - //when & then - assertThatThrownBy(() -> instanceDetailService.getInstanceDetailInformation(savedUser, 1L)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); + @Nested + @DisplayName("상세 조회") + class Describe_getInstanceDetailInformation { + + @Nested + @DisplayName("사용자가 참여한 인스턴스의 상세 정보를 조회할 때") + class Context_when_joinedInstance { + + private User savedUser; + private Instance savedInstance; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); + } + + @Test + @DisplayName("필요한 데이터를 반환한다.") + void it_returnsInstanceDetail() { + instanceDetailFacade.joinNewChallenge(savedUser, + new JoinRequest(savedInstance.getId(), targetRepo, LocalDate.of(2024, 1, 30))); + InstanceResponse instanceResponse = instanceDetailFacade.getInstanceDetailInformation(savedUser, + savedInstance.getId()); + + assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); + assertThat(instanceResponse.progress()).isEqualTo(Progress.PREACTIVITY); + assertThat(instanceResponse.remainDays()).isEqualTo(2); + assertThat(instanceResponse.participantCount()).isEqualTo(1); + assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); + assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); + assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.YES); + assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().likesId()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().isLiked()).isFalse(); + } + } + + @Nested + @DisplayName("사용자가 참여하지 않은 인스턴스의 상세 정보를 조회할 때") + class Context_when_notJoinedInstance { + + private User savedUser; + private Instance savedInstance; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); + } + + @Test + @DisplayName("필요한 정보를 반환할 수 있다.") + void it_returnsInstanceDetail() { + InstanceResponse instanceResponse = instanceDetailFacade.getInstanceDetailInformation(savedUser, + savedInstance.getId()); + + assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); + assertThat(instanceResponse.remainDays()).isEqualTo(2); + assertThat(instanceResponse.participantCount()).isEqualTo(0); + assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); + assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); + assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.NO); + assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().likesId()).isEqualTo(0); + assertThat(instanceResponse.likesInfo().isLiked()).isFalse(); + } + } + + @Nested + @DisplayName("좋아요를 한 이후 상세 정보를 조회할 때") + class Context_when_userPushLikes { + + private User savedUser; + private Instance savedInstance; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + savedInstance = getSavedInstance(Progress.PREACTIVITY, LocalDate.now().plusDays(2)); + likesFacade.addLikes(savedUser, savedUser.getIdentifier(), savedInstance.getId()); + } + + @Test + @DisplayName("좋아요 관련된 정보를 반환한다.") + void it_returnsLikesData() { + InstanceResponse instanceResponse = instanceDetailFacade.getInstanceDetailInformation(savedUser, + savedInstance.getId()); + + assertThat(instanceResponse.instanceId()).isEqualTo(savedInstance.getId()); + assertThat(instanceResponse.remainDays()).isEqualTo(2); + assertThat(instanceResponse.participantCount()).isEqualTo(0); + assertThat(instanceResponse.pointPerPerson()).isEqualTo(100); + assertThat(instanceResponse.description()).isEqualTo(savedInstance.getDescription()); + assertThat(instanceResponse.joinStatus()).isEqualTo(JoinStatus.NO); + assertThat(instanceResponse.likesInfo().likesCount()).isEqualTo(1); + assertThat(instanceResponse.likesInfo().likesId()).isNotEqualTo(0); + assertThat(instanceResponse.likesInfo().isLiked()).isTrue(); + } + } + + @Nested + @DisplayName("상세 정보를 요청하는 인스턴스가 존재하지 않을 때") + class Context_when_instanceNotExist { + + private User savedUser; + + @BeforeEach + void setUp() { + savedUser = getSavedUser(githubId); + } + + @Test + @DisplayName("예외가 발생해야 한다.") + void it_throwsException() { + assertThatThrownBy(() -> instanceDetailFacade.getInstanceDetailInformation(savedUser, 1L)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(INSTANCE_NOT_FOUND.getMessage()); + } + } } + // 유틸리티 메서드 private User getSavedUser(String githubId) { User user = userRepository.save( User.builder() @@ -387,4 +505,4 @@ private Instance getSavedInstance(Progress progress, LocalDate startedDate) { .build() ); } -} \ No newline at end of file +} diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java index 0bd5716a..f4aa324f 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/ProgressServiceTest.java @@ -38,8 +38,6 @@ @Transactional @ActiveProfiles({"github"}) class ProgressServiceTest { - @Autowired - private InstanceDetailService instanceDetailService; @Autowired private ProgressService scheduleService; @Autowired @@ -53,7 +51,8 @@ class ProgressServiceTest { @Autowired private CertificationRepository certificationRepository; @Autowired - private InstanceProvider instanceProvider; + private InstanceDetailFacade instanceDetailFacade; + @Value("${github.yeon-personalKey}") private String personalKey; @@ -77,7 +76,7 @@ public void should_updateToActivity_when_conditionMatches() { getSavedInstance(startedDate, completedDate); githubFacade.registerGithubPersonalToken(user, personalKey); - instanceDetailService.joinNewChallenge( + instanceDetailFacade.joinNewChallenge( user, JoinRequest.builder() .repository(targetRepo) @@ -90,10 +89,10 @@ public void should_updateToActivity_when_conditionMatches() { .orElseThrow(() -> new BusinessException(ErrorCode.PARTICIPANT_NOT_FOUND)); //when - List preActivities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + List preActivities = instanceRepository.findAllByProgress(Progress.PREACTIVITY); assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.READY); scheduleService.updateToActivity(currentDate); - List activities = instanceProvider.findAllByProgress(Progress.ACTIVITY); + List activities = instanceRepository.findAllByProgress(Progress.ACTIVITY); //then assertThat(preActivities.size()).isEqualTo(3); @@ -116,7 +115,7 @@ public void should_updateToActivity_when_dDay() { getSavedInstance(startedDate, completedDate); githubFacade.registerGithubPersonalToken(user, personalKey); - instanceDetailService.joinNewChallenge( + instanceDetailFacade.joinNewChallenge( user, JoinRequest.builder() .repository(targetRepo) @@ -129,10 +128,10 @@ public void should_updateToActivity_when_dDay() { .orElseThrow(() -> new BusinessException(ErrorCode.PARTICIPANT_NOT_FOUND)); //when - List preActivities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + List preActivities = instanceRepository.findAllByProgress(Progress.PREACTIVITY); assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.READY); scheduleService.updateToActivity(currentDate); - List activities = instanceProvider.findAllByProgress(Progress.ACTIVITY); + List activities = instanceRepository.findAllByProgress(Progress.ACTIVITY); //then assertThat(preActivities.size()).isEqualTo(3); @@ -153,9 +152,9 @@ public void should_updateToDone_when_conditionMatches() { getSavedInstance(startedDate, completedDate); //when - List activities = instanceProvider.findAllByProgress(Progress.PREACTIVITY); + List activities = instanceRepository.findAllByProgress(Progress.PREACTIVITY); scheduleService.updateToDone(currentDate); - List done = instanceProvider.findAllByProgress(Progress.DONE); + List done = instanceRepository.findAllByProgress(Progress.DONE); //then assertThat(activities.size()).isEqualTo(3); @@ -183,7 +182,7 @@ public void should_updateToSuccess_then_rateOverThreshold() { scheduleService.updateToDone(currentDate); //then - List done = instanceProvider.findAllByProgress(Progress.DONE); + List done = instanceRepository.findAllByProgress(Progress.DONE); assertThat(done.size()).isEqualTo(3); assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.SUCCESS); } @@ -209,7 +208,7 @@ public void should_updateToFail_when_rateUnderThreshold() { scheduleService.updateToDone(currentDate); //then - List done = instanceProvider.findAllByProgress(Progress.DONE); + List done = instanceRepository.findAllByProgress(Progress.DONE); assertThat(done.size()).isEqualTo(3); assertThat(participant1.getJoinResult()).isEqualTo(JoinResult.FAIL); } From c3269d61eea4e357cb77890986cbb8c4f91204a7 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:52:51 +0900 Subject: [PATCH 216/234] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#244)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파사드 패턴 적용에 따라 FileService와 FileManager의 이름을 서로 변경 --- .../service/CertificationFacadeService.java | 8 +++--- .../facade/InstanceFacadeService.java | 8 +++--- .../facade/InstanceHomeFacadeService.java | 8 +++--- .../service/InstanceDetailFacadeService.java | 10 +++---- .../instance/service/InstanceService.java | 6 ++-- .../likes/facade/LikesFacadeService.java | 10 +++---- .../facade/MyChallengeFacadeService.java | 12 ++++---- .../challenge/user/service/UserService.java | 6 ++-- .../file/controller/FileTestController.java | 28 +++++++++---------- .../file/controller/FilesController.java | 12 ++++---- .../{FileManager.java => FileService.java} | 2 +- .../{FilesService.java => FilesManager.java} | 20 ++++++------- ...FileManager.java => LocalFileService.java} | 4 +-- ...{S3FileManager.java => S3FileService.java} | 6 ++-- .../gitget/global/util/config/AppConfig.java | 12 ++++---- .../profile/service/ProfileFacadeService.java | 14 +++++----- .../topic/facade/TopicFacadeService.java | 8 +++--- .../controller/InstanceControllerTest.java | 4 +-- .../instance/service/InstanceFacadeTest.java | 4 +-- .../likes/controller/LikesControllerTest.java | 4 +-- .../global/file/service/FileUtilTest.java | 2 +- .../controller/PaymentControllerTest.java | 4 +-- .../controller/ProfileControllerTest.java | 4 +-- .../topic/controller/TopicControllerTest.java | 4 +-- 24 files changed, 100 insertions(+), 100 deletions(-) rename src/main/java/com/genius/gitget/global/file/service/{FileManager.java => FileService.java} (99%) rename src/main/java/com/genius/gitget/global/file/service/{FilesService.java => FilesManager.java} (88%) rename src/main/java/com/genius/gitget/global/file/service/{LocalFileManager.java => LocalFileService.java} (97%) rename src/main/java/com/genius/gitget/global/file/service/{S3FileManager.java => S3FileService.java} (96%) diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java index 8583ab85..022d9149 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java @@ -23,7 +23,7 @@ import com.genius.gitget.challenge.user.dto.UserProfileInfo; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; @@ -42,7 +42,7 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class CertificationFacadeService implements CertificationFacade { - private final FilesService filesService; + private final FilesManager filesManager; private final UserService userService; private final InstanceService instanceService; @@ -167,7 +167,7 @@ public ActivatedResponse passCertification(Long userId, CertificationRequest cer certification.updateToPass(targetDate); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); return ActivatedResponse.of(instance, certification.getCertificationStatus(), 0, participant.getRepositoryName(), fileResponse); } @@ -206,7 +206,7 @@ public CertificationResponse updateCertification(User user, CertificationRequest @Override public InstancePreviewResponse getInstancePreview(Long instanceId) { Instance instance = instanceService.findInstanceById(instanceId); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); return InstancePreviewResponse.createByEntity(instance, fileResponse); } diff --git a/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacadeService.java b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacadeService.java index e491d45c..ad3e907c 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacadeService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceFacadeService.java @@ -8,7 +8,7 @@ import com.genius.gitget.challenge.instance.dto.crud.InstanceUpdateRequest; import com.genius.gitget.challenge.instance.service.InstanceService; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import java.time.LocalDate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -22,7 +22,7 @@ @Slf4j @Transactional public class InstanceFacadeService implements InstanceFacade { - private final FilesService filesService; + private final FilesManager filesManager; private final InstanceService instanceService; // 인스턴스 생성 @@ -56,7 +56,7 @@ public void removeInstance(Long id) { @Override public InstanceDetailResponse findOne(Long id) { Instance instance = instanceService.findInstanceById(id); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); return InstanceDetailResponse.of(instance, fileResponse); } @@ -75,7 +75,7 @@ public Page getAllInstancesOfSpecificTopic(Pageable page } private InstancePagingResponse mapToInstancePagingResponse(Instance instance) { - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); return InstancePagingResponse.of(instance, fileResponse); } } diff --git a/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacadeService.java b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacadeService.java index 9f039b98..f614ac9c 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacadeService.java +++ b/src/main/java/com/genius/gitget/challenge/instance/facade/InstanceHomeFacadeService.java @@ -9,7 +9,7 @@ import com.genius.gitget.challenge.instance.service.InstanceSearchService; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -29,7 +29,7 @@ public class InstanceHomeFacadeService implements InstanceHomeFacade { private final InstanceRecommendationService instanceRecommendationService; private final InstanceSearchService instanceSearchService; - private final FilesService filesService; + private final FilesManager filesManager; @Override public Page searchInstancesByKeywordAndProgress(InstanceSearchRequest instanceSearchRequest, @@ -54,7 +54,7 @@ public Slice findInstancesByCondition(Pageable pageable) { } private InstanceSearchResponse convertToSearchResponse(Instance instance) { - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); return InstanceSearchResponse.builder() .topicId(instance.getTopic().getId()) .instanceId(instance.getId()) @@ -72,7 +72,7 @@ private List convertToHomeInstanceResponseList(List getLikesList(User user, Pageable pageable) { for (Likes like : likesList) { Instance instance = like.getInstance(); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); UserLikesResponse userLikesResponse = getUserLikesResponse(like, instance, fileResponse); userLikesResponses.addFirst(userLikesResponse); } diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java b/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java index cc4ef7a7..8af025e5 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/facade/MyChallengeFacadeService.java @@ -18,7 +18,7 @@ import com.genius.gitget.challenge.participant.service.ParticipantService; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.service.ItemService; import com.genius.gitget.store.item.service.OrdersService; @@ -33,7 +33,7 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class MyChallengeFacadeService implements MyChallengeFacade { - private final FilesService filesService; + private final FilesManager filesManager; private final ParticipantService participantService; private final CertificationService certificationService; private final ItemService itemService; @@ -47,7 +47,7 @@ public List getPreActivityInstances(User user, LocalDate ta for (Participant participant : participants) { Instance instance = participant.getInstance(); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); int remainDays = DateUtil.getRemainDaysToStart(participant.getStartedDate(), targetDate); PreActivityResponse preActivityResponse = PreActivityResponse.of(instance, remainDays, fileResponse); @@ -64,7 +64,7 @@ public List getActivatedInstances(User user, LocalDate target for (Participant participant : participants) { Instance instance = participant.getInstance(); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); Certification certification = certificationService.findOrSave(participant, NOT_YET, targetDate); @@ -88,7 +88,7 @@ public List getDoneInstances(User user, LocalDate targetDate) { for (Participant participant : participants) { Instance instance = participant.getInstance(); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); double achievementRate = certificationService.getAchievementRate(instance, participant.getId(), targetDate); @@ -120,7 +120,7 @@ public DoneResponse getRewards(RewardRequest rewardRequest) { ); Instance instance = participant.getInstance(); - FileResponse fileResponse = filesService.convertToFileResponse(instance.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(instance.getFiles()); int rewardPoints = instance.getPointPerPerson(); participantService.getRewards(participant, rewardPoints); diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index c6498440..67f1804b 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -12,7 +12,7 @@ import com.genius.gitget.challenge.user.dto.UserProfileInfo; import com.genius.gitget.challenge.user.repository.UserRepository; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; @@ -34,7 +34,7 @@ public class UserService { private final UserRepository userRepository; private final OrdersService ordersService; - private final FilesService filesService; + private final FilesManager filesManager; private final EncryptUtil encryptUtil; private final SignoutRepository signoutRepository; @@ -126,7 +126,7 @@ public AuthResponse getUserAuthInfo(String identifier) { public UserProfileInfo getUserProfileInfo(User user) { Long frameId = ordersService.getUsingFrameItem(user.getId()).getId(); - FileResponse fileResponse = filesService.convertToFileResponse(user.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(user.getFiles()); return UserProfileInfo.createByEntity(user, frameId, fileResponse); } diff --git a/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java b/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java index 8c7e2089..35c9919a 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java @@ -5,8 +5,8 @@ import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FileManager; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FileService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.util.Optional; @@ -29,16 +29,16 @@ @RequiredArgsConstructor @RequestMapping("/api/file/test") public class FileTestController { - private final FilesService filesService; - private final FileManager fileManager; + private final FilesManager filesManager; + private final FileService fileService; @GetMapping("/{fileId}") public ResponseEntity> download( @PathVariable Long fileId ) { - Files files = filesService.findById(fileId); - FileResponse fileResponse = filesService.convertToFileResponse(Optional.ofNullable(files)); + Files files = filesManager.findById(fileId); + FileResponse fileResponse = filesManager.convertToFileResponse(Optional.ofNullable(files)); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) @@ -51,8 +51,8 @@ public ResponseEntity> upload( @RequestParam("type") String type ) { FileType fileType = FileType.findType(type); - Files files = filesService.uploadFile(multipartFile, fileType); - String encodedImage = fileManager.getEncodedImage(files); + Files files = filesManager.uploadFile(multipartFile, fileType); + String encodedImage = fileService.getEncodedImage(files); FileResponse fileResponse = FileResponse.createExistFile(files.getId(), encodedImage); return ResponseEntity.ok().body( @@ -64,8 +64,8 @@ public ResponseEntity> upload( public ResponseEntity> update( @PathVariable Long fileId, @RequestParam("files") MultipartFile multipartFile) { - Files files = filesService.updateFile(fileId, multipartFile); - String encodedImage = fileManager.getEncodedImage(files); + Files files = filesManager.updateFile(fileId, multipartFile); + String encodedImage = fileService.getEncodedImage(files); FileResponse fileResponse = FileResponse.createExistFile(files.getId(), encodedImage); return ResponseEntity.ok().body( @@ -79,10 +79,10 @@ public ResponseEntity> copy( @RequestParam("type") String type) { FileType fileType = FileType.findType(type); - Files files = filesService.findById(fileId); - Files copiedFile = filesService.copyFile(files, fileType); + Files files = filesManager.findById(fileId); + Files copiedFile = filesManager.copyFile(files, fileType); - String encodedImage = fileManager.getEncodedImage(copiedFile); + String encodedImage = fileService.getEncodedImage(copiedFile); FileResponse fileResponse = FileResponse.createExistFile(copiedFile.getId(), encodedImage); return ResponseEntity.ok().body( @@ -94,7 +94,7 @@ public ResponseEntity> copy( public ResponseEntity delete( @PathVariable Long fileId ) { - filesService.deleteFile(fileId); + filesManager.deleteFile(fileId); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java index 3a3adc48..2605d665 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FilesController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FilesController.java @@ -7,7 +7,7 @@ import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FileHolderFinder; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -27,7 +27,7 @@ @RequestMapping("/api/file") public class FilesController { private final FileHolderFinder finder; - private final FilesService filesService; + private final FilesManager filesManager; @PostMapping("/{id}") @@ -40,8 +40,8 @@ public ResponseEntity> uploadFile( FileHolder fileHolder = finder.findByInfo(id, fileType); Files files; - files = filesService.uploadFile(fileHolder, multipartFile, fileType); - FileResponse fileResponse = filesService.convertToFileResponse(Optional.ofNullable(files)); + files = filesManager.uploadFile(fileHolder, multipartFile, fileType); + FileResponse fileResponse = filesManager.convertToFileResponse(Optional.ofNullable(files)); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) @@ -56,8 +56,8 @@ public ResponseEntity> updateFile( ) { FileType fileType = FileType.findType(type); FileHolder fileHolder = finder.findByInfo(id, fileType); - Files files = filesService.updateFile(fileHolder.getFiles(), multipartFile); - FileResponse fileResponse = filesService.convertToFileResponse(Optional.ofNullable(files)); + Files files = filesManager.updateFile(fileHolder.getFiles(), multipartFile); + FileResponse fileResponse = filesManager.convertToFileResponse(Optional.ofNullable(files)); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) diff --git a/src/main/java/com/genius/gitget/global/file/service/FileManager.java b/src/main/java/com/genius/gitget/global/file/service/FileService.java similarity index 99% rename from src/main/java/com/genius/gitget/global/file/service/FileManager.java rename to src/main/java/com/genius/gitget/global/file/service/FileService.java index 90263563..7b6c5687 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileService.java @@ -11,7 +11,7 @@ @Service @Transactional(readOnly = true) -public interface FileManager { +public interface FileService { /** * Files 내에 저장된 값들을 통해 UrlResource 등으로 다운받은 후, base64로 인코딩한 결과 반환 diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesService.java b/src/main/java/com/genius/gitget/global/file/service/FilesManager.java similarity index 88% rename from src/main/java/com/genius/gitget/global/file/service/FilesService.java rename to src/main/java/com/genius/gitget/global/file/service/FilesManager.java index 2b044a56..91cd459a 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesManager.java @@ -22,14 +22,14 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class FilesService { - private final FileManager fileManager; +public class FilesManager { + private final FileService fileService; private final FilesRepository filesRepository; @Transactional public Files uploadFile(MultipartFile multipartFile, FileType fileType) { - FileDTO fileDTO = fileManager.upload(multipartFile, fileType); + FileDTO fileDTO = fileService.upload(multipartFile, fileType); Files file = Files.builder() .originalFilename(fileDTO.originalFilename()) @@ -45,7 +45,7 @@ public Files uploadFile(FileHolder fileHolder, MultipartFile multipartFile, File if (multipartFile == null) { throw new BusinessException(MULTIPART_FILE_NOT_EXIST); } - FileDTO fileDTO = fileManager.upload(multipartFile, fileType); + FileDTO fileDTO = fileService.upload(multipartFile, fileType); Files file = Files.builder() .originalFilename(fileDTO.originalFilename()) @@ -59,7 +59,7 @@ public Files uploadFile(FileHolder fileHolder, MultipartFile multipartFile, File @Transactional public Files copyFile(Files files, FileType fileType) { - FileDTO fileDTO = fileManager.copy(files, fileType); + FileDTO fileDTO = fileService.copy(files, fileType); Files copyFiles = Files.create(fileDTO); return filesRepository.save(copyFiles); @@ -74,7 +74,7 @@ public Files updateFile(Long fileId, MultipartFile multipartFile) { return files; } - UpdateDTO updateDTO = fileManager.update(files, multipartFile); + UpdateDTO updateDTO = fileService.update(files, multipartFile); files.updateFiles(updateDTO); return files; } @@ -86,7 +86,7 @@ public Files updateFile(Optional optionalFiles, MultipartFile multipartFi return files; } - UpdateDTO updateDTO = fileManager.update(files, multipartFile); + UpdateDTO updateDTO = fileService.update(files, multipartFile); files.updateFiles(updateDTO); return files; } @@ -101,7 +101,7 @@ public void deleteFile(Long fileId) { Files files = filesRepository.findById(fileId) .orElseThrow(() -> new BusinessException(FILE_NOT_EXIST)); - fileManager.deleteInStorage(files); + fileService.deleteInStorage(files); filesRepository.delete(files); } @@ -112,7 +112,7 @@ public void deleteFile(Optional optionalFiles) { } Files files = optionalFiles.get(); - fileManager.deleteInStorage(files); + fileService.deleteInStorage(files); filesRepository.delete(files); } @@ -124,7 +124,7 @@ public Files findById(Long fileId) { public FileResponse convertToFileResponse(Optional optionalFiles) { return optionalFiles .map(files -> { - String encodedImage = fileManager.getEncodedImage(files); + String encodedImage = fileService.getEncodedImage(files); return FileResponse.createExistFile(files.getId(), encodedImage); }) .orElseGet(FileResponse::createNotExistFile); diff --git a/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java b/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java similarity index 97% rename from src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java rename to src/main/java/com/genius/gitget/global/file/service/LocalFileService.java index e249ff3f..e3f57e18 100644 --- a/src/main/java/com/genius/gitget/global/file/service/LocalFileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java @@ -20,12 +20,12 @@ import org.springframework.core.io.UrlResource; import org.springframework.web.multipart.MultipartFile; -public class LocalFileManager implements FileManager { +public class LocalFileService implements FileService { private final String UPLOAD_PATH; private final FileUtil fileUtil; - public LocalFileManager(FileUtil fileUtil, @Value("${file.upload.path}") String UPLOAD_PATH) { + public LocalFileService(FileUtil fileUtil, @Value("${file.upload.path}") String UPLOAD_PATH) { this.fileUtil = fileUtil; this.UPLOAD_PATH = UPLOAD_PATH; } diff --git a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java b/src/main/java/com/genius/gitget/global/file/service/S3FileService.java similarity index 96% rename from src/main/java/com/genius/gitget/global/file/service/S3FileManager.java rename to src/main/java/com/genius/gitget/global/file/service/S3FileService.java index 5fc5c069..c895522a 100644 --- a/src/main/java/com/genius/gitget/global/file/service/S3FileManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/S3FileService.java @@ -18,12 +18,12 @@ import org.springframework.core.io.UrlResource; import org.springframework.web.multipart.MultipartFile; -public class S3FileManager implements FileManager { +public class S3FileService implements FileService { private final AmazonS3 amazonS3; private final FileUtil fileUtil; private final String bucket; - public S3FileManager(AmazonS3 amazonS3, FileUtil fileUtil, String bucket) { + public S3FileService(AmazonS3 amazonS3, FileUtil fileUtil, String bucket) { this.amazonS3 = amazonS3; this.fileUtil = fileUtil; this.bucket = bucket; @@ -61,7 +61,7 @@ public FileDTO upload(MultipartFile multipartFile, FileType fileType) { @Override public FileDTO copy(Files files, FileType fileType) { validateFileExist(files); - + CopyDTO copyDTO = fileUtil.getCopyInfo(files, fileType, ""); CopyObjectRequest copyObjectRequest = new CopyObjectRequest( diff --git a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java index 03461aac..fd90402b 100644 --- a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java @@ -1,9 +1,9 @@ package com.genius.gitget.global.util.config; -import com.genius.gitget.global.file.service.FileManager; +import com.genius.gitget.global.file.service.FileService; import com.genius.gitget.global.file.service.FileUtil; -import com.genius.gitget.global.file.service.LocalFileManager; -import com.genius.gitget.global.file.service.S3FileManager; +import com.genius.gitget.global.file.service.LocalFileService; +import com.genius.gitget.global.file.service.S3FileService; import com.genius.gitget.global.util.formatter.LocalDateFormatter; import com.genius.gitget.global.util.formatter.LocalDateTimeFormatter; import lombok.RequiredArgsConstructor; @@ -25,17 +25,17 @@ public FileUtil fileUtil() { } @Bean - public FileManager fileManager() { + public FileService fileManager() { final String fileMode = env.getProperty("file.mode"); assert fileMode != null; if (fileMode.equals("local")) { final String UPLOAD_PATH = env.getProperty("file.upload.path"); - return new LocalFileManager(fileUtil(), UPLOAD_PATH); + return new LocalFileService(fileUtil(), UPLOAD_PATH); } final String bucket = env.getProperty("cloud.aws.s3.bucket"); - return new S3FileManager(s3Config.amazonS3Client(), fileUtil(), bucket); + return new S3FileService(s3Config.amazonS3Client(), fileUtil(), bucket); } @Bean diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java b/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java index d019a424..f3376061 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java @@ -11,7 +11,7 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.profile.dto.UserChallengeResultResponse; import com.genius.gitget.profile.dto.UserDetailsInformationResponse; @@ -31,13 +31,13 @@ public class ProfileFacadeService implements ProfileFacade { private final UserService userService; private final OrdersService ordersService; - private final FilesService filesService; + private final FilesManager filesManager; public ProfileFacadeService(UserService userService, OrdersService ordersService, - FilesService filesService) { + FilesManager filesManager) { this.userService = userService; this.ordersService = ordersService; - this.filesService = filesService; + this.filesManager = filesManager; } @Override @@ -53,7 +53,7 @@ public UserPointResponse getUserPoint(User user) { public UserInformationResponse getUserInformation(Long userId) { User findUser = userService.findUserById(userId); Long frameId = ordersService.getUsingFrameItem(userId).getId(); - FileResponse fileResponse = filesService.convertToFileResponse(findUser.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(findUser.getFiles()); return UserInformationResponse.createByEntity(findUser, frameId, fileResponse); } @@ -70,7 +70,7 @@ public UserDetailsInformationResponse getUserDetailsInformation(User user) { participantCount = (joinResult == SUCCESS) ? participantCount + 1 : participantCount - 1; } } - FileResponse fileResponse = filesService.convertToFileResponse(findUser.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(findUser.getFiles()); return UserDetailsInformationResponse.createByEntity(findUser, participantCount, fileResponse); } @@ -88,7 +88,7 @@ public Long updateUserInformation(User user, UserInformationUpdateRequest userIn public void deleteUserInformation(User user, String reason) { User findUser = userService.findUserByIdentifier(user.getIdentifier()); - filesService.deleteFile(findUser.getFiles()); + filesManager.deleteFile(findUser.getFiles()); findUser.setFiles(null); findUser.deleteLikesList(); diff --git a/src/main/java/com/genius/gitget/topic/facade/TopicFacadeService.java b/src/main/java/com/genius/gitget/topic/facade/TopicFacadeService.java index 45a31007..b58254a0 100644 --- a/src/main/java/com/genius/gitget/topic/facade/TopicFacadeService.java +++ b/src/main/java/com/genius/gitget/topic/facade/TopicFacadeService.java @@ -1,7 +1,7 @@ package com.genius.gitget.topic.facade; import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.dto.TopicCreateRequest; import com.genius.gitget.topic.dto.TopicDetailResponse; @@ -19,7 +19,7 @@ @Transactional public class TopicFacadeService implements TopicFacade { - private final FilesService filesService; + private final FilesManager filesManager; private final TopicService topicService; @@ -32,7 +32,7 @@ public Page findTopics(Pageable pageable) { @Override public TopicDetailResponse findOne(Long id) { Topic findTopic = topicService.findOne(id); - FileResponse fileResponse = filesService.convertToFileResponse(findTopic.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(findTopic.getFiles()); return TopicDetailResponse.of(findTopic, fileResponse); } @@ -62,7 +62,7 @@ public void delete(Long id) { } private TopicPagingResponse convertToTopicPagingResponseDto(Topic topic) { - FileResponse fileResponse = filesService.convertToFileResponse(topic.getFiles()); + FileResponse fileResponse = filesManager.convertToFileResponse(topic.getFiles()); return TopicPagingResponse.of(topic, fileResponse); } } diff --git a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java index 59a209eb..346ca66a 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/controller/InstanceControllerTest.java @@ -12,7 +12,7 @@ import com.genius.gitget.challenge.instance.domain.Progress; import com.genius.gitget.challenge.instance.repository.InstanceRepository; import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.util.security.TokenTestUtil; @@ -44,7 +44,7 @@ public class InstanceControllerTest { @Autowired InstanceRepository instanceRepository; @Autowired - FilesService filesService; + FilesManager filesManager; @BeforeEach public void setup() { diff --git a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceFacadeTest.java b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceFacadeTest.java index 7757ebe6..f1eac0c3 100644 --- a/src/test/java/com/genius/gitget/challenge/instance/service/InstanceFacadeTest.java +++ b/src/test/java/com/genius/gitget/challenge/instance/service/InstanceFacadeTest.java @@ -16,7 +16,7 @@ import com.genius.gitget.global.file.domain.FileType; import com.genius.gitget.global.file.domain.Files; import com.genius.gitget.global.file.repository.FilesRepository; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.repository.TopicRepository; @@ -45,7 +45,7 @@ public class InstanceFacadeTest { @Autowired TopicRepository topicRepository; @Autowired - FilesService filesService; + FilesManager filesManager; @Autowired FilesRepository filesRepository; @Autowired diff --git a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java index 9e106dc4..f5e0417f 100644 --- a/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java +++ b/src/test/java/com/genius/gitget/challenge/likes/controller/LikesControllerTest.java @@ -19,7 +19,7 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.repository.TopicRepository; @@ -54,7 +54,7 @@ public class LikesControllerTest { @Autowired InstanceRepository instanceRepository; @Autowired - FilesService filesService; + FilesManager filesManager; @Autowired LikesService likesService; @Autowired diff --git a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java index 339a4a98..195217f9 100644 --- a/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java +++ b/src/test/java/com/genius/gitget/global/file/service/FileUtilTest.java @@ -33,7 +33,7 @@ class FileUtilTest { @Autowired private FileUtil fileUtil; @Autowired - private FilesService filesService; + private FilesManager filesManager; @Value("${file.upload.path}") private String UPLOAD_PATH; diff --git a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java index fa7b9f97..493d4520 100644 --- a/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java +++ b/src/test/java/com/genius/gitget/payment/controller/PaymentControllerTest.java @@ -7,7 +7,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.util.security.TokenTestUtil; import com.genius.gitget.util.security.WithMockCustomUser; @@ -38,7 +38,7 @@ public class PaymentControllerTest { @Autowired TopicRepository topicRepository; @Autowired - FilesService filesService; + FilesManager filesManager; @Autowired private ObjectMapper objectMapper; diff --git a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java index fbfa2751..44bc56ec 100644 --- a/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java +++ b/src/test/java/com/genius/gitget/profile/controller/ProfileControllerTest.java @@ -17,7 +17,7 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.global.security.constants.ProviderInfo; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.repository.TopicRepository; @@ -57,7 +57,7 @@ public class ProfileControllerTest { @Autowired InstanceRepository instanceRepository; @Autowired - FilesService filesService; + FilesManager filesManager; @Autowired LikesService likesService; @Autowired diff --git a/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java b/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java index 3f4193df..d190e3a2 100644 --- a/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java +++ b/src/test/java/com/genius/gitget/topic/controller/TopicControllerTest.java @@ -9,7 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.global.file.service.FilesService; +import com.genius.gitget.global.file.service.FilesManager; import com.genius.gitget.topic.domain.Topic; import com.genius.gitget.topic.repository.TopicRepository; import com.genius.gitget.util.security.TokenTestUtil; @@ -38,7 +38,7 @@ public class TopicControllerTest { @Autowired TopicRepository topicRepository; @Autowired - FilesService filesService; + FilesManager filesManager; @Autowired private ObjectMapper objectMapper; From 042bbd92b0af9cd61587ed1f9ce5dbf5ce3e6229 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:02:06 +0900 Subject: [PATCH 217/234] =?UTF-8?q?feat:=20JWT=20=EB=B0=9C=EA=B8=89=20API?= =?UTF-8?q?=EC=97=90=EC=84=9C=20header=20=EC=B6=94=EA=B0=80=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20(#250)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JWT 발급 API에서 token-reissued의 값을 true로 설정 --- .../genius/gitget/global/security/controller/AuthController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 9640b41f..269b65ec 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -41,6 +41,7 @@ public ResponseEntity> generateToken(HttpServletRes jwtFacade.generateAccessToken(response, user); jwtFacade.generateRefreshToken(response, user); + jwtFacade.setReissuedHeader(response); AuthResponse authResponse = userService.getUserAuthInfo(user.getIdentifier()); From 3cb16ac47bc10a952f81bbfb1c4663210d2b16fc Mon Sep 17 00:00:00 2001 From: DoHyung Kim Date: Mon, 26 Aug 2024 15:19:19 +0900 Subject: [PATCH 218/234] =?UTF-8?q?feat:=20Pageble=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=EA=B0=80=20=EB=93=A4=EC=96=B4=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=EC=97=90=20Lim?= =?UTF-8?q?itedSizePagination=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=A0=81=EC=9A=A9=20(#255)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gitget/global/page/CustomPageImpl.java | 42 +++++++++++++++++++ .../global/page/LimitedSizePagination.java | 18 ++++++++ .../gitget/page/CustomPageImplTest.java | 42 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 src/main/java/com/genius/gitget/global/page/CustomPageImpl.java create mode 100644 src/main/java/com/genius/gitget/global/page/LimitedSizePagination.java create mode 100644 src/test/java/com/genius/gitget/page/CustomPageImplTest.java diff --git a/src/main/java/com/genius/gitget/global/page/CustomPageImpl.java b/src/main/java/com/genius/gitget/global/page/CustomPageImpl.java new file mode 100644 index 00000000..c69ea0b3 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/page/CustomPageImpl.java @@ -0,0 +1,42 @@ +package com.genius.gitget.global.page; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CustomPageImpl extends PageImpl { + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public CustomPageImpl(@JsonProperty("content") List content, @JsonProperty("number") int number, + @JsonProperty("size") int size, + @JsonProperty("totalElements") Long totalElements, @JsonProperty("pageable") JsonNode pageable, + @JsonProperty("last") boolean last, + @JsonProperty("totalPages") int totalPages, @JsonProperty("sort") JsonNode sort, + @JsonProperty("first") boolean first, + @JsonProperty("numberOfElements") int numberOfElements) { + super(content, PageRequest.of(number, size), totalElements); + } + + public CustomPageImpl(List content, Pageable pageable, Long total) { + super(content, pageable, total); + } + + public CustomPageImpl(List content) { + super(content); + } + + public CustomPageImpl() { + super(new ArrayList()); + } +} + diff --git a/src/main/java/com/genius/gitget/global/page/LimitedSizePagination.java b/src/main/java/com/genius/gitget/global/page/LimitedSizePagination.java new file mode 100644 index 00000000..486dde52 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/page/LimitedSizePagination.java @@ -0,0 +1,18 @@ +package com.genius.gitget.global.page; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.validation.constraints.Positive; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface LimitedSizePagination { + @Positive + int maxSize() default 200; +} + diff --git a/src/test/java/com/genius/gitget/page/CustomPageImplTest.java b/src/test/java/com/genius/gitget/page/CustomPageImplTest.java new file mode 100644 index 00000000..6440a913 --- /dev/null +++ b/src/test/java/com/genius/gitget/page/CustomPageImplTest.java @@ -0,0 +1,42 @@ +package com.genius.gitget.page; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.genius.gitget.global.page.CustomPageImpl; + +public class CustomPageImplTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void testCustomPageImplSerialization() throws JsonProcessingException { + + // 데이터 목록과 페이지 정보를 설정 + List data = List.of("item1", "item2", "item3"); + PageRequest pageRequest = PageRequest.of(0, 10); + + // CustomPageImpl 객체 생성 + CustomPageImpl customPage = new CustomPageImpl<>(data, pageRequest, 3L); + + // CustomPageImpl 객체를 JSON으로 직렬화 + String json = objectMapper.writeValueAsString(customPage); + + System.out.println(json); + + // JSON 문자열을 다시 CustomPageImpl 객체로 역직렬화 + CustomPageImpl deserializedPage = objectMapper.readValue(json, CustomPageImpl.class); + + assertNotNull(deserializedPage); + assertEquals(customPage.getContent(), deserializedPage.getContent()); + assertEquals(customPage.getTotalElements(), deserializedPage.getTotalElements()); + assertEquals(customPage.getPageable().getPageNumber(), deserializedPage.getPageable().getPageNumber()); + } +} + From a6912a0992acf66eae95d5e3e0adf6966137cf19 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:58:36 +0900 Subject: [PATCH 219/234] =?UTF-8?q?[FEAT]=20Spring=20security=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 커스텀 어노테이션 개발 - AuthenticationPrincipal 의 값을 받을 수 있는 커스텀 어노테이션 개발 * refactor: 커스텀 어노테이션 적용 - @AuthenticationPrincipal 어노테이션이 적용되어 있는 곳을 @GitGetUser 어노테이션으로 변경 * refactor: 커스텀 어노테이션 적용 - @AuthenticationPrincipal 어노테이션 있던 곳에 커스텀 어노테이션(@GitGetUser) 적용 --- .../controller/CertificationController.java | 23 +++++------ .../controller/GithubController.java | 24 ++++++------ .../controller/InstanceDetailController.java | 16 ++++---- .../controller/InstanceHomeController.java | 8 ++-- .../likes/controller/LikesController.java | 16 ++++---- .../controller/MyChallengeController.java | 23 +++++------ .../security/controller/AuthController.java | 9 ++--- .../global/util/annotation/GitGetUser.java | 13 +++++++ .../profile/controller/ProfileController.java | 38 +++++++++---------- .../item/controller/StoreController.java | 23 +++++------ .../payment/controller/PaymentController.java | 10 ++--- .../controller/PaymentTossController.java | 8 ++-- 12 files changed, 102 insertions(+), 109 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/util/annotation/GitGetUser.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java index 07f32680..79842ad0 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/CertificationController.java @@ -17,7 +17,7 @@ import com.genius.gitget.challenge.participant.service.ParticipantService; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.response.dto.SingleResponse; import com.genius.gitget.global.util.response.dto.SlicingResponse; import java.time.LocalDate; @@ -28,7 +28,6 @@ import org.springframework.data.domain.Slice; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -61,11 +60,11 @@ public ResponseEntity> getInstanceInform @PostMapping("/today") public ResponseEntity> certificateByGithub( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestBody CertificationRequest certificationRequest ) { CertificationResponse certificationResponse = certificationFacade.updateCertification( - userPrincipal.getUser(), certificationRequest); + user, certificationRequest); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), certificationResponse) @@ -74,10 +73,9 @@ public ResponseEntity> certificateByGithub @PostMapping("/pass") public ResponseEntity> passCertification( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestBody CertificationRequest certificationRequest ) { - User user = userPrincipal.getUser(); ActivatedResponse activatedResponse = certificationFacade.passCertification( user.getId(), certificationRequest); @@ -88,11 +86,11 @@ public ResponseEntity> passCertification( @GetMapping("/week/{instanceId}") public ResponseEntity> getWeekCertification( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable Long instanceId ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); - Participant participant = participantService.findByJoinInfo(userPrincipal.getUser().getId(), instanceId); + Participant participant = participantService.findByJoinInfo(user.getId(), instanceId); WeekResponse weekResponse = certificationFacade.getMyWeekCertifications(participant.getId(), kstDate); return ResponseEntity.ok().body( @@ -102,12 +100,11 @@ public ResponseEntity> getWeekCertification( @GetMapping("/week/all/{instanceId}") public ResponseEntity> getAllUserWeekCertification( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable Long instanceId, @PageableDefault Pageable pageable ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); - User user = userPrincipal.getUser(); Slice certifications = certificationFacade.getOthersWeekCertifications( user.getId(), instanceId, kstDate, pageable); @@ -134,15 +131,13 @@ public ResponseEntity> getTotalCertifications( @GetMapping("/information/{instanceId}") public ResponseEntity> getCertificationInformation( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable Long instanceId ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); Instance instance = instanceService.findInstanceById(instanceId); - Participant participant = participantService.findByJoinInfo( - userPrincipal.getUser().getId(), - instanceId); + Participant participant = participantService.findByJoinInfo(user.getId(), instanceId); CertificationInformation certificationInformation = certificationFacade.getCertificationInformation( instance, participant, kstDate); diff --git a/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java b/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java index 385bdd41..750f11db 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java +++ b/src/main/java/com/genius/gitget/challenge/certification/controller/GithubController.java @@ -6,7 +6,8 @@ import com.genius.gitget.challenge.certification.dto.github.PullRequestResponse; import com.genius.gitget.challenge.certification.facade.GithubFacade; import com.genius.gitget.challenge.certification.util.DateUtil; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.ListResponse; import java.time.LocalDateTime; @@ -14,7 +15,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -31,10 +31,10 @@ public class GithubController { @PostMapping("/register/token") public ResponseEntity registerGithubToken( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestBody GithubTokenRequest githubTokenRequest ) { - githubFacade.registerGithubPersonalToken(userPrincipal.getUser(), githubTokenRequest.githubToken()); + githubFacade.registerGithubPersonalToken(user, githubTokenRequest.githubToken()); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) @@ -43,9 +43,9 @@ public ResponseEntity registerGithubToken( @GetMapping("/repositories") public ResponseEntity> getPublicRepositories( - @AuthenticationPrincipal UserPrincipal userPrincipal + @GitGetUser User user ) { - List repositories = githubFacade.getPublicRepositories(userPrincipal.getUser()); + List repositories = githubFacade.getPublicRepositories(user); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), repositories) @@ -54,9 +54,9 @@ public ResponseEntity> getPublicRepositories( @GetMapping("/verify/token") public ResponseEntity verifyGithubToken( - @AuthenticationPrincipal UserPrincipal userPrincipal + @GitGetUser User user ) { - githubFacade.verifyGithubToken(userPrincipal.getUser()); + githubFacade.verifyGithubToken(user); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) @@ -65,11 +65,11 @@ public ResponseEntity verifyGithubToken( @GetMapping("/verify/repository") public ResponseEntity verifyRepository( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestParam String repo ) { - githubFacade.verifyRepository(userPrincipal.getUser(), repo); + githubFacade.verifyRepository(user, repo); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) @@ -78,12 +78,12 @@ public ResponseEntity verifyRepository( @GetMapping("/verify/pull-request") public ResponseEntity> verifyPullRequest( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestParam String repo ) { List pullRequestResponses = githubFacade.verifyPullRequest( - userPrincipal.getUser(), repo, DateUtil.convertToKST(LocalDateTime.now()) + user, repo, DateUtil.convertToKST(LocalDateTime.now()) ); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java index 0dbaf390..b12926b1 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceDetailController.java @@ -9,14 +9,14 @@ import com.genius.gitget.challenge.instance.dto.detail.JoinRequest; import com.genius.gitget.challenge.instance.dto.detail.JoinResponse; import com.genius.gitget.challenge.instance.service.InstanceDetailFacade; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.time.LocalDate; import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -35,11 +35,11 @@ public class InstanceDetailController { @GetMapping("/{instanceId}") public ResponseEntity> getInstanceDetail( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable Long instanceId ) { InstanceResponse instanceDetailInformation = instanceDetailFacade.getInstanceDetailInformation( - userPrincipal.getUser(), instanceId); + user, instanceId); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), instanceDetailInformation) @@ -48,7 +48,7 @@ public ResponseEntity> getInstanceDetail( @PostMapping("/{instanceId}") public ResponseEntity> joinChallenge( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable Long instanceId, @RequestParam String repo ) { @@ -58,7 +58,7 @@ public ResponseEntity> joinChallenge( .repository(repo) .todayDate(kstDate) .build(); - JoinResponse joinResponse = instanceDetailFacade.joinNewChallenge(userPrincipal.getUser(), joinRequest); + JoinResponse joinResponse = instanceDetailFacade.joinNewChallenge(user, joinRequest); return ResponseEntity.ok().body( new SingleResponse<>(JOIN_SUCCESS.getStatus(), JOIN_SUCCESS.getMessage(), joinResponse) @@ -67,10 +67,10 @@ public ResponseEntity> joinChallenge( @DeleteMapping("/{instanceId}") public ResponseEntity> quitChallenge( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable Long instanceId ) { - JoinResponse joinResponse = instanceDetailFacade.quitChallenge(userPrincipal.getUser(), instanceId); + JoinResponse joinResponse = instanceDetailFacade.quitChallenge(user, instanceId); return ResponseEntity.ok().body( new SingleResponse<>(QUIT_SUCCESS.getStatus(), QUIT_SUCCESS.getMessage(), joinResponse) diff --git a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java index f0f83a3b..dd8721e7 100644 --- a/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java +++ b/src/main/java/com/genius/gitget/challenge/instance/controller/InstanceHomeController.java @@ -6,7 +6,8 @@ import com.genius.gitget.challenge.instance.dto.search.InstanceSearchRequest; import com.genius.gitget.challenge.instance.dto.search.InstanceSearchResponse; import com.genius.gitget.challenge.instance.facade.InstanceHomeFacade; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.global.util.response.dto.SlicingResponse; @@ -18,7 +19,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -46,13 +46,13 @@ public ResponseEntity> searchInstances( @GetMapping("/recommend") public ResponseEntity> getRecommendInstances( Pageable pageable, - @AuthenticationPrincipal UserPrincipal userPrincipal) { + @GitGetUser User user) { PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(Direction.DESC, "participantCount")); Slice recommendations = instanceHomeFacade.recommendInstances( - userPrincipal.getUser(), pageRequest); + user, pageRequest); return ResponseEntity.ok().body( new SlicingResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), recommendations) ); diff --git a/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java b/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java index 95fb667a..5eabc5dd 100644 --- a/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java +++ b/src/main/java/com/genius/gitget/challenge/likes/controller/LikesController.java @@ -4,7 +4,8 @@ import com.genius.gitget.challenge.likes.dto.UserLikesAddResponse; import com.genius.gitget.challenge.likes.dto.UserLikesResponse; import com.genius.gitget.challenge.likes.facade.LikesFacade; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.PagingResponse; @@ -15,7 +16,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -35,10 +35,10 @@ public class LikesController { @GetMapping("/likes") public ResponseEntity> getLikesListOfUser( Pageable pageable, - @AuthenticationPrincipal UserPrincipal userPrincipal) { + @GitGetUser User user) { PageRequest pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); - Page likesResponses = likesFacade.getLikesList(userPrincipal.getUser(), pageRequest); + Page likesResponses = likesFacade.getLikesList(user, pageRequest); return ResponseEntity.ok().body( new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), likesResponses) @@ -48,9 +48,9 @@ public ResponseEntity> getLikesListOfUser( // 좋아요 목록 추가 @PostMapping("/likes") public ResponseEntity> addLikes( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestBody UserLikesAddRequest userLikesAddRequest) { - UserLikesAddResponse userLikesAddResponse = likesFacade.addLikes(userPrincipal.getUser(), + UserLikesAddResponse userLikesAddResponse = likesFacade.addLikes(user, userLikesAddRequest.getIdentifier(), userLikesAddRequest.getInstanceId()); return ResponseEntity.ok().body( @@ -61,9 +61,9 @@ public ResponseEntity> addLikes( // 좋아요 목록 삭제 @DeleteMapping("/likes/{likesId}") - public ResponseEntity deleteLikes(@AuthenticationPrincipal UserPrincipal userPrincipal, + public ResponseEntity deleteLikes(@GitGetUser User user, @PathVariable(value = "likesId") Long likesId) { - likesFacade.deleteLikes(userPrincipal.getUser(), likesId); + likesFacade.deleteLikes(user, likesId); return ResponseEntity.ok().body( new CommonResponse(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage()) ); diff --git a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java index 22f17812..3ebe05eb 100644 --- a/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java +++ b/src/main/java/com/genius/gitget/challenge/myChallenge/controller/MyChallengeController.java @@ -8,7 +8,8 @@ import com.genius.gitget.challenge.myChallenge.dto.PreActivityResponse; import com.genius.gitget.challenge.myChallenge.dto.RewardRequest; import com.genius.gitget.challenge.myChallenge.facade.MyChallengeFacade; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.response.dto.ListResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import java.time.LocalDate; @@ -16,7 +17,6 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -32,11 +32,10 @@ public class MyChallengeController { @GetMapping("/my/pre-activity") public ResponseEntity> getPreActivityChallenges( - @AuthenticationPrincipal UserPrincipal userPrincipal + @GitGetUser User user ) { List preActivityInstances = myChallengeFacade.getPreActivityInstances( - userPrincipal.getUser(), - DateUtil.convertToKST(LocalDateTime.now())); + user, DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), preActivityInstances) @@ -46,11 +45,10 @@ public ResponseEntity> getPreActivityChallenge @GetMapping("/my/activity") public ResponseEntity> getActivatedChallenges( - @AuthenticationPrincipal UserPrincipal userPrincipal + @GitGetUser User user ) { List activatedInstances = myChallengeFacade.getActivatedInstances( - userPrincipal.getUser(), - DateUtil.convertToKST(LocalDateTime.now())); + user, DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), activatedInstances) @@ -59,11 +57,10 @@ public ResponseEntity> getActivatedChallenges( @GetMapping("/my/done") public ResponseEntity> getDoneChallenges( - @AuthenticationPrincipal UserPrincipal userPrincipal + @GitGetUser User user ) { List doneInstances = myChallengeFacade.getDoneInstances( - userPrincipal.getUser(), - DateUtil.convertToKST(LocalDateTime.now())); + user, DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), doneInstances) @@ -72,11 +69,11 @@ public ResponseEntity> getDoneChallenges( @GetMapping("/reward/{instanceId}") public ResponseEntity> getRewards( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable Long instanceId ) { LocalDate kstDate = DateUtil.convertToKST(LocalDateTime.now()); - RewardRequest rewardRequest = new RewardRequest(userPrincipal.getUser().getId(), instanceId, kstDate); + RewardRequest rewardRequest = new RewardRequest(user.getId(), instanceId, kstDate); DoneResponse doneResponse = myChallengeFacade.getRewards(rewardRequest); return ResponseEntity.ok().body( diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 269b65ec..71e358fa 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -5,10 +5,10 @@ import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.service.UserService; -import com.genius.gitget.global.security.domain.UserPrincipal; import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.security.dto.TokenRequest; import com.genius.gitget.global.security.service.JwtFacade; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -16,7 +16,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -51,10 +50,8 @@ public ResponseEntity> generateToken(HttpServletRes } @PostMapping("/logout") - public ResponseEntity logout( - @AuthenticationPrincipal UserPrincipal userPrincipal, - HttpServletResponse response) { - jwtFacade.logout(response, userPrincipal.getUser().getIdentifier()); + public ResponseEntity logout(@GitGetUser User user, HttpServletResponse response) { + jwtFacade.logout(response, user.getIdentifier()); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) diff --git a/src/main/java/com/genius/gitget/global/util/annotation/GitGetUser.java b/src/main/java/com/genius/gitget/global/util/annotation/GitGetUser.java new file mode 100644 index 00000000..bd20f7a4 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/util/annotation/GitGetUser.java @@ -0,0 +1,13 @@ +package com.genius.gitget.global.util.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.security.core.annotation.AuthenticationPrincipal; + +@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : user") +public @interface GitGetUser { +} diff --git a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java index 0be708a9..854baad2 100644 --- a/src/main/java/com/genius/gitget/profile/controller/ProfileController.java +++ b/src/main/java/com/genius/gitget/profile/controller/ProfileController.java @@ -2,7 +2,8 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import com.genius.gitget.profile.dto.UserChallengeResultResponse; @@ -18,7 +19,6 @@ import com.genius.gitget.profile.service.ProfileFacade; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -35,9 +35,8 @@ public class ProfileController { // 마이페이지 - 사용자 상세 정보 조회 @GetMapping public ResponseEntity> getUserDetailsInformation( - @AuthenticationPrincipal UserPrincipal userPrincipal) { - UserDetailsInformationResponse userInformation = profileFacade.getUserDetailsInformation( - userPrincipal.getUser()); + @GitGetUser User user) { + UserDetailsInformationResponse userInformation = profileFacade.getUserDetailsInformation(user); return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userInformation) @@ -58,10 +57,10 @@ public ResponseEntity> getUserInformatio // 마이페이지 - 회원 정보 수정 @PostMapping("/information") public ResponseEntity> updateUserInformation( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestBody UserInformationUpdateRequest userInformationUpdateRequest) { - Long userId = profileFacade.updateUserInformation(userPrincipal.getUser(), userInformationUpdateRequest); + Long userId = profileFacade.updateUserInformation(user, userInformationUpdateRequest); UserIndexResponse userIndexResponse = new UserIndexResponse(userId); return ResponseEntity.ok().body( @@ -71,9 +70,8 @@ public ResponseEntity> updateUserInformation( // 마이페이지 - 관심사 조회 @GetMapping("/interest") - public ResponseEntity> getUserInterest( - @AuthenticationPrincipal UserPrincipal userPrincipal) { - UserInterestResponse userInterest = profileFacade.getUserInterest(userPrincipal.getUser()); + public ResponseEntity> getUserInterest(@GitGetUser User user) { + UserInterestResponse userInterest = profileFacade.getUserInterest(user); return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), @@ -83,9 +81,9 @@ public ResponseEntity> getUserInterest( // 마이페이지 - 관심사 수정 @PostMapping("/interest") - public ResponseEntity updateUserTags(@AuthenticationPrincipal UserPrincipal userPrincipal, + public ResponseEntity updateUserTags(@GitGetUser User user, @RequestBody UserInterestUpdateRequest userInterestUpdateRequest) { - profileFacade.updateUserTags(userPrincipal.getUser(), userInterestUpdateRequest); + profileFacade.updateUserTags(user, userInterestUpdateRequest); return ResponseEntity.ok() .body(new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage())); @@ -94,10 +92,9 @@ public ResponseEntity updateUserTags(@AuthenticationPrincipal Us // 마이페이지 - 챌린지 현황 @GetMapping("/challenges") - public ResponseEntity> getUserChallengeResult( - @AuthenticationPrincipal UserPrincipal userPrincipal) { - UserChallengeResultResponse userChallengeResult = profileFacade.getUserChallengeResult( - userPrincipal.getUser()); + public ResponseEntity> getUserChallengeResult(@GitGetUser User user) { + UserChallengeResultResponse userChallengeResult = profileFacade.getUserChallengeResult(user); + return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), userChallengeResult)); @@ -106,9 +103,9 @@ public ResponseEntity> getUserChalle // 마이페이지 - 탈퇴하기 @DeleteMapping - public ResponseEntity deleteUserInformation(@AuthenticationPrincipal UserPrincipal userPrincipal, + public ResponseEntity deleteUserInformation(@GitGetUser User user, @RequestBody UserSignoutRequest userSignoutRequest) { - profileFacade.deleteUserInformation(userPrincipal.getUser(), userSignoutRequest.getReason()); + profileFacade.deleteUserInformation(user, userSignoutRequest.getReason()); return ResponseEntity.ok() .body(new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage())); @@ -117,9 +114,8 @@ public ResponseEntity deleteUserInformation(@AuthenticationPrinc // 포인트 조회 @GetMapping("/point") - public ResponseEntity> getUserPoint( - @AuthenticationPrincipal UserPrincipal userPrincipal) { - UserPointResponse userPoint = profileFacade.getUserPoint(userPrincipal.getUser()); + public ResponseEntity> getUserPoint(@GitGetUser User user) { + UserPointResponse userPoint = profileFacade.getUserPoint(user); return ResponseEntity.ok() .body(new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), diff --git a/src/main/java/com/genius/gitget/store/item/controller/StoreController.java b/src/main/java/com/genius/gitget/store/item/controller/StoreController.java index 5d58c671..3aab21e3 100644 --- a/src/main/java/com/genius/gitget/store/item/controller/StoreController.java +++ b/src/main/java/com/genius/gitget/store/item/controller/StoreController.java @@ -3,7 +3,8 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.challenge.certification.util.DateUtil; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.ListResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -16,7 +17,6 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -29,15 +29,14 @@ @RequestMapping("/api") public class StoreController { private final StoreFacade storeFacade; -// private final ItemService itemService; @GetMapping("/items") public ResponseEntity> getItemList( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestParam String category ) { ItemCategory itemCategory = ItemCategory.findCategory(category); - List itemResponses = storeFacade.getItemsByCategory(userPrincipal.getUser(), itemCategory); + List itemResponses = storeFacade.getItemsByCategory(user, itemCategory); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemResponses) @@ -46,10 +45,10 @@ public ResponseEntity> getItemList( @PostMapping("/items/order/{identifier}") public ResponseEntity> purchaseItem( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable int identifier ) { - ItemResponse itemResponse = storeFacade.orderItem(userPrincipal.getUser(), identifier); + ItemResponse itemResponse = storeFacade.orderItem(user, identifier); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), itemResponse) @@ -58,11 +57,11 @@ public ResponseEntity> purchaseItem( @PostMapping("/items/use/{identifier}") public ResponseEntity useItem( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @PathVariable int identifier, @RequestParam(required = false) Long instanceId ) { - OrderResponse orderResponse = storeFacade.useItem(userPrincipal.getUser(), identifier, + OrderResponse orderResponse = storeFacade.useItem(user, identifier, instanceId, DateUtil.convertToKST(LocalDateTime.now())); return ResponseEntity.ok().body( @@ -71,10 +70,8 @@ public ResponseEntity useItem( } @PostMapping("/items/unuse") - public ResponseEntity> unmountItem( - @AuthenticationPrincipal UserPrincipal userPrincipal - ) { - List profileResponses = storeFacade.unmountFrame(userPrincipal.getUser()); + public ResponseEntity> unmountItem(@GitGetUser User user) { + List profileResponses = storeFacade.unmountFrame(user); return ResponseEntity.ok().body( new ListResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), profileResponses) diff --git a/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java b/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java index 99017a14..b38ae9ef 100644 --- a/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java +++ b/src/main/java/com/genius/gitget/store/payment/controller/PaymentController.java @@ -1,6 +1,7 @@ package com.genius.gitget.store.payment.controller; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.PagingResponse; import com.genius.gitget.store.payment.dto.PaymentDetailsResponse; @@ -11,7 +12,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -25,13 +25,11 @@ public class PaymentController { private final PaymentService paymentService; @GetMapping - public ResponseEntity> getPaymentDetails(@AuthenticationPrincipal - UserPrincipal userPrincipal, + public ResponseEntity> getPaymentDetails(@GitGetUser User user, @PageableDefault Pageable pageable) { - Page paymentDetails = paymentService.getPaymentDetails(userPrincipal.getUser(), - pageable); + Page paymentDetails = paymentService.getPaymentDetails(user, pageable); return ResponseEntity.ok().body( new PagingResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), paymentDetails) diff --git a/src/main/java/com/genius/gitget/store/payment/controller/PaymentTossController.java b/src/main/java/com/genius/gitget/store/payment/controller/PaymentTossController.java index 8abe77c9..0cfd9d9f 100644 --- a/src/main/java/com/genius/gitget/store/payment/controller/PaymentTossController.java +++ b/src/main/java/com/genius/gitget/store/payment/controller/PaymentTossController.java @@ -1,6 +1,7 @@ package com.genius.gitget.store.payment.controller; -import com.genius.gitget.global.security.domain.UserPrincipal; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.global.util.annotation.GitGetUser; import com.genius.gitget.global.util.exception.SuccessCode; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -13,7 +14,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -29,9 +29,9 @@ public class PaymentTossController { @PostMapping public ResponseEntity> requestTossPayment( - @AuthenticationPrincipal UserPrincipal userPrincipal, + @GitGetUser User user, @RequestBody PaymentRequest paymentRequest) { - PaymentResponse paymentResponse = paymentService.requestTossPayment(userPrincipal.getUser(), paymentRequest); + PaymentResponse paymentResponse = paymentService.requestTossPayment(user, paymentRequest); return ResponseEntity.ok().body( new SingleResponse<>(SuccessCode.SUCCESS.getStatus(), SuccessCode.SUCCESS.getMessage(), paymentResponse) ); From 77323c64b7cd9ce5c870b54813b22a4464218f1f Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 2 Sep 2024 07:00:06 +0900 Subject: [PATCH 220/234] =?UTF-8?q?[REFACTOR]=20UserService=20=EA=B3=84?= =?UTF-8?q?=EC=B8=B5=EC=97=90=20Facade=20pattern=20=EC=A0=81=EC=9A=A9=20(#?= =?UTF-8?q?259)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: User 계층에 Facade pattern 적용 * chore: 클래스 네이밍 변경 * fix: identifier 자료형을 Boxing type으로 변경 - identifier의 자료형을 null을 받을 수 있는 Boxing type으로 변경 * fix: 사용 중인 프레임이 없을 때의 응답값 수정 - 사용 중인 프레임이 없을 때 identifier의 값으로 null을 전달하게끔 수정 * test: UserFacadeTest에 DCI패턴 적용 * test: 테스트 코드 수정 --- .../service/CertificationFacadeService.java | 11 +- .../user/controller/UserController.java | 13 +- .../challenge/user/facade/UserFacade.java | 16 + .../user/facade/UserFacadeService.java | 78 +++++ .../challenge/user/service/UserService.java | 74 +---- .../security/controller/AuthController.java | 18 +- .../global/security/dto/AuthResponse.java | 2 +- .../global/security/dto/SignupResponse.java | 4 + .../filter/JwtAuthenticationFilter.java | 2 +- .../profile/service/ProfileFacadeService.java | 10 +- .../genius/gitget/store/item/domain/Item.java | 4 +- .../store/item/service/OrdersService.java | 1 + .../user/service/UserFacadeTest.java | 212 +++++++++++++ .../user/service/UserServiceTest.java | 279 ------------------ .../payment/service/PaymentServiceTest.java | 1 + ...hMockCustomUserSecurityContextFactory.java | 10 +- .../genius/gitget/util/user/UserFactory.java | 8 + 17 files changed, 364 insertions(+), 379 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/user/facade/UserFacade.java create mode 100644 src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java create mode 100644 src/test/java/com/genius/gitget/challenge/user/service/UserFacadeTest.java delete mode 100644 src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java diff --git a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java index 022d9149..dda44f25 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java +++ b/src/main/java/com/genius/gitget/challenge/certification/service/CertificationFacadeService.java @@ -24,6 +24,7 @@ import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.file.dto.FileResponse; import com.genius.gitget.global.file.service.FilesManager; +import com.genius.gitget.store.item.service.OrdersService; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; @@ -49,6 +50,7 @@ public class CertificationFacadeService implements CertificationFacade { private final ParticipantService participantService; private final GithubService githubService; private final CertificationService certificationService; + private final OrdersService ordersService; @Override public WeekResponse getMyWeekCertifications(Long participantId, LocalDate currentDate) { @@ -70,7 +72,7 @@ private WeekResponse getWeekResponse(Participant participant, LocalDate currentD LocalDate instanceStartDate = instance.getStartedDate().toLocalDate(); LocalDate weekStartDate = DateUtil.getWeekStartDate(instanceStartDate, currentDate); - UserProfileInfo userProfileInfo = userService.getUserProfileInfo(participant.getUser()); + UserProfileInfo userProfileInfo = getUserProfileInfo(participant.getUser()); if (!instance.isActivatedInstance()) { return WeekResponse.create(userProfileInfo, new ArrayList<>()); @@ -85,6 +87,13 @@ private WeekResponse getWeekResponse(Participant participant, LocalDate currentD return WeekResponse.create(userProfileInfo, weekCertifications); } + private UserProfileInfo getUserProfileInfo(User user) { + Long frameId = ordersService.getUsingFrameItem(user.getId()).getId(); + FileResponse fileResponse = filesManager.convertToFileResponse(user.getFiles()); + + return UserProfileInfo.createByEntity(user, frameId, fileResponse); + } + private List getWeekCertifications(List certifications, LocalDate startDate, LocalDate currentDate) { List results = new ArrayList<>(); diff --git a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java index 3c6eac51..924457a6 100644 --- a/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java +++ b/src/main/java/com/genius/gitget/challenge/user/controller/UserController.java @@ -4,7 +4,7 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.challenge.user.dto.SignupRequest; -import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.challenge.user.facade.UserFacade; import com.genius.gitget.global.security.dto.SignupResponse; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; @@ -21,22 +21,19 @@ @RequiredArgsConstructor @RequestMapping("/api") public class UserController { - private final UserService userService; + private final UserFacade userFacade; @GetMapping("/auth/check-nickname") public ResponseEntity checkNicknameDuplicate(@RequestParam(value = "nickname") String nickname) { - userService.isNicknameDuplicate(nickname); + userFacade.isNicknameDuplicate(nickname); return ResponseEntity.ok().body( new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage()) ); } @PostMapping("/auth/signup") - public ResponseEntity> signup( - @RequestBody SignupRequest signupRequest) { - Long userId = userService.signup(signupRequest); - String identifier = userService.findUserById(userId).getIdentifier(); - SignupResponse signupResponse = new SignupResponse(userId, identifier); + public ResponseEntity> signup(@RequestBody SignupRequest signupRequest) { + SignupResponse signupResponse = userFacade.signup(signupRequest); return ResponseEntity.ok().body( new SingleResponse<>(CREATED.getStatus(), CREATED.getMessage(), signupResponse) diff --git a/src/main/java/com/genius/gitget/challenge/user/facade/UserFacade.java b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacade.java new file mode 100644 index 00000000..e64a4d53 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacade.java @@ -0,0 +1,16 @@ +package com.genius.gitget.challenge.user.facade; + +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.global.security.dto.AuthResponse; +import com.genius.gitget.global.security.dto.SignupResponse; + +public interface UserFacade { + void isNicknameDuplicate(String nickname); + + SignupResponse signup(SignupRequest signupRequest); + + AuthResponse getUserAuthInfo(String identifier); + + User getAuthUser(String identifier); +} diff --git a/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java new file mode 100644 index 00000000..22a86990 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java @@ -0,0 +1,78 @@ +package com.genius.gitget.challenge.user.facade; + +import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REGISTERED; +import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; + +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.security.dto.AuthResponse; +import com.genius.gitget.global.security.dto.SignupResponse; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.service.OrdersService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class UserFacadeService implements UserFacade { + private final UserService userService; + private final OrdersService ordersService; + + @Value("${admin.githubId}") + private List adminIds; + + @Override + public void isNicknameDuplicate(String nickname) { + String target = nickname.trim(); + if (userService.findByNickname(target).isPresent()) { + throw new BusinessException(DUPLICATED_NICKNAME); + } + } + + @Override + public SignupResponse signup(SignupRequest signupRequest) { + User user = userService.findByIdentifier(signupRequest.identifier()); + + if (user.getRole() != Role.NOT_REGISTERED) { + throw new BusinessException(ALREADY_REGISTERED); + } + + String interest = String.join(",", signupRequest.interest()); + user.updateUser(signupRequest.nickname(), signupRequest.information(), interest); + updateRole(user); + + return SignupResponse.of(user.getId(), user.getIdentifier()); + } + + private void updateRole(User user) { + if (adminIds.contains(user.getIdentifier())) { + user.updateRole(Role.ADMIN); + return; + } + user.updateRole(Role.USER); + } + + @Override + public AuthResponse getUserAuthInfo(String identifier) { + User user = userService.findByIdentifier(identifier); + Item usingFrame = ordersService.getUsingFrameItem(user.getId()); + return new AuthResponse(user.getRole(), usingFrame.getIdentifier()); + } + + @Override + public User getAuthUser(String identifier) { + User user = userService.findByIdentifier(identifier); + if (!user.isRegistered()) { + throw new BusinessException(NOT_AUTHENTICATED_USER); + } + return user; + } +} diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 67f1804b..96ecc01c 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -1,29 +1,18 @@ package com.genius.gitget.challenge.user.service; -import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REGISTERED; -import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; import com.genius.gitget.challenge.certification.util.EncryptUtil; -import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.dto.SignupRequest; -import com.genius.gitget.challenge.user.dto.UserProfileInfo; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.file.dto.FileResponse; -import com.genius.gitget.global.file.service.FilesManager; -import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.exception.ErrorCode; import com.genius.gitget.signout.Signout; import com.genius.gitget.signout.SignoutRepository; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.service.OrdersService; -import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,13 +22,8 @@ @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; - private final OrdersService ordersService; - private final FilesManager filesManager; - private final EncryptUtil encryptUtil; private final SignoutRepository signoutRepository; - - @Value("${admin.githubId}") - private List adminIds; + private final EncryptUtil encryptUtil; public User findUserById(Long id) { @@ -47,11 +31,15 @@ public User findUserById(Long id) { .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); } - public User findUserByIdentifier(String identifier) { + public User findByIdentifier(String identifier) { return userRepository.findByIdentifier(identifier) .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); } + public Optional findByNickname(String nickname) { + return userRepository.findByNickname(nickname); + } + @Transactional public Long save(User user) { return userRepository.saveAndFlush(user).getId(); @@ -75,34 +63,6 @@ public Long getUserPoint(Long userId) { return user.getPoint(); } - @Transactional - public Long signup(SignupRequest requestUser) { - User user = findUserByIdentifier(requestUser.identifier()); - isAlreadyRegistered(user); - - String interest = String.join(",", requestUser.interest()); - user.updateUser(requestUser.nickname(), - requestUser.information(), - interest); - updateRole(user); - - return user.getId(); - } - - private void updateRole(User user) { - if (adminIds.contains(user.getIdentifier())) { - user.updateRole(Role.ADMIN); - return; - } - user.updateRole(Role.USER); - } - - public void isNicknameDuplicate(String nickname) { - if (userRepository.findByNickname(nickname).isPresent()) { - throw new BusinessException(DUPLICATED_NICKNAME); - } - } - public String getGithubToken(User user) { String githubToken = user.getGithubToken(); if (githubToken == null || githubToken.isEmpty() || githubToken.isBlank()) { @@ -110,24 +70,4 @@ public String getGithubToken(User user) { } return encryptUtil.decrypt(githubToken); } - - public void isAlreadyRegistered(User user) { - if (user.getRole() != Role.NOT_REGISTERED) { - throw new BusinessException(ALREADY_REGISTERED); - } - } - - public AuthResponse getUserAuthInfo(String identifier) { - User user = userRepository.findByIdentifier(identifier) - .orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND)); - Item usingFrame = ordersService.getUsingFrameItem(user.getId()); - return new AuthResponse(user.getRole(), usingFrame.getId()); - } - - public UserProfileInfo getUserProfileInfo(User user) { - Long frameId = ordersService.getUsingFrameItem(user.getId()).getId(); - FileResponse fileResponse = filesManager.convertToFileResponse(user.getFiles()); - - return UserProfileInfo.createByEntity(user, frameId, fileResponse); - } } \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 71e358fa..89e45030 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -1,15 +1,13 @@ package com.genius.gitget.global.security.controller; -import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.challenge.user.facade.UserFacade; import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.security.dto.TokenRequest; import com.genius.gitget.global.security.service.JwtFacade; import com.genius.gitget.global.util.annotation.GitGetUser; -import com.genius.gitget.global.util.exception.BusinessException; import com.genius.gitget.global.util.response.dto.CommonResponse; import com.genius.gitget.global.util.response.dto.SingleResponse; import jakarta.servlet.http.HttpServletResponse; @@ -27,22 +25,20 @@ @RequiredArgsConstructor @RequestMapping("/api") public class AuthController { - private final UserService userService; + private final UserFacade userFacade; private final JwtFacade jwtFacade; @PostMapping("/auth") public ResponseEntity> generateToken(HttpServletResponse response, @RequestBody TokenRequest tokenRequest) { - User user = userService.findUserByIdentifier(tokenRequest.identifier()); - if (!user.isRegistered()) { - throw new BusinessException(NOT_AUTHENTICATED_USER); - } - jwtFacade.generateAccessToken(response, user); - jwtFacade.generateRefreshToken(response, user); + User authUser = userFacade.getAuthUser(tokenRequest.identifier()); + + jwtFacade.generateAccessToken(response, authUser); + jwtFacade.generateRefreshToken(response, authUser); jwtFacade.setReissuedHeader(response); - AuthResponse authResponse = userService.getUserAuthInfo(user.getIdentifier()); + AuthResponse authResponse = userFacade.getUserAuthInfo(authUser.getIdentifier()); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), authResponse) diff --git a/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java b/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java index 100dbce9..dd5a3e7c 100644 --- a/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java +++ b/src/main/java/com/genius/gitget/global/security/dto/AuthResponse.java @@ -4,6 +4,6 @@ public record AuthResponse( Role role, - Long frameId + Integer frameId ) { } diff --git a/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java b/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java index a420e497..8a7db7f5 100644 --- a/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java +++ b/src/main/java/com/genius/gitget/global/security/dto/SignupResponse.java @@ -4,4 +4,8 @@ public record SignupResponse( Long userId, String identifier ) { + + public static SignupResponse of(Long userId, String identifier) { + return new SignupResponse(userId, identifier); + } } diff --git a/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java index d1e6778e..65492803 100644 --- a/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/genius/gitget/global/security/filter/JwtAuthenticationFilter.java @@ -64,7 +64,7 @@ private boolean isPermittedURI(String requestURI) { private User findUserByRefreshToken(String refreshToken) { String identifier = jwtFacade.getIdentifierFromRefresh(refreshToken); - return userService.findUserByIdentifier(identifier); + return userService.findByIdentifier(identifier); } private void setAuthenticationToContext(String accessToken) { diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java b/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java index f3376061..7bd4664a 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java @@ -59,7 +59,7 @@ public UserInformationResponse getUserInformation(Long userId) { @Override public UserDetailsInformationResponse getUserDetailsInformation(User user) { - User findUser = userService.findUserByIdentifier(user.getIdentifier()); + User findUser = userService.findByIdentifier(user.getIdentifier()); int participantCount = 0; List participantInfoList = findUser.getParticipantList(); @@ -76,7 +76,7 @@ public UserDetailsInformationResponse getUserDetailsInformation(User user) { @Override public Long updateUserInformation(User user, UserInformationUpdateRequest userInformationUpdateRequest) { - User findUser = userService.findUserByIdentifier(user.getIdentifier()); + User findUser = userService.findByIdentifier(user.getIdentifier()); findUser.updateUserInformation( userInformationUpdateRequest.getNickname(), userInformationUpdateRequest.getInformation()); @@ -86,7 +86,7 @@ public Long updateUserInformation(User user, UserInformationUpdateRequest userIn @Override public void deleteUserInformation(User user, String reason) { - User findUser = userService.findUserByIdentifier(user.getIdentifier()); + User findUser = userService.findByIdentifier(user.getIdentifier()); filesManager.deleteFile(findUser.getFiles()); findUser.setFiles(null); @@ -100,7 +100,7 @@ public void updateUserTags(User user, UserInterestUpdateRequest userInterestUpda if (userInterestUpdateRequest.getTags() == null) { throw new BusinessException(); } - User findUser = userService.findUserByIdentifier(user.getIdentifier()); + User findUser = userService.findByIdentifier(user.getIdentifier()); String interest = String.join(",", userInterestUpdateRequest.getTags()); findUser.updateUserTags(interest); userService.save(findUser); @@ -121,7 +121,7 @@ public UserInterestResponse getUserInterest(User user) { @Override public UserChallengeResultResponse getUserChallengeResult(User user) { - User findUser = userService.findUserByIdentifier(user.getIdentifier()); + User findUser = userService.findByIdentifier(user.getIdentifier()); HashMap> participantHashMap = new HashMap<>() { { put(READY, new ArrayList<>()); diff --git a/src/main/java/com/genius/gitget/store/item/domain/Item.java b/src/main/java/com/genius/gitget/store/item/domain/Item.java index 6597a5c4..c95d4115 100644 --- a/src/main/java/com/genius/gitget/store/item/domain/Item.java +++ b/src/main/java/com/genius/gitget/store/item/domain/Item.java @@ -29,7 +29,7 @@ public class Item extends BaseTimeEntity { private List ordersList = new ArrayList<>(); @Column(unique = true) - private int identifier; + private Integer identifier; private String name; @@ -41,7 +41,7 @@ public class Item extends BaseTimeEntity { private String details; @Builder - public Item(String name, int cost, int identifier, + public Item(String name, int cost, Integer identifier, ItemCategory itemCategory, String details) { this.name = name; this.cost = cost; diff --git a/src/main/java/com/genius/gitget/store/item/service/OrdersService.java b/src/main/java/com/genius/gitget/store/item/service/OrdersService.java index 26457721..6d981a3d 100644 --- a/src/main/java/com/genius/gitget/store/item/service/OrdersService.java +++ b/src/main/java/com/genius/gitget/store/item/service/OrdersService.java @@ -72,6 +72,7 @@ public Item getUsingFrameItem(Long userId) { if (usingFrames.isEmpty()) { return Item.builder() .itemCategory(ItemCategory.PROFILE_FRAME) + .identifier(null) .build(); } return usingFrames.get(0).getItem(); diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserFacadeTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserFacadeTest.java new file mode 100644 index 00000000..5d918cb9 --- /dev/null +++ b/src/test/java/com/genius/gitget/challenge/user/service/UserFacadeTest.java @@ -0,0 +1,212 @@ +package com.genius.gitget.challenge.user.service; + +import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REGISTERED; +import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; +import static com.genius.gitget.store.item.domain.ItemCategory.PROFILE_FRAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.genius.gitget.challenge.user.domain.Role; +import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.challenge.user.facade.UserFacade; +import com.genius.gitget.challenge.user.repository.UserRepository; +import com.genius.gitget.global.security.dto.AuthResponse; +import com.genius.gitget.global.security.dto.SignupResponse; +import com.genius.gitget.global.util.exception.BusinessException; +import com.genius.gitget.global.util.exception.ErrorCode; +import com.genius.gitget.store.item.domain.EquipStatus; +import com.genius.gitget.store.item.domain.Item; +import com.genius.gitget.store.item.domain.Orders; +import com.genius.gitget.store.item.repository.ItemRepository; +import com.genius.gitget.store.item.repository.OrdersRepository; +import com.genius.gitget.util.store.StoreFactory; +import com.genius.gitget.util.user.UserFactory; +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@Slf4j +class UserFacadeTest { + @Autowired + private UserRepository userRepository; + @Autowired + private ItemRepository itemRepository; + @Autowired + private OrdersRepository ordersRepository; + @Autowired + private UserFacade userFacade; + + @Value("${github.yeon-githubId}") + private String githubId; + + private User user; + + @Nested + @DisplayName("회원 가입 시도 시") + class context_try_register { + @Nested + @DisplayName("사용자의 정보 확인했을 때") + class describe_check_user_info { + @Test + @DisplayName("어드민 깃허브 계정에 해당하는 경우, ADMIN으로 설정된다.") + public void it_set_role_admin() { + userRepository.save(UserFactory.createUnregistered(githubId)); + SignupResponse signupResponse = userFacade.signup(getSignupRequest(githubId)); + + Optional optionalUser = userRepository.findById(signupResponse.userId()); + assertThat(optionalUser).isPresent(); + assertThat(optionalUser.get().getId()).isEqualTo(signupResponse.userId()); + assertThat(optionalUser.get().getRole()).isEqualTo(Role.ADMIN); + } + + @Test + @DisplayName("어드민 깃허브 계정에 해당하지 않는 경우, USER로 설정된다.") + public void it_set_role_user() { + String identifier = "identifier"; + userRepository.save(UserFactory.createUnregistered(identifier)); + SignupResponse signupResponse = userFacade.signup(getSignupRequest(identifier)); + + Optional optionalUser = userRepository.findById(signupResponse.userId()); + assertThat(optionalUser).isPresent(); + assertThat(optionalUser.get().getId()).isEqualTo(signupResponse.userId()); + assertThat(optionalUser.get().getRole()).isEqualTo(Role.USER); + } + + @Test + @DisplayName("이미 회원가입된 사용자인 경우 ALREADY_REGISTERED 예외가 발생한다.") + public void it_throw_ALREADY_REGISTERED_exception() { + userRepository.save(UserFactory.createUnregistered(githubId)); + SignupRequest signupRequest = getSignupRequest(githubId); + userFacade.signup(signupRequest); + + assertThatThrownBy(() -> userFacade.signup(signupRequest)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ALREADY_REGISTERED.getMessage()); + } + + SignupRequest getSignupRequest(String identifier) { + return SignupRequest.builder() + .identifier(identifier) + .information("information") + .interest(List.of("java", "BE")) + .nickname("nickname") + .build(); + } + } + } + + @Nested + @DisplayName("사용자의 닉네임 중복 확인 시") + class context_check_nickname_duplication { + String nickname = "nickname"; + + @Nested + @DisplayName("닉네임을 전달했을 때") + class describe_pass_nickname { + @Test + @DisplayName("닉네임이 기존에 존재하지 않는다면, 예외가 발생하지 않는다.") + public void it_not_throw_exception_nickname_not_exist() { + assertThatNoException().isThrownBy( + () -> userFacade.isNicknameDuplicate(nickname) + ); + } + + @Test + @DisplayName("닉네임이 기존에 존재한다면, DUPLICATED_NICKNAME 예외가 발생한다.") + public void it_throw_DUPLICATED_NICKNAME_exception_nickname_exist() { + user = userRepository.save(UserFactory.createUnregistered(githubId)); + user.updateUserInformation(nickname, "information"); + + assertThatThrownBy(() -> userFacade.isNicknameDuplicate(nickname)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(DUPLICATED_NICKNAME.getMessage()); + } + } + } + + @Nested + @DisplayName("토큰 발급 이후 사용자의 정보 조회 시") + class context_inquiry_user_after_issue_token { + @Nested + @DisplayName("사용자의 identifier 전달 시") + class describe_pass_identifier { + Item item; + Orders orders; + + @BeforeEach + void setup() { + user = userRepository.save(UserFactory.createByInfo(githubId, Role.USER)); + item = itemRepository.save(StoreFactory.createItem(PROFILE_FRAME)); + } + + @Test + @DisplayName("identifier에 해당하는 사용자가 없으면 MEMBER_NOT_FOUND 예외가 발생한다.") + public void it_throw_MEMBER_NOT_FOUND_exception() { + assertThatThrownBy(() -> userFacade.getUserAuthInfo("identifier")) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(ErrorCode.MEMBER_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("사용자가 프로필 프레임을 장착하고 있지 않을 때, 프레임 정보에 null이 담긴다.") + public void it_return_null_when_not_use_frame() { + AuthResponse authResponse = userFacade.getUserAuthInfo(githubId); + assertThat(authResponse.role()).isEqualTo(Role.USER); + assertThat(authResponse.frameId()).isNull(); + } + + @Test + @DisplayName("사용자가 프로필 프레임을 장착하고 있을 때, 프로필 정보에 아이템의 PK가 담긴다.") + public void it_return_itemPK_when_use_frame() { + orders = ordersRepository.save(StoreFactory.createOrders(user, item, PROFILE_FRAME, 3)); + orders.updateEquipStatus(EquipStatus.IN_USE); + + AuthResponse authResponse = userFacade.getUserAuthInfo(githubId); + assertThat(authResponse.role()).isEqualTo(Role.USER); + assertThat(authResponse.frameId()).isEqualTo(item.getIdentifier()); + } + } + } + + @Nested + @DisplayName("인증 가능한 사용자 조회 시") + class context_inquiry_auth_user_info { + @Nested + @DisplayName("사용자의 identifier 전달 시") + class describe_pass_identifier { + @Test + @DisplayName("회원 가입이 완료된 사용자라면 User 엔티티를 반환한다.") + public void it_return_user_when_registered() { + user = userRepository.save(UserFactory.createByInfo(githubId, Role.USER)); + + User authUser = userFacade.getAuthUser(user.getIdentifier()); + + assertThat(authUser.getId()).isEqualTo(user.getId()); + assertThat(authUser.getIdentifier()).isEqualTo(user.getIdentifier()); + } + + @Test + @DisplayName("회원 가입이 안 된 사용자라면 NOT_AUTHENTICATED_USER 예외가 발생한다.") + public void it_throws_NOT_AUTHENTICATED_USER_exception() { + user = userRepository.save(UserFactory.createUnregistered(githubId)); + + assertThatThrownBy(() -> userFacade.getAuthUser(githubId)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(NOT_AUTHENTICATED_USER.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java b/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java deleted file mode 100644 index ea738091..00000000 --- a/src/test/java/com/genius/gitget/challenge/user/service/UserServiceTest.java +++ /dev/null @@ -1,279 +0,0 @@ -package com.genius.gitget.challenge.user.service; - -import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REGISTERED; -import static com.genius.gitget.global.util.exception.ErrorCode.GITHUB_TOKEN_NOT_FOUND; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.genius.gitget.challenge.user.domain.Role; -import com.genius.gitget.challenge.user.domain.User; -import com.genius.gitget.challenge.user.dto.SignupRequest; -import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.global.security.constants.ProviderInfo; -import com.genius.gitget.global.security.dto.AuthResponse; -import com.genius.gitget.global.util.exception.BusinessException; -import com.genius.gitget.global.util.exception.ErrorCode; -import com.genius.gitget.store.item.domain.EquipStatus; -import com.genius.gitget.store.item.domain.Item; -import com.genius.gitget.store.item.domain.ItemCategory; -import com.genius.gitget.store.item.domain.Orders; -import com.genius.gitget.store.item.repository.ItemRepository; -import com.genius.gitget.store.item.repository.OrdersRepository; -import com.genius.gitget.util.file.FileTestUtil; -import java.util.List; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.EnumSource.Mode; -import org.junit.jupiter.params.provider.ValueSource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@SpringBootTest -@Transactional -@Slf4j -class UserServiceTest { - @Autowired - private UserRepository userRepository; - @Autowired - private ItemRepository itemRepository; - @Autowired - private OrdersRepository ordersRepository; - @Autowired - private UserService userService; - - @Test - @DisplayName("특정 사용자를 가입한 이후, 사용자를 찾았을 때 가입했을 때 입력한 정보와 일치해야 한다.") - public void should_matchValues_when_signupUser() { - //given - String identifier = "identifier"; - saveUnsignedUser(identifier, Role.NOT_REGISTERED); - SignupRequest signupRequest = SignupRequest.builder() - .identifier(identifier) - .nickname("nickname") - .information("information") - .interest(List.of("관심사1", "관심사2")) - .build(); - - //when - User user = userService.findUserByIdentifier(identifier); - - Long signupUserId = userService.signup(signupRequest); - User foundUser = userService.findUserById(signupUserId); - - //then - assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); - assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); - assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); - assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); - assertThat(user.getTags()).isEqualTo(foundUser.getTags()); - assertThat(user.getRole()).isEqualTo(Role.USER); - } - - @Test - @DisplayName("어드민 깃허브 계정에 해당하는 사용자가 가입을 요청하는 경우, ROLE이 자동으로 ADMIN으로 설정된다.") - public void should_setRoleAdmin_when_identifierMatchesWithAdmin() { - //given - String identifier = "SSung023"; - saveUnsignedUser(identifier, Role.NOT_REGISTERED); - SignupRequest signupRequest = SignupRequest.builder() - .identifier(identifier) - .nickname("nickname") - .information("information") - .interest(List.of("관심사1", "관심사2")) - .build(); - MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); - - //when - Long signupUserId = userService.signup(signupRequest); - User signupUser = userService.findUserById(signupUserId); - - //then - assertThat(signupUser.getRole()).isEqualTo(Role.ADMIN); - } - - @Test - @DisplayName("사용자가 한 차례 회원가입을 진행한 후, 한 번 더 회원가입을 요청하면 예외가 발생해야 한다.") - public void should_throwException_when_requestRegisterAgain() { - //given - String identifier = "identifier"; - saveUnsignedUser(identifier, Role.NOT_REGISTERED); - SignupRequest signupRequest = SignupRequest.builder() - .identifier(identifier) - .nickname("nickname") - .information("information") - .interest(List.of("관심사1", "관심사2")) - .build(); - MultipartFile multipartFile = FileTestUtil.getMultipartFile("profile"); - - //when - User user = userService.findUserByIdentifier(identifier); - Long signupUserId = userService.signup(signupRequest); - - //then - assertThatThrownBy(() -> userService.signup(signupRequest)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.ALREADY_REGISTERED.getMessage()); - } - - @Test - @DisplayName("저장되어 있는 사용자를 PK를 통해 찾을 수 있다.") - public void should_returnUser_when_passPK() { - //given - User user = getSavedUser(); - - //when - User foundUser = userService.findUserById(user.getId()); - - //then - assertThat(user.getId()).isEqualTo(foundUser.getId()); - assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); - assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); - assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); - assertThat(user.getRole()).isEqualTo(foundUser.getRole()); - assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); - assertThat(user.getTags()).isEqualTo(foundUser.getTags()); - } - - @Test - @DisplayName("저장되어 있는 사용자를 identifier를 통해 찾을 수 있다.") - public void should_returnUser_when_passIdentifier() { - //given - User user = getSavedUser(); - - //when - User foundUser = userService.findUserByIdentifier(user.getIdentifier()); - - //then - assertThat(user.getId()).isEqualTo(foundUser.getId()); - assertThat(user.getIdentifier()).isEqualTo(foundUser.getIdentifier()); - assertThat(user.getProviderInfo()).isEqualTo(foundUser.getProviderInfo()); - assertThat(user.getNickname()).isEqualTo(foundUser.getNickname()); - assertThat(user.getRole()).isEqualTo(foundUser.getRole()); - assertThat(user.getInformation()).isEqualTo(foundUser.getInformation()); - assertThat(user.getTags()).isEqualTo(foundUser.getTags()); - } - - @Test - @DisplayName("이미 등록되어 있는 닉네임인 경우 예외가 발생한다.") - public void should_throwException_when_nicknameIsDuplicated() { - //given - User user = getSavedUser(); - - //when & then - assertThatThrownBy(() -> userService.isNicknameDuplicate(user.getNickname())) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.DUPLICATED_NICKNAME.getMessage()); - } - - @ParameterizedTest - @DisplayName("User 엔티티로부터 깃허브 토큰을 불러올 때 길이가 0이거나, 공백으로 이루어져 있다면 예외가 발생한다.") - @ValueSource(strings = {"", " "}) - public void should_throwException_when_githubTokenInvalid(String githubToken) { - //given - User user = getSavedUser(); - - //when - user.updateGithubPersonalToken(githubToken); - - //then - assertThatThrownBy(() -> userService.getGithubToken(user)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("User 엔티티로부터 깃허브 토큰을 불러올 때 null 이라면 예외가 발생한다.") - public void should_throwException_when_githubTokenNull() { - //given - User user = getSavedUser(); - - //when - user.updateGithubPersonalToken(null); - - //then - assertThatThrownBy(() -> userService.getGithubToken(user)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(GITHUB_TOKEN_NOT_FOUND.getMessage()); - } - - @ParameterizedTest - @DisplayName("Role이 NOT_REGISTERED가 아닌 경우에는 이미 등록이 되어 있다는 예외가 발생한다.") - @EnumSource(mode = Mode.INCLUDE, names = {"USER", "ADMIN"}) - public void should_throwException_when_roleIsNotNOT_REGISTERED(Role role) { - assertThatThrownBy( - () -> userService.isAlreadyRegistered(saveUnsignedUser("identifier", role))) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ALREADY_REGISTERED.getMessage()); - } - - @Test - @DisplayName("사용자가 프로필 프레임을 장착하고 있지 않을 때, 사용자의 ROLE과 프레임의 PK는 null로 받는다.") - public void should_getUserInfo_when_notEquipFrame() { - //given - User user = getSavedUser(); - - //when - AuthResponse authResponse = userService.getUserAuthInfo(user.getIdentifier()); - - //then - assertThat(authResponse.role()).isEqualTo(Role.USER); - assertThat(authResponse.frameId()).isEqualTo(null); - } - - @Test - @DisplayName("사용자가 프로필 프레임을 장착하고 있을 때, 사용자의 ROLE과 사용 중인 프레임의 PK를 받을 수 있다.") - public void should_getUserInfo_when_equipFrame() { - //given - User user = getSavedUser(); - Item item = getSavedItem(ItemCategory.PROFILE_FRAME); - Orders orders = getSavedOrders(user, item); - orders.updateEquipStatus(EquipStatus.IN_USE); - - //when - AuthResponse authResponse = userService.getUserAuthInfo(user.getIdentifier()); - - //then - assertThat(authResponse.role()).isEqualTo(Role.USER); - assertThat(authResponse.frameId()).isEqualTo(item.getId()); - } - - - private User saveUnsignedUser(String identifier, Role role) { - return userRepository.save(User.builder() - .role(role) - .providerInfo(ProviderInfo.NAVER) - .identifier(identifier) - .build()); - } - - private User getSavedUser() { - return userRepository.save(User.builder() - .identifier("identifier") - .role(Role.USER) - .information("information") - .tags("interest1,interest2") - .nickname("nickname") - .providerInfo(ProviderInfo.GITHUB) - .build()); - } - - private Item getSavedItem(ItemCategory itemCategory) { - return itemRepository.save( - Item.builder() - .itemCategory(itemCategory) - .build() - ); - } - - private Orders getSavedOrders(User user, Item item) { - Orders orders = Orders.of(1, item.getItemCategory()); - orders.setUser(user); - orders.setItem(item); - return ordersRepository.save(orders); - } -} \ No newline at end of file diff --git a/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java index 68f70e9f..950437a1 100644 --- a/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java +++ b/src/test/java/com/genius/gitget/payment/service/PaymentServiceTest.java @@ -111,6 +111,7 @@ private Item getSavedItem(ItemCategory itemCategory) { .itemCategory(itemCategory) .cost(100) .name(itemCategory.getName()) + .identifier(8) .build()); } diff --git a/src/test/java/com/genius/gitget/util/security/WithMockCustomUserSecurityContextFactory.java b/src/test/java/com/genius/gitget/util/security/WithMockCustomUserSecurityContextFactory.java index 6e49bc3e..f1eaa450 100644 --- a/src/test/java/com/genius/gitget/util/security/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/com/genius/gitget/util/security/WithMockCustomUserSecurityContextFactory.java @@ -3,8 +3,9 @@ import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; import com.genius.gitget.challenge.user.dto.SignupRequest; +import com.genius.gitget.challenge.user.facade.UserFacade; import com.genius.gitget.challenge.user.repository.UserRepository; -import com.genius.gitget.challenge.user.service.UserService; +import com.genius.gitget.global.security.dto.SignupResponse; import com.genius.gitget.global.security.service.CustomUserDetailsService; import java.util.List; import java.util.Objects; @@ -22,7 +23,7 @@ @Slf4j public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory { private final UserRepository userRepository; - private final UserService userService; + private final UserFacade userFacade; private final CustomUserDetailsService customUserDetailsService; @Value("${github.yeon-githubId}") @@ -49,10 +50,11 @@ public SecurityContext createSecurityContext(WithMockCustomUser customUser) { .build(); User savedUser = userRepository.save(user); - Long signupId = userService.signup(signupRequest); + SignupResponse signupResponse = userFacade.signup(signupRequest); savedUser.updateRole(customUser.role()); - UserDetails principal = customUserDetailsService.loadUserByUsername(String.valueOf(signupId)); + Long userId = signupResponse.userId(); + UserDetails principal = customUserDetailsService.loadUserByUsername(String.valueOf(userId)); Authentication auth = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities()); diff --git a/src/test/java/com/genius/gitget/util/user/UserFactory.java b/src/test/java/com/genius/gitget/util/user/UserFactory.java index 06ba2d76..148138f2 100644 --- a/src/test/java/com/genius/gitget/util/user/UserFactory.java +++ b/src/test/java/com/genius/gitget/util/user/UserFactory.java @@ -37,4 +37,12 @@ public static User createAdmin() { .tags("BE,FE") .build(); } + + public static User createUnregistered(String identifier) { + return User.builder() + .identifier(identifier) + .role(Role.NOT_REGISTERED) + .providerInfo(ProviderInfo.GITHUB) + .build(); + } } From ed653efbf5ff05411b21f7fa70d5487698660685 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 2 Sep 2024 07:00:27 +0900 Subject: [PATCH 221/234] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=9D=B4=ED=9B=84=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=B4=20=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=ED=94=BD?= =?UTF-8?q?=EC=8A=A4=20(#262)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProfileFacadeService, UserService의 Dirty checking이 필요한 메서드에 대해 @Transactional 어노테이션 추가 --- .../com/genius/gitget/challenge/user/service/UserService.java | 1 + .../genius/gitget/profile/service/ProfileFacadeService.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java index 96ecc01c..01e63d4b 100644 --- a/src/main/java/com/genius/gitget/challenge/user/service/UserService.java +++ b/src/main/java/com/genius/gitget/challenge/user/service/UserService.java @@ -46,6 +46,7 @@ public Long save(User user) { } + @Transactional public void delete(Long userId, String identifier, String reason) { userRepository.deleteById(userId); signoutRepository.save( diff --git a/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java b/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java index 7bd4664a..0f93fb61 100644 --- a/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java +++ b/src/main/java/com/genius/gitget/profile/service/ProfileFacadeService.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.List; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @Component public class ProfileFacadeService implements ProfileFacade { @@ -75,6 +76,7 @@ public UserDetailsInformationResponse getUserDetailsInformation(User user) { } @Override + @Transactional public Long updateUserInformation(User user, UserInformationUpdateRequest userInformationUpdateRequest) { User findUser = userService.findByIdentifier(user.getIdentifier()); findUser.updateUserInformation( @@ -85,6 +87,7 @@ public Long updateUserInformation(User user, UserInformationUpdateRequest userIn } @Override + @Transactional public void deleteUserInformation(User user, String reason) { User findUser = userService.findByIdentifier(user.getIdentifier()); @@ -96,6 +99,7 @@ public void deleteUserInformation(User user, String reason) { } @Override + @Transactional public void updateUserTags(User user, UserInterestUpdateRequest userInterestUpdateRequest) { if (userInterestUpdateRequest.getTags() == null) { throw new BusinessException(); From fa98a43e64789cf3068993b8d255bfdf26ce6dda Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sat, 7 Sep 2024 23:08:09 +0900 Subject: [PATCH 222/234] =?UTF-8?q?[FEAT]=20=ED=8C=8C=EC=9D=BC=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?(#263)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 파일 조회 응답 데이터 변경 - 파일 조회 시 Base64 인코딩 문자열을 보냈던 방식에서, 파일의 URI를 반환하는 방식으로 변경 * fix: local 버전에서 경로가 중복되어 응답하는 버그 픽스 - local 버전에서 이미지 경로가 중복되어 전달되는 버그 픽스 * chore: 인터페이스의 주석 수정 --- .../file/controller/FileTestController.java | 12 +++++------ .../gitget/global/file/dto/FileResponse.java | 6 +++--- .../global/file/service/FileService.java | 7 +++---- .../global/file/service/FilesManager.java | 4 ++-- .../global/file/service/LocalFileService.java | 14 ++----------- .../global/file/service/S3FileService.java | 21 +++++++------------ .../gitget/global/util/config/AppConfig.java | 3 ++- .../gitget/global/file/domain/FilesTest.java | 4 ++-- 8 files changed, 27 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java b/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java index 35c9919a..f6ac8c7b 100644 --- a/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java +++ b/src/main/java/com/genius/gitget/global/file/controller/FileTestController.java @@ -52,8 +52,8 @@ public ResponseEntity> upload( ) { FileType fileType = FileType.findType(type); Files files = filesManager.uploadFile(multipartFile, fileType); - String encodedImage = fileService.getEncodedImage(files); - FileResponse fileResponse = FileResponse.createExistFile(files.getId(), encodedImage); + String accessURI = fileService.getFileAccessURI(files); + FileResponse fileResponse = FileResponse.createExistFile(files.getId(), accessURI); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) @@ -65,8 +65,8 @@ public ResponseEntity> update( @PathVariable Long fileId, @RequestParam("files") MultipartFile multipartFile) { Files files = filesManager.updateFile(fileId, multipartFile); - String encodedImage = fileService.getEncodedImage(files); - FileResponse fileResponse = FileResponse.createExistFile(files.getId(), encodedImage); + String accessURI = fileService.getFileAccessURI(files); + FileResponse fileResponse = FileResponse.createExistFile(files.getId(), accessURI); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) @@ -82,8 +82,8 @@ public ResponseEntity> copy( Files files = filesManager.findById(fileId); Files copiedFile = filesManager.copyFile(files, fileType); - String encodedImage = fileService.getEncodedImage(copiedFile); - FileResponse fileResponse = FileResponse.createExistFile(copiedFile.getId(), encodedImage); + String accessURI = fileService.getFileAccessURI(copiedFile); + FileResponse fileResponse = FileResponse.createExistFile(copiedFile.getId(), accessURI); return ResponseEntity.ok().body( new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), fileResponse) diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index 6505d331..a5ac6f09 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -2,10 +2,10 @@ public record FileResponse( Long fileId, - String encodedFile) { + String accessURI) { - public static FileResponse createExistFile(Long filesId, String encodedFile) { - return new FileResponse(filesId, encodedFile); + public static FileResponse createExistFile(Long filesId, String accessURI) { + return new FileResponse(filesId, accessURI); } public static FileResponse createNotExistFile() { diff --git a/src/main/java/com/genius/gitget/global/file/service/FileService.java b/src/main/java/com/genius/gitget/global/file/service/FileService.java index 7b6c5687..43ec8c29 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FileService.java +++ b/src/main/java/com/genius/gitget/global/file/service/FileService.java @@ -14,13 +14,12 @@ public interface FileService { /** - * Files 내에 저장된 값들을 통해 UrlResource 등으로 다운받은 후, base64로 인코딩한 결과 반환 + * Files에 저장된 파일의 접근 URI 반환 * * @param files 얻기 원하는 파일의 정보를 담고 있는 Files 객체 - * @return base64로 encode한 결과 값(문자열) 반환 - * 파일을 받아오지 못한 경우에는 빈 문자열("") 반환 + * @return */ - String getEncodedImage(Files files); + String getFileAccessURI(Files files); /** * 전달한 파일 저장 후, Files 객체 형성에 필요한 정보를 담은 객체 반환 diff --git a/src/main/java/com/genius/gitget/global/file/service/FilesManager.java b/src/main/java/com/genius/gitget/global/file/service/FilesManager.java index 91cd459a..2f2b5322 100644 --- a/src/main/java/com/genius/gitget/global/file/service/FilesManager.java +++ b/src/main/java/com/genius/gitget/global/file/service/FilesManager.java @@ -124,8 +124,8 @@ public Files findById(Long fileId) { public FileResponse convertToFileResponse(Optional optionalFiles) { return optionalFiles .map(files -> { - String encodedImage = fileService.getEncodedImage(files); - return FileResponse.createExistFile(files.getId(), encodedImage); + String fileAccessURI = fileService.getFileAccessURI(files); + return FileResponse.createExistFile(files.getId(), fileAccessURI); }) .orElseGet(FileResponse::createNotExistFile); } diff --git a/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java b/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java index e3f57e18..9c5fe69d 100644 --- a/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java +++ b/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java @@ -13,11 +13,8 @@ import com.genius.gitget.global.util.exception.BusinessException; import java.io.File; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.StandardCopyOption; -import java.util.Base64; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.UrlResource; import org.springframework.web.multipart.MultipartFile; public class LocalFileService implements FileService { @@ -47,15 +44,8 @@ public FileDTO upload(MultipartFile multipartFile, FileType fileType) { } @Override - public String getEncodedImage(Files files) { - try { - UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); - byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); - return new String(encode, StandardCharsets.UTF_8); - } catch (IOException e) { - //TODO: 불러오는 중의 예외에 대해 Logging 추가하기 - return ""; - } + public String getFileAccessURI(Files files) { + return files.getFileURI(); } @Override diff --git a/src/main/java/com/genius/gitget/global/file/service/S3FileService.java b/src/main/java/com/genius/gitget/global/file/service/S3FileService.java index c895522a..dccba2af 100644 --- a/src/main/java/com/genius/gitget/global/file/service/S3FileService.java +++ b/src/main/java/com/genius/gitget/global/file/service/S3FileService.java @@ -13,32 +13,25 @@ import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.util.exception.BusinessException; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import org.springframework.core.io.UrlResource; +import java.net.URL; import org.springframework.web.multipart.MultipartFile; public class S3FileService implements FileService { private final AmazonS3 amazonS3; private final FileUtil fileUtil; private final String bucket; - - public S3FileService(AmazonS3 amazonS3, FileUtil fileUtil, String bucket) { + private final String cloudFrontDomain; + public S3FileService(AmazonS3 amazonS3, FileUtil fileUtil, String bucket, String cloudFrontDomain) { this.amazonS3 = amazonS3; this.fileUtil = fileUtil; this.bucket = bucket; + this.cloudFrontDomain = cloudFrontDomain; } @Override - public String getEncodedImage(Files files) { - try { - UrlResource urlResource = new UrlResource(amazonS3.getUrl(bucket, files.getFileURI())); - byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); - return new String(encode, StandardCharsets.UTF_8); - } catch (IOException e) { - //TODO: 불러오는 중의 예외에 대해 Logging 추가하기 - return ""; - } + public String getFileAccessURI(Files files) { + URL url = amazonS3.getUrl(bucket, files.getFileURI()); + return cloudFrontDomain + url.getFile(); } @Override diff --git a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java index fd90402b..2b2ee7b0 100644 --- a/src/main/java/com/genius/gitget/global/util/config/AppConfig.java +++ b/src/main/java/com/genius/gitget/global/util/config/AppConfig.java @@ -35,7 +35,8 @@ public FileService fileManager() { } final String bucket = env.getProperty("cloud.aws.s3.bucket"); - return new S3FileService(s3Config.amazonS3Client(), fileUtil(), bucket); + final String cloudFrontDomain = env.getProperty("cloud.aws.cloud-front.domain"); + return new S3FileService(s3Config.amazonS3Client(), fileUtil(), bucket, cloudFrontDomain); } @Bean diff --git a/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java b/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java index 9b41b3c7..4abda17a 100644 --- a/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java +++ b/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java @@ -20,13 +20,13 @@ public void should_updateFiles_when_passUpdateDTO() { .fileType(FileType.INSTANCE) .originalFilename("originalFilename") .savedFilename("savedFilename") - .fileURI("fileURI") + .fileURI("accessURI") .build(); UpdateDTO updateDTO = UpdateDTO.builder() .savedFilename("new savedFilename") .originalFilename("new originalFilename") - .fileURI("new fileURI") + .fileURI("new accessURI") .build(); //when From 767d40fa2c6b942b4f4e30ba872aa9f85912e736 Mon Sep 17 00:00:00 2001 From: kimdozzi Date: Wed, 23 Oct 2024 14:15:20 +0900 Subject: [PATCH 223/234] =?UTF-8?q?2.1=EC=B0=A8=20=EB=B0=B0=ED=8F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/genius/gitget/GitgetApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/genius/gitget/GitgetApplication.java b/src/main/java/com/genius/gitget/GitgetApplication.java index d12c2cf2..20dc5e46 100644 --- a/src/main/java/com/genius/gitget/GitgetApplication.java +++ b/src/main/java/com/genius/gitget/GitgetApplication.java @@ -11,7 +11,6 @@ @EnableJpaAuditing @EnableMongoRepositories public class GitgetApplication { - public static void main(String[] args) { SpringApplication.run(GitgetApplication.class, args); } From cc53be42e7d092e9246f246e66c36abe59e66c29 Mon Sep 17 00:00:00 2001 From: SSung023 <50323157+SSung023@users.noreply.github.com> Date: Fri, 25 Oct 2024 22:30:14 +0900 Subject: [PATCH 224/234] =?UTF-8?q?HOTFIX:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=B2=98=EB=A6=AC=EA=B0=80=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=ED=94=BD=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 트랜잭션 미적용으로 인해 회원가입 처리가 이루어지지 않은 버그 핫픽스 --- .../genius/gitget/challenge/user/facade/UserFacadeService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java index 22a86990..a802ff82 100644 --- a/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java +++ b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java @@ -38,6 +38,7 @@ public void isNicknameDuplicate(String nickname) { } @Override + @Transactional public SignupResponse signup(SignupRequest signupRequest) { User user = userService.findByIdentifier(signupRequest.identifier()); From 43b6b9187d034fc3c535e9d64edc6fb74865342c Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Fri, 8 Nov 2024 16:17:07 +0900 Subject: [PATCH 225/234] =?UTF-8?q?[FEAT]=20CertificationRepository=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20(#264)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Certification 테이블에 DB index 적용 * feat: CertificationRepository의 쿼리 수정 * fix: Certification 테이블의 인덱스 수정 --- .../challenge/certification/domain/Certification.java | 8 ++++++++ .../certification/repository/CertificationRepository.java | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java index f6375952..9248f88d 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java +++ b/src/main/java/com/genius/gitget/challenge/certification/domain/Certification.java @@ -15,8 +15,10 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Index; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import java.time.LocalDate; import lombok.AccessLevel; import lombok.Builder; @@ -29,6 +31,12 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @DynamicInsert +@Table(indexes = { + @Index( + name = "idx_participant_cert_attempt", + columnList = "participant_id, certificated_at, current_attempt DESC" + ) +}) public class Certification extends BaseTimeEntity { @Id @Column(name = "certification_id") diff --git a/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java b/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java index 2a2a2ffc..5329be0c 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java +++ b/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java @@ -15,7 +15,7 @@ public interface CertificationRepository extends JpaRepository findByDate(@Param("targetDate") LocalDate targetDate, @Param("participantId") Long participantId); - @Query("select c from Certification c where c.participant.id = :participantId and c.certificatedAt >= :startDate and c.certificatedAt <= :endDate order by c.currentAttempt desc") + @Query("select c from Certification c where c.participant.id = :participantId and c.certificatedAt between :startDate AND :endDate order by c.currentAttempt desc") List findByDuration(@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate, @Param("participantId") Long participantId); From 2e54fb61fba89a22bd75a2dbdf542b4c745710a5 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:32:54 +0900 Subject: [PATCH 226/234] =?UTF-8?q?[FEAT]=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20API=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20(#267)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: guest 계정 로그인 API 개발 * fix: 게스트 계정 로그인 시, 아이디&비밀번호 확인하는 로직 추가 * feat: 게스트 계정 추가하는 SQL문 작성 - Users 테이블에 게스트 계정 정보가 없을 때, 게스트 계정을 추가하는 SQL문 작성 * test: data.sql 문 수정으로 인해 실패하는 테스트 수정 * feat: 게스트 로그인 시 전달하는 데이터 추가 - 게스트 로그인 응답 데이터에 identifier 정보 추가 --- .../challenge/user/dto/LoginRequest.java | 11 +++ .../challenge/user/facade/UserFacade.java | 3 + .../user/facade/UserFacadeService.java | 18 ++++ .../security/controller/AuthController.java | 19 ++++ .../global/security/dto/GuestResponse.java | 15 +++ src/main/resources/data.sql | 15 ++- .../challenge/user/domain/UserTest.java | 2 +- .../profile/service/ProfileServiceTest.java | 97 ++++++++++--------- 8 files changed, 131 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/genius/gitget/challenge/user/dto/LoginRequest.java create mode 100644 src/main/java/com/genius/gitget/global/security/dto/GuestResponse.java diff --git a/src/main/java/com/genius/gitget/challenge/user/dto/LoginRequest.java b/src/main/java/com/genius/gitget/challenge/user/dto/LoginRequest.java new file mode 100644 index 00000000..8e089e77 --- /dev/null +++ b/src/main/java/com/genius/gitget/challenge/user/dto/LoginRequest.java @@ -0,0 +1,11 @@ +package com.genius.gitget.challenge.user.dto; + +import jakarta.validation.constraints.NotEmpty; + +public record LoginRequest( + @NotEmpty + String id, + @NotEmpty + String password +) { +} diff --git a/src/main/java/com/genius/gitget/challenge/user/facade/UserFacade.java b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacade.java index e64a4d53..44f3974f 100644 --- a/src/main/java/com/genius/gitget/challenge/user/facade/UserFacade.java +++ b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacade.java @@ -1,6 +1,7 @@ package com.genius.gitget.challenge.user.facade; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.LoginRequest; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.global.security.dto.AuthResponse; import com.genius.gitget.global.security.dto.SignupResponse; @@ -13,4 +14,6 @@ public interface UserFacade { AuthResponse getUserAuthInfo(String identifier); User getAuthUser(String identifier); + + User getGuestUser(LoginRequest loginRequest); } diff --git a/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java index a802ff82..282a2232 100644 --- a/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java +++ b/src/main/java/com/genius/gitget/challenge/user/facade/UserFacadeService.java @@ -2,10 +2,12 @@ import static com.genius.gitget.global.util.exception.ErrorCode.ALREADY_REGISTERED; import static com.genius.gitget.global.util.exception.ErrorCode.DUPLICATED_NICKNAME; +import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND; import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER; import com.genius.gitget.challenge.user.domain.Role; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.LoginRequest; import com.genius.gitget.challenge.user.dto.SignupRequest; import com.genius.gitget.challenge.user.service.UserService; import com.genius.gitget.global.security.dto.AuthResponse; @@ -14,6 +16,7 @@ import com.genius.gitget.store.item.domain.Item; import com.genius.gitget.store.item.service.OrdersService; import java.util.List; +import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -26,6 +29,12 @@ public class UserFacadeService implements UserFacade { private final UserService userService; private final OrdersService ordersService; + @Value("${guest.id}") + private String guest_id; + + @Value("${guest.password}") + private String guest_password; + @Value("${admin.githubId}") private List adminIds; @@ -76,4 +85,13 @@ public User getAuthUser(String identifier) { } return user; } + + @Override + public User getGuestUser(LoginRequest loginRequest) { + if (!Objects.equals(loginRequest.id(), guest_id) || !Objects.equals(loginRequest.password(), guest_password)) { + throw new BusinessException(MEMBER_NOT_FOUND); + } + + return userService.findByIdentifier(guest_id); + } } diff --git a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java index 89e45030..1f400a31 100644 --- a/src/main/java/com/genius/gitget/global/security/controller/AuthController.java +++ b/src/main/java/com/genius/gitget/global/security/controller/AuthController.java @@ -3,8 +3,10 @@ import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS; import com.genius.gitget.challenge.user.domain.User; +import com.genius.gitget.challenge.user.dto.LoginRequest; import com.genius.gitget.challenge.user.facade.UserFacade; import com.genius.gitget.global.security.dto.AuthResponse; +import com.genius.gitget.global.security.dto.GuestResponse; import com.genius.gitget.global.security.dto.TokenRequest; import com.genius.gitget.global.security.service.JwtFacade; import com.genius.gitget.global.util.annotation.GitGetUser; @@ -58,4 +60,21 @@ public ResponseEntity logout(@GitGetUser User user, HttpServletR public String healthCheck() { return "health-check-ok"; } + + @PostMapping("/auth/guest") + public ResponseEntity> loginWithGuest(HttpServletResponse response, + @RequestBody LoginRequest loginRequest) { + User authUser = userFacade.getGuestUser(loginRequest); + + jwtFacade.generateAccessToken(response, authUser); + jwtFacade.generateRefreshToken(response, authUser); + jwtFacade.setReissuedHeader(response); + + AuthResponse authResponse = userFacade.getUserAuthInfo(authUser.getIdentifier()); + GuestResponse guestResponse = GuestResponse.from(authResponse, authUser.getIdentifier()); + + return ResponseEntity.ok().body( + new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), guestResponse) + ); + } } diff --git a/src/main/java/com/genius/gitget/global/security/dto/GuestResponse.java b/src/main/java/com/genius/gitget/global/security/dto/GuestResponse.java new file mode 100644 index 00000000..a4403e2c --- /dev/null +++ b/src/main/java/com/genius/gitget/global/security/dto/GuestResponse.java @@ -0,0 +1,15 @@ +package com.genius.gitget.global.security.dto; + +import com.genius.gitget.challenge.user.domain.Role; +import jakarta.validation.constraints.NotNull; + +public record GuestResponse( + String identifier, + Role role, + Integer frameId +) { + + public static GuestResponse from(@NotNull AuthResponse authResponse, @NotNull String identifier) { + return new GuestResponse(identifier, authResponse.role(), authResponse.frameId()); + } +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index d25726d5..d865b7e3 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -42,4 +42,17 @@ FROM (SELECT 1 AS identifier, '프로필을 꾸밀 수 있는 프레임입니다.', '무섭지롱 프레임', 'PROFILE_FRAME') AS new_items -WHERE (SELECT COUNT(*) FROM item) < 3; \ No newline at end of file +WHERE (SELECT COUNT(*) FROM item) < 3; + +INSERT INTO users (`point`, user_id, nickname, information, identifier, tags, provider_info, `role`) +SELECT 0, + 104, + 'Guest', + '자기 소개입니다.', + 'Guest', + 'Java,Spring', + 'GITHUB', + 'USER' +WHERE NOT EXISTS (SELECT 1 + FROM users + WHERE identifier = 'Guest'); diff --git a/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java b/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java index b947147f..954705de 100644 --- a/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java +++ b/src/test/java/com/genius/gitget/challenge/user/domain/UserTest.java @@ -41,7 +41,7 @@ public class UserTest { User savedUser2 = userRepository.save(userB()); List users = userRepository.findAll(); - assertThat(count(users)).isEqualTo(2); + assertThat(count(users)).isEqualTo(3); assertThat(savedUser1).isNotSameAs(savedUser2); } diff --git a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java index 0a09c914..9bee10bb 100644 --- a/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java +++ b/src/test/java/com/genius/gitget/profile/service/ProfileServiceTest.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -45,6 +46,9 @@ @Rollback public class ProfileServiceTest { + static User user1, user2; + static Topic topic1; + static Instance instance1, instance2, instance3; @Autowired UserRepository userRepository; @Autowired @@ -60,10 +64,6 @@ public class ProfileServiceTest { @Autowired SignoutRepository signoutRepository; - static User user1, user2; - static Topic topic1; - static Instance instance1, instance2, instance3; - @BeforeEach void setup() { user1 = getSavedUser("neo5188@gmail.com", GITHUB, "alias1"); @@ -88,6 +88,49 @@ void setup() { likesRepository.save(likes3); } + private User getSavedUser(String identifier, ProviderInfo providerInfo, String nickname) { + User user = userRepository.save( + User.builder() + .identifier(identifier) + .providerInfo(providerInfo) + .role(Role.ADMIN) + .tags("BE,FE") + .nickname(nickname) + .build() + ); + return user; + } + + private Topic getSavedTopic(String title, String tags) { + Topic topic = topicRepository.save( + Topic.builder() + .title(title) + .tags(tags) + .description("토픽 설명") + .pointPerPerson(100) + .build() + ); + return topic; + } + + private Instance getSavedInstance(String title, String tags, int participantCnt) { + LocalDateTime now = LocalDateTime.now(); + Instance instance = instanceRepository.save( + Instance.builder() + .tags(tags) + .title(title) + .description("description") + .progress(Progress.PREACTIVITY) + .pointPerPerson(100) + .certificationMethod("인증 방법") + .startedDate(now) + .completedDate(now.plusDays(1)) + .build() + ); + instance.updateParticipantCount(participantCnt); + return instance; + } + @Nested @DisplayName("유저 상세 정보 조회") class Describe_getUserDetailsInformation { @@ -110,6 +153,9 @@ void it_returns_user_information() { List userIdList = new ArrayList<>(); List all = userRepository.findAll(); for (User user : all) { + if (Objects.equals(user.getNickname(), "Guest")) { + continue; + } Long id = user.getId(); userIdList.add(id); } @@ -226,47 +272,4 @@ void it_returns_user_challenge_result() { System.out.println(userChallengeResult.getSuccess()); } } - - private User getSavedUser(String identifier, ProviderInfo providerInfo, String nickname) { - User user = userRepository.save( - User.builder() - .identifier(identifier) - .providerInfo(providerInfo) - .role(Role.ADMIN) - .tags("BE,FE") - .nickname(nickname) - .build() - ); - return user; - } - - private Topic getSavedTopic(String title, String tags) { - Topic topic = topicRepository.save( - Topic.builder() - .title(title) - .tags(tags) - .description("토픽 설명") - .pointPerPerson(100) - .build() - ); - return topic; - } - - private Instance getSavedInstance(String title, String tags, int participantCnt) { - LocalDateTime now = LocalDateTime.now(); - Instance instance = instanceRepository.save( - Instance.builder() - .tags(tags) - .title(title) - .description("description") - .progress(Progress.PREACTIVITY) - .pointPerPerson(100) - .certificationMethod("인증 방법") - .startedDate(now) - .completedDate(now.plusDays(1)) - .build() - ); - instance.updateParticipantCount(participantCnt); - return instance; - } } From 3712faa52d0d492acd8369744a38ad56009b86e8 Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Mon, 6 Jan 2025 11:59:26 +0900 Subject: [PATCH 227/234] =?UTF-8?q?[REFACTOR]=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EB=B2=84=EC=A0=84=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?(#271)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 파일 버전 표시를 위한 클래스 추가 - yml 파일의 값을 읽은 후, 로컬/프로덕션 버전 정보를 저장하는 클래스 작성 * feat: 파일 응답 클래스 구조 변경 - accessURI를 source로 변경 - 파일 버전 정보(environment) 추가 - LOCAL / PROD * fix: 쿼리문 오류 수정 - PK 관련 에러 수정 * refactor: 버전 별 처리 방법 변경 - LOCAL 버전의 경우 파일을 다운로드 받아 BASE64로 인코딩한 문자열을 반환하도록 변경 - PROD 버전의 경우 파일에 접근할 수 있는 URI 반환 --- .../gitget/global/file/dto/FileEnv.java | 19 +++++++++++++++++++ .../gitget/global/file/dto/FileResponse.java | 7 ++++--- .../global/file/service/LocalFileService.java | 11 ++++++++++- .../global/file/service/S3FileService.java | 5 ++--- src/main/resources/data.sql | 3 +-- .../gitget/global/file/domain/FilesTest.java | 4 ++-- 6 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/genius/gitget/global/file/dto/FileEnv.java diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileEnv.java b/src/main/java/com/genius/gitget/global/file/dto/FileEnv.java new file mode 100644 index 00000000..b8a7d667 --- /dev/null +++ b/src/main/java/com/genius/gitget/global/file/dto/FileEnv.java @@ -0,0 +1,19 @@ +package com.genius.gitget.global.file.dto; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +public class FileEnv { + private static Environment environment; + + @Autowired + public FileEnv(Environment env) { + environment = env; + } + + public static String getFileEnvironment() { + return environment.getProperty("file.mode").toUpperCase(); + } +} \ No newline at end of file diff --git a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java index a5ac6f09..793b51c4 100644 --- a/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java +++ b/src/main/java/com/genius/gitget/global/file/dto/FileResponse.java @@ -2,13 +2,14 @@ public record FileResponse( Long fileId, - String accessURI) { + String source, + String environment) { public static FileResponse createExistFile(Long filesId, String accessURI) { - return new FileResponse(filesId, accessURI); + return new FileResponse(filesId, accessURI, FileEnv.getFileEnvironment()); } public static FileResponse createNotExistFile() { - return new FileResponse(0L, ""); + return new FileResponse(0L, "", FileEnv.getFileEnvironment()); } } diff --git a/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java b/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java index 9c5fe69d..56298525 100644 --- a/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java +++ b/src/main/java/com/genius/gitget/global/file/service/LocalFileService.java @@ -13,8 +13,11 @@ import com.genius.gitget.global.util.exception.BusinessException; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.StandardCopyOption; +import java.util.Base64; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.UrlResource; import org.springframework.web.multipart.MultipartFile; public class LocalFileService implements FileService { @@ -45,7 +48,13 @@ public FileDTO upload(MultipartFile multipartFile, FileType fileType) { @Override public String getFileAccessURI(Files files) { - return files.getFileURI(); + try { + UrlResource urlResource = new UrlResource("file:" + files.getFileURI()); + byte[] encode = Base64.getEncoder().encode(urlResource.getContentAsByteArray()); + return new String(encode, StandardCharsets.UTF_8); + } catch (IOException e) { + return ""; + } } @Override diff --git a/src/main/java/com/genius/gitget/global/file/service/S3FileService.java b/src/main/java/com/genius/gitget/global/file/service/S3FileService.java index dccba2af..44ad4201 100644 --- a/src/main/java/com/genius/gitget/global/file/service/S3FileService.java +++ b/src/main/java/com/genius/gitget/global/file/service/S3FileService.java @@ -13,7 +13,6 @@ import com.genius.gitget.global.file.dto.UpdateDTO; import com.genius.gitget.global.util.exception.BusinessException; import java.io.IOException; -import java.net.URL; import org.springframework.web.multipart.MultipartFile; public class S3FileService implements FileService { @@ -21,6 +20,7 @@ public class S3FileService implements FileService { private final FileUtil fileUtil; private final String bucket; private final String cloudFrontDomain; + public S3FileService(AmazonS3 amazonS3, FileUtil fileUtil, String bucket, String cloudFrontDomain) { this.amazonS3 = amazonS3; this.fileUtil = fileUtil; @@ -30,8 +30,7 @@ public S3FileService(AmazonS3 amazonS3, FileUtil fileUtil, String bucket, String @Override public String getFileAccessURI(Files files) { - URL url = amazonS3.getUrl(bucket, files.getFileURI()); - return cloudFrontDomain + url.getFile(); + return cloudFrontDomain + files.getFileURI(); } @Override diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index d865b7e3..a101bb92 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -44,9 +44,8 @@ FROM (SELECT 1 AS identifier, 'PROFILE_FRAME') AS new_items WHERE (SELECT COUNT(*) FROM item) < 3; -INSERT INTO users (`point`, user_id, nickname, information, identifier, tags, provider_info, `role`) +INSERT INTO users (`point`, nickname, information, identifier, tags, provider_info, `role`) SELECT 0, - 104, 'Guest', '자기 소개입니다.', 'Guest', diff --git a/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java b/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java index 4abda17a..f45070d2 100644 --- a/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java +++ b/src/test/java/com/genius/gitget/global/file/domain/FilesTest.java @@ -20,13 +20,13 @@ public void should_updateFiles_when_passUpdateDTO() { .fileType(FileType.INSTANCE) .originalFilename("originalFilename") .savedFilename("savedFilename") - .fileURI("accessURI") + .fileURI("source") .build(); UpdateDTO updateDTO = UpdateDTO.builder() .savedFilename("new savedFilename") .originalFilename("new originalFilename") - .fileURI("new accessURI") + .fileURI("new source") .build(); //when From 243902cee37a8ab51a87d037ef4b4ba68f07b437 Mon Sep 17 00:00:00 2001 From: SSung023 Date: Sun, 30 Mar 2025 19:52:31 +0900 Subject: [PATCH 228/234] =?UTF-8?q?chore:=20git=20submodule=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 4 ++-- .gitmodules | 3 +++ GitGet-BACK-SECRET | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .gitmodules create mode 160000 GitGet-BACK-SECRET diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2f76c2e2..46d28e4c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,8 +8,8 @@ on: env: AWS_REGION: ap-northeast-2 - AWS_S3_BUCKET: gitget-deploy-bucket - AWS_CODE_DEPLOY_APPLICATION: GitGet-Application-CD + AWS_S3_BUCKET: gitget-deploy-bucket2 + AWS_CODE_DEPLOY_APPLICATION: GitGet-Application AWS_CODE_DEPLOY_GROUP: GitGet-Deployment-Group jobs: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..9fa3da91 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "GitGet-BACK-SECRET"] + path = GitGet-BACK-SECRET + url = https://github.com/TeamTheGenius/GitGet-BACK-SECRET diff --git a/GitGet-BACK-SECRET b/GitGet-BACK-SECRET new file mode 160000 index 00000000..d3101d08 --- /dev/null +++ b/GitGet-BACK-SECRET @@ -0,0 +1 @@ +Subproject commit d3101d0881a3ea01d7dba39a954c8aa04d9a12c3 From a4d5a80c552507c69cbf5a94e4577b809c96d33e Mon Sep 17 00:00:00 2001 From: SSung023 Date: Sun, 30 Mar 2025 19:55:38 +0900 Subject: [PATCH 229/234] =?UTF-8?q?chore:=20git=20submodule=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=9B=8C=ED=81=AC?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/prTest.yml | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/workflows/prTest.yml b/.github/workflows/prTest.yml index 1b91fbf2..743750d3 100644 --- a/.github/workflows/prTest.yml +++ b/.github/workflows/prTest.yml @@ -2,7 +2,7 @@ name: Run gradlew clean test when PR on: pull_request: - branches: [ "main" ] + branches: [ "main", "production" ] jobs: PRTest: @@ -19,23 +19,21 @@ jobs: - name: make application.yml run: | - mkdir -p ./src/main/resources - cd ./src/main/resources - touch ./application.yml - touch ./application-common.yml - touch ./application-prod.yml - echo "${{ secrets.APPLICATION }}" > ./application.yml - echo "${{ secrets.COMMON }}" > ./application-common.yml - echo "${{ secrets.PROD }}" > ./application-prod.yml - - - name: make test application.yml - run: | - mkdir -p ./src/test/resources - cd ./src/test/resources - touch ./application.yml - touch ./application-test.yml - echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml - echo "${{ secrets.TEST }}" > ./application-test.yml + mkdir -p src/main/resources + cp HERE-WE-SECRET/main/resources/application.yml src/main/resources/ + cp HERE-WE-SECRET/main/resources/application-dev.yml src/main/resources/ + cp HERE-WE-SECRET/main/resources/application-prod.yml src/main/resources/ + cp HERE-WE-SECRET/main/resources/application-common.yml src/main/resources/ + + mkdir -p src/test/resources + cp HERE-WE-SECRET/test/resources/application.yml src/test/resources/ + cp HERE-WE-SECRET/test/resources/application-test.yml src/test/resources/ + + echo "Main resources contents:" + ls -la src/main/resources/ + echo "Test resources contents:" + ls -la src/test/resources/ + - name: Grant execute permission for gradlew run: chmod +x ./gradlew From 7346234a98f380b2c1848c7a8fd4788be678cbff Mon Sep 17 00:00:00 2001 From: HEY <50323157+SSung023@users.noreply.github.com> Date: Sun, 30 Mar 2025 20:28:02 +0900 Subject: [PATCH 230/234] =?UTF-8?q?[REFACTOR]CertificationRepository=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?(#272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 쿼리의 성능 개선 * chore: git submodule 갱신 * chore: 워크플로우 갱신 * chore: git submodule 갱신 * chore: 워크플로우 변경 --- .github/workflows/main.yml | 30 ++++++++----------- .github/workflows/prTest.yml | 25 +++++++--------- GitGet-BACK-SECRET | 2 +- .../repository/CertificationRepository.java | 28 +++++++++-------- 4 files changed, 40 insertions(+), 45 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 46d28e4c..df1cbb43 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,23 +29,19 @@ jobs: - name: make application.yml run: | - mkdir -p ./src/main/resources - cd ./src/main/resources - touch ./application.yml - touch ./application-common.yml - touch ./application-prod.yml - echo "${{ secrets.APPLICATION }}" > ./application.yml - echo "${{ secrets.COMMON }}" > ./application-common.yml - echo "${{ secrets.PROD }}" > ./application-prod.yml - - - name: make test application.yml - run: | - mkdir -p ./src/test/resources - cd ./src/test/resources - touch ./application.yml - touch ./application-test.yml - echo "${{ secrets.APPLICATION_TEST }}" > ./application.yml - echo "${{ secrets.TEST }}" > ./application-test.yml + mkdir -p src/main/resources + cp GitGet-BACK-SECRET/main/resources/application.yml src/main/resources/ + cp GitGet-BACK-SECRET/main/resources/application-prod.yml src/main/resources/ + cp GitGet-BACK-SECRET/main/resources/application-common.yml src/main/resources/ + + mkdir -p src/test/resources + cp GitGet-BACK-SECRET/test/resources/application.yml src/test/resources/ + cp HGitGet-BACK-SECRET/test/resources/application-test.yml src/test/resources/ + + echo "Main resources contents:" + ls -la src/main/resources/ + echo "Test resources contents:" + ls -la src/test/resources/ - name: Grant execute permission for gradlew run: chmod +x ./gradlew diff --git a/.github/workflows/prTest.yml b/.github/workflows/prTest.yml index 743750d3..6f8cf990 100644 --- a/.github/workflows/prTest.yml +++ b/.github/workflows/prTest.yml @@ -8,32 +8,29 @@ jobs: PRTest: runs-on: ubuntu-latest permissions: write-all + steps: - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 with: - java-version: '17' - distribution: 'temurin' + token: ${{ secrets.ACTIONS_TOKEN }} + submodules: true - name: make application.yml run: | mkdir -p src/main/resources - cp HERE-WE-SECRET/main/resources/application.yml src/main/resources/ - cp HERE-WE-SECRET/main/resources/application-dev.yml src/main/resources/ - cp HERE-WE-SECRET/main/resources/application-prod.yml src/main/resources/ - cp HERE-WE-SECRET/main/resources/application-common.yml src/main/resources/ - + cp GitGet-BACK-SECRET/main/resources/application.yml src/main/resources/ + cp GitGet-BACK-SECRET/main/resources/application-prod.yml src/main/resources/ + cp GitGet-BACK-SECRET/main/resources/application-common.yml src/main/resources/ + mkdir -p src/test/resources - cp HERE-WE-SECRET/test/resources/application.yml src/test/resources/ - cp HERE-WE-SECRET/test/resources/application-test.yml src/test/resources/ - + cp GitGet-BACK-SECRET/test/resources/application.yml src/test/resources/ + cp GitGet-BACK-SECRET/test/resources/application-test.yml src/test/resources/ + echo "Main resources contents:" ls -la src/main/resources/ echo "Test resources contents:" ls -la src/test/resources/ - + - name: Grant execute permission for gradlew run: chmod +x ./gradlew diff --git a/GitGet-BACK-SECRET b/GitGet-BACK-SECRET index d3101d08..1b02d687 160000 --- a/GitGet-BACK-SECRET +++ b/GitGet-BACK-SECRET @@ -1 +1 @@ -Subproject commit d3101d0881a3ea01d7dba39a954c8aa04d9a12c3 +Subproject commit 1b02d6877d424fa6b75ed362e774c1bde48c413c diff --git a/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java b/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java index 5329be0c..d27abbed 100644 --- a/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java +++ b/src/main/java/com/genius/gitget/challenge/certification/repository/CertificationRepository.java @@ -1,27 +1,29 @@ package com.genius.gitget.challenge.certification.repository; -import com.genius.gitget.challenge.certification.domain.CertificateStatus; -import com.genius.gitget.challenge.certification.domain.Certification; import java.time.LocalDate; import java.util.List; import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import com.genius.gitget.challenge.certification.domain.CertificateStatus; +import com.genius.gitget.challenge.certification.domain.Certification; + public interface CertificationRepository extends JpaRepository { - @Query("select c from Certification c where c.certificatedAt = :targetDate and c.participant.id = :participantId") - Optional findByDate(@Param("targetDate") LocalDate targetDate, - @Param("participantId") Long participantId); + @Query("select c from Certification c where c.participant.id = :participantId AND c.certificatedAt = :targetDate") + Optional findByDate(@Param("targetDate") LocalDate targetDate, + @Param("participantId") Long participantId); - @Query("select c from Certification c where c.participant.id = :participantId and c.certificatedAt between :startDate AND :endDate order by c.currentAttempt desc") - List findByDuration(@Param("startDate") LocalDate startDate, - @Param("endDate") LocalDate endDate, - @Param("participantId") Long participantId); + @Query("select c from Certification c where c.participant.id = :participantId and c.certificatedAt between :startDate AND :endDate order by c.currentAttempt desc") + List findByDuration(@Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate, + @Param("participantId") Long participantId); - @Query("select c from Certification c where c.participant.id = :participantId and c.certificationStatus = :status and c.certificatedAt <= :currentDate") - List findByStatus(@Param("participantId") Long participantId, - @Param("status") CertificateStatus status, - @Param("currentDate") LocalDate currentDate); + @Query("select c from Certification c where c.participant.id = :participantId and c.certificatedAt <= :currentDate AND c.certificationStatus = :status") + List findByStatus(@Param("participantId") Long participantId, + @Param("status") CertificateStatus status, + @Param("currentDate") LocalDate currentDate); } From f9e82b18cd8052525243180a33a84f0fcc61d8b7 Mon Sep 17 00:00:00 2001 From: SSung023 Date: Sun, 30 Mar 2025 20:38:24 +0900 Subject: [PATCH 231/234] =?UTF-8?q?chore:=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=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 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index df1cbb43..d7e3b1ee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,7 @@ jobs: mkdir -p src/test/resources cp GitGet-BACK-SECRET/test/resources/application.yml src/test/resources/ - cp HGitGet-BACK-SECRET/test/resources/application-test.yml src/test/resources/ + cp GitGet-BACK-SECRET/test/resources/application-test.yml src/test/resources/ echo "Main resources contents:" ls -la src/main/resources/ From f4c94e5b83011404829759175aaf8eb91c5eb360 Mon Sep 17 00:00:00 2001 From: SSung023 Date: Sun, 30 Mar 2025 20:46:28 +0900 Subject: [PATCH 232/234] =?UTF-8?q?chore:=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=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 --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d7e3b1ee..410b8d2a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,8 +24,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '17' - distribution: 'temurin' + token: ${{ secrets.ACTIONS_TOKEN }} + submodules: true - name: make application.yml run: | From a0f2ceafa743c3f0dc0814f2a8e48fa0e7efa838 Mon Sep 17 00:00:00 2001 From: SSung023 Date: Sun, 30 Mar 2025 20:48:19 +0900 Subject: [PATCH 233/234] =?UTF-8?q?chore:=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=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 --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 410b8d2a..470bbfb1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,9 +20,6 @@ jobs: packages: write steps: - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 with: token: ${{ secrets.ACTIONS_TOKEN }} submodules: true From c9698b843133df7cb035c7f21e828bcd8679089c Mon Sep 17 00:00:00 2001 From: SSung023 Date: Mon, 31 Mar 2025 09:19:01 +0900 Subject: [PATCH 234/234] =?UTF-8?q?chore:=20submodule=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GitGet-BACK-SECRET | 2 +- scripts/deploy.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GitGet-BACK-SECRET b/GitGet-BACK-SECRET index 1b02d687..5cbf067e 160000 --- a/GitGet-BACK-SECRET +++ b/GitGet-BACK-SECRET @@ -1 +1 @@ -Subproject commit 1b02d6877d424fa6b75ed362e774c1bde48c413c +Subproject commit 5cbf067e9af6779fe46703f5529056455d1b3ee4 diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 50bf0d3a..cf21924c 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -13,7 +13,7 @@ sudo ps -ef | grep java | awk '{print $2}' | xargs kill -15 DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log -echo ">>> $DEPLOY_JAR의 $JAR_NAME를 실행합니다" >> /home/ubunru/deploy.log +echo ">>> $DEPLOY_JAR의 $JAR_NAME를 실행합니다" >> /home/ubuntu/deploy.log nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log & ## backup