From 44f2e07c362df1c128d9632b3b8b7f191480e50f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 27 Sep 2023 21:17:16 +0900 Subject: [PATCH 001/734] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 39 +++ README.md | 50 +++- build.gradle | 25 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63375 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 248 ++++++++++++++++++ gradlew.bat | 92 +++++++ settings.gradle | 1 + .../java/io/oduck/api/ApiApplication.java | 13 + src/main/resources/application.properties | 1 + .../io/oduck/api/ApiApplicationTests.java | 13 + 11 files changed, 488 insertions(+), 1 deletion(-) 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/io/oduck/api/ApiApplication.java create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/io/oduck/api/ApiApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..40d1f550 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +.DS_Store +*/.DS_Store + +### 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/ diff --git a/README.md b/README.md index 4514178d..834728af 100644 --- a/README.md +++ b/README.md @@ -1 +1,49 @@ -# oduck-server \ No newline at end of file +# oduck-server + +## How To Start? + +## How To Use? + +## 커밋 메세지 컨벤션 + +### 커밋 형식 + +커밋 메세지 형식은 다음을 따릅니다: + +```bash +type: subject #issue-number + +body + +footer +``` + +- `type` : 커밋의 종류를 나타냅니다. 종류는 다음 항목을 참고해주세요. +- `subject` : 커밋의 요약을 나타냅니다. +- `iuuse-number` : 관련된 이슈 번호를 나타냅니다. +- `body` : 커밋의 자세한 내용을 나타냅니다. (선택) +- `footer` : 커밋과 관련된 이슈를 닫거나, 어떤 변경을 가져오는지 등 추가적인 정보를 나타냅니다. (선택) + +예시는 다음과 같습니다: + +> Header 컴포넌트를 개발, 이에 따른 스타일을 변경함 + +```bash +feat: Header 컴포넌트 구현 및 스타일 수정 #123 + +* 헤더 컴포넌트를 개발합니다. 새 컴포넌트에 맞게 style.css를 수정했습니다. + +* Close #123 +``` + +### 커밋 종류 (type) + +- `feat` : 새로운 기능 +- `fix` : 버그 수정 +- `docs` : 문서만 변경 +- `style` : 코드에 영향을 주지 않는 변경 (공백, 세미콜론, css 등) +- `refactor` : 코드 리팩토링. 코드의 기능 변경이 아닌 가독성을 높이거나 재사용성 향상, 주석 추가 등 +- `test` : 테스트 추가, 기존 테스트 수정 등 +- `cleanup` : 불필요 파일 삭제, 코드 삭제 +- `rename` : 파일, 폴더 이름 변경 +- `chore` : 프로젝트 운영(유지보수, 개선) 업데이트. 빌드 설정, 의존성 변경, 스크립트 추가 등 diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..b9cdea50 --- /dev/null +++ b/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.1.4' + id 'io.spring.dependency-management' version '1.1.3' +} + +group = 'io.oduck' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.boot:spring-boot-starter-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..033e24c4cdf41af1ab109bc7f253b2b887023340 GIT binary patch literal 63375 zcmb5VV{~QRw)Y#`wrv{~+qP{x72B%VwzFc}c2cp;N~)5ZbDrJayPv(!dGEd-##*zr z)#n-$y^sH|_dchh3@8{H5D*j;5D<{i*8l5IFJ|DjL!e)upfGNX(kojugZ3I`oH1PvW`wFW_ske0j@lB9bX zO;2)`y+|!@X(fZ1<2n!Qx*)_^Ai@Cv-dF&(vnudG?0CsddG_&Wtae(n|K59ew)6St z#dj7_(Cfwzh$H$5M!$UDd8=4>IQsD3xV=lXUq($;(h*$0^yd+b{qq63f0r_de#!o_ zXDngc>zy`uor)4A^2M#U*DC~i+dc<)Tb1Tv&~Ev@oM)5iJ4Sn#8iRw16XXuV50BS7 zdBL5Mefch(&^{luE{*5qtCZk$oFr3RH=H!c3wGR=HJ(yKc_re_X9pD` zJ;uxPzUfVpgU>DSq?J;I@a+10l0ONXPcDkiYcihREt5~T5Gb}sT0+6Q;AWHl`S5dV>lv%-p9l#xNNy7ZCr%cyqHY%TZ8Q4 zbp&#ov1*$#grNG#1vgfFOLJCaNG@K|2!W&HSh@3@Y%T?3YI75bJp!VP*$*!< z;(ffNS_;@RJ`=c7yX04!u3JP*<8jeqLHVJu#WV&v6wA!OYJS4h<_}^QI&97-;=ojW zQ-1t)7wnxG*5I%U4)9$wlv5Fr;cIizft@&N+32O%B{R1POm$oap@&f| zh+5J{>U6ftv|vAeKGc|zC=kO(+l7_cLpV}-D#oUltScw})N>~JOZLU_0{Ka2e1evz z{^a*ZrLr+JUj;)K&u2CoCAXLC2=fVScI(m_p~0FmF>>&3DHziouln?;sxW`NB}cSX z8?IsJB)Z=aYRz!X=yJn$kyOWK%rCYf-YarNqKzmWu$ZvkP12b4qH zhS9Q>j<}(*frr?z<%9hl*i^#@*O2q(Z^CN)c2c z>1B~D;@YpG?G!Yk+*yn4vM4sO-_!&m6+`k|3zd;8DJnxsBYtI;W3We+FN@|tQ5EW= z!VU>jtim0Mw#iaT8t_<+qKIEB-WwE04lBd%Letbml9N!?SLrEG$nmn7&W(W`VB@5S zaY=sEw2}i@F_1P4OtEw?xj4@D6>_e=m=797#hg}f*l^`AB|Y0# z9=)o|%TZFCY$SzgSjS|8AI-%J4x}J)!IMxY3_KYze`_I=c1nmrk@E8c9?MVRu)7+Ue79|)rBX7tVB7U|w4*h(;Gi3D9le49B38`wuv zp7{4X^p+K4*$@gU(Tq3K1a#3SmYhvI42)GzG4f|u zwQFT1n_=n|jpi=70-yE9LA+d*T8u z`=VmmXJ_f6WmZveZPct$Cgu^~gFiyL>Lnpj*6ee>*0pz=t$IJ}+rE zsf@>jlcG%Wx;Cp5x)YSVvB1$yyY1l&o zvwX=D7k)Dn;ciX?Z)Pn8$flC8#m`nB&(8?RSdBvr?>T9?E$U3uIX7T?$v4dWCa46 z+&`ot8ZTEgp7G+c52oHJ8nw5}a^dwb_l%MOh(ebVj9>_koQP^$2B~eUfSbw9RY$_< z&DDWf2LW;b0ZDOaZ&2^i^g+5uTd;GwO(-bbo|P^;CNL-%?9mRmxEw~5&z=X^Rvbo^WJW=n_%*7974RY}JhFv46> zd}`2|qkd;89l}R;i~9T)V-Q%K)O=yfVKNM4Gbacc7AOd>#^&W&)Xx!Uy5!BHnp9kh z`a(7MO6+Ren#>R^D0K)1sE{Bv>}s6Rb9MT14u!(NpZOe-?4V=>qZ>}uS)!y~;jEUK z&!U7Fj&{WdgU#L0%bM}SYXRtM5z!6M+kgaMKt%3FkjWYh=#QUpt$XX1!*XkpSq-pl zhMe{muh#knk{9_V3%qdDcWDv}v)m4t9 zQhv{;} zc{}#V^N3H>9mFM8`i`0p+fN@GqX+kl|M94$BK3J-X`Hyj8r!#x6Vt(PXjn?N)qedP z=o1T^#?1^a{;bZ&x`U{f?}TMo8ToN zkHj5v|}r}wDEi7I@)Gj+S1aE-GdnLN+$hw!=DzglMaj#{qjXi_dwpr|HL(gcCXwGLEmi|{4&4#OZ4ChceA zKVd4K!D>_N=_X;{poT~4Q+!Le+ZV>=H7v1*l%w`|`Dx8{)McN@NDlQyln&N3@bFpV z_1w~O4EH3fF@IzJ9kDk@7@QctFq8FbkbaH7K$iX=bV~o#gfh?2JD6lZf(XP>~DACF)fGFt)X%-h1yY~MJU{nA5 ze2zxWMs{YdX3q5XU*9hOH0!_S24DOBA5usB+Ws$6{|AMe*joJ?RxfV}*7AKN9V*~J zK+OMcE@bTD>TG1*yc?*qGqjBN8mgg@h1cJLDv)0!WRPIkC` zZrWXrceVw;fB%3`6kq=a!pq|hFIsQ%ZSlo~)D z|64!aCnw-?>}AG|*iOl44KVf8@|joXi&|)1rB;EQWgm+iHfVbgllP$f!$Wf42%NO5b(j9Bw6L z;0dpUUK$5GX4QbMlTmLM_jJt!ur`_0~$b#BB7FL*%XFf<b__1o)Ao3rlobbN8-(T!1d-bR8D3S0@d zLI!*GMb5s~Q<&sjd}lBb8Nr0>PqE6_!3!2d(KAWFxa{hm`@u|a(%#i(#f8{BP2wbs zt+N_slWF4IF_O|{w`c~)Xvh&R{Au~CFmW#0+}MBd2~X}t9lz6*E7uAD`@EBDe$>7W zzPUkJx<`f$0VA$=>R57^(K^h86>09?>_@M(R4q($!Ck6GG@pnu-x*exAx1jOv|>KH zjNfG5pwm`E-=ydcb+3BJwuU;V&OS=6yM^4Jq{%AVqnTTLwV`AorIDD}T&jWr8pB&j28fVtk_y*JRP^t@l*($UZ z6(B^-PBNZ+z!p?+e8@$&jCv^EWLb$WO=}Scr$6SM*&~B95El~;W_0(Bvoha|uQ1T< zO$%_oLAwf1bW*rKWmlD+@CP&$ObiDy=nh1b2ejz%LO9937N{LDe7gle4i!{}I$;&Y zkexJ9Ybr+lrCmKWg&}p=`2&Gf10orS?4$VrzWidT=*6{KzOGMo?KI0>GL0{iFWc;C z+LPq%VH5g}6V@-tg2m{C!-$fapJ9y}c$U}aUmS{9#0CM*8pC|sfer!)nG7Ji>mfRh z+~6CxNb>6eWKMHBz-w2{mLLwdA7dA-qfTu^A2yG1+9s5k zcF=le_UPYG&q!t5Zd_*E_P3Cf5T6821bO`daa`;DODm8Ih8k89=RN;-asHIigj`n=ux>*f!OC5#;X5i;Q z+V!GUy0|&Y_*8k_QRUA8$lHP;GJ3UUD08P|ALknng|YY13)}!!HW@0z$q+kCH%xet zlWf@BXQ=b=4}QO5eNnN~CzWBbHGUivG=`&eWK}beuV*;?zt=P#pM*eTuy3 zP}c#}AXJ0OIaqXji78l;YrP4sQe#^pOqwZUiiN6^0RCd#D271XCbEKpk`HI0IsN^s zES7YtU#7=8gTn#lkrc~6)R9u&SX6*Jk4GFX7){E)WE?pT8a-%6P+zS6o&A#ml{$WX zABFz#i7`DDlo{34)oo?bOa4Z_lNH>n;f0nbt$JfAl~;4QY@}NH!X|A$KgMmEsd^&Y zt;pi=>AID7ROQfr;MsMtClr5b0)xo|fwhc=qk33wQ|}$@?{}qXcmECh>#kUQ-If0$ zseb{Wf4VFGLNc*Rax#P8ko*=`MwaR-DQ8L8V8r=2N{Gaips2_^cS|oC$+yScRo*uF zUO|5=?Q?{p$inDpx*t#Xyo6=s?bbN}y>NNVxj9NZCdtwRI70jxvm3!5R7yiWjREEd zDUjrsZhS|P&|Ng5r+f^kA6BNN#|Se}_GF>P6sy^e8kBrgMv3#vk%m}9PCwUWJg-AD zFnZ=}lbi*mN-AOm zCs)r=*YQAA!`e#1N>aHF=bb*z*hXH#Wl$z^o}x##ZrUc=kh%OHWhp=7;?8%Xj||@V?1c ziWoaC$^&04;A|T)!Zd9sUzE&$ODyJaBpvqsw19Uiuq{i#VK1!htkdRWBnb z`{rat=nHArT%^R>u#CjjCkw-7%g53|&7z-;X+ewb?OLWiV|#nuc8mp*LuGSi3IP<<*Wyo9GKV7l0Noa4Jr0g3p_$ z*R9{qn=?IXC#WU>48-k5V2Oc_>P;4_)J@bo1|pf=%Rcbgk=5m)CJZ`caHBTm3%!Z9 z_?7LHr_BXbKKr=JD!%?KhwdYSdu8XxPoA{n8^%_lh5cjRHuCY9Zlpz8g+$f@bw@0V z+6DRMT9c|>1^3D|$Vzc(C?M~iZurGH2pXPT%F!JSaAMdO%!5o0uc&iqHx?ImcX6fI zCApkzc~OOnfzAd_+-DcMp&AOQxE_EsMqKM{%dRMI5`5CT&%mQO?-@F6tE*xL?aEGZ z8^wH@wRl`Izx4sDmU>}Ym{ybUm@F83qqZPD6nFm?t?(7>h*?`fw)L3t*l%*iw0Qu#?$5eq!Qc zpQvqgSxrd83NsdO@lL6#{%lsYXWen~d3p4fGBb7&5xqNYJ)yn84!e1PmPo7ChVd%4 zHUsV0Mh?VpzZD=A6%)Qrd~i7 z96*RPbid;BN{Wh?adeD_p8YU``kOrGkNox3D9~!K?w>#kFz!4lzOWR}puS(DmfjJD z`x0z|qB33*^0mZdM&6$|+T>fq>M%yoy(BEjuh9L0>{P&XJ3enGpoQRx`v6$txXt#c z0#N?b5%srj(4xmPvJxrlF3H%OMB!jvfy z;wx8RzU~lb?h_}@V=bh6p8PSb-dG|-T#A?`c&H2`_!u+uenIZe`6f~A7r)`9m8atC zt(b|6Eg#!Q*DfRU=Ix`#B_dK)nnJ_+>Q<1d7W)eynaVn`FNuN~%B;uO2}vXr5^zi2 z!ifIF5@Zlo0^h~8+ixFBGqtweFc`C~JkSq}&*a3C}L?b5Mh-bW=e)({F_g4O3 zb@SFTK3VD9QuFgFnK4Ve_pXc3{S$=+Z;;4+;*{H}Rc;845rP?DLK6G5Y-xdUKkA6E3Dz&5f{F^FjJQ(NSpZ8q-_!L3LL@H* zxbDF{gd^U3uD;)a)sJwAVi}7@%pRM&?5IaUH%+m{E)DlA_$IA1=&jr{KrhD5q&lTC zAa3c)A(K!{#nOvenH6XrR-y>*4M#DpTTOGQEO5Jr6kni9pDW`rvY*fs|ItV;CVITh z=`rxcH2nEJpkQ^(;1c^hfb8vGN;{{oR=qNyKtR1;J>CByul*+=`NydWnSWJR#I2lN zTvgnR|MBx*XFsfdA&;tr^dYaqRZp*2NwkAZE6kV@1f{76e56eUmGrZ>MDId)oqSWw z7d&r3qfazg+W2?bT}F)4jD6sWaw`_fXZGY&wnGm$FRPFL$HzVTH^MYBHWGCOk-89y zA+n+Q6EVSSCpgC~%uHfvyg@ufE^#u?JH?<73A}jj5iILz4Qqk5$+^U(SX(-qv5agK znUkfpke(KDn~dU0>gdKqjTkVk`0`9^0n_wzXO7R!0Thd@S;U`y)VVP&mOd-2 z(hT(|$=>4FY;CBY9#_lB$;|Wd$aOMT5O_3}DYXEHn&Jrc3`2JiB`b6X@EUOD zVl0S{ijm65@n^19T3l%>*;F(?3r3s?zY{thc4%AD30CeL_4{8x6&cN}zN3fE+x<9; zt2j1RRVy5j22-8U8a6$pyT+<`f+x2l$fd_{qEp_bfxfzu>ORJsXaJn4>U6oNJ#|~p z`*ZC&NPXl&=vq2{Ne79AkQncuxvbOG+28*2wU$R=GOmns3W@HE%^r)Fu%Utj=r9t` zd;SVOnA(=MXgnOzI2@3SGKHz8HN~Vpx&!Ea+Df~`*n@8O=0!b4m?7cE^K*~@fqv9q zF*uk#1@6Re_<^9eElgJD!nTA@K9C732tV~;B`hzZ321Ph=^BH?zXddiu{Du5*IPg} zqDM=QxjT!Rp|#Bkp$(mL)aar)f(dOAXUiw81pX0DC|Y4;>Vz>>DMshoips^8Frdv} zlTD=cKa48M>dR<>(YlLPOW%rokJZNF2gp8fwc8b2sN+i6&-pHr?$rj|uFgktK@jg~ zIFS(%=r|QJ=$kvm_~@n=ai1lA{7Z}i+zj&yzY+!t$iGUy|9jH#&oTNJ;JW-3n>DF+ z3aCOzqn|$X-Olu_p7brzn`uk1F*N4@=b=m;S_C?#hy{&NE#3HkATrg?enaVGT^$qIjvgc61y!T$9<1B@?_ibtDZ{G zeXInVr5?OD_nS_O|CK3|RzzMmu+8!#Zb8Ik;rkIAR%6?$pN@d<0dKD2c@k2quB%s( zQL^<_EM6ow8F6^wJN1QcPOm|ehA+dP(!>IX=Euz5qqIq}Y3;ibQtJnkDmZ8c8=Cf3 zu`mJ!Q6wI7EblC5RvP*@)j?}W=WxwCvF3*5Up_`3*a~z$`wHwCy)2risye=1mSp%p zu+tD6NAK3o@)4VBsM!@);qgsjgB$kkCZhaimHg&+k69~drbvRTacWKH;YCK(!rC?8 zP#cK5JPHSw;V;{Yji=55X~S+)%(8fuz}O>*F3)hR;STU`z6T1aM#Wd+FP(M5*@T1P z^06O;I20Sk!bxW<-O;E081KRdHZrtsGJflFRRFS zdi5w9OVDGSL3 zNrC7GVsGN=b;YH9jp8Z2$^!K@h=r-xV(aEH@#JicPy;A0k1>g1g^XeR`YV2HfmqXY zYbRwaxHvf}OlCAwHoVI&QBLr5R|THf?nAevV-=~V8;gCsX>jndvNOcFA+DI+zbh~# zZ7`qNk&w+_+Yp!}j;OYxIfx_{f0-ONc?mHCiCUak=>j>~>YR4#w# zuKz~UhT!L~GfW^CPqG8Lg)&Rc6y^{%3H7iLa%^l}cw_8UuG;8nn9)kbPGXS}p3!L_ zd#9~5CrH8xtUd?{d2y^PJg+z(xIfRU;`}^=OlehGN2=?}9yH$4Rag}*+AWotyxfCJ zHx=r7ZH>j2kV?%7WTtp+-HMa0)_*DBBmC{sd$)np&GEJ__kEd`xB5a2A z*J+yx>4o#ZxwA{;NjhU*1KT~=ZK~GAA;KZHDyBNTaWQ1+;tOFFthnD)DrCn`DjBZ% zk$N5B4^$`n^jNSOr=t(zi8TN4fpaccsb`zOPD~iY=UEK$0Y70bG{idLx@IL)7^(pL z{??Bnu=lDeguDrd%qW1)H)H`9otsOL-f4bSu};o9OXybo6J!Lek`a4ff>*O)BDT_g z<6@SrI|C9klY(>_PfA^qai7A_)VNE4c^ZjFcE$Isp>`e5fLc)rg@8Q_d^Uk24$2bn z9#}6kZ2ZxS9sI(RqT7?El2@B+($>eBQrNi_k#CDJ8D9}8$mmm z4oSKO^F$i+NG)-HE$O6s1--6EzJa?C{x=QgK&c=)b(Q9OVoAXYEEH20G|q$}Hue%~ zO3B^bF=t7t48sN zWh_zA`w~|){-!^g?6Mqf6ieV zFx~aPUOJGR=4{KsW7I?<=J2|lY`NTU=lt=%JE9H1vBpkcn=uq(q~=?iBt_-r(PLBM zP-0dxljJO>4Wq-;stY)CLB4q`-r*T$!K2o}?E-w_i>3_aEbA^MB7P5piwt1dI-6o!qWCy0 ztYy!x9arGTS?kabkkyv*yxvsPQ7Vx)twkS6z2T@kZ|kb8yjm+^$|sEBmvACeqbz)RmxkkDQX-A*K!YFziuhwb|ym>C$}U|J)4y z$(z#)GH%uV6{ec%Zy~AhK|+GtG8u@c884Nq%w`O^wv2#A(&xH@c5M`Vjk*SR_tJnq z0trB#aY)!EKW_}{#L3lph5ow=@|D5LzJYUFD6 z7XnUeo_V0DVSIKMFD_T0AqAO|#VFDc7c?c-Q%#u00F%!_TW1@JVnsfvm@_9HKWflBOUD~)RL``-!P;(bCON_4eVdduMO>?IrQ__*zE@7(OX zUtfH@AX*53&xJW*Pu9zcqxGiM>xol0I~QL5B%Toog3Jlenc^WbVgeBvV8C8AX^Vj& z^I}H})B=VboO%q1;aU5ACMh{yK4J;xlMc`jCnZR^!~LDs_MP&8;dd@4LDWw~*>#OT zeZHwdQWS!tt5MJQI~cw|Ka^b4c|qyd_ly(+Ql2m&AAw^ zQeSXDOOH!!mAgzAp0z)DD>6Xo``b6QwzUV@w%h}Yo>)a|xRi$jGuHQhJVA%>)PUvK zBQ!l0hq<3VZ*RnrDODP)>&iS^wf64C;MGqDvx>|p;35%6(u+IHoNbK z;Gb;TneFo*`zUKS6kwF*&b!U8e5m4YAo03a_e^!5BP42+r)LFhEy?_7U1IR<; z^0v|DhCYMSj<-;MtY%R@Fg;9Kky^pz_t2nJfKWfh5Eu@_l{^ph%1z{jkg5jQrkvD< z#vdK!nku*RrH~TdN~`wDs;d>XY1PH?O<4^U4lmA|wUW{Crrv#r%N>7k#{Gc44Fr|t z@UZP}Y-TrAmnEZ39A*@6;ccsR>)$A)S>$-Cj!=x$rz7IvjHIPM(TB+JFf{ehuIvY$ zsDAwREg*%|=>Hw$`us~RP&3{QJg%}RjJKS^mC_!U;E5u>`X`jW$}P`Mf}?7G7FX#{ zE(9u1SO;3q@ZhDL9O({-RD+SqqPX)`0l5IQu4q)49TUTkxR(czeT}4`WV~pV*KY&i zAl3~X%D2cPVD^B43*~&f%+Op)wl<&|D{;=SZwImydWL6@_RJjxP2g)s=dH)u9Npki zs~z9A+3fj0l?yu4N0^4aC5x)Osnm0qrhz@?nwG_`h(71P znbIewljU%T*cC=~NJy|)#hT+lx#^5MuDDnkaMb*Efw9eThXo|*WOQzJ*#3dmRWm@! zfuSc@#kY{Um^gBc^_Xdxnl!n&y&}R4yAbK&RMc+P^Ti;YIUh|C+K1|=Z^{nZ}}rxH*v{xR!i%qO~o zTr`WDE@k$M9o0r4YUFFeQO7xCu_Zgy)==;fCJ94M_rLAv&~NhfvcLWCoaGg2ao~3e zBG?Ms9B+efMkp}7BhmISGWmJsKI@a8b}4lLI48oWKY|8?zuuNc$lt5Npr+p7a#sWu zh!@2nnLBVJK!$S~>r2-pN||^w|fY`CT{TFnJy`B|e5;=+_v4l8O-fkN&UQbA4NKTyntd zqK{xEKh}U{NHoQUf!M=2(&w+eef77VtYr;xs%^cPfKLObyOV_9q<(%76-J%vR>w9!us-0c-~Y?_EVS%v!* z15s2s3eTs$Osz$JayyH|5nPAIPEX=U;r&p;K14G<1)bvn@?bM5kC{am|C5%hyxv}a z(DeSKI5ZfZ1*%dl8frIX2?);R^^~LuDOpNpk-2R8U1w92HmG1m&|j&J{EK=|p$;f9 z7Rs5|jr4r8k5El&qcuM+YRlKny%t+1CgqEWO>3;BSRZi(LA3U%Jm{@{y+A+w(gzA< z7dBq6a1sEWa4cD0W7=Ld9z0H7RI^Z7vl(bfA;72j?SWCo`#5mVC$l1Q2--%V)-uN* z9ha*s-AdfbDZ8R8*fpwjzx=WvOtmSzGFjC#X)hD%Caeo^OWjS(3h|d9_*U)l%{Ab8 zfv$yoP{OuUl@$(-sEVNt{*=qi5P=lpxWVuz2?I7Dc%BRc+NGNw+323^ z5BXGfS71oP^%apUo(Y#xkxE)y?>BFzEBZ}UBbr~R4$%b7h3iZu3S(|A;&HqBR{nK& z$;GApNnz=kNO^FL&nYcfpB7Qg;hGJPsCW44CbkG1@l9pn0`~oKy5S777uH)l{irK!ru|X+;4&0D;VE*Ii|<3P zUx#xUqvZT5kVQxsF#~MwKnv7;1pR^0;PW@$@T7I?s`_rD1EGUdSA5Q(C<>5SzE!vw z;{L&kKFM-MO>hy#-8z`sdVx})^(Dc-dw;k-h*9O2_YZw}|9^y-|8RQ`BWJUJL(Cer zP5Z@fNc>pTXABbTRY-B5*MphpZv6#i802giwV&SkFCR zGMETyUm(KJbh+&$8X*RB#+{surjr;8^REEt`2&Dubw3$mx>|~B5IKZJ`s_6fw zKAZx9&PwBqW1Oz0r0A4GtnZd7XTKViX2%kPfv+^X3|_}RrQ2e3l=KG_VyY`H?I5&CS+lAX5HbA%TD9u6&s#v!G> zzW9n4J%d5ye7x0y`*{KZvqyXUfMEE^ZIffzI=Hh|3J}^yx7eL=s+TPH(Q2GT-sJ~3 zI463C{(ag7-hS1ETtU;_&+49ABt5!A7CwLwe z=SoA8mYZIQeU;9txI=zcQVbuO%q@E)JI+6Q!3lMc=Gbj(ASg-{V27u>z2e8n;Nc*pf}AqKz1D>p9G#QA+7mqqrEjGfw+85Uyh!=tTFTv3|O z+)-kFe_8FF_EkTw!YzwK^Hi^_dV5x-Ob*UWmD-})qKj9@aE8g240nUh=g|j28^?v7 zHRTBo{0KGaWBbyX2+lx$wgXW{3aUab6Bhm1G1{jTC7ota*JM6t+qy)c5<@ zpc&(jVdTJf(q3xB=JotgF$X>cxh7k*(T`-V~AR+`%e?YOeALQ2Qud( zz35YizXt(aW3qndR}fTw1p()Ol4t!D1pitGNL95{SX4ywzh0SF;=!wf=?Q?_h6!f* zh7<+GFi)q|XBsvXZ^qVCY$LUa{5?!CgwY?EG;*)0ceFe&=A;!~o`ae}Z+6me#^sv- z1F6=WNd6>M(~ z+092z>?Clrcp)lYNQl9jN-JF6n&Y0mp7|I0dpPx+4*RRK+VQI~>en0Dc;Zfl+x z_e_b7s`t1_A`RP3$H}y7F9_na%D7EM+**G_Z0l_nwE+&d_kc35n$Fxkd4r=ltRZhh zr9zER8>j(EdV&Jgh(+i}ltESBK62m0nGH6tCBr90!4)-`HeBmz54p~QP#dsu%nb~W z7sS|(Iydi>C@6ZM(Us!jyIiszMkd)^u<1D+R@~O>HqZIW&kearPWmT>63%_t2B{_G zX{&a(gOYJx!Hq=!T$RZ&<8LDnxsmx9+TBL0gTk$|vz9O5GkK_Yx+55^R=2g!K}NJ3 zW?C;XQCHZl7H`K5^BF!Q5X2^Mj93&0l_O3Ea3!Ave|ixx+~bS@Iv18v2ctpSt4zO{ zp#7pj!AtDmti$T`e9{s^jf(ku&E|83JIJO5Qo9weT6g?@vX!{7)cNwymo1+u(YQ94 zopuz-L@|5=h8A!(g-MXgLJC0MA|CgQF8qlonnu#j z;uCeq9ny9QSD|p)9sp3ebgY3rk#y0DA(SHdh$DUm^?GI<>%e1?&}w(b zdip1;P2Z=1wM+$q=TgLP$}svd!vk+BZ@h<^4R=GS2+sri7Z*2f`9 z5_?i)xj?m#pSVchk-SR!2&uNhzEi+#5t1Z$o0PoLGz*pT64%+|Wa+rd5Z}60(j?X= z{NLjtgRb|W?CUADqOS@(*MA-l|E342NxRaxLTDqsOyfWWe%N(jjBh}G zm7WPel6jXijaTiNita+z(5GCO0NM=Melxud57PP^d_U## zbA;9iVi<@wr0DGB8=T9Ab#2K_#zi=$igyK48@;V|W`fg~7;+!q8)aCOo{HA@vpSy-4`^!ze6-~8|QE||hC{ICKllG9fbg_Y7v z$jn{00!ob3!@~-Z%!rSZ0JO#@>|3k10mLK0JRKP-Cc8UYFu>z93=Ab-r^oL2 zl`-&VBh#=-?{l1TatC;VweM^=M7-DUE>m+xO7Xi6vTEsReyLs8KJ+2GZ&rxw$d4IT zPXy6pu^4#e;;ZTsgmG+ZPx>piodegkx2n0}SM77+Y*j^~ICvp#2wj^BuqRY*&cjmL zcKp78aZt>e{3YBb4!J_2|K~A`lN=u&5j!byw`1itV(+Q_?RvV7&Z5XS1HF)L2v6ji z&kOEPmv+k_lSXb{$)of~(BkO^py&7oOzpjdG>vI1kcm_oPFHy38%D4&A4h_CSo#lX z2#oqMCTEP7UvUR3mwkPxbl8AMW(e{ARi@HCYLPSHE^L<1I}OgZD{I#YH#GKnpRmW3 z2jkz~Sa(D)f?V?$gNi?6)Y;Sm{&?~2p=0&BUl_(@hYeX8YjaRO=IqO7neK0RsSNdYjD zaw$g2sG(>JR=8Iz1SK4`*kqd_3-?;_BIcaaMd^}<@MYbYisWZm2C2|Np_l|8r9yM|JkUngSo@?wci(7&O9a z%|V(4C1c9pps0xxzPbXH=}QTxc2rr7fXk$9`a6TbWKPCz&p=VsB8^W96W=BsB|7bc zf(QR8&Ktj*iz)wK&mW`#V%4XTM&jWNnDF56O+2bo<3|NyUhQ%#OZE8$Uv2a@J>D%t zMVMiHh?es!Ex19q&6eC&L=XDU_BA&uR^^w>fpz2_`U87q_?N2y;!Z!bjoeKrzfC)} z?m^PM=(z{%n9K`p|7Bz$LuC7!>tFOuN74MFELm}OD9?%jpT>38J;=1Y-VWtZAscaI z_8jUZ#GwWz{JqvGEUmL?G#l5E=*m>`cY?m*XOc*yOCNtpuIGD+Z|kn4Xww=BLrNYS zGO=wQh}Gtr|7DGXLF%|`G>J~l{k^*{;S-Zhq|&HO7rC_r;o`gTB7)uMZ|WWIn@e0( zX$MccUMv3ABg^$%_lNrgU{EVi8O^UyGHPNRt%R!1#MQJn41aD|_93NsBQhP80yP<9 zG4(&0u7AtJJXLPcqzjv`S~5;Q|5TVGccN=Uzm}K{v)?f7W!230C<``9(64}D2raRU zAW5bp%}VEo{4Rko`bD%Ehf=0voW?-4Mk#d3_pXTF!-TyIt6U+({6OXWVAa;s-`Ta5 zTqx&8msH3+DLrVmQOTBOAj=uoxKYT3DS1^zBXM?1W+7gI!aQNPYfUl{3;PzS9*F7g zWJN8x?KjBDx^V&6iCY8o_gslO16=kh(|Gp)kz8qlQ`dzxQv;)V&t+B}wwdi~uBs4? zu~G|}y!`3;8#vIMUdyC7YEx6bb^1o}G!Jky4cN?BV9ejBfN<&!4M)L&lRKiuMS#3} z_B}Nkv+zzxhy{dYCW$oGC&J(Ty&7%=5B$sD0bkuPmj7g>|962`(Q{ZZMDv%YMuT^KweiRDvYTEop3IgFv#)(w>1 zSzH>J`q!LK)c(AK>&Ib)A{g`Fdykxqd`Yq@yB}E{gnQV$K!}RsgMGWqC3DKE(=!{}ekB3+(1?g}xF>^icEJbc z5bdxAPkW90atZT+&*7qoLqL#p=>t-(-lsnl2XMpZcYeW|o|a322&)yO_8p(&Sw{|b zn(tY$xn5yS$DD)UYS%sP?c|z>1dp!QUD)l;aW#`%qMtQJjE!s2z`+bTSZmLK7SvCR z=@I4|U^sCwZLQSfd*ACw9B@`1c1|&i^W_OD(570SDLK`MD0wTiR8|$7+%{cF&){$G zU~|$^Ed?TIxyw{1$e|D$050n8AjJvvOWhLtLHbSB|HIfhMpqVf>AF&}ZQHhOJ14Bz zww+XL+qP}nww+W`F>b!by|=&a(cM4JIDhsTXY8@|ntQG}-}jm0&Bcj|LV(#sc=BNS zRjh;k9l>EdAFdd)=H!U`~$WP*}~^3HZ_?H>gKw>NBa;tA8M1{>St|)yDF_=~{KEPAGkg3VB`QCHol!AQ0|?e^W?81f{@()Wy!vQ$bY; z0ctx)l7VK83d6;dp!s{Nu=SwXZ8lHQHC*J2g@P0a={B8qHdv(+O3wV=4-t4HK1+smO#=S; z3cSI#Nh+N@AqM#6wPqjDmQM|x95JG|l1#sAU|>I6NdF*G@bD?1t|ytHlkKD+z9}#j zbU+x_cR-j9yX4s{_y>@zk*ElG1yS({BInGJcIT>l4N-DUs6fufF#GlF2lVUNOAhJT zGZThq54GhwCG(h4?yWR&Ax8hU<*U)?g+HY5-@{#ls5CVV(Wc>Bavs|l<}U|hZn z_%m+5i_gaakS*Pk7!v&w3&?R5Xb|AkCdytTY;r+Z7f#Id=q+W8cn)*9tEet=OG+Y} z58U&!%t9gYMx2N=8F?gZhIjtkH!`E*XrVJ?$2rRxLhV1z82QX~PZi8^N5z6~f-MUE zLKxnNoPc-SGl7{|Oh?ZM$jq67sSa)Wr&3)0YxlJt(vKf!-^L)a|HaPv*IYXb;QmWx zsqM>qY;tpK3RH-omtta+Xf2Qeu^$VKRq7`e$N-UCe1_2|1F{L3&}M0XbJ@^xRe&>P zRdKTgD6601x#fkDWkoYzRkxbn#*>${dX+UQ;FbGnTE-+kBJ9KPn)501#_L4O_k`P3 zm+$jI{|EC?8BXJY{P~^f-{**E53k%kVO$%p+=H5DiIdwMmUo>2euq0UzU90FWL!>; z{5@sd0ecqo5j!6AH@g6Mf3keTP$PFztq}@)^ZjK;H6Go$#SV2|2bAFI0%?aXgVH$t zb4Kl`$Xh8qLrMbZUS<2*7^F0^?lrOE=$DHW+O zvLdczsu0^TlA6RhDy3=@s!k^1D~Awulk!Iyo#}W$xq8{yTAK!CLl={H0@YGhg-g~+ z(u>pss4k#%8{J%~%8=H5!T`rqK6w^es-cNVE}=*lP^`i&K4R=peg1tdmT~UAbDKc& zg%Y*1E{hBf<)xO>HDWV7BaMWX6FW4ou1T2m^6{Jb!Su1UaCCYY8RR8hAV$7ho|FyEyP~ zEgK`@%a$-C2`p zV*~G>GOAs*3KN;~IY_UR$ISJxB(N~K>=2C2V6>xTmuX4klRXdrJd&UPAw7&|KEwF8Zcy2j-*({gSNR1^p02Oj88GN9a_Hq;Skdp}kO0;FLbje%2ZvPiltDZgv^ z#pb4&m^!79;O8F+Wr9X71laPY!CdNXG?J6C9KvdAE2xWW1>U~3;0v≫L+crb^Bz zc+Nw%zgpZ6>!A3%lau!Pw6`Y#WPVBtAfKSsqwYDWQK-~ zz(mx=nJ6-8t`YXB{6gaZ%G}Dmn&o500Y}2Rd?e&@=hBEmB1C=$OMBfxX__2c2O4K2#(0ksclP$SHp*8jq-1&(<6(#=6&H`Nlc2RVC4->r6U}sTY<1? zn@tv7XwUs-c>Lcmrm5AE0jHI5={WgHIow6cX=UK)>602(=arbuAPZ37;{HTJSIO%9EL`Et5%J7$u_NaC(55x zH^qX^H}*RPDx)^c46x>js=%&?y?=iFs^#_rUl@*MgLD92E5y4B7#EDe9yyn*f-|pQ zi>(!bIg6zY5fLSn@;$*sN|D2A{}we*7+2(4&EhUV%Qqo5=uuN^xt_hll7=`*mJq6s zCWUB|s$)AuS&=)T&_$w>QXHqCWB&ndQ$y4-9fezybZb0bYD^zeuZ>WZF{rc>c4s`` zgKdppTB|o>L1I1hAbnW%H%EkFt%yWC|0~+o7mIyFCTyb?@*Ho)eu(x`PuO8pLikN> z6YeI`V?AUWD(~3=8>}a6nZTu~#QCK(H0+4!ql3yS`>JX;j4+YkeG$ZTm33~PLa3L} zksw7@%e-mBM*cGfz$tS4LC^SYVdBLsR}nAprwg8h2~+Cv*W0%izK+WPVK}^SsL5R_ zpA}~G?VNhJhqx2he2;2$>7>DUB$wN9_-adL@TqVLe=*F8Vsw-yho@#mTD6*2WAr6B zjtLUh`E(;#p0-&$FVw(r$hn+5^Z~9J0}k;j$jL1;?2GN9s?}LASm?*Rvo@?E+(}F& z+=&M-n`5EIz%%F^e)nnWjkQUdG|W^~O|YeY4Fz}>qH2juEere}vN$oJN~9_Th^&b{ z%IBbET*E8%C@jLTxV~h#mxoRrJCF{!CJOghjuKOyl_!Jr?@4Upo7u>fTGtfm|CH2v z&9F+>;6aFbYXLj3{yZ~Yn1J2%!)A3~j2$`jOy{XavW@t)g}}KUVjCWG0OUc7aBc=2 zR3^u=dT47=5SmT{K1aGaVZkOx|24T-J0O$b9dfB25J|7yb6frwS6wZ1^y%EWOm}S< zc1SdYhfsdLG*FB-;!QLV3D!d~hnXTGVQVck9x%=B(Kk8c3y%f0nR95_TbY;l=obSl zEE@fp0|8Q$b3(+DXh?d0FEloGhO0#11CLQT5qtEckBLe-VN-I>9ys}PVK0r;0!jIG zH_q$;a`3Xv9P_V2ekV1SMzd#SKo<1~Dq2?M{(V;AwhH_2x@mN$=|=cG0<3o^j_0OF z7|WJ-f2G=7sA4NVGU2X5`o*D2T7(MbmZ2(oipooE{R?9!{WxX!%ofhsrPAxoIk!Kr z>I$a{Zq=%KaLrDCIL^gmA3z{2z%Wkr)b$QHcNUA^QwydWMJmxymO0QS22?mo%4(Md zgME(zE}ub--3*wGjV`3eBMCQG-@Gel1NKZDGuqobN|mAt0{@ZC9goI|BSmGBTUZ(`Xt z^e2LiMg?6E?G*yw(~K8lO(c4)RY7UWxrXzW^iCg-P41dUiE(i+gDmmAoB?XOB}+Ln z_}rApiR$sqNaT4frw69Wh4W?v(27IlK$Toy<1o)GeF+sGzYVeJ`F)3`&2WDi^_v67 zg;@ehwl3=t+}(DJtOYO!s`jHyo-}t@X|U*9^sIfaZfh;YLqEFmZ^E;$_XK}%eq;>0 zl?+}*kh)5jGA}3daJ*v1knbW0GusR1+_xD`MFPZc3qqYMXd>6*5?%O5pC7UVs!E-` zuMHc6igdeFQ`plm+3HhP)+3I&?5bt|V8;#1epCsKnz0%7m9AyBmz06r90n~9o;K30 z=fo|*`Qq%dG#23bVV9Jar*zRcV~6fat9_w;x-quAwv@BkX0{9e@y0NB(>l3#>82H6 z^US2<`=M@6zX=Pz>kb8Yt4wmeEo%TZ=?h+KP2e3U9?^Nm+OTx5+mVGDvgFee%}~~M zK+uHmj44TVs}!A}0W-A92LWE%2=wIma(>jYx;eVB*%a>^WqC7IVN9{o?iw{e4c=CG zC#i=cRJZ#v3 zF^9V+7u?W=xCY%2dvV_0dCP%5)SH*Xm|c#rXhwEl*^{Ar{NVoK*H6f5qCSy`+|85e zjGaKqB)p7zKNKI)iWe6A9qkl=rTjs@W1Crh(3G57qdT0w2ig^{*xerzm&U>YY{+fZbkQ#;^<$JniUifmAuEd^_M(&?sTrd(a*cD! zF*;`m80MrZ^> zaF{}rDhEFLeH#`~rM`o903FLO?qw#_Wyb5}13|0agjSTVkSI6Uls)xAFZifu@N~PM zQ%o?$k)jbY0u|45WTLAirUg3Zi1E&=G#LnSa89F3t3>R?RPcmkF}EL-R!OF_r1ZN` z?x-uHH+4FEy>KrOD-$KHg3$-Xl{Cf0;UD4*@eb~G{CK-DXe3xpEEls?SCj^p z$Uix(-j|9f^{z0iUKXcZQen}*`Vhqq$T?^)Ab2i|joV;V-qw5reCqbh(8N)c%!aB< zVs+l#_)*qH_iSZ_32E~}>=wUO$G_~k0h@ch`a6Wa zsk;<)^y=)cPpHt@%~bwLBy;>TNrTf50BAHUOtt#9JRq1ro{w80^sm-~fT>a$QC;<| zZIN%&Uq>8`Js_E((_1sewXz3VlX|-n8XCfScO`eL|H&2|BPZhDn}UAf_6s}|!XpmUr90v|nCutzMjb9|&}#Y7fj_)$alC zM~~D6!dYxhQof{R;-Vp>XCh1AL@d-+)KOI&5uKupy8PryjMhTpCZnSIQ9^Aq+7=Mb zCYCRvm4;H=Q8nZWkiWdGspC_Wvggg|7N`iED~Eap)Th$~wsxc(>(KI>{i#-~Dd8iQ zzonqc9DW1w4a*}k`;rxykUk+~N)|*I?@0901R`xy zN{20p@Ls<%`1G1Bx87Vm6Z#CA`QR(x@t8Wc?tpaunyV^A*-9K9@P>hAWW9Ev)E$gb z<(t?Te6GcJX2&0% z403pe>e)>m-^qlJU^kYIH)AutgOnq!J>FoMXhA-aEx-((7|(*snUyxa+5$wx8FNxS zKuVAVWArlK#kDzEM zqR?&aXIdyvxq~wF?iYPho*(h?k zD(SBpRDZ}z$A})*Qh!9&pZZRyNixD!8)B5{SK$PkVET(yd<8kImQ3ILe%jhx8Ga-1 zE}^k+Eo^?c4Y-t2_qXiVwW6i9o2qosBDj%DRPNT*UXI0=D9q{jB*22t4HHcd$T&Xi zT=Vte*Gz2E^qg%b7ev04Z&(;=I4IUtVJkg<`N6i7tjUn-lPE(Y4HPyJKcSjFnEzCH zPO(w%LmJ_=D~}PyfA91H4gCaf-qur3_KK}}>#9A}c5w@N;-#cHph=x}^mQ3`oo`Y$ope#)H9(kQK zGyt<7eNPuSAs$S%O>2ElZ{qtDIHJ!_THqTwcc-xfv<@1>IJ;YTv@!g-zDKBKAH<

Zet1e^8c}8fE97XH}+lF{qbF<`Y%dU|I!~Y`ZrVfKX82i z)(%!Tcf~eE^%2_`{WBPGPU@1NB5SCXe1sAI<4&n1IwO{&S$ThWn37heGOSW%nW7*L zxh0WK!E7zh%6yF-7%~l@I~b`2=*$;RYbi(I#zp$gL_d39U4A)KuB( zcS0bt48&%G_I~( zL(}w&2NA6#$=|g)J+-?ehHflD^lr77ngdz=dszFI;?~ZxeJv=gsm?4$$6#V==H{fa zqO!EkT>1-OQSJoX)cN}XsB;shvrHRwTH(I2^Ah4|rizn!V7T7fLh~Z<`Q+?zEMVxh z$=-x^RR*PlhkV_8mshTvs+zmZWY&Jk{9LX0Nx|+NAEq-^+Rh|ZlinVZ=e8=`WQt;e@= zPU}^1cG*O;G7l{Y#nl znp`y%CO_SC7gk0i0gY&phM04Y)~vU0!3$V$2T+h(1ZS+cCgc zaC?3M;B48^faGo>h~--#FNFauH?0BJJ6_nG5qOlr>k~%DCSJaOfl%KWHusw>tGrTxAhlEVDxc8R2C-)LCt&$Rt9IKor=ml7jirX@?WW+M z^I{b}MD5r$s>^^sN@&g`cXD~S_u09xo;{;noKZatIuzqd zW1e7oTl9>g8opPBT(p+&fo0F#!c{NFYYpIZ6u8hOB{F#{nP)@})X20$3iJtG$cO zJ$Oxl_qH{sL5d?=D$2M4C3Ajc;GN0(B-HVT;@pJ-LvIrN%|SY?t}g!J>ufQrR%hoY z!nr$tq~N%)9}^tEip93XW=MQ1@XovSvn`PTqXeT9@_7hGv4%LK1M**Q%UKi|(v@1_ zKGe*@+1%Y4v&`;5vUL`C&{tc+_7HFs7*OtjY8@Gg`C4O&#An{0xOvgNSehTHS~_1V z=daxCMzI5b_ydM5$z zZl`a{mM}i@x;=QyaqJY&{Q^R*^1Yzq!dHH~UwCCga+Us~2wk59ArIYtSw9}tEmjbo z5!JA=`=HP*Ae~Z4Pf7sC^A3@Wfa0Ax!8@H_&?WVe*)9B2y!8#nBrP!t1fqhI9jNMd zM_5I)M5z6Ss5t*f$Eh{aH&HBeh310Q~tRl3wCEcZ>WCEq%3tnoHE)eD=)XFQ7NVG5kM zaUtbnq2LQomJSWK)>Zz1GBCIHL#2E>T8INWuN4O$fFOKe$L|msB3yTUlXES68nXRX zP6n*zB+kXqqkpQ3OaMc9GqepmV?Ny!T)R@DLd`|p5ToEvBn(~aZ%+0q&vK1)w4v0* zgW44F2ixZj0!oB~^3k|vni)wBh$F|xQN>~jNf-wFstgiAgB!=lWzM&7&&OYS=C{ce zRJw|)PDQ@3koZfm`RQ$^_hEN$GuTIwoTQIDb?W&wEo@c75$dW(ER6q)qhF`{#7UTuPH&)w`F!w z0EKs}=33m}_(cIkA2rBWvApydi0HSOgc>6tu&+hmRSB%)s`v_NujJNhKLS3r6hv~- z)Hm@?PU{zd0Tga)cJWb2_!!9p3sP%Z zAFT|jy;k>4X)E>4fh^6=SxV5w6oo`mus&nWo*gJL zZH{SR!x)V)y=Qc7WEv-xLR zhD4OcBwjW5r+}pays`o)i$rcJb2MHLGPmeOmt5XJDg@(O3PCbxdDn{6qqb09X44T zh6I|s=lM6Nr#cGaA5-eq*T=LQ6SlRq*`~`b+dVi5^>el1p;#si6}kK}>w;1 z6B1dz{q_;PY{>DBQ+v@1pfXTd5a*^H9U*;qdj@XBF}MoSSQxVXeUpEM5Z0909&8$pRfR|B(t0ox&xl8{8mUNd#(zWONW{oycv$VjP1>q;jU@ z@+8E~fjz*I54OFFaQ{A5jn1w>r;l!NRlI(8q3*%&+tM?lov_G3wB`<}bQ>1=&xUht zmti5VZzV1Cx006Yzt|%Vwid>QPX8Nfa8|sue7^un@C+!3h!?-YK>lSfNIHh|0kL8v zbv_BklQ4HOqje|@Fyxn%IvL$N&?m(KN;%`I$N|muStjSsgG;gP4Smgz$2u(mG;DXP zf~uQ z212x^l6!MW>V@ORUGSFLAAjz3i5zO$=UmD_zhIk2OXUz^LkDLWjla*PW?l;`LLos> z7FBvCr)#)XBByDm(=n%{D>BcUq>0GOV9`i-(ZSI;RH1rdrAJ--f0uuAQ4odl z_^$^U_)0BBJwl@6R#&ZtJN+@a(4~@oYF)yG+G#3=)ll8O#Zv3SjV#zSXTW3h9kqn* z@AHL=vf~KMas}6{+u=}QFumr-!c=(BFP_dwvrdehzTyqco)m@xRc=6b#Dy+KD*-Bq zK=y*1VAPJ;d(b?$2cz{CUeG(0`k9_BIuUki@iRS5lp3=1#g)A5??1@|p=LOE|FNd; z-?5MLKd-5>yQ7n__5W^3C!_`hP(o%_E3BKEmo1h=H(7;{6$XRRW6{u+=oQX<((xAJ zNRY`Egtn#B1EBGHLy^eM5y}Jy0h!GAGhb7gZJoZI-9WuSRw)GVQAAcKd4Qm)pH`^3 zq6EIM}Q zxZGx%aLnNP1an=;o8p9+U^>_Bi`e23E^X|}MB&IkS+R``plrRzTE%ncmfvEW#AHJ~ znmJ`x&ez6eT21aLnoI`%pYYj zzQ?f^ob&Il;>6Fe>HPhAtTZa*B*!;;foxS%NGYmg!#X%)RBFe-acahHs3nkV61(E= zhekiPp1d@ACtA=cntbjuv+r-Zd`+lwKFdqZuYba_ey`&H<Psu;Tzwt;-LQxvv<_D5;ik7 zwETZe`+voUhk%$s2-7Rqfl`Ti_{(fydI(DAHKr<66;rYa6p8AD+NEc@Fd@%m`tiK% z=Mebzrtp=*Q%a}2UdK4J&5#tCN5PX>W=(9rUEXZ8yjRu+7)mFpKh{6;n%!bI(qA9kfyOtstGtOl zX!@*O0fly*L4k##fsm&V0j9Lj<_vu1)i?!#xTB7@2H&)$Kzt@r(GH=xRZlIimTDd_o(%9xO388LwC#;vQ?7OvRU_s< zDS@6@g}VnvQ+tn(C#sx0`J^T4WvFxYI17;uPs-Ub{R`J-NTdtBGl+Q>e81Z3#tDUr ztnVc*p{o|RNnMYts4pdw=P!uJkF@8~h)oV4dXu5F7-j0AW|=mt!QhP&ZV!!82*c7t zuOm>B*2gFtq;A8ynZ~Ms?!gEi5<{R_8tRN%aGM!saR4LJQ|?9w>Ff_61(+|ol_vL4 z-+N>fushRbkB4(e{{SQ}>6@m}s1L!-#20N&h%srA=L50?W9skMF9NGfQ5wU*+0<@> zLww8%f+E0Rc81H3e_5^DB@Dn~TWYk}3tqhO{7GDY;K7b*WIJ-tXnYM@z4rn(LGi?z z8%$wivs)fC#FiJh?(SbH-1bgdmHw&--rn7zBWe1xAhDdv#IRB@DGy}}zS%M0(F_3_ zLb-pWsdJ@xXE;=tpRAw?yj(Gz=i$;bsh&o2XN%24b6+?_gJDBeY zws3PE2u!#Cec>aFMk#ECxDlAs;|M7@LT8)Y4(`M}N6IQ{0YtcA*8e42!n^>`0$LFU zUCq2IR2(L`f++=85M;}~*E($nE&j;p{l%xchiTau*tB9bI= zn~Ygd@<+9DrXxoGPq}@vI1Q3iEfKRleuy*)_$+hg?+GOgf1r?d@Or42|s|D>XMa;ebr1uiTNUq@heusd6%WwJqyCCv!L*qou9l!B22H$bQ z)<)IA>Yo77S;|`fqBk!_PhLJEQb0wd1Z|`pCF;hol!34iQYtqu3K=$QxLW7(HFx~v>`vVRr zyqk^B4~!3F8t8Q_D|GLRrAbbQDf??D&Jd|mgw*t1YCd)CM2$76#Cqj1bD*vADwavp zS<`n@gLU4pwCqNPsIfHKl{5}gu9t-o+O< z??!fMqMrt$s}02pdBbOScUrc1T*{*-ideR6(1q4@oC6mxg8v8Y^h^^hfx6| z|Mld6Ax1CuSlmSJmHwdOix?$8emihK#&8&}u8m!#T1+c5u!H)>QW<7&R$eih)xkov zHvvEIJHbkt+2KQ<-bMR;2SYX?8SI=_<-J!GD5@P2FJ}K z5u82YFotCJF(dUeJFRX_3u8%iIYbRS??A?;iVO?84c}4Du9&jG<#urlZ_Unrcg8dR z!5I3%9F*`qwk#joKG_Q%5_xpU7|jm4h0+l$p;g%Tr>i74#3QnMXdz|1l2MQN$yw|5 zThMw15BxjWf2{KM)XtZ+e#N)ihlkxPe=5ymT9>@Ym%_LF}o z1XhCP`3E1A{iVoHA#|O|&5=w;=j*Qf`;{mBAK3={y-YS$`!0UmtrvzHBfR*s{z<0m zW>4C=%N98hZlUhwAl1X`rR)oL0&A`gv5X79??p_==g*n4$$8o5g9V<)F^u7v0Vv^n z1sp8{W@g6eWv2;A31Rhf5j?KJhITYfXWZsl^`7z`CFtnFrHUWiD?$pwU6|PQjs|7RA0o9ARk^9$f`u3&C|#Z3iYdh<0R`l2`)6+ z6tiDj@xO;Q5PDTYSxsx6n>bj+$JK8IPJ=U5#dIOS-zwyK?+t^V`zChdW|jpZuReE_ z)e~ywgFe!0q|jzsBn&(H*N`%AKpR@qM^|@qFai0};6mG_TvXjJ`;qZ{lGDZHScZk( z>pO+%icp)SaPJUwtIPo1BvGyP8E@~w2y}=^PnFJ$iHod^JH%j1>nXl<3f!nY9K$e` zq-?XYl)K`u*cVXM=`ym{N?z=dHQNR23M8uA-(vsA$6(xn+#B-yY!CB2@`Uz({}}w+ z0sni*39>rMC!Ay|1B@;al%T&xE(wCf+`3w>N)*LxZZZYi{5sqiVWgbNd>W*X?V}C- zjQ4F7e_uCUOHbtewQkq?m$*#@ZvWbu{4i$`aeKM8tc^ zL5!GL8gX}c+qNUtUIcps1S)%Gsx*MQLlQeoZz2y2OQb(A73Jc3`LmlQf0N{RTt;wa`6h|ljX1V7UugML=W5-STDbeWTiEMjPQ$({hn_s&NDXzs6?PLySp$?L`0ilH3vCUO{JS0Dp`z;Ry$6}R@1NdY7rxccbm$+;ApSe=2q!0 z()3$vYN0S$Cs)#-OBs{_2uFf}L4h$;7^2w20=l%5r9ui&pTEgg4U!FoCqyA6r2 zC5s72l}i*9y|KTjDE5gVlYe4I2gGZD)e`Py2gq7cK4at{bT~DSbQQ4Z4sl)kqXbbr zqvXtSqMrDdT2qt-%-HMoqeFEMsv~u)-NJ%Z*ipSJUm$)EJ+we|4*-Mi900K{K|e0; z1_j{X5)a%$+vM7;3j>skgrji92K1*Ip{SfM)=ob^E374JaF!C(cZ$R_E>Wv+?Iy9M z?@`#XDy#=z%3d9&)M=F8Xq5Zif%ldIT#wrlw(D_qOKo4wD(fyDHM5(wm1%7hy6euJ z%Edg!>Egs;ZC6%ktLFtyN0VvxN?*4C=*tOEw`{KQvS7;c514!FP98Nf#d#)+Y-wsl zP3N^-Pnk*{o(3~m=3DX$b76Clu=jMf9E?c^cbUk_h;zMF&EiVz*4I(rFoaHK7#5h0 zW7CQx+xhp}Ev+jw;SQ6P$QHINCxeF8_VX=F3&BWUd(|PVViKJl@-sYiUp@xLS2NuF z8W3JgUSQ&lUp@2E(7MG`sh4X!LQFa6;lInWqx}f#Q z4xhgK1%}b(Z*rZn=W{wBOe7YQ@1l|jQ|9ELiXx+}aZ(>{c7Ltv4d>PJf7f+qjRU8i%XZZFJkj&6D^s;!>`u%OwLa*V5Js9Y$b-mc!t@{C415$K38iVu zP7!{3Ff%i_e!^LzJWhBgQo=j5k<<($$b&%%Xm_f8RFC_(97&nk83KOy@I4k?(k<(6 zthO$3yl&0x!Pz#!79bv^?^85K5e7uS$ zJ33yka2VzOGUhQXeD{;?%?NTYmN3{b0|AMtr(@bCx+c=F)&_>PXgAG}4gwi>g82n> zL3DlhdL|*^WTmn;XPo62HhH-e*XIPSTF_h{#u=NY8$BUW=5@PD{P5n~g5XDg?Fzvb_u ziK&CJqod4srfY2T?+4x@)g9%3%*(Q2%YdCA3yM{s=+QD0&IM`8k8N&-6%iIL3kon> z0>p3BUe!lrz&_ZX2FiP%MeuQY-xVV%K?=bGPOM&XM0XRd7or< zy}jn_eEzuQ>t2fM9ict#ZNxD7HUycsq76IavfoNl$G1|t*qpUSX;YgpmJrr_8yOJ2 z(AwL;Ugi{gJ29@!G-mD82Z)46T`E+s86Qw|YSPO*OoooraA!8x_jQXYq5vUw!5f_x zubF$}lHjIWxFar8)tTg8z-FEz)a=xa`xL~^)jIdezZsg4%ePL$^`VN#c!c6`NHQ9QU zkC^<0f|Ksp45+YoX!Sv>+57q}Rwk*2)f{j8`d8Ctz^S~me>RSakEvxUa^Pd~qe#fb zN7rnAQc4u$*Y9p~li!Itp#iU=*D4>dvJ{Z~}kqAOBcL8ln3YjR{Sp!O`s=5yM zWRNP#;2K#+?I&?ZSLu)^z-|*$C}=0yi7&~vZE$s``IE^PY|dj^HcWI$9ZRm>3w(u` z-1%;;MJbzHFNd^!Ob!^PLO-xhhj@XrI81Y)x4@FdsI( za`o4Gy(`T$P?PB?s>o+eIOtuirMykbuAi65Y_UN1(?jTCy@J8Px`%;bcNmPm#Fr!= z5V!YViFJ!FBfEq>nJFk0^RAV1(7w+X`HRgP;nJHJdMa!}&vvduCMoslwHTes_I76|h>;(-9lbfGnt zoZomakOt759AuTX4b$)G8TzJ&m*BV8!vMs9#=e0tWa z%)84R=3?tfh72~=Rc;fXwj+x z+25xapYK@2@;}6)@8IL+F6iuJ_B{&A-0=U=U6WMbY>~ykVFp$XkH)f**b>TE5)shN z39E2L@JPCSl!?pkvFeh@6dCv9oE}|{GbbVM!XIgByN#md&tXy@>QscU0#z!I&X4;d z&B&ZA4lbrHJ!x4lCN4KC-)u#gT^cE{Xnhu`0RXVKn|j$vz8m}v^%*cQ{(h%FW8_8a zFM{$PirSI8@#*xg2T){A+EKX(eTC66Fb})w{vg%Vw)hvV-$tttI^V5wvU?a{(G}{G z@ob7Urk1@hDN&C$N!Nio9YrkiUC{5qA`KH*7CriaB;2~2Od>2l=WytBRl#~j`EYsj}jqK2xD*3 ztEUiPZzEJC??#Tj^?f)=sRXOJ_>5aO(|V#Yqro05p6)F$j5*wYr1zz|T4qz$0K(5! zr`6Pqd+)%a9Xq3aNKrY9843)O56F%=j_Yy_;|w8l&RU1+B4;pP*O_}X8!qD?IMiyT zLXBOOPg<*BZtT4LJ7DfyghK|_*mMP7a1>zS{8>?}#_XXaLoUBAz(Wi>$Q!L;oQ&cL z6O|T6%Dxq3E35$0g5areq9$2+R(911!Z9=wRPq-pju7DnN9LAfOu3%&onnfx^Px5( zT2^sU>Y)88F5#ATiVoS$jzC-M`vY8!{8#9O#3c&{7J1lo-rcNK7rlF0Zt*AKE(WN* z*o?Tv?Sdz<1v6gfCok8MG6Pzecx9?C zrQG5j^2{V556Hj=xTiU-seOCr2ni@b<&!j>GyHbv!&uBbHjH-U5Ai-UuXx0lcz$D7%=! z&zXD#Jqzro@R=hy8bv>D_CaOdqo6)vFjZldma5D+R;-)y1NGOFYqEr?h zd_mTwQ@K2veZTxh1aaV4F;YnaWA~|<8$p}-eFHashbWW6Dzj=3L=j-C5Ta`w-=QTw zA*k9!Ua~-?eC{Jc)xa;PzkUJ#$NfGJOfbiV^1au;`_Y8|{eJ(~W9pP9q?gLl5E6|e{xkT@s|Ac;yk01+twk_3nuk|lRu{7-zOjLAGe!)j?g+@-;wC_=NPIhk(W zfEpQrdRy z^Q$YBs%>$=So>PAMkrm%yc28YPi%&%=c!<}a=)sVCM51j+x#<2wz?2l&UGHhOv-iu z64x*^E1$55$wZou`E=qjP1MYz0xErcpMiNYM4+Qnb+V4MbM;*7vM_Yp^uXUuf`}-* z_2CnbQ);j5;Rz?7q)@cGmwE^P>4_u9;K|BFlOz_|c^1n~%>!uO#nA?5o4A>XLO{X2 z=8M%*n=IdnXQ}^+`DXRKM;3juVrXdgv79;E=ovQa^?d7wuw~nbu%%lsjUugE8HJ9zvZIM^nWvjLc-HKc2 zbj{paA}ub~4N4Vw5oY{wyop9SqPbWRq=i@Tbce`r?6e`?`iOoOF;~pRyJlKcIJf~G z)=BF$B>YF9>qV#dK^Ie#{0X(QPnOuu((_-u?(mxB7c9;LSS-DYJ8Wm4gz1&DPQ8;0 z=Wao(zb1RHXjwbu_Zv<=9njK28sS}WssjOL!3-E5>d17Lfnq0V$+IU84N z-4i$~!$V-%Ik;`Z3MOqYZdiZ^3nqqzIjLE+zpfQC+LlomQu-uNCStj%MsH(hsimN# z%l4vpJBs_2t7C)x@6*-k_2v0FOk<1nIRO3F{E?2DnS}w> z#%9Oa{`RB5FL5pKLkg59#x~)&I7GzfhiVC@LVFSmxZuiRUPVW*&2ToCGST0K`kRK) z02#c8W{o)w1|*YmjGSUO?`}ukX*rHIqGtFH#!5d1Jd}&%4Kc~Vz`S7_M;wtM|6PgI zNb-Dy-GI%dr3G3J?_yBX#NevuYzZgzZ!vN>$-aWOGXqX!3qzCIOzvA5PLC6GLIo|8 zQP^c)?NS29hPmk5WEP>cHV!6>u-2rR!tit#F6`_;%4{q^6){_CHGhvAs=1X8Fok+l zt&mk>{4ARXVvE-{^tCO?inl{)o}8(48az1o=+Y^r*AIe%0|{D_5_e>nUu`S%zR6|1 zu0$ov7c`pQEKr0sIIdm7hm{4K_s0V%M-_Mh;^A0*=$V9G1&lzvN9(98PEo=Zh$`Vj zXh?fZ;9$d!6sJRSjTkOhb7@jgSV^2MOgU^s2Z|w*e*@;4h?A8?;v8JaLPCoKP_1l- z=Jp0PYDf(d2Z`;O7mb6(_X_~z0O2yq?H`^c=h|8%gfywg#}wIyv&_uW{-e8e)YmGR zI0NNSDoJWa%0ztGzkwl>IYW*DesPRY?oH+ow^(>(47XUm^F`fAa0B~ja-ae$e>4-A z64lb_;|W0ppKI+ zxu2VLZzv4?Mr~mi?WlS-1L4a^5k+qb5#C)ktAYGUE1H?Vbg9qsRDHAvwJUN=w~AuT zUXYioFg2Dx-W)}w9VdFK#vpjoSc!WcvRZ_;TgHu;LSY*i7K_>Px{%C4-IL?6q?Qa_ zL7l=EEo|@X&$gX;fYP02qJF~LN9?E-OL2G(Fo4hW)G{`qnW zTIuc+-1VJvKgph0jAc(LzM);Pg$MPln?U|ek{_5nNJHfm-Y#ec+n#Yf_e>XfbLbN)eqHEDr0#?<;TskL5-0JGv|Ut{=$Xk8hlwbaMXdcI3GL zY-hykR{zX9liy$Z2F3!z346uu%9@-y6Gda`X2*ixlD_P@<}K?AoV?(%lM%* z(xNk=|A()443aGj)-~IDf3J+UA2p2lh6ei^pG*HL#SiThnIr5WZDXebI)F7X zGmP-3bH$i$+(IwqgbM7h%G5oJ@4{Z~qZ#Zs*k7eXJIqg;@0kAGV|b=F#hZs)2BYu1 zr8sj#Zd+Iu^G}|@-dR5S*U-;DqzkX3V0@q-k8&VHW?h0b0?tJ-Atqmg^J8iF7DP6k z)W{g?5~F*$5x?6W)3YKcrNu8%%(DglnzMx5rsU{#AD+WPpRBf``*<8F-x75D$$13U zcaNXYC0|;r&(F@!+E=%+;bFKwKAB$?6R%E_QG5Yn5xX#h+zeI-=mdXD5+D+lEuM`M ze+*G!zX^xbnA?~LnPI=D2`825Ax8rM()i*{G0gcV5MATV?<7mh+HDA7-f6nc@95st zzC_si${|&=$MUj@nLxl_HwEXb2PDH+V?vg zA^DJ%dn069O9TNK-jV}cQKh|$L4&Uh`?(z$}#d+{X zm&=KTJ$+KvLZv-1GaHJm{>v=zXW%NSDr8$0kSQx(DQ)6S?%sWSHUazXSEg_g3agt2@0nyD?A?B%9NYr(~CYX^&U#B4XwCg{%YMYo%e68HVJ7`9KR`mE*Wl7&5t71*R3F>*&hVIaZXaI;2a$?;{Ew{e3Hr1* zbf$&Fyhnrq7^hNC+0#%}n^U2{ma&eS)7cWH$bA@)m59rXlh96piJu@lcKl<>+!1#s zW#6L5Ov%lS(?d66-(n`A%UuiIqs|J|Ulq0RYq-m&RR0>wfA1?<34tI?MBI#a8lY{m z{F2m|A@=`DpZpwdIH#4)9$#H3zr4kn2OX!UE=r8FEUFAwq6VB?DJ8h59z$GXud$#+ zjneIq8uSi&rnG0IR8}UEn5OcZC?@-;$&Ry9hG{-1ta`8aAcOe1|82R7EH`$Qd3sf* zbrOk@G%H7R`j;hOosRVIP_2_-TuyB@rdj?(+k-qQwnhV3niH+CMl>ELX(;X3VzZVJ ztRais0C^L*lmaE(nmhvep+peCqr!#|F?iVagZcL>NKvMS_=*Yl%*OASDl3(mMOY9! z=_J$@nWpA-@><43m4olSQV8(PwhsO@+7#qs@0*1fDj70^UfQ(ORV0N?H{ceLX4<43 zEn)3CGoF&b{t2hbIz;Og+$+WiGf+x5mdWASEWIA*HQ9K9a?-Pf9f1gO6LanVTls)t z^f6_SD|>2Kx8mdQuiJwc_SmZOZP|wD7(_ti#0u=io|w~gq*Odv>@8JBblRCzMKK_4 zM-uO0Ud9>VD>J;zZzueo#+jbS7k#?W%`AF1@ZPI&q%}beZ|ThISf-ly)}HsCS~b^g zktgqOZ@~}1h&x50UQD~!xsW-$K~whDQNntLW=$oZDClUJeSr2$r3}94Wk1>co3beS zoY-7t{rGv|6T?5PNkY zj*XjF()ybvnVz5=BFnLO=+1*jG>E7F%&vm6up*QgyNcJJPD|pHoZ!H6?o3Eig0>-! zt^i-H@bJ;^!$6ZSH}@quF#RO)j>7A5kq4e+7gK=@g;POXcGV28Zv$jybL1J`g@wC# z_DW1ck}3+n@h2LFQhwVfaV@D+-kff4celZC0;0ef?pA#*PPd8Kk8sO1wza&BHQFblVU8P1=-qScHff^^fR zycH!hlHQs7iejITpc4UaBxzqTJ}Z#^lk{W(cr`qtW~Ap;HvuUf#MxgEG?tEU+B?G% znub0I(s@XvI(lva}$Z7<}Qg=rWd5n)}rX{nb+Aw;}?l9LZI-`N-*hts=c6XgjfJs ztp>-686v6ug{glEZ}K=jVG|N1WSWrU*&ue|4Q|O@;s0#L5P*U%Vx;)w7S0ZmLuvwA z@zs2Kut)n1K7qaywO#TbBR`Q~%mdr`V)D`|gN0!07C1!r3{+!PYf9*;h?;dE@#z(k z;o`g~<>P|Sy$ldHTUR3v=_X0Iw6F>3GllrFXVW?gU0q6|ocjd!glA)#f0G7i20ly>qxRljgfO2)RVpvmg#BSrN)GbGsrIb}9 z1t+r;Q>?MGLk#LI5*vR*C8?McB|=AoAjuDk&Pn`KQo z`!|mi{Cz@BGJ!TwMUUTkKXKNtS#OVNxfFI_Gfq3Kpw0`2AsJv9PZPq9x?~kNNR9BR zw#2jp%;FJNoOzW>tE#zskPICp>XSs?|B0E%DaJH)rtLA}$Y>?P+vEOvr#8=pylh zch;H3J`RE1{97O+1(1msdshZx$it^VfM$`-Gw>%NN`K|Tr$0}U`J?EBgR%bg=;et0 z_en)!x`~3so^V9-jffh3G*8Iy6sUq=uFq%=OkYvHaL~#3jHtr4sGM?&uY&U8N1G}QTMdqBM)#oLTLdKYOdOY%{5#Tgy$7QA! zWQmP!Wny$3YEm#Lt8TA^CUlTa{Cpp=x<{9W$A9fyKD0ApHfl__Dz4!HVVt(kseNzV z5Fb`|7Mo>YDTJ>g;7_MOpRi?kl>n(ydAf7~`Y6wBVEaxqK;l;}6x8(SD7}Tdhe2SR zncsdn&`eI}u}@^~_9(0^r!^wuKTKbs-MYjXy#-_#?F=@T*vUG@p4X+l^SgwF>TM}d zr2Ree{TP5x@ZtVcWd3++o|1`BCFK(ja-QP?zj6=ZOq)xf$CfSv{v;jCcNt4{r8f+m zz#dP|-~weHla%rsyYhB_&LHkwuj83RuCO0p;wyXsxW5o6{)zFAC~2%&NL? z=mA}szjHKsVSSnH#hM|C%;r0D$7)T`HQ1K5vZGOyUbgXjxD%4xbs$DAEz)-;iO?3& zXcyU*Z8zm?pP}w&9ot_5I;x#jIn^Joi5jBDOBP1)+p@G1U)pL6;SIO>Nhw?9St2UN zMedM(m(T6bNcPPD`%|9dvXAB&IS=W4?*7-tqldqALH=*UapL!4`2TM_{`W&pm*{?| z0DcsaTdGA%RN={Ikvaa&6p=Ux5ycM){F1OgOh(^Yk-T}a5zHH|=%Jk)S^vv9dY~`x zG+!=lsDjp!D}7o94RSQ-o_g#^CnBJlJ@?saH&+j0P+o=eKqrIApyR7ttQu*0 z1f;xPyH2--)F9uP2#Mw}OQhOFqXF#)W#BAxGP8?an<=JBiokg;21gKG_G8X!&Hv;7 zP9Vpzm#@;^-lf=6POs>UrGm-F>-! zm;3qp!Uw?VuXW~*Fw@LC)M%cvbe9!F(Oa^Y6~mb=8%$lg=?a0KcGtC$5y?`L5}*-j z7KcU8WT>2PpKx<58`m((l9^aYa3uP{PMb)nvu zgt;ia9=ZofxkrW7TfSrQf4(2juZRBgcE1m;WF{v1Fbm}zqsK^>sj=yN(x}v9#_{+C zR4r7abT2cS%Wz$RVt!wp;9U7FEW&>T>YAjpIm6ZSM4Q<{Gy+aN`Vb2_#Q5g@62uR_>II@eiHaay+JU$J=#>DY9jX*2A=&y8G%b zIY6gcJ@q)uWU^mSK$Q}?#Arq;HfChnkAOZ6^002J>fjPyPGz^D5p}o;h2VLNTI{HGg!obo3K!*I~a7)p-2Z3hCV_hnY?|6i`29b zoszLpkmch$mJeupLbt4_u-<3k;VivU+ww)a^ekoIRj4IW4S z{z%4_dfc&HAtm(o`d{CZ^AAIE5XCMvwQSlkzx3cLi?`4q8;iFTzuBAddTSWjfcZp* zn{@Am!pl&fv#k|kj86e$2%NK1G4kU=E~z9L^`@%2<%Dx%1TKk_hb-K>tq8A9bCDfW z@;Dc3KqLafkhN6414^46Hl8Tcv1+$q_sYjj%oHz)bsoGLEY1)ia5p=#eii(5AM|TW zA8=;pt?+U~>`|J(B85BKE0cB4n> zWrgZ)Rbu}^A=_oz65LfebZ(1xMjcj_g~eeoj74-Ex@v-q9`Q{J;M!mITVEfk6cn!u zn;Mj8C&3^8Kn%<`Di^~Y%Z$0pb`Q3TA}$TiOnRd`P1XM=>5)JN9tyf4O_z}-cN|i> zwpp9g`n%~CEa!;)nW@WUkF&<|wcWqfL35A}<`YRxV~$IpHnPQs2?+Fg3)wOHqqAA* zPv<6F6s)c^o%@YqS%P{tB%(Lxm`hsKv-Hb}MM3=U|HFgh8R-|-K(3m(eU$L@sg=uW zB$vAK`@>E`iM_rSo;Cr*?&wss@UXi19B9*0m3t3q^<)>L%4j(F85Ql$i^;{3UIP0c z*BFId*_mb>SC)d#(WM1%I}YiKoleKqQswkdhRt9%_dAnDaKM4IEJ|QK&BnQ@D;i-ame%MR5XbAfE0K1pcxt z{B5_&OhL2cx9@Sso@u2T56tE0KC`f4IXd_R3ymMZ%-!e^d}v`J?XC{nv1mAbaNJX| zXau+s`-`vAuf+&yi2bsd5%xdqyi&9o;h&fcO+W|XsKRFOD+pQw-p^pnwwYGu=hF7& z{cZj$O5I)4B1-dEuG*tU7wgYxNEhqAxH?p4Y1Naiu8Lt>FD%AxJ811`W5bveUp%*e z9H+S}!nLI;j$<*Dn~I*_H`zM^j;!rYf!Xf#X;UJW<0gic?y>NoFw}lBB6f#rl%t?k zm~}eCw{NR_%aosL*t$bmlf$u|U2hJ*_rTcTwgoi_N=wDhpimYnf5j!bj0lQ*Go`F& z6Wg+xRv55a(|?sCjOIshTEgM}2`dN-yV>)Wf$J58>lNVhjRagGZw?U9#2p!B5C3~Nc%S>p`H4PK z7vX@|Uo^*F4GXiFnMf4gwHB;Uk8X4TaLX4A>B&L?mw4&`XBnLCBrK2FYJLrA{*))0 z$*~X?2^Q0KS?Yp##T#ohH1B)y4P+rR7Ut^7(kCwS8QqgjP!aJ89dbv^XBbLhTO|=A z|3FNkH1{2Nh*j{p-58N=KA#6ZS}Ir&QWV0CU)a~{P%yhd-!ehF&~gkMh&Slo9gAT+ zM_&3ms;1Um8Uy0S|0r{{8xCB&Tg{@xotF!nU=YOpug~QlZRKR{DHGDuk(l{)d$1VD zj)3zgPeP%wb@6%$zYbD;Uhvy4(D|u{Q_R=fC+9z#sJ|I<$&j$|kkJiY?AY$ik9_|% z?Z;gOQG5I%{2{-*)Bk|Tia8n>TbrmjnK+8u*_cS%*;%>R|K|?urtIdgTM{&}Yn1;| zk`xq*Bn5HP5a`ANv`B$IKaqA4e-XC`sRn3Z{h!hN0=?x(kTP+fE1}-<3eL+QDFXN- z1JmcDt0|7lZN8sh^=$e;P*8;^33pN>?S7C0BqS)ow4{6ODm~%3018M6P^b~(Gos!k z2AYScAdQf36C)D`w&p}V89Lh1s88Dw@zd27Rv0iE7k#|U4jWDqoUP;-He5cd4V7Ql)4S+t>u9W;R-8#aee-Ct1{fPD+jv&zV(L&k z)!65@R->DB?K6Aml57?psj5r;%w9Vc3?zzGs&kTA>J9CmtMp^Wm#1a@cCG!L46h-j z8ZUL4#HSfW;2DHyGD|cXHNARk*{ql-J2W`9DMxzI0V*($9{tr|O3c;^)V4jwp^RvW z2wzIi`B8cYISb;V5lK}@xtm3NB;88)Kn}2fCH(WRH1l@3XaO7{R*Lc7{ZN1m+#&diI7_qzE z?BS+v<)xVMwt{IJ4yS2Q4(77II<>kqm$Jc3yWL42^gG6^Idg+y3)q$-(m2>E49-fV zyvsCzJ5EM4hyz1r#cOh5vgrzNGCBS}(Bupe`v6z{e z)cP*a8VCbRuhPp%BUwIRvj-$`3vrbp;V3wmAUt{?F z0OO?Mw`AS?y@>w%(pBO=0lohnxFWx`>Hs}V$j{XI2?}BtlvIl7!ZMZukDF7 z^6Rq2H*36KHxJ1xWm5uTy@%7;N0+|<>Up>MmxKhb;WbH1+=S94nOS-qN(IKDIw-yr zi`Ll^h%+%k`Yw?o3Z|ObJWtfO|AvPOc96m5AIw;4;USG|6jQKr#QP}+BLy*5%pnG2 zyN@VMHkD`(66oJ!GvsiA`UP;0kTmUST4|P>jTRfbf&Wii8~a`wMwVZoJ@waA{(t(V zwoc9l*4F>YUM8!aE1{?%{P4IM=;NUF|8YkmG0^Y_jTJtKClDV3D3~P7NSm7BO^r7& zWn!YrNc-ryEvhN$$!P%l$Y_P$s8E>cdAe3=@!Igo^0diL6`y}enr`+mQD;RC?w zb8}gXT!aC`%rdxx2_!`Qps&&w4i0F95>;6;NQ-ys;?j#Gt~HXzG^6j=Pv{3l1x{0( z4~&GNUEbH=9_^f@%o&BADqxb54EAq=8rKA~4~A!iDp9%eFHeA1L!Bb8Lz#kF(p#)X zn`CglEJ(+tr=h4bIIHlLkxP>exGw~{Oe3@L^zA)|Vx~2yNuPKtF^cV6X^5lw8hU*b zK-w6x4l&YWVB%0SmN{O|!`Sh6H45!7}oYPOc+a#a|n3f%G@eO)N>W!C|!FNXV3taFdpEK*A1TFGcRK zV$>xN%??ii7jx5D69O>W6O`$M)iQU7o!TPG*+>v6{TWI@p)Yg$;8+WyE9DVBMB=vnONSQ6k1v z;u&C4wZ_C`J-M0MV&MpOHuVWbq)2LZGR0&@A!4fZwTM^i;GaN?xA%0)q*g(F0PIB( zwGrCC#}vtILC_irDXI5{vuVO-(`&lf2Q4MvmXuU8G0+oVvzZp0Y)zf}Co0D+mUEZz zgwR+5y!d(V>s1} zji+mrd_6KG;$@Le2Ic&am6O+Rk1+QS?urB4$FQNyg2%9t%!*S5Ts{8j*&(H1+W;0~ z$frd%jJjlV;>bXD7!a-&!n52H^6Yp}2h3&v=}xyi>EXXZDtOIq@@&ljEJG{D`7Bjr zaibxip6B6Mf3t#-*Tn7p z96yx1Qv-&r3)4vg`)V~f8>>1_?E4&$bR~uR;$Nz=@U(-vyap|Jx zZ;6Ed+b#GXN+gN@ICTHx{=c@J|97TIPWs(_kjEIwZFHfc!rl8Ep-ZALBEZEr3^R-( z7ER1YXOgZ)&_=`WeHfWsWyzzF&a;AwTqzg~m1lOEJ0Su=C2<{pjK;{d#;E zr2~LgXN?ol2ua5Y*1)`(be0tpiFpKbRG+IK(`N?mIgdd9&e6vxzqxzaa`e7zKa3D_ zHi+c1`|720|dn(z4Qos^e7sn(PU%NYLv$&!|4kEse%DK;YAD06@XO3!EpKpz!^*?(?-Ip zC_Zlb(-_as+-D?0Ag9`|4?)bN)5o(J=&udAY|YgV(YuK9k=E>0z`$dSaL(wmxd!1f zME&3wwv@#{dgeMlZ4}GL!I`VZxtdQY$lmauCN_|mGXqEEj@i~du$|>5UvLjsbq!{; z@jEf;21iC1jFEmIPE^4gykHQzCMLj=2Ek4&FvlpqTlS(0YT%*W<>XgH$4ww`D`aihBGkPM(&EG};Cl&wzg8!jL z`rkqPzvH(0Kd{2n=?Bt8aAU&0IyiA+V-qnXVId^qG!SWZ7%_f&i!D{R#7Jo$%tICxY%j)ebORE>3H_c|to}c#HX;HAC?~B;2mmQrMp2;8T zmzde!k7BYg^Z1r|DUvSD3@{6S<1kndb%Qt%GA# z+sB2&F5L`R&fLRdAlpU_pVsJsYDEz{^ zKGaAz#%W+MPGT+D$+xowMY0=ipM)0p?zym&Aoi)qL(pO_weO(k?s|ELHl^W zviJiFUXRL&?`;3_;mvc02A@sbsW9}#{anvGafZ#ST;}za?XS3}ZG3B4m(SW{>w}Fh z)T5Yi*``Tstmi9SHXmuWSND@cj}qtY!`tuD29Dpu+-D3$h<5FY>jE>YJvqBmhw?oll`x7Ono(}R~P zle_eBwYy0Rr7kmf_SEt_gn4)AO-r`}^Z5Y%Rm8)K-?X>rvDL+QT?#)QwDsQ2c$tc* z&#hbgkL6}GnBDH;+lREM6MGIskRa@r>5Iq(ll2IepuhW86w@14=E{6$cz*cBDQ)CT>}v-DLM-v8)xaPBnmGBKM63RgDGqh!<*j90tSE4|G^+r@#-7g2 zs8KE8eZPZhQuN>wBU%8CmkE9LH1%O;-*ty0&K~01>F3XB>6sAm*m3535)9T&Fz}A4 zwGjZYVea@Fesd=Rv?ROE#q=}yfvQEP8*4zoEw4@^Qvw54utUfaR1T6gLmq?c9sON> z>Np6|0hdP_VURy81;`8{ZYS)EpU9-3;huFq)N3r{yP1ZBCHH7=b?Ig6OFK~%!GwtQ z3`RLKe8O&%^V`x=J4%^Oqg4ZN9rW`UQN^rslcr_Utzd-@u-Sm{rphS-y}{k41)Y4E zfzu}IC=J0JmRCV6a3E38nWl1G495grsDDc^H0Fn%^E0FZ=CSHB4iG<6jW1dY`2gUr zF>nB!y@2%rouAUe9m0VQIg$KtA~k^(f{C*Af_tOl=>vz>$>7qh+fPrSD0YVUnTt)? z;@1E0a*#AT{?oUs#bol@SPm0U5g<`AEF^=b-~&4Er)MsNnPsLb^;fL2kwp|$dwiE3 zNc5VDOQ%Q8j*d5vY##)PGXx51s8`0}2_X9u&r(k?s7|AgtW0LYbtlh!KJ;C9QZuz< zq>??uxAI1YP|JpN$+{X=97Cdu^mkwlB={`aUp+Uyu1P139=t%pSVKo7ZGi_v(0z>l zHLGxV%0w&#xvev)KCQ{7GC$nc3H?1VOsYGgjTK;Px(;o0`lerxB<+EJX9G9f8b+)VJdm(Ia)xjD&5ZL45Np?9 zB%oU;z05XN7zt{Q!#R~gcV^5~Y^gn+Lbad7C{UDX2Nznj8e{)TLH|zEc|{a#idm@z z6(zon+{a>FopmQsCXIs*4-dLGgTc)iOhO3r=l?imNUR-pWl!ktO0r_a0Nqo@bu8MzyjSq9zkqPe*`Sxz75rZ zr9X%(=PVqCRB=zfX+_u&*k4#s1k4OV11YgkCrlr6V;vz<{99HKC@qQ+H8xv5)sc63 z69;U4O&{fb5(fN``jJH#3=GHsV56@{d@7`VhA$K^;GU+R-V%%cnmjYs?>c5^6Ugv} zn<}L&i;2`zzW@(kxf$$gVH@7nh}2%G%ciQ_B?r{13?Q@=Q+6msQGtnyY%Gkjeor?g z7F*tMqLdhcq+LCCo^D;CtOACCBhXgK-M&w{*dcUdmtv@XFTofmmpcWKtCn^`#?oZC zUOm52 z7sK$hR|Vh6y&pfIUK&!`8HH*>12$nWA)Ynp+XwOj=jNLD z{QA4gezbe>wiP?`jJO;c&EId;=2u80s_r97;TX!6@*(<%WL+^bmxheMB3pKx0OpH^ zPs}knV+jpJ4TaD@r^V`mTsjf`7!z^H}eHQ#Rp z72(>Dm#QO!ZYR*O@yHic`3*T^t7jc=d`Jz6Lk@Y-bL%cOp_~=#xzIJl?`{Qu;$uC~NkePE+7wSW_FM`&V{gFN zl;lq@;FtAsl!h;tnOvj z#gYx!q$5MdZ0Jxjy=t*q)HFeeyI-vgaGdh1QNhqGRy8qS)|6S0QK7Gj9R?Co{Knh> za>xkQZ0}bBx!9@EUxRBYGm25^G}&j-`0VWX04E|J!kJ8^WoZ(jbhU_twFwWIH32fv zi=pg~(b#ajW=`)Vikwwe39lpML?|sY$?*6*kYBxku_<=#$gfTqQ_F!9F0=OkHnzBo zEwR!H_h|MNjuG$Tj6zaaouO}HYWCF8vN4C%EX-%Iu%ho;q$G#ErnafhXR*4J2Rp5* zhsi0;wlSwE*inVFO>{(8?N~82zijpt+9Y_-^>xnE%T*zk9gi|j7b@s<5{|qEquUD( zS;-%RySZOCOEh*>!kvbsQ265* z>X8*_Wy&~FB@aDHz%glyiAujXq-|2kDUjFTn9Rafsl+XNyFP%PG|l&ZGWBcEXxy=9 zeDn2PIoVuL$gX0RgVK1O$x3%pOzS7x^U5Pi;mtT)%cY;&e&M7GLM}zP+IPbqLt=^5 z7qLfri8myf;~2psc@^cA6mG&{C%e_(M$$!wC^5p^T1QzrS%I?(U{qcd+oJJkQxe10 zON{Q*?iz%F4MbEsoEc+x3E?&2wVR^v3|Q0lDaMvgS7mNjI{2w! z9|~=!83T%GW*iaChSS!`Xd^beFp9N4%K+k*j#jFumk}U?=WKL_kJAltxnxp~+lZzT zp@&&kSPTg3oSGos`rVBhK0|4NdHM_hnKuw1#0JV{gi_dKDJLB+ix~~HpU9%jD)@YY zOK)L7kgbLyN2%Dx#fuY}8swh4ACk7%BpP-n5(RhDq{gEHP*Fo4IviX{C49|B5h~SC zFr`=0)=h2^F5UpCAgt?R5u{6VvpUf#*nC zCQ`$!|C;L2lpjlG?(>T$(_$O3_YNNbPT~(?!j3aD8k=yu^ogw4bkjvgF|3BOq(hB& zG;^cPXmcUP$ox8zElCJ-zMbK9q^8{rri#8Cek5Ydr0YT-KTh@J z6^AcB9ejew8BY5kzZUZX(7Po==eW<(;uV~E7(BY5c0^xr`cuRwn)47bN?zOb!0?cw z#v}R$z66&m#+AHfo@(^V2#S~bhoUkkTArg+6w>JzZ52r96^({1W!?>4$h0l|-jDfj z>7(<+%67#(A|4hZ3>Y;hd&S?}F;`Vtqz|pK&B>NJ=Faci;gkf-+GmfQR8^zo_vul2 zB!)kfu4Dq_g)8TBBo52*sB6F`qa&JCR=_A$QWgX_K}fZm{Cb2#1q`^S3+WaS>sS#@ z-4k*G=#?z6d_e7JJ+Z8^(t0tNdL{K5F;2nfQbXgld}a(X)Gr;WojOy`^?es~AClT$ z5^lD{WJek0!p-QEH5E7n6DKQ0%_ZBZ=|jfV_MM{VmL8y-Wd|>OmeemP=C@xI@@M~1 zW2S*im@Rc=O>V886_UJ@oh1!2H$Ku&U*Hh_oxd{32)vf1$cRiepv28ricM;}#p!+k zaK{z1I=9Y%3m4|Pj*BD*Fn5Vh?O@oD^1UcjyeNh0fbhh~V6xb#4njlGW8OehUe!MnoR(wn#nsoyL1m!Rov)Nv4~&JEVl7L z#^qYdTpNI#u`N0UbVMiDmD>g2VQcG3>4D6gErgddZnSQTs){BExxRJRB?bIxTdZa z;!S8FHJPPiIDQ*FAUiWSYnjILFjDvxvSC zk z=j4Kx@Pg~&2Z?cmMDa;)#xVeorJrxDBqy{+`kG+ZPQqC@#ku-c3ucU+69$#q_*se` z-H#PFW^>-C0>++|6r=<$Z8)ZFaK=ZjwsNYXqRpl9G|yme@Eld5B-*I69Nx_TResHi z!5nm+>6zaJYQO#%D{~o-oOJ;q`fa5}l!8G*U-E$OM&7@dqciBCWtd}|SrDXz$TB($&m*=Epuolu2k`KUwO7maP3P0ok zmF57lSh0Ba@&sO1iZ5^+3s8{B8t|M;Pg&O+{tZJCiLWd6H@{b~9{CLF9s3Kn zt5)Rs9ejne?o{%f>B$Dl%X7fd~KY)I|(pxUeHj;gNsK6;ZR>`ciu;GxvhDUt!+31Knss2U(%ts8K z18)8;<2ax9RG?!|Lwdt^i5L^&O788roKmVAB)=EdK~HqR2Q=)H_VW}xY=95MP_Ov< zPEz3%DRK}+(aUBwsr83H8>`H^v~|A_t}0vPmRwKPt1{|qOY|PZu}j9+{ZhF&-H_TB zU9xWLpNTc`enI|)h9jQeqf5RfGLFk_vfX`40iMpd%KZF!lKbZTdBw$<^G6nuS+$fT zrbK)xo&;buPJcpOZ=x>n+bRXVFDs(23Xr=rDE&!)pVXZ;;A07NXGl_0m`{Z)DQIu$ zFDvY4xu-ifTe_$|n2B83eI;KUg6pVbw+N!nyLj~wnRi{4mNy{WDV)G1!6$y=+x6U{ z%4_9=Q^L!x_gAYp?J3+u5hA5cO8aHeI=6AC8^S{mzhqCBvBLYEutUC(X0>hKg|AvN zvkmJCQNA45_KjW{aEcyrBppcO6G0zTy%v1&@~+2!n?kA9?>0>AjFN|JdCnHQ8$hEU zw#mwGifHppLP?89LMb(Y3Li9iCPx7W%ek}2FgD2YSzjsR4Xj<=zN{Yo@7s7(k%mP4 znT2p&4EQ@q_chd-E z78uvD*C@oba`U3W2Iw`M#`5C8jOHv8^Li<|j^SI>>>`77Dp71Vtz=J?4Zck4SdRbd zfF}C_>Y(#)r@y!Q0`tMlG#b9>5`fAI$B&tWJfbGlYW$J4V+-s=HH!`+;1XeL@USdx zR0$G&&XBf9lQtkH5)p=U!8J!1{oc4E!N-~Abxl6E;;=3-hMYZ+44?u}zabmCE)yB?*_w91m$n1Yskp&@ z;kxeJX-#ioX^{elyLu~gzx|_KxLpX62MF%Axq3$!Z_P`pBWR?zP8OI`PV~6Aa0Oi0 zv_Ot1m&plf-ZF{e(z(Ms3*S5q$e|j;gOwGrmWsCHfLi(h8y?gc$(2H{884C1FvHQQ12tX=qFUsK~zM!W=K>;zaRsu4Xmcc@8nSs!vK+{ z?}bq}-m&p5jRSam67n>yG9ez=I^|J1O;Np8s=P~9MXYLxD+cFQK7PhG=bkjo{Naae zjp3NWWrlFWDb3Z5D07Q|WjZ=wOQ=aKA%en=O@hL$QCKpIXNZE=InFk|Fhq-&H!6&X z*MVy8=hL7Aw&pQjHrFf27C%3B<>FX{@fOLNhUoxL4*@nY}&M3G*T-p67a zo}~_&yGOB)#vbU|Q3FA8S^X)c-yBlmN(_%}`7Ha3uWFe?>9f=3hlO{^gv~$p`v?vk z_P*r43|(S{%ihs;)YH|jAMpP=-Ms7Ne75_YZZiL3CHVjSU`X1|?Ehh&gA=Xn7W7d@ zf8bM9Y>lG!`PWFDDA9G;x*{1Eh^55u66*9D+-4^dYZ{xXP@?sQLVrY%(azM;C^4FuN7CQ%$!3sr1JL=!Be& zuOZL^bLp$Qo2rL=WDzQIls%s!Go z{s}Q0b#+#8bKga|01t%^9Z=wEsevvXM_{$dCR97ed3@1kX)mtSS!JN^rtqKOj}p~> zfpCI@DX*DqcB6ZnBcl~}sGO~1s$AtfkX6fy3N8*ebvZc*KBW;dA=)?#BE&}-or74i zZUt5;{FBPnkZD8YUXDsx&2LvSziAlec3oc>&Lf1Doc3g?H9{OO_$M4B0qTat0UsWP zTlxUeQ3B;oJ%en4n?zQB6*Fb#wH7`$SQN5GI|=DnJKiYm{?-?#-H;#sIjz7kQ4&VW zN9d1(1$_W~S=<%qDD!mwRytas=eqX^iW}YSx3;wJ#)Xp_`Qk1DFiXac$-3;jQbCif zLA-T_s~5yP@Q@W>pXKl^gipQ>gp@HlBB>WDVpW199;V%?N1`U$ovLE;NI2?|_q2~5 zlg>xT9NADWkv5-*FjS~nP^7$k!N2z?dr!)&l0+4xDK7=-6Rkd$+_^`{bVx!5LgC#N z-dv-k@OlYCEvBfcr1*RsNwcV?QT0bm(q-IyJJ$hm2~mq{6zIn!D20k5)fe(+iM6DJ ze-w_*F|c%@)HREgpRrl@W5;_J5vB4c?UW8~%o0)(A4`%-yNk1(H z5CGuzH(uHQ`&j+IRmTOKoJ?#Ct$+1grR|IitpDGt!~ZdqSJ?cOtw-R=EQ+q4UvclH zdX=xlK-fhQKoKCPBoFAZ*(~11O6-tXo>i0w!T$u{lg!#itEUX3V{$S*naW!C@%rll zS{L(1t%xz(*B`{1NL!*aMc<~fE=g;gXi&Gb$HpD!P)8?JzfN;4F&wv(5HH<=c>>)n z({271)xREH89=C(5YKL{mmJJ_d>qHz;;gTvTlgM*vz9@YTTYZ#%_2A zS0G-t9oMQEpvfv(UjfQ8T$vAHi)zOj3>D*{xSRiu3acc=7cvLyD?_ZObdu$5@b*!y zaZ#u?7uF}SrHVQa=sTOhGW{6WUlq#RhPPm^GsRH#qlX8{Kq-i~98l;eq>KdCnWyKl zUu&UWBqu#Tt9jQ97U4}3)&(p2-eCLznXMEm!>i^EMpeVzPg%p;?@O;dJBQQY(vV;d z3v+-3oTPC!2LTUAx^S2t{v;S_h(EZ^0_dS5g^F*m{TEIy^Qal~%mu3h7*o`jWOH}i ztv8M)3X3a*+ry_KkYXYE4dB0?M|t}#Tp+(}6CQ zBbq;xhoHj}b@j-@koDB#XcCY~>_x&Y;i%MH|3tF^X2h{36UCVfQ-;oEA+4ZkJ`^Qi zQf^8}6eFO$Z+Dj-F1wkG##tTx>FjR2oOXFmbKFj6K3+=kePQ<4d7%z5R5cOB;zO6| zm9^m#U4lcA;7t&*=q|a-!`!)}SgYXT#i8hnxtx@kaoBF$QAS-hT7N5kH^l zB^i+})V>L;9_0Qqf-dyF%ky8Mp-dp#%!Nls3vCt}q3QLM3M-(Zs1k}1bqQ9PVU)U` ztE=?;^6=x}_VD%N@${>qhpkU*)AuUBu_cqYiY&@;O$HV*z@~#Tzh?#=CK`=KwBv+o zh%zu%0xPKYtyC)DaQ zpDW}*86g%>BH3IcWMq`g$j()0kWE(qkIL8A&A0mf&+BzxpKF}=`#jG% z&*wa!&pGFLs5_b#QTZE4Bp+})qzyPQ7B4Z7Y*&?0PSX&|FIR;WBP1|coF9ZeP*$9w z!6aJ_3%Sh=HY3FAt8V144|yfu}IAyYHr1OYKIZ51F>_uY^%N#!k~eU53at-_E-Gh?ahmM5y* z+BTIbeH;%v1}Cjo{8d%UeSMWg(nphxEU`sL< zQR~LrTq>Da(FqSP2%&^1ZL#DTo5Sbl9;&57tQ-@U&I#lj)aNSkcfEJwQD!33?anVU z?pw2q7WtMvfji493`rSFnyp7{w87cW`ak=UEYlk5PCB1K6UDVKXyozOChH4yHh~Q< zv>yvKw6WLfi!PZUx60JZcTNM7jo{ww9b8Q+S7C3WA5&llSwdwh$=Q(*(f3ofqcz=nwOmOy z(J!K=*wNoRU*${{Mbwapi9pTB(&VVKefqd-qrUb9*Eyr2E@oZ9Cgf}Mc;QP<0D)R4 zz=!*^VIG4T*7Xl=sJxrWv9hW^eJ%qYp5(d0?E6LZzJ}=7E+1{?GQA;z+!^VBD81}O z0kJ^dKy&WMw+1+aGVYY-v@i28@Gm+sX5=@U%F=Z?W)oar}2~Rc&F|+3A)n-U2GF10+QdxDb^iA@7eL$c7yhBtL z>lABrh^qy9XZ${E1}Ss5!N4;ig0-pUh6@|RPCHOWvgG{|l}2enRgJftsN%D|ck0YO zuAQd2aMPSyGuJ~jm)aY=+p~mGudw4erwE%P^)5f<*$$2C-4^I=e8-}7##ZQ!8!Tep z+Z_!}CAI~sry$|XK$ktXaxP*x<_ijCPp`2=6sNLZU<@9Sz-rz7^BCE9yh0jV4(I!Z zxmA4d;>B-!vD}Xp*&*N%`b^e&R;D97WS}{~{O-EtXeZNfdf51tw!WR6Noo4hjHPv5 z?heYYRSBPjMc}tFEU^|U8a1CxxK%)WTcn9P%`wR^I$QSeMn6=w>Z9OoVvcrl`zYlZ z2y`mAu0bV(Scc>G_EmIo_4 zm*~h`mxYZC&+U>C5G1FZH5L^U>Cq-9UDRQa35jz&NBj*0{uJKfZs5=Fn@&)Xh6aX(H3w9m9BGLePqVotxTeSPh5-mc7$# z-80t6yB0$Nx<54ohdO*QL7m_(&+#*=eoNiYDB4rE4Cag@qfyZS};Fx;Vf1;oync2k z9v#-w?d6R& zOI`CCS_d=tf3|?g3Z}b6-_Rdg3y~enQhmgkni0Cvf9m6%Ft8r;NC5|b%t&?lkl*4{ z8Ui^;Ds^gq6ti(1xB7y_$zA!i-M~#!!tl$ErTR>P~>T=Yky)8(uvPbvLmB=UfoD zrfl}8<1OQrm?8#j1!?s*T>AoectQl&m!o&*^JcIW`_&bk3tN}k^0rjl=HL$z*uIYt z?7l?^Dqr?q1210Sp$xoAy!&{2^{^Anl460 zI&7urrc&|Y{rjv04VOl{y7c82N6xzg5ueYmQ(q(zC3w_C#x*~%yf5j7MI{W`tsoxzA*PrmK)cTskU| zf2C}Bq$>S$-1JgIh0aW@LxI|-8(OGuD#^M01ghh}&#ObO>tZgSw_LW`zdf&IN$YO# z)|X_9m#JwLW5pErZB3ScggKcNzxA9(hyKkK9I#pR&79&*+SV_eu={00{HF=Bb+AEe znaSof+r1jZ!EL5XgqXWkckaFSSyEk}o!%p8XsD}O>borZ6x%X2b&q!s&1-O(>`kZ$ zB2l^5Cx9xQx9)PXN1xPM)@+LxACH_iZ8zGc(>wnFS_O|@hKsxpMjXOzLEa7OvSlM&&G9ioQw9~RsD4F zK7Q+_&|Q6{eZ^8Rx@pKL`le6kH+(fLc{=V&{b%I5=n}VHV4)X_2Y!pYxgC8wU)yP! zPF3t$?(jsC>Ge=&{kmPGUEETpaw(QTAl)m#{qR3_aq9!wK%6XHfV4C>Y^>Z|%ns7j z{Ja?^IA{+@;kR#IjHxkar%3$eJT4?xNBKUVmoO z`A8Zo-{~_;vcikZ(p}EZzU4kO6WPqkMyE{VvS?;44Z@lj zz^fKX9UL!8Wc(9VgI?P4*zpis8dzl};I>yr1>dtXU=FTAlx}Eht4-*7RACL^AflGh zyZb1hTf(~CkMo%#Q%NMgM9tE2D+)joqbtHYA89Ql1nqVTt+MxZ^*FRd&n5YlIi!8m z>$Ysd!l{+C)y;Wa(ZV-=<+NZKV;v4mt}v2m>`v$-$3b;GsLxf= zd~f(rmfpl``{0aVwN7y!>eGyJFP`L+TxHjHTOS{K^$L2`@6(Rli`{EFwpH@R%eZ6g zwf7rc43Yk!=k;{ z-Rn%~B3amGr}}SxfE$vS8FIPL=Qt57$|R#sSoFgdNUT?fYOYjPl%ZBFpi=jq=DWby7Zxm@y;B<89!9= zbgEH*Uy)~iq5kJLX$+ps$kV`#6jW#|9BGz^`ivNeid(wVbk4jl)VBpW&~;eXNi{#` zwx?{DXR~*sqQcFhY0XCfQ4-*2aN1BGX>$_swtKEqnd>j6vcZ!#0)pXRi?<{!P?tGw z2x_`RD$W)qD{?z}VDPt?+)8*rqLWFIPQ(9-VbBdf{7ff?w9CZ{sIi_gnuC$I0(+P8 zms9XB%}VQ>>pve##}jog6+cD?v~n4Pa9Vmc zg#K$|+`adO=B7`uj35Y}6EZ z{dY`x@w8;R-7zrsr1O_~Jvl*|o-x%jF=Rr1C}GXP^|IYN`1sqmG-oI@R#%X66c#5W z$$tQB)sqwiVm;Y^`Dw3mo|firP{*HsOQJre5%Dm^H@we0FN88VWJ0dja?_U38z73f zrCV!b3qNP0kM#%9T!W5`ynGcg%BL28FW1J-J1_S`BJGCaReQ!am(2%qZ3lLgzq|ns z!!fF@`0=*z)J2BwZ*hO|Yu^cI_nF$9l-Pb3jE7=P8gZ#!xiuZ7-cSa`gb`6mxGTgg z-DLdID?M!Z%+hHB#{?&0$GFRpf+_}q<_wbzX6K?w;%6szz1RbySDSr2r^h_qi$khs zXdZ9A0!_Bf)TR2-^-K~q`FQ!#1x(U4VbV%AA@Ei{%cA(EwC{XfjRi?`&9rav5;Q5% zO1`Rn@OA_ZB@N*mC#)?d3P!}Eh;=NgpIKsy{(yr`hv=aouwt@r&P&}Z3DNWo9ro30 zX52~(aTV$*HHlgB66-4GQru!_AZ|)V*I5X=WG)`N@U&D>e@@C#V@JwEL*L`7#$yes z62C^5%Qniaow2$3HrAc7U{qzpb&FA*xLI1JSWR@`RF=JCcvTI)%dH7;sWInt9JLu# z|Ao|Q?K)cDg_JKsym=joo5gR80wtv01N`um1nQ@Ms0Y*bVzxL34} zo?gizp?`=Y{*W>^Hy2%Jl)y?A+&7s1UVHFixuIy~sawXjcDCL`129cK7|ZQS0u;A} zTJC#WNmqkIrnHpAhHVcM(U^vJA~dl@jf_bs*3?i+=&vuC?Aiy_pcB~=1syDni4 zw+FLuz>F773u#$;NUQ9WDtUPY@+rA3WBhQdKFKOyzkA(URa7;4tW>3jQIfi8v0h3g zJC_HVDXS#>DWb|&se7FHnr=q&l#xg9o02}}u=b-R>@sw={Z zHF*?t2FmhqZ=|qa>x=A!*$S+0T zhO*D*M?NTf-eX`eO)9TIQu{7Dm77Acnj4b1jI9@c*ZL8wL%8kLEhd$KM8=Y!fbN@9 zC7B5#y>JM1n5M)!&im==EgHs2j+xCZG~+~QWCi?s!QyFo2kqx{%jE2n3^N*Ayz6Lp zhg5g^3# z+5FoJ@$u@9WJgPKpUWEd4}4AK9TJKU8W%ms!d0p%OIOX+bY+55zl!vIaz$XFI9Ep+ z;bL_}7PDI2Y`Ng*XY(65 zh0%`@Lve%fc;)N4_g12bNrt6gH=N#OHtxO`$lpWlw=Z6MF+E@;>GkZ#lAZTn`aHwf z&I1|aV#b_VHMIgBN*RzU9i@Z@m}0i>o?({&%fpEfaOpFeaJ7V37;m0?kzd}}Lk@9$ zL}8TEo7WZAcRi%zFZxkr6<0k#X-;lTD`Oc~cDb@olwgWCewvk{GJ}hCXbF!AdiLpd z|Cck$ZTKI?Ack{34Lva7+k=H8K2HTZiurox6F+>dy+@R9T^awxj590D$|kXUg+Ygc z(f)jlRwN(4z$#%PnOVc;#Fv{nAi{#UcXPNcmP#5O{zh_*`=q^JCeia{sN4zHjk2*y zqUVh{Ya{j>SPmP^i#Qfcq_MTqo8g52Fi^F zKBc$$HVI!xFx*4Y9l+nt)$AoZORD}%5I10oI3kx`-N30QueiwIw#0VV2E*Fb-nKW% z=+r^hos`Y-7~{cA1FVbK$_=~*z53+Q8KGjg;>ztg((H12%QTf4OYU8y)C}h5yo#$% z&Q$`vMM*g?ZcatAn2j!hFv8KuN(dw)T*}sF#THDHxo8xC^?vJ zc`U6bVo~hOr6I!8*GTZ<^D~;unKjK=!IR|GB4E>Mcvt*2GK);93jIDd<(nNjHO z4Hi@2^%Uyx=^Z~5eZ!5rO5%4H|eFoNjD#+Kcu%_57zZb4Z@Ak#X6txD^{U3wBl^r+W- zLorkK;uc;NgTj7dGxHQS+@T*T>Q*j4^Ll$ejQqWrwcHyG9y%Mk%m8nBVG5hvSaYm5 zJN^#-Q46kZG)@T8n2^QCjxIwxUVi%s>EY`E?#@_(A~njFrTiDq;8v|W-1jT|ROlNI zU$h|YoD4PVTE^&NC6_m{EAFBVqsM`P*`-AcDGWQygURzM32Xeq2xng~XQsYeTZ5v$ zQLaa2M_Iplw}4eL6fLPu`6`PYcVMysO>`{8CB~glD=TX7?JZcHfHNmykBM?QD)#D) zGp>R*<^D?WhFQKRc^}22l6F=D2RPrxaX2ZF!b1X0XF*d4%=!sbNcS1q2WOUE(7e4$ z^L8f;F)__d3>&KQFE8%$I4h^y5FYBfB&fWzn71_OSrPe-DHV{O#Q;GP z+Tw!J?eVjX19RKH?*hKQWQt8r7B#lYX8xoSHFGCW-*DSQ4EM4M3Mw%gkSYNK18@(e zfzMF}WWaCyS@1y%-~Xg0ry~tkQkUmKuI5lGAua{{vn22V!2T()AU5FpKh@Nv)s^Js zv~@VuUG;=CnLmQR{PeUBQf2;lAV!vG>^Z0N zL88rrjL-*J!43;7C=w9xhcw`yjRKq7o4L9=0SmR9PA-nX12@#h(iIu-0N_xm2OV)( zU_raT0y>$wm^oMi2|U3N;OhF9uy}`<-xVka#DV*l{O0yHzi9vUxa1Qtpi$buR*8cU zd4~lS1pT$L^!0=6qUKOpM+XPsy{f7W#1bjrEwaeN!Ik9(zySIT^pEHvHgJUneFN4) zk=k|$55(g8slmS|@+*4fr2urd3LwjIIZA**g+%l(SZNn4HwQ}y6o`vw>2&mR1X+&q zDa1Af0B;4rAMZMOlHbAqK|R_xuwJ7ANARtFE({-P2o{tJJR<>2KVp)ZK-M;)ejx zd*E~Mka<{OL7%CAhk4n|1qg?97-I!l0rOinjVi#arbgg4bi5;nY5oFL`UWtPk5&L#grSxv zE3!}=1px!ZTLT90aYc^s`~{VojjJml&<`@e41dFP+XU6D0AOkbn2rlI3>^LcqauG& zc$m3Z{!u8LvUrm^fT{qX5yD9{?r(CCiUdck%!T`KIZd2oQJz1joB&M(Teg_>;yS<2-5>BWfSPpG`Rt{!j6>kqMAvl^zk0JUEfy$HVJMkxP-GkwZuxL62me2#pj_5*ZIU zP~#C^OZLfl$HO)v;~~c&JHivn|1I9H5y_CDkt0JLLGKm(4*KLVhJ2jh2#vJuM6`b& zE==-lvME^Oj022xF&IV*? '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && 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=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=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, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +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..5cd7dd3b --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'api' diff --git a/src/main/java/io/oduck/api/ApiApplication.java b/src/main/java/io/oduck/api/ApiApplication.java new file mode 100644 index 00000000..77bbb8ff --- /dev/null +++ b/src/main/java/io/oduck/api/ApiApplication.java @@ -0,0 +1,13 @@ +package io.oduck.api; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ApiApplication { + + public static void main(String[] args) { + SpringApplication.run(ApiApplication.class, args); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/src/test/java/io/oduck/api/ApiApplicationTests.java b/src/test/java/io/oduck/api/ApiApplicationTests.java new file mode 100644 index 00000000..36560b1b --- /dev/null +++ b/src/test/java/io/oduck/api/ApiApplicationTests.java @@ -0,0 +1,13 @@ +package io.oduck.api; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApiApplicationTests { + + @Test + void contextLoads() { + } + +} From e72f6b9c6409fcbd10758a737b6e77107ef6b823 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 27 Sep 2023 23:07:54 +0900 Subject: [PATCH 002/734] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B8=B0=EB=B3=B8=20=EC=84=A4=EC=A0=95=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 55 ++++++++++++++++++++++- src/main/resources/application.properties | 1 - src/main/resources/application.yml | 27 +++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml diff --git a/build.gradle b/build.gradle index b9cdea50..e85c3e4c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.1.4' id 'io.spring.dependency-management' version '1.1.3' + id "org.asciidoctor.jvm.convert" version "3.3.2" } group = 'io.oduck' @@ -11,15 +12,67 @@ java { sourceCompatibility = '17' } +configurations { + asciidoctorExt + compileOnly { + extendsFrom annotationProcessor + } +} + repositories { mavenCentral() } +ext { + snippetsDir = file('build/generated-snippets') +} + dependencies { + // 서버 상태 체크 + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // 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' + + compileOnly 'org.projectlombok:lombok' + + developmentOnly 'org.springframework.boot:spring-boot-devtools' + + runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + // testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + testImplementation 'com.h2database:h2' + + asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' } -tasks.named('test') { +test { + outputs.dir snippetsDir useJUnitPlatform() } + +asciidoctor { + inputs.dir snippetsDir + configurations "asciidoctorExt" + dependsOn test +} + +tasks.register('copyDocument', Copy) { + dependsOn asciidoctor + println "asciidoctor output: ${asciidoctor.outputDir}" + from file("${asciidoctor.outputDir}") + into file("build/resources/main/static") + into file("src/main/resources/static/docs") +} + +build { + dependsOn copyDocument +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b137891..00000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..261a92a6 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,27 @@ +spring: + h2: + console: + enabled: true + path: /h2 + jpa: + hibernate: + ddl-auto: create + show-sql: true + datasource: + driver-class-name: org.mariadb.jdbc.Driver + url: jdbc:mariadb://localhost:3306/testDB?useSSL=false&serverTimezone=UTC + username: ${DB_USER} + password: ${DB_PASSWORD} + +logging: + level: + root: INFO + pattern: + file: '%-5level %d{yy-MM-dd HH:mm:SSS}[%thread] %logger[%thod:%line] - %msg%n' + file: + name: ./logs/oDuck.log + logback: + rollingpolicy: + max-file-size: 100KB #default 10M + max-history: 30 #default 7 + file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz From e4463a3c1e483236ad1ff42d2008cec03e661169 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 27 Sep 2023 23:08:13 +0900 Subject: [PATCH 003/734] =?UTF-8?q?chore:=20gitignore=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 40d1f550..41c37199 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ build/ !**/src/test/**/build/ .DS_Store */.DS_Store +*.log +*.gz ### STS ### .apt_generated From 36d7370c178b270922f998a9059269b7ce52f32c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 27 Sep 2023 23:09:40 +0900 Subject: [PATCH 004/734] feat: Base Entity #3 --- .../java/io/oduck/api/ApiApplication.java | 2 ++ .../io/oduck/api/golbal/audit/BaseEntity.java | 20 +++++++++++++++++++ .../api/golbal/audit/DeletableEntity.java | 10 ++++++++++ .../api/golbal/audit/UpdatableEntity.java | 13 ++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 src/main/java/io/oduck/api/golbal/audit/BaseEntity.java create mode 100644 src/main/java/io/oduck/api/golbal/audit/DeletableEntity.java create mode 100644 src/main/java/io/oduck/api/golbal/audit/UpdatableEntity.java diff --git a/src/main/java/io/oduck/api/ApiApplication.java b/src/main/java/io/oduck/api/ApiApplication.java index 77bbb8ff..efc53abf 100644 --- a/src/main/java/io/oduck/api/ApiApplication.java +++ b/src/main/java/io/oduck/api/ApiApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class ApiApplication { diff --git a/src/main/java/io/oduck/api/golbal/audit/BaseEntity.java b/src/main/java/io/oduck/api/golbal/audit/BaseEntity.java new file mode 100644 index 00000000..2be163e1 --- /dev/null +++ b/src/main/java/io/oduck/api/golbal/audit/BaseEntity.java @@ -0,0 +1,20 @@ +package io.oduck.api.golbal.audit; + +import java.time.LocalDateTime; + +import org.hibernate.annotations.CreationTimestamp; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + @CreationTimestamp + @Column(nullable = false, updatable = false) + protected LocalDateTime createdAt; +} diff --git a/src/main/java/io/oduck/api/golbal/audit/DeletableEntity.java b/src/main/java/io/oduck/api/golbal/audit/DeletableEntity.java new file mode 100644 index 00000000..8dba5ca7 --- /dev/null +++ b/src/main/java/io/oduck/api/golbal/audit/DeletableEntity.java @@ -0,0 +1,10 @@ +package io.oduck.api.golbal.audit; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; + +public abstract class DeletableEntity extends UpdatableEntity { + @Column(nullable = true) + protected LocalDateTime deletedAt; +} diff --git a/src/main/java/io/oduck/api/golbal/audit/UpdatableEntity.java b/src/main/java/io/oduck/api/golbal/audit/UpdatableEntity.java new file mode 100644 index 00000000..e15aac17 --- /dev/null +++ b/src/main/java/io/oduck/api/golbal/audit/UpdatableEntity.java @@ -0,0 +1,13 @@ +package io.oduck.api.golbal.audit; + +import java.time.LocalDateTime; + +import org.hibernate.annotations.UpdateTimestamp; + +import jakarta.persistence.Column; + +public abstract class UpdatableEntity extends BaseEntity { + @UpdateTimestamp + @Column(nullable = false) + protected LocalDateTime updatedAt; +} From 5e416e52f078f903a9dddb25a278c32a96784ac3 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 27 Sep 2023 23:10:01 +0900 Subject: [PATCH 005/734] =?UTF-8?q?feat:=20anime=20Entity=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/entity/Anime.java | 86 +++++++++++++++++++ .../domain/anime/entity/BroadcastType.java | 8 ++ .../api/domain/anime/entity/Quarter.java | 15 ++++ .../oduck/api/domain/anime/entity/Rating.java | 5 ++ .../oduck/api/domain/anime/entity/Status.java | 8 ++ .../api/domain/series/entity/Series.java | 28 ++++++ 6 files changed, 150 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/Anime.java create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/BroadcastType.java create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/Quarter.java create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/Rating.java create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/Status.java create mode 100644 src/main/java/io/oduck/api/domain/series/entity/Series.java diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java new file mode 100644 index 00000000..a0b8a541 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -0,0 +1,86 @@ +package io.oduck.api.domain.anime.entity; + +import org.hibernate.annotations.ColumnDefault; + +import io.oduck.api.domain.anime.dto.AnimeReq; +import io.oduck.api.domain.series.entity.Series; +import io.oduck.api.golbal.audit.DeletableEntity; +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 lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class Anime extends DeletableEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String title; + + @Column(nullable = false, length = 255) + private String summary; + + @Enumerated(EnumType.STRING) + private BroadcastType broadcastType; + + @Column(nullable = false) + private int episodeCount; + + @Column(nullable = true, length = 500) + private String thumbnail; + + private int year; + + @Enumerated(EnumType.STRING) + private Quarter quarter; + + @Enumerated(EnumType.STRING) + private Rating rating; + + @Enumerated(EnumType.STRING) + private Status status; + + @Column(nullable = false) + private boolean isReleased; + + @ColumnDefault("0") + private long viewCount; + + @ColumnDefault("0") + private long reviewCount; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "series_id") + private Series series; + + static public Anime creatAnime(AnimeReq req, Series series) { + Anime anime = new Anime(); + anime.broadcastType = req.getBroadcastType(); + anime.episodeCount = req.getEposideCount(); + anime.quarter = req.getQuarter(); + anime.rating = req.getRating(); + anime.status = req.getStatus(); + anime.summary = req.getSummary(); + anime.thumbnail = req.getThumbnail(); + anime.title = req.getTitle(); + anime.year = req.getYear(); + anime.isReleased = false; + anime.viewCount = 0; + anime.reviewCount = 0; + anime.series = series; + return anime; + } +} diff --git a/src/main/java/io/oduck/api/domain/anime/entity/BroadcastType.java b/src/main/java/io/oduck/api/domain/anime/entity/BroadcastType.java new file mode 100644 index 00000000..3e0041e8 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/BroadcastType.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.anime.entity; + +public enum BroadcastType { + TVA, + ONA, + OVA, + MOV, +} diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Quarter.java b/src/main/java/io/oduck/api/domain/anime/entity/Quarter.java new file mode 100644 index 00000000..10333c5d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/Quarter.java @@ -0,0 +1,15 @@ +package io.oduck.api.domain.anime.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Quarter { + Q1(1), + Q2(2), + Q3(3), + Q4(4); + + private final int value; +} diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Rating.java b/src/main/java/io/oduck/api/domain/anime/entity/Rating.java new file mode 100644 index 00000000..fab4c31b --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/Rating.java @@ -0,0 +1,5 @@ +package io.oduck.api.domain.anime.entity; + +public enum Rating { + ADULT, FIFTEEN, TWELVE, ALL +} diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Status.java b/src/main/java/io/oduck/api/domain/anime/entity/Status.java new file mode 100644 index 00000000..e30c4017 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/Status.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.anime.entity; + +public enum Status { + ONGOING, + FINISHED, + UPCOMING, + UNKNOWN +} diff --git a/src/main/java/io/oduck/api/domain/series/entity/Series.java b/src/main/java/io/oduck/api/domain/series/entity/Series.java new file mode 100644 index 00000000..e06d4866 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/series/entity/Series.java @@ -0,0 +1,28 @@ +package io.oduck.api.domain.series.entity; + +import io.oduck.api.golbal.audit.DeletableEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class Series extends DeletableEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String title; + + static public Series create(String title) { + Series series = new Series(); + series.title = title; + return series; + } +} From fca948aed3ea12ea0086c5c2f1f1309839292a30 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 27 Sep 2023 23:10:11 +0900 Subject: [PATCH 006/734] =?UTF-8?q?feat:=20anime=20DTO=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/dto/AnimeReq.java | 22 +++++++++++++++++++ .../oduck/api/domain/anime/dto/AnimeRes.java | 5 +++++ 2 files changed, 27 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java create mode 100644 src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java new file mode 100644 index 00000000..4cb3feea --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java @@ -0,0 +1,22 @@ +package io.oduck.api.domain.anime.dto; + +import io.oduck.api.domain.anime.entity.BroadcastType; +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Rating; +import io.oduck.api.domain.anime.entity.Status; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class AnimeReq { + private String title; + private String thumbnail; + private String summary; + private BroadcastType broadcastType; + private int eposideCount; + private int year; + private Quarter quarter; + private Rating rating; + private Status status; +} diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java new file mode 100644 index 00000000..1aaec6df --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -0,0 +1,5 @@ +package io.oduck.api.domain.anime.dto; + +public class AnimeRes { + +} From 52237968ec1a7c3e7f2af35af88198a25777c807 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 17:25:06 +0900 Subject: [PATCH 007/734] =?UTF-8?q?refactor:=20global=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EB=AA=85=20=EC=98=A4=ED=83=88=EC=9E=90=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/{golbal => global}/audit/BaseEntity.java | 2 +- .../io/oduck/api/{golbal => global}/audit/DeletableEntity.java | 2 +- .../io/oduck/api/{golbal => global}/audit/UpdatableEntity.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/main/java/io/oduck/api/{golbal => global}/audit/BaseEntity.java (93%) rename src/main/java/io/oduck/api/{golbal => global}/audit/DeletableEntity.java (85%) rename src/main/java/io/oduck/api/{golbal => global}/audit/UpdatableEntity.java (88%) diff --git a/src/main/java/io/oduck/api/golbal/audit/BaseEntity.java b/src/main/java/io/oduck/api/global/audit/BaseEntity.java similarity index 93% rename from src/main/java/io/oduck/api/golbal/audit/BaseEntity.java rename to src/main/java/io/oduck/api/global/audit/BaseEntity.java index 2be163e1..264e2696 100644 --- a/src/main/java/io/oduck/api/golbal/audit/BaseEntity.java +++ b/src/main/java/io/oduck/api/global/audit/BaseEntity.java @@ -1,4 +1,4 @@ -package io.oduck.api.golbal.audit; +package io.oduck.api.global.audit; import java.time.LocalDateTime; diff --git a/src/main/java/io/oduck/api/golbal/audit/DeletableEntity.java b/src/main/java/io/oduck/api/global/audit/DeletableEntity.java similarity index 85% rename from src/main/java/io/oduck/api/golbal/audit/DeletableEntity.java rename to src/main/java/io/oduck/api/global/audit/DeletableEntity.java index 8dba5ca7..d03e9759 100644 --- a/src/main/java/io/oduck/api/golbal/audit/DeletableEntity.java +++ b/src/main/java/io/oduck/api/global/audit/DeletableEntity.java @@ -1,4 +1,4 @@ -package io.oduck.api.golbal.audit; +package io.oduck.api.global.audit; import java.time.LocalDateTime; diff --git a/src/main/java/io/oduck/api/golbal/audit/UpdatableEntity.java b/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java similarity index 88% rename from src/main/java/io/oduck/api/golbal/audit/UpdatableEntity.java rename to src/main/java/io/oduck/api/global/audit/UpdatableEntity.java index e15aac17..f7e60696 100644 --- a/src/main/java/io/oduck/api/golbal/audit/UpdatableEntity.java +++ b/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java @@ -1,4 +1,4 @@ -package io.oduck.api.golbal.audit; +package io.oduck.api.global.audit; import java.time.LocalDateTime; From 361dc47aa51b5a4923003a0f7752d903343cb44f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 17:25:54 +0900 Subject: [PATCH 008/734] =?UTF-8?q?feat:=20Member=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/entity/AuthLocal.java | 31 ++++++++++++ .../api/domain/member/entity/AuthSocial.java | 37 ++++++++++++++ .../api/domain/member/entity/LoginType.java | 5 ++ .../api/domain/member/entity/Member.java | 48 +++++++++++++++++++ .../domain/member/entity/MemberProfile.java | 39 +++++++++++++++ .../oduck/api/domain/member/entity/Role.java | 5 ++ .../api/domain/member/entity/SocialType.java | 5 ++ 7 files changed, 170 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java create mode 100644 src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java create mode 100644 src/main/java/io/oduck/api/domain/member/entity/LoginType.java create mode 100644 src/main/java/io/oduck/api/domain/member/entity/Member.java create mode 100644 src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java create mode 100644 src/main/java/io/oduck/api/domain/member/entity/Role.java create mode 100644 src/main/java/io/oduck/api/domain/member/entity/SocialType.java diff --git a/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java b/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java new file mode 100644 index 00000000..e7dd1baa --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java @@ -0,0 +1,31 @@ +package io.oduck.api.domain.member.entity; + +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.OneToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AuthLocal { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @Column(nullable = false, length = 100) + private String email; + + @Column(nullable = false, length = 75) + private String password; +} diff --git a/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java b/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java new file mode 100644 index 00000000..01fda906 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java @@ -0,0 +1,37 @@ +package io.oduck.api.domain.member.entity; + +import aj.org.objectweb.asm.Type; +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.OneToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AuthSocial { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @Column(nullable = false, length = 100) + private String email; + + @Column(nullable = false, length = 50) + private String socialId; + + @Enumerated(EnumType.STRING) + private SocialType socialType; +} diff --git a/src/main/java/io/oduck/api/domain/member/entity/LoginType.java b/src/main/java/io/oduck/api/domain/member/entity/LoginType.java new file mode 100644 index 00000000..63f4012b --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/entity/LoginType.java @@ -0,0 +1,5 @@ +package io.oduck.api.domain.member.entity; + +public enum LoginType { + LOCAL, GOOGLE, KAKAO, NAVER +} diff --git a/src/main/java/io/oduck/api/domain/member/entity/Member.java b/src/main/java/io/oduck/api/domain/member/entity/Member.java new file mode 100644 index 00000000..dea56c67 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/entity/Member.java @@ -0,0 +1,48 @@ +package io.oduck.api.domain.member.entity; + +import io.oduck.api.domain.bookmark.entity.Bookmark; +import io.oduck.api.domain.review.entity.ShortReview; +import io.oduck.api.domain.reviewLike.entity.ShortReviewLike; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import java.util.Set; + +import jakarta.persistence.CascadeType; +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.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class Member { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + private LoginType loginType; + + @OneToOne(mappedBy = "member", cascade = CascadeType.PERSIST) + private AuthSocial authSocial; + + @OneToOne(mappedBy = "member", cascade = CascadeType.PERSIST) + private AuthLocal authLocal; + + @OneToOne(mappedBy = "member", cascade = CascadeType.PERSIST) + private MemberProfile memberProfile; + + @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) + private Set bookMarks; + + @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) + private Set shortReviews; + + @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) + private Set shortReviewLikes; +} diff --git a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java new file mode 100644 index 00000000..53db5c0d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java @@ -0,0 +1,39 @@ +package io.oduck.api.domain.member.entity; + +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.OneToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class MemberProfile { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @Column(nullable = false, length = 15, unique = true) + private String name; + + @Column(nullable = false, length = 50) + private String info; + + private Role role; + + private String thumbnail; + + private String backgrounImage; + + private Long point; +} diff --git a/src/main/java/io/oduck/api/domain/member/entity/Role.java b/src/main/java/io/oduck/api/domain/member/entity/Role.java new file mode 100644 index 00000000..7945f8e3 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/entity/Role.java @@ -0,0 +1,5 @@ +package io.oduck.api.domain.member.entity; + +public enum Role { + ADMIN, MEMBER, GUEST +} diff --git a/src/main/java/io/oduck/api/domain/member/entity/SocialType.java b/src/main/java/io/oduck/api/domain/member/entity/SocialType.java new file mode 100644 index 00000000..4222dcfd --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/entity/SocialType.java @@ -0,0 +1,5 @@ +package io.oduck.api.domain.member.entity; + +public enum SocialType { + GOOGLE, NAVER, KAKAO +} From 06837b525963eae5e11234629ccab1fe4b0b6cf7 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 17:28:26 +0900 Subject: [PATCH 009/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=EB=A9=94=20?= =?UTF-8?q?=EC=97=B0=EA=B4=80=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/anime/entity/AnimeGenre.java | 30 ++++++++++++++++ .../anime/entity/AnimeOriginalAuthor.java | 31 +++++++++++++++++ .../api/domain/anime/entity/AnimeStudio.java | 31 +++++++++++++++++ .../domain/anime/entity/AnimeVoiceActor.java | 34 +++++++++++++++++++ .../oduck/api/domain/genre/entity/Genre.java | 28 +++++++++++++++ .../originalAuthor/entity/OriginalAuthor.java | 28 +++++++++++++++ .../api/domain/studio/entity/Studio.java | 28 +++++++++++++++ .../domain/voiceActor/entity/VoiceActor.java | 28 +++++++++++++++ 8 files changed, 238 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java create mode 100644 src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java create mode 100644 src/main/java/io/oduck/api/domain/genre/entity/Genre.java create mode 100644 src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java create mode 100644 src/main/java/io/oduck/api/domain/studio/entity/Studio.java create mode 100644 src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java new file mode 100644 index 00000000..0d1e9d10 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java @@ -0,0 +1,30 @@ +package io.oduck.api.domain.anime.entity; + +import io.oduck.api.domain.genre.entity.Genre; +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 lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class AnimeGenre { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anime_id") + private Anime anime; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "genre_id") + private Genre genre; +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java new file mode 100644 index 00000000..b98a0c81 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java @@ -0,0 +1,31 @@ +package io.oduck.api.domain.anime.entity; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +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; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AnimeOriginalAuthor { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anime_id") + private Anime anime; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "original_author_id") + private OriginalAuthor originalAuthor; +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java new file mode 100644 index 00000000..a53635f1 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java @@ -0,0 +1,31 @@ +package io.oduck.api.domain.anime.entity; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import io.oduck.api.domain.studio.entity.Studio; +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; + +@Entity +@Getter +@NoArgsConstructor +public class AnimeStudio { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anime_id") + private Anime anime; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "studio_id") + private Studio studio; +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java new file mode 100644 index 00000000..42a5609f --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java @@ -0,0 +1,34 @@ +package io.oduck.api.domain.anime.entity; + +import jakarta.persistence.Column; +import lombok.Getter; +import lombok.NoArgsConstructor; +import io.oduck.api.domain.voiceActor.entity.VoiceActor; +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; + +@Entity +@Getter +@NoArgsConstructor +public class AnimeVoiceActor { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anime_id") + private Anime anime; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "voice_actor_id") + private VoiceActor voiceActor; + + @Column(nullable = false, length = 100) + private String part; +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/genre/entity/Genre.java b/src/main/java/io/oduck/api/domain/genre/entity/Genre.java new file mode 100644 index 00000000..374754a1 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/genre/entity/Genre.java @@ -0,0 +1,28 @@ +package io.oduck.api.domain.genre.entity; + +import io.oduck.api.domain.anime.entity.AnimeGenre; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import java.util.Set; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class Genre { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 15) + private String name; + + @OneToMany(mappedBy = "genre", cascade = CascadeType.PERSIST) + private Set animeGenres; +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java b/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java new file mode 100644 index 00000000..bd01bfa5 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java @@ -0,0 +1,28 @@ +package io.oduck.api.domain.originalAuthor.entity; + +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import java.util.Set; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class OriginalAuthor { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @OneToMany(mappedBy = "originalAuthor", cascade = CascadeType.PERSIST) + private Set animeOriginalAuthors; +} diff --git a/src/main/java/io/oduck/api/domain/studio/entity/Studio.java b/src/main/java/io/oduck/api/domain/studio/entity/Studio.java new file mode 100644 index 00000000..00fc1615 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/studio/entity/Studio.java @@ -0,0 +1,28 @@ +package io.oduck.api.domain.studio.entity; + +import io.oduck.api.domain.anime.entity.AnimeStudio; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import java.util.Set; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class Studio { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @OneToMany(mappedBy = "studio", cascade = CascadeType.PERSIST) + private Set animeStudios; +} diff --git a/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java b/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java new file mode 100644 index 00000000..ee8fdafd --- /dev/null +++ b/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java @@ -0,0 +1,28 @@ +package io.oduck.api.domain.voiceActor.entity; + +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import java.util.Set; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class VoiceActor { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @OneToMany(mappedBy = "voiceActor", cascade = CascadeType.PERSIST) + private Set animeVoiceActors; +} \ No newline at end of file From acf8e71c8e4b7b942519c6965c0e39d6b27c42e7 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 17:29:18 +0900 Subject: [PATCH 010/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?,=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20=EC=96=91=EB=B0=A9=ED=96=A5?= =?UTF-8?q?=20=EB=A7=A4=ED=95=91=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/entity/Anime.java | 33 ++++++++++++++++++- .../api/domain/series/entity/Series.java | 13 ++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index a0b8a541..d509cfc0 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -1,10 +1,16 @@ package io.oduck.api.domain.anime.entity; +import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; +import io.oduck.api.domain.bookmark.entity.Bookmark; +import io.oduck.api.domain.review.entity.ShortReview; +import jakarta.persistence.CascadeType; +import jakarta.persistence.OneToMany; +import java.util.Set; import org.hibernate.annotations.ColumnDefault; import io.oduck.api.domain.anime.dto.AnimeReq; import io.oduck.api.domain.series.entity.Series; -import io.oduck.api.golbal.audit.DeletableEntity; +import io.oduck.api.global.audit.DeletableEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -66,6 +72,31 @@ public class Anime extends DeletableEntity { @JoinColumn(name = "series_id") private Series series; + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) + private Set animeOriginalAuthors; + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) + private Set animeStudios; + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) + private Set animeVoiceActors; + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) + private Set animeGenres; + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) + private Set bookMarks; + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) + private Set shortReviews; + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) + private Set ratings; + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) + private Set attractionPoints; + + static public Anime creatAnime(AnimeReq req, Series series) { Anime anime = new Anime(); anime.broadcastType = req.getBroadcastType(); diff --git a/src/main/java/io/oduck/api/domain/series/entity/Series.java b/src/main/java/io/oduck/api/domain/series/entity/Series.java index e06d4866..aa55a695 100644 --- a/src/main/java/io/oduck/api/domain/series/entity/Series.java +++ b/src/main/java/io/oduck/api/domain/series/entity/Series.java @@ -1,11 +1,15 @@ package io.oduck.api.domain.series.entity; -import io.oduck.api.golbal.audit.DeletableEntity; +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.global.audit.DeletableEntity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import java.util.Set; import lombok.Getter; import lombok.NoArgsConstructor; @@ -20,9 +24,6 @@ public class Series extends DeletableEntity { @Column(nullable = false, length = 50) private String title; - static public Series create(String title) { - Series series = new Series(); - series.title = title; - return series; - } + @OneToMany(mappedBy = "series", cascade = CascadeType.PERSIST) + private Set animes; } From 77491495918902d2cf5e530760c7a3faf1df3c09 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 17:31:25 +0900 Subject: [PATCH 011/734] =?UTF-8?q?feat:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=8F?= =?UTF-8?q?=20=ED=8F=89=EA=B0=80=20=EA=B4=80=EB=A0=A8=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/AttractionElement.java | 9 ++++ .../entity/AttractionPoint.java | 35 +++++++++++++++ .../api/domain/review/entity/ShortReview.java | 44 +++++++++++++++++++ .../reviewLike/entity/ShortReviewLike.java | 31 +++++++++++++ .../domain/starRating/entity/StarRating.java | 34 ++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionElement.java create mode 100644 src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java create mode 100644 src/main/java/io/oduck/api/domain/review/entity/ShortReview.java create mode 100644 src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java create mode 100644 src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionElement.java b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionElement.java new file mode 100644 index 00000000..2e88a5fa --- /dev/null +++ b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionElement.java @@ -0,0 +1,9 @@ +package io.oduck.api.domain.attractionPoint.entity; + +public enum AttractionElement { + ART, + STORY, + MUSIC, + CHARACTER, + VOICE_ACTOR, +} diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java new file mode 100644 index 00000000..bbc6f5c2 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java @@ -0,0 +1,35 @@ +package io.oduck.api.domain.attractionPoint.entity; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.member.entity.Member; +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 lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class AttractionPoint { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anime_id") + private Anime anime; + + @Enumerated(EnumType.STRING) + private AttractionElement attractionElement; +} diff --git a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java new file mode 100644 index 00000000..82279ca6 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java @@ -0,0 +1,44 @@ +package io.oduck.api.domain.review.entity; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.reviewLike.entity.ShortReviewLike; +import jakarta.persistence.CascadeType; +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.OneToMany; +import java.util.Set; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class ShortReview { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anime_id") + private Anime anime; + + @Column(nullable = false, length = 100) + private String content; + + @Column(nullable = false) + private boolean has_spoiler; + + @OneToMany(mappedBy = "shortReview", cascade = CascadeType.PERSIST) + private Set shortReviewLikes; +} diff --git a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java new file mode 100644 index 00000000..1e5b4615 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java @@ -0,0 +1,31 @@ +package io.oduck.api.domain.reviewLike.entity; + +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.review.entity.ShortReview; +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 lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class ShortReviewLike { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "short_review_id") + private ShortReview shortReview; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; +} diff --git a/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java b/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java new file mode 100644 index 00000000..e7d51ad8 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java @@ -0,0 +1,34 @@ +package io.oduck.api.domain.starRating.entity; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.member.entity.Member; +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 lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class StarRating { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anime_id") + private Anime anime; + + @Column(nullable = false) + private int score; +} From 40b0503dcb06f506f480a9fec0da24d586a91dc8 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 17:31:43 +0900 Subject: [PATCH 012/734] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=B6=94=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/bookmark/entity/Bookmark.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java diff --git a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java new file mode 100644 index 00000000..1eabe9b2 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java @@ -0,0 +1,30 @@ +package io.oduck.api.domain.bookmark.entity; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.member.entity.Member; +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 lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +public class Bookmark { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anime_id") + private Anime anime; +} From 20c2a5be87b28fb4896bced9f601b3646eaa9ae6 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 17:31:55 +0900 Subject: [PATCH 013/734] =?UTF-8?q?feat:=20=EB=AC=B8=EC=9D=98=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=B6=94=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/inquiry/entity/Inquiry.java | 10 ++++++++++ .../io/oduck/api/domain/inquiry/entity/Result.java | 7 +++++++ .../io/oduck/api/domain/inquiry/entity/Status.java | 8 ++++++++ 3 files changed, 25 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java create mode 100644 src/main/java/io/oduck/api/domain/inquiry/entity/Result.java create mode 100644 src/main/java/io/oduck/api/domain/inquiry/entity/Status.java diff --git a/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java b/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java new file mode 100644 index 00000000..65ff35de --- /dev/null +++ b/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.inquiry.entity; + +public class Inquiry { + private Long id; + private String email; + private String title; + private String content; + private Status status; + private Result result; +} diff --git a/src/main/java/io/oduck/api/domain/inquiry/entity/Result.java b/src/main/java/io/oduck/api/domain/inquiry/entity/Result.java new file mode 100644 index 00000000..534856ce --- /dev/null +++ b/src/main/java/io/oduck/api/domain/inquiry/entity/Result.java @@ -0,0 +1,7 @@ +package io.oduck.api.domain.inquiry.entity; + +public enum Result { + DELETE, + KEEP, + INACTIVE, +} diff --git a/src/main/java/io/oduck/api/domain/inquiry/entity/Status.java b/src/main/java/io/oduck/api/domain/inquiry/entity/Status.java new file mode 100644 index 00000000..94a7424c --- /dev/null +++ b/src/main/java/io/oduck/api/domain/inquiry/entity/Status.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.inquiry.entity; + +public enum Status { + WAITING, + PROCESSING, + DONE, + REJECTED, +} From f31382f1376083aa6a5b76306ff255eb342d0d98 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 20:34:46 +0900 Subject: [PATCH 014/734] =?UTF-8?q?refactor:=20BaseEntity=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EC=95=88=20=EB=90=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/global/audit/DeletableEntity.java | 7 +++++++ .../java/io/oduck/api/global/audit/UpdatableEntity.java | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/io/oduck/api/global/audit/DeletableEntity.java b/src/main/java/io/oduck/api/global/audit/DeletableEntity.java index d03e9759..9e056845 100644 --- a/src/main/java/io/oduck/api/global/audit/DeletableEntity.java +++ b/src/main/java/io/oduck/api/global/audit/DeletableEntity.java @@ -1,9 +1,16 @@ package io.oduck.api.global.audit; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; import jakarta.persistence.Column; +import lombok.Getter; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) public abstract class DeletableEntity extends UpdatableEntity { @Column(nullable = true) protected LocalDateTime deletedAt; diff --git a/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java b/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java index f7e60696..d13f55cb 100644 --- a/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java +++ b/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java @@ -1,11 +1,18 @@ package io.oduck.api.global.audit; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; +import lombok.Getter; import org.hibernate.annotations.UpdateTimestamp; import jakarta.persistence.Column; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) public abstract class UpdatableEntity extends BaseEntity { @UpdateTimestamp @Column(nullable = false) From 2c47337ccdf96ecd138420910823ee5b05de3a1e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 28 Sep 2023 20:43:44 +0900 Subject: [PATCH 015/734] =?UTF-8?q?refactpr:=20=EC=97=B0=EA=B4=80=20?= =?UTF-8?q?=EA=B4=80=EA=B3=84=20=EC=97=94=ED=8B=B0=ED=8B=B0=20Set=20->=20L?= =?UTF-8?q?ist=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/entity/Anime.java | 20 +++++++-------- .../api/domain/anime/entity/AnimeStudio.java | 1 - .../oduck/api/domain/genre/entity/Genre.java | 4 +-- .../api/domain/inquiry/entity/Inquiry.java | 25 +++++++++++++++++++ .../api/domain/member/entity/AuthSocial.java | 1 - .../api/domain/member/entity/Member.java | 16 +++++++++--- .../domain/member/entity/MemberProfile.java | 10 +++++++- .../originalAuthor/entity/OriginalAuthor.java | 4 +-- .../api/domain/review/entity/ShortReview.java | 4 +-- .../api/domain/series/entity/Series.java | 5 ++-- .../api/domain/studio/entity/Studio.java | 4 +-- .../domain/voiceActor/entity/VoiceActor.java | 4 +-- 12 files changed, 69 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index d509cfc0..3ed22211 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -3,9 +3,10 @@ import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; import io.oduck.api.domain.bookmark.entity.Bookmark; import io.oduck.api.domain.review.entity.ShortReview; +import io.oduck.api.domain.starRating.entity.StarRating; import jakarta.persistence.CascadeType; import jakarta.persistence.OneToMany; -import java.util.Set; +import java.util.List; import org.hibernate.annotations.ColumnDefault; import io.oduck.api.domain.anime.dto.AnimeReq; @@ -73,29 +74,28 @@ public class Anime extends DeletableEntity { private Series series; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private Set animeOriginalAuthors; + private List animeOriginalAuthors; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private Set animeStudios; + private List animeStudios; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private Set animeVoiceActors; + private List animeVoiceActors; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private Set animeGenres; + private List animeGenres; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private Set bookMarks; + private List bookMarks; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private Set shortReviews; + private List shortReviews; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private Set ratings; + private List starRatings; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private Set attractionPoints; - + private List attractionPoints; static public Anime creatAnime(AnimeReq req, Series series) { Anime anime = new Anime(); diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java index a53635f1..a08c8616 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import io.oduck.api.domain.studio.entity.Studio; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; diff --git a/src/main/java/io/oduck/api/domain/genre/entity/Genre.java b/src/main/java/io/oduck/api/domain/genre/entity/Genre.java index 374754a1..b6063938 100644 --- a/src/main/java/io/oduck/api/domain/genre/entity/Genre.java +++ b/src/main/java/io/oduck/api/domain/genre/entity/Genre.java @@ -8,7 +8,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; -import java.util.Set; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -24,5 +24,5 @@ public class Genre { private String name; @OneToMany(mappedBy = "genre", cascade = CascadeType.PERSIST) - private Set animeGenres; + private List animeGenres; } \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java b/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java index 65ff35de..54391cd5 100644 --- a/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java +++ b/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java @@ -1,10 +1,35 @@ package io.oduck.api.domain.inquiry.entity; +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.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) public class Inquiry { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(nullable = false, length = 100) private String email; + + @Column(nullable = false, length = 100) private String title; + + @Column(nullable = false, length = 1000) private String content; + + @Enumerated(EnumType.STRING) private Status status; + + @Enumerated(EnumType.STRING) private Result result; } diff --git a/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java b/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java index 01fda906..197363ad 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java +++ b/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java @@ -1,6 +1,5 @@ package io.oduck.api.domain.member.entity; -import aj.org.objectweb.asm.Type; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/io/oduck/api/domain/member/entity/Member.java b/src/main/java/io/oduck/api/domain/member/entity/Member.java index dea56c67..062d1569 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/Member.java +++ b/src/main/java/io/oduck/api/domain/member/entity/Member.java @@ -1,11 +1,13 @@ package io.oduck.api.domain.member.entity; +import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; import io.oduck.api.domain.bookmark.entity.Bookmark; import io.oduck.api.domain.review.entity.ShortReview; import io.oduck.api.domain.reviewLike.entity.ShortReviewLike; +import io.oduck.api.domain.starRating.entity.StarRating; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; -import java.util.Set; +import java.util.List; import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; @@ -38,11 +40,17 @@ public class Member { private MemberProfile memberProfile; @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) - private Set bookMarks; + private List bookMarks; @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) - private Set shortReviews; + private List shortReviews; @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) - private Set shortReviewLikes; + private List shortReviewLikes; + + @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) + private List starRatings; + + @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) + private List attractionPoints; } diff --git a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java index 53db5c0d..21dd6fd3 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java +++ b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java @@ -2,6 +2,8 @@ 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; @@ -10,6 +12,7 @@ import jakarta.persistence.OneToOne; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; @Entity @Getter @@ -29,11 +32,16 @@ public class MemberProfile { @Column(nullable = false, length = 50) private String info; + @Enumerated(EnumType.STRING) private Role role; + @Column(length = 100) private String thumbnail; - private String backgrounImage; + @Column(length = 100) + private String backgroundImage; + @Column(nullable = false) + @ColumnDefault("0") private Long point; } diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java b/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java index bd01bfa5..e963377b 100644 --- a/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java +++ b/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java @@ -8,7 +8,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; -import java.util.Set; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -24,5 +24,5 @@ public class OriginalAuthor { private String name; @OneToMany(mappedBy = "originalAuthor", cascade = CascadeType.PERSIST) - private Set animeOriginalAuthors; + private List animeOriginalAuthors; } diff --git a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java index 82279ca6..21e9e7e3 100644 --- a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java +++ b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java @@ -13,7 +13,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; -import java.util.Set; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -40,5 +40,5 @@ public class ShortReview { private boolean has_spoiler; @OneToMany(mappedBy = "shortReview", cascade = CascadeType.PERSIST) - private Set shortReviewLikes; + private List shortReviewLikes; } diff --git a/src/main/java/io/oduck/api/domain/series/entity/Series.java b/src/main/java/io/oduck/api/domain/series/entity/Series.java index aa55a695..d222350f 100644 --- a/src/main/java/io/oduck/api/domain/series/entity/Series.java +++ b/src/main/java/io/oduck/api/domain/series/entity/Series.java @@ -9,7 +9,8 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; -import java.util.Set; +import java.util.List; + import lombok.Getter; import lombok.NoArgsConstructor; @@ -25,5 +26,5 @@ public class Series extends DeletableEntity { private String title; @OneToMany(mappedBy = "series", cascade = CascadeType.PERSIST) - private Set animes; + private List animes; } diff --git a/src/main/java/io/oduck/api/domain/studio/entity/Studio.java b/src/main/java/io/oduck/api/domain/studio/entity/Studio.java index 00fc1615..983a1d48 100644 --- a/src/main/java/io/oduck/api/domain/studio/entity/Studio.java +++ b/src/main/java/io/oduck/api/domain/studio/entity/Studio.java @@ -8,7 +8,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; -import java.util.Set; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -24,5 +24,5 @@ public class Studio { private String name; @OneToMany(mappedBy = "studio", cascade = CascadeType.PERSIST) - private Set animeStudios; + private List animeStudios; } diff --git a/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java b/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java index ee8fdafd..07624877 100644 --- a/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java +++ b/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java @@ -8,7 +8,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; -import java.util.Set; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; @@ -24,5 +24,5 @@ public class VoiceActor { private String name; @OneToMany(mappedBy = "voiceActor", cascade = CascadeType.PERSIST) - private Set animeVoiceActors; + private List animeVoiceActors; } \ No newline at end of file From 0163181287ef01c0017b26c3587faf2db5e9dba1 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 29 Sep 2023 15:28:36 +0900 Subject: [PATCH 016/734] =?UTF-8?q?refactor:=20BaseEntity=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/audit/BaseEntity.java | 8 ++++++++ .../api/global/audit/DeletableEntity.java | 17 ---------------- .../api/global/audit/UpdatableEntity.java | 20 ------------------- 3 files changed, 8 insertions(+), 37 deletions(-) delete mode 100644 src/main/java/io/oduck/api/global/audit/DeletableEntity.java delete mode 100644 src/main/java/io/oduck/api/global/audit/UpdatableEntity.java diff --git a/src/main/java/io/oduck/api/global/audit/BaseEntity.java b/src/main/java/io/oduck/api/global/audit/BaseEntity.java index 264e2696..6308dee4 100644 --- a/src/main/java/io/oduck/api/global/audit/BaseEntity.java +++ b/src/main/java/io/oduck/api/global/audit/BaseEntity.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import jakarta.persistence.Column; @@ -17,4 +18,11 @@ public abstract class BaseEntity { @CreationTimestamp @Column(nullable = false, updatable = false) protected LocalDateTime createdAt; + + @UpdateTimestamp + @Column(nullable = false) + protected LocalDateTime updatedAt; + + @Column(nullable = true) + protected LocalDateTime deletedAt; } diff --git a/src/main/java/io/oduck/api/global/audit/DeletableEntity.java b/src/main/java/io/oduck/api/global/audit/DeletableEntity.java deleted file mode 100644 index 9e056845..00000000 --- a/src/main/java/io/oduck/api/global/audit/DeletableEntity.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.oduck.api.global.audit; - -import jakarta.persistence.EntityListeners; -import jakarta.persistence.MappedSuperclass; -import java.time.LocalDateTime; - -import jakarta.persistence.Column; -import lombok.Getter; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -@Getter -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -public abstract class DeletableEntity extends UpdatableEntity { - @Column(nullable = true) - protected LocalDateTime deletedAt; -} diff --git a/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java b/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java deleted file mode 100644 index d13f55cb..00000000 --- a/src/main/java/io/oduck/api/global/audit/UpdatableEntity.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.oduck.api.global.audit; - -import jakarta.persistence.EntityListeners; -import jakarta.persistence.MappedSuperclass; -import java.time.LocalDateTime; - -import lombok.Getter; -import org.hibernate.annotations.UpdateTimestamp; - -import jakarta.persistence.Column; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -@Getter -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -public abstract class UpdatableEntity extends BaseEntity { - @UpdateTimestamp - @Column(nullable = false) - protected LocalDateTime updatedAt; -} From 07f4cedfc10b6a10f45789643ff64952e2fe0023 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 29 Sep 2023 15:33:20 +0900 Subject: [PATCH 017/734] =?UTF-8?q?refactor:=20BaseEntity=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/anime/entity/Anime.java | 4 ++-- .../api/domain/attractionPoint/entity/AttractionPoint.java | 3 ++- .../java/io/oduck/api/domain/bookmark/entity/Bookmark.java | 7 +++++++ src/main/java/io/oduck/api/domain/genre/entity/Genre.java | 3 ++- .../java/io/oduck/api/domain/inquiry/entity/Inquiry.java | 6 ++++++ .../java/io/oduck/api/domain/member/entity/AuthLocal.java | 3 ++- .../java/io/oduck/api/domain/member/entity/AuthSocial.java | 3 ++- .../java/io/oduck/api/domain/member/entity/Member.java | 3 ++- .../io/oduck/api/domain/member/entity/MemberProfile.java | 3 ++- .../api/domain/originalAuthor/entity/OriginalAuthor.java | 3 ++- .../io/oduck/api/domain/review/entity/ShortReview.java | 3 ++- .../api/domain/reviewLike/entity/ShortReviewLike.java | 7 +++++++ .../java/io/oduck/api/domain/series/entity/Series.java | 4 ++-- .../io/oduck/api/domain/starRating/entity/StarRating.java | 3 ++- .../java/io/oduck/api/domain/studio/entity/Studio.java | 3 ++- .../io/oduck/api/domain/voiceActor/entity/VoiceActor.java | 3 ++- 16 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 3ed22211..57c05623 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -4,6 +4,7 @@ import io.oduck.api.domain.bookmark.entity.Bookmark; import io.oduck.api.domain.review.entity.ShortReview; import io.oduck.api.domain.starRating.entity.StarRating; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.OneToMany; import java.util.List; @@ -11,7 +12,6 @@ import io.oduck.api.domain.anime.dto.AnimeReq; import io.oduck.api.domain.series.entity.Series; -import io.oduck.api.global.audit.DeletableEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -28,7 +28,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class Anime extends DeletableEntity { +public class Anime extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java index bbc6f5c2..6de3d3b5 100644 --- a/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java +++ b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java @@ -2,6 +2,7 @@ import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -17,7 +18,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class AttractionPoint { +public class AttractionPoint extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java index 1eabe9b2..c317bb65 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java +++ b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java @@ -2,6 +2,7 @@ import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.domain.member.entity.Member; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -9,8 +10,10 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; @Entity @Getter @@ -27,4 +30,8 @@ public class Bookmark { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "anime_id") private Anime anime; + + @CreationTimestamp + @Column(nullable = false, updatable = false) + protected LocalDateTime createdAt; } diff --git a/src/main/java/io/oduck/api/domain/genre/entity/Genre.java b/src/main/java/io/oduck/api/domain/genre/entity/Genre.java index b6063938..278d0b89 100644 --- a/src/main/java/io/oduck/api/domain/genre/entity/Genre.java +++ b/src/main/java/io/oduck/api/domain/genre/entity/Genre.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.genre.entity; import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -15,7 +16,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class Genre { +public class Genre extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java b/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java index 54391cd5..aa696916 100644 --- a/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java +++ b/src/main/java/io/oduck/api/domain/inquiry/entity/Inquiry.java @@ -7,8 +7,10 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; @Entity @Getter @@ -32,4 +34,8 @@ public class Inquiry { @Enumerated(EnumType.STRING) private Result result; + + @CreationTimestamp + @Column(nullable = false, updatable = false) + protected LocalDateTime createdAt; } diff --git a/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java b/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java index e7dd1baa..cec23c2d 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java +++ b/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java @@ -1,5 +1,6 @@ package io.oduck.api.domain.member.entity; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -14,7 +15,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class AuthLocal { +public class AuthLocal extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java b/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java index 197363ad..a7653470 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java +++ b/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java @@ -1,5 +1,6 @@ package io.oduck.api.domain.member.entity; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -16,7 +17,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class AuthSocial { +public class AuthSocial extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/member/entity/Member.java b/src/main/java/io/oduck/api/domain/member/entity/Member.java index 062d1569..bc24421a 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/Member.java +++ b/src/main/java/io/oduck/api/domain/member/entity/Member.java @@ -5,6 +5,7 @@ import io.oduck.api.domain.review.entity.ShortReview; import io.oduck.api.domain.reviewLike.entity.ShortReviewLike; import io.oduck.api.domain.starRating.entity.StarRating; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import java.util.List; @@ -22,7 +23,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class Member { +public class Member extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java index 21dd6fd3..9fd20c01 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java +++ b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java @@ -1,5 +1,6 @@ package io.oduck.api.domain.member.entity; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -17,7 +18,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class MemberProfile { +public class MemberProfile extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java b/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java index e963377b..65c5ecf0 100644 --- a/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java +++ b/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.originalAuthor.entity; import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -15,7 +16,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class OriginalAuthor { +public class OriginalAuthor extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java index 21e9e7e3..26601b4f 100644 --- a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java +++ b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java @@ -3,6 +3,7 @@ import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.domain.member.entity.Member; import io.oduck.api.domain.reviewLike.entity.ShortReviewLike; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -20,7 +21,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class ShortReview { +public class ShortReview extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java index 1e5b4615..bd1b9db0 100644 --- a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java +++ b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java @@ -2,6 +2,7 @@ import io.oduck.api.domain.member.entity.Member; import io.oduck.api.domain.review.entity.ShortReview; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -9,8 +10,10 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; @Entity @Getter @@ -28,4 +31,8 @@ public class ShortReviewLike { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; + + @CreationTimestamp + @Column(nullable = false, updatable = false) + protected LocalDateTime createdAt; } diff --git a/src/main/java/io/oduck/api/domain/series/entity/Series.java b/src/main/java/io/oduck/api/domain/series/entity/Series.java index d222350f..84d46c2e 100644 --- a/src/main/java/io/oduck/api/domain/series/entity/Series.java +++ b/src/main/java/io/oduck/api/domain/series/entity/Series.java @@ -1,7 +1,7 @@ package io.oduck.api.domain.series.entity; import io.oduck.api.domain.anime.entity.Anime; -import io.oduck.api.global.audit.DeletableEntity; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -17,7 +17,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class Series extends DeletableEntity { +public class Series extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java b/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java index e7d51ad8..c156af61 100644 --- a/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java +++ b/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java @@ -2,6 +2,7 @@ import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -16,7 +17,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class StarRating { +public class StarRating extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/studio/entity/Studio.java b/src/main/java/io/oduck/api/domain/studio/entity/Studio.java index 983a1d48..995fec0e 100644 --- a/src/main/java/io/oduck/api/domain/studio/entity/Studio.java +++ b/src/main/java/io/oduck/api/domain/studio/entity/Studio.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.studio.entity; import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -15,7 +16,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class Studio { +public class Studio extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java b/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java index 07624877..b530f9bf 100644 --- a/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java +++ b/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.voiceActor.entity; import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -15,7 +16,7 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -public class VoiceActor { +public class VoiceActor extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; From 72358ecca60d32183a1c126f21638a307cdd9dcc Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:35:51 +0900 Subject: [PATCH 018/734] =?UTF-8?q?chore:=20RestDocs=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e85c3e4c..49fb34bd 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ 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 'com.google.code.gson:gson' compileOnly 'org.projectlombok:lombok' @@ -60,8 +61,8 @@ test { } asciidoctor { - inputs.dir snippetsDir configurations "asciidoctorExt" + inputs.dir snippetsDir dependsOn test } @@ -74,5 +75,12 @@ tasks.register('copyDocument', Copy) { } build { + dependsOn bootJar +} + +bootJar { dependsOn copyDocument -} \ No newline at end of file + from ("${asciidoctor.outputDir}") { + into 'static/docs' + } +} From 0604d7c648863d774a26ef84c82a333afcf5a4d4 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:36:13 +0900 Subject: [PATCH 019/734] =?UTF-8?q?test:=20restDocs=20snippet=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../restdocs/templates/request-fields.snippet | 13 +++++++++++++ .../restdocs/templates/request-headers.snippet | 11 +++++++++++ .../restdocs/templates/request-parameters.snippet | 11 +++++++++++ 3 files changed, 35 insertions(+) create mode 100644 src/test/resources/org/springframework/restdocs/templates/request-fields.snippet create mode 100644 src/test/resources/org/springframework/restdocs/templates/request-headers.snippet create mode 100644 src/test/resources/org/springframework/restdocs/templates/request-parameters.snippet diff --git a/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet new file mode 100644 index 00000000..e6cef379 --- /dev/null +++ b/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet @@ -0,0 +1,13 @@ +//request-fileds.snippet +//.{{title}} + +|=== +|필드 명|타입|제약 조건|필수 여부|설명 +{{#fields}} +|{{path}} +|{{type}} +|{{constraints}} +|{{^optional}}true{{/optional}} +|{{description}} +{{/fields}} +|=== \ No newline at end of file diff --git a/src/test/resources/org/springframework/restdocs/templates/request-headers.snippet b/src/test/resources/org/springframework/restdocs/templates/request-headers.snippet new file mode 100644 index 00000000..e19d718f --- /dev/null +++ b/src/test/resources/org/springframework/restdocs/templates/request-headers.snippet @@ -0,0 +1,11 @@ +|=== +|헤더 명|제약 조건|필수 여부|설명 + +{{#headers}} +|{{name}} +|{{constraints}} +|{{^optional}}true{{/optional}} +|{{description}} + +{{/headers}} +|=== \ No newline at end of file diff --git a/src/test/resources/org/springframework/restdocs/templates/request-parameters.snippet b/src/test/resources/org/springframework/restdocs/templates/request-parameters.snippet new file mode 100644 index 00000000..5a64787e --- /dev/null +++ b/src/test/resources/org/springframework/restdocs/templates/request-parameters.snippet @@ -0,0 +1,11 @@ +|=== +|파라미터명|필수 여부|설명 + +{{#parameters}} +|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{^optional}}true{{/optional}}{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/parameters}} + +|=== \ No newline at end of file From 8361ec675a0583d5f8803f021431751b216cb368 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:37:02 +0900 Subject: [PATCH 020/734] =?UTF-8?q?chore:=20api=20=EB=AC=B8=EC=84=9C=20git?= =?UTF-8?q?ignore=20=EB=93=B1=EB=A1=9D=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 41c37199..9c416a72 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build/ */.DS_Store *.log *.gz +src/main/resources/static/docs/index.html ### STS ### .apt_generated From 284166bd9a049cc7b6668f76744c0726d8512796 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:39:13 +0900 Subject: [PATCH 021/734] =?UTF-8?q?chore:=20url=20prefix=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 261a92a6..ebc36069 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,7 @@ +server: + servlet: + contextPath: /api/v1 + spring: h2: console: From 4adfdfc6bcf5e3954702af03dfff2dad2dd877c0 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:40:35 +0900 Subject: [PATCH 022/734] =?UTF-8?q?feat:=20Member=20Stub=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 64 +++++++++++++++++++ .../api/domain/member/dto/MemberReqDto.java | 46 +++++++++++++ .../api/domain/member/dto/MemberResDto.java | 50 +++++++++++++++ .../domain/member/service/MemberService.java | 14 ++++ .../member/service/MemberServiceStub.java | 32 ++++++++++ .../api/global/common/SingleResponse.java | 14 ++++ 6 files changed, 220 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/member/controller/MemberController.java create mode 100644 src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java create mode 100644 src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java create mode 100644 src/main/java/io/oduck/api/domain/member/service/MemberService.java create mode 100644 src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java create mode 100644 src/main/java/io/oduck/api/global/common/SingleResponse.java diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java new file mode 100644 index 00000000..3f9bbd39 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -0,0 +1,64 @@ +package io.oduck.api.domain.member.controller; + +import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; +import io.oduck.api.domain.member.dto.MemberReqDto.PatchReq; +import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; +import io.oduck.api.domain.member.service.MemberService; +import io.oduck.api.global.common.SingleResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Validated +@RequestMapping("/members") +@RequiredArgsConstructor +@RestController +public class MemberController { + private final MemberService memberService; + + // 로컬 회원 가입 + @PostMapping + public ResponseEntity PostMember( + @RequestBody @Valid CreateReq body) { + // TODO: 회원 가입 로직 구현 + // memberService.signUpByLocal(body); + + return ResponseEntity.ok().build(); + } + + // 이름으로 회원 프로필 조회 + @GetMapping("/{name}") + public ResponseEntity getProfileByName( + @PathVariable("name") String name + // TODO: 인증 정보 추가 + ) { + // TODO: 회원 프로필 조회 로직 구현 + MemberProfileRes res = memberService.getProfileByName(name); + + return ResponseEntity.ok(SingleResponse.of(res)); + } + + // 회원 프로필 수정 + @PatchMapping + public ResponseEntity patchProfile( + @RequestBody PatchReq body + // TODO: 인증 정보 추가 + ) { + // TODO: 회원 정보 수정 로직 구현 + // memberService.updateProfile(body); + + return ResponseEntity.noContent().build(); + } + + // 회원이 작성한 리뷰 목록 + + // 회원 북마크 애니 목록 +} diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java new file mode 100644 index 00000000..bee94abc --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java @@ -0,0 +1,46 @@ +package io.oduck.api.domain.member.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class MemberReqDto { + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class CreateReq { + + @NotBlank + @Email(message = "이메일 형식이 아닙니다.") + private String email; + + @NotBlank + @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_=+-])(?=.*[0-9]).{8,20}$", + message = "비밀번호는 영문 대소문자, 숫자, 특수문자를 포함한 8~20자리여야 합니다.") + private String password; + } + + @Getter + @NoArgsConstructor + public static class PatchReq { + + @NotBlank + @Pattern(regexp = "^[0-9A-Za-z가-힣]{2,10}$") + private String name; + + @Length(min = 0, max = 100) + private String description; + + @Builder + public PatchReq(String name, String description) { + this.name = name; + this.description = description; + } + } +} diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java new file mode 100644 index 00000000..bf6ebf64 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java @@ -0,0 +1,50 @@ +package io.oduck.api.domain.member.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class MemberResDto { + @Getter + @NoArgsConstructor + public static class MemberProfileRes { + private boolean isMine; + private String name; + private String description; + private String thumbnail; + private String backgroundImage; + private Activity activity; + private int point; + + @Builder + public MemberProfileRes(boolean isMine, String name, String description, String thumbnail, + String backgroundImage, Activity activity, int point) { + this.isMine = isMine; + this.name = name; + this.description = description; + this.thumbnail = thumbnail; + this.backgroundImage = backgroundImage; + this.activity = activity; + this.point = point; + } + + public boolean getIsMine() { + return isMine; + } + } + + @Getter + @NoArgsConstructor + public static class Activity { + private int reviews; + private int threads; + private int likes; + + @Builder + public Activity(int reviews, int threads, int likes) { + this.reviews = reviews; + this.threads = threads; + this.likes = likes; + } + } +} diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberService.java b/src/main/java/io/oduck/api/domain/member/service/MemberService.java new file mode 100644 index 00000000..fd6e595f --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/service/MemberService.java @@ -0,0 +1,14 @@ +package io.oduck.api.domain.member.service; + +import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; + +public interface MemberService { + // 로컬 회원가입 로직 + // Member signUpByLocal(Member member); + + // 이름으로 회원 프로필 조회 로직 + MemberProfileRes getProfileByName(String name); + + // 회원 정보 수정 로직 + // void updateProfile(PatchReq body); +} diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java new file mode 100644 index 00000000..e7c1cf2e --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java @@ -0,0 +1,32 @@ +package io.oduck.api.domain.member.service; + +import io.oduck.api.domain.member.dto.MemberResDto.Activity; +import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; +import org.springframework.stereotype.Service; + +@Service +public class MemberServiceStub implements MemberService{ + + + @Override + public MemberProfileRes getProfileByName(String name) { + + Activity activity = Activity.builder() + .reviews(0) + .threads(0) + .likes(0) + .build(); + + MemberProfileRes memberProfile = MemberProfileRes.builder() + .isMine(true) + .name(name) + .description("자기소개") + .thumbnail("썸네일") + .backgroundImage("배경 이미지") + .activity(activity) + .point(0) + .build(); + + return memberProfile; + } +} diff --git a/src/main/java/io/oduck/api/global/common/SingleResponse.java b/src/main/java/io/oduck/api/global/common/SingleResponse.java new file mode 100644 index 00000000..ecc100be --- /dev/null +++ b/src/main/java/io/oduck/api/global/common/SingleResponse.java @@ -0,0 +1,14 @@ +package io.oduck.api.global.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class SingleResponse { + private T item; + + public static SingleResponse of(T item) { + return new SingleResponse<>(item); + } +} \ No newline at end of file From 4b63dff9ef7375b19a72d4fc0b603d887e21c59e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:41:16 +0900 Subject: [PATCH 023/734] =?UTF-8?q?test:=20restDocs=20request-filed=20?= =?UTF-8?q?=EC=A0=9C=EC=95=BD=EC=A1=B0=EA=B1=B4=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/config/RestDocsConfig.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/test/java/io/oduck/api/config/RestDocsConfig.java diff --git a/src/test/java/io/oduck/api/config/RestDocsConfig.java b/src/test/java/io/oduck/api/config/RestDocsConfig.java new file mode 100644 index 00000000..b3148803 --- /dev/null +++ b/src/test/java/io/oduck/api/config/RestDocsConfig.java @@ -0,0 +1,13 @@ +package io.oduck.api.config; + +import org.springframework.boot.test.context.TestConfiguration; +import static org.springframework.restdocs.snippet.Attributes.Attribute; + +@TestConfiguration +public class RestDocsConfig { + public static final Attribute field( + final String key, + final String value){ + return new Attribute(key,value); + } +} From 5e5fc3d4e4db7905204a514248d58eaf4ce7c1ff Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:41:41 +0900 Subject: [PATCH 024/734] =?UTF-8?q?test:=20Member=20Test=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EB=AC=B8=EC=84=9C=ED=99=94=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/member/MemberControllerTest.java | 224 ++++++++++++++++++ .../member/service/MemberServiceTest.java | 44 ++++ 2 files changed, 268 insertions(+) create mode 100644 src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java create mode 100644 src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java new file mode 100644 index 00000000..f85bfe19 --- /dev/null +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -0,0 +1,224 @@ +package io.oduck.api.e2e.member; + +import static io.oduck.api.config.RestDocsConfig.field; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.google.gson.Gson; +import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; +import io.oduck.api.domain.member.dto.MemberReqDto.PatchReq; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@AutoConfigureRestDocs +@AutoConfigureMockMvc +@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) +@SpringBootTest +public class MemberControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + private final String BASE_URL = "/members"; + + @Nested + @DisplayName("회원 가입") + class PostMembers { + + @DisplayName("회원 가입 성공시 200 OK 반환") + @Test + void postMember() throws Exception { + // given + // TODO: 회원 가입에 필요한 데이터 + CreateReq body = CreateReq.builder() + .email("bob@gmail.com") + .password("Qwer1234!") + .build(); + + String content = gson.toJson(body); + + // when + // 요청 실행 + ResultActions actions = mockMvc.perform( + post("/members") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + // then + // 응답 결과 검증 후 문서화 + actions + .andExpect(status().isOk()) + .andDo(document("postMember/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("email") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "이메일 형식")) + .description("회원 가입에 필요한 이메일"), + fieldWithPath("password") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "숫자, 영문대소문자, 특수문자(!@#$%^&*()-_=+)를 포함한 8~20자리")) + .description("회원 가입에 필요한 비밀 번호")) + )); + } + + // TODO: 회원 가입 실패시 + } + + @Nested + @DisplayName("회원 프로필 조회") + class GetProfileByName { + + @DisplayName("회원 이름으로 프로필 조회 성공시 200 OK 반환") + @Test + void getProfileByName() throws Exception { + // given + // 회원 프로필 조회에 필요한 데이터 + String name = "이름"; + + // when + // 요청 실행 + ResultActions actions = mockMvc.perform( + get("/members"+ "/{name}", name) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + // 응답 결과 검증 후 문서화 + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.item.name").exists()) + .andExpect(jsonPath("$.item.isMine").exists()) + .andExpect(jsonPath("$.item.description").exists()) + .andExpect(jsonPath("$.item.thumbnail").exists()) + .andExpect(jsonPath("$.item.backgroundImage").exists()) + .andExpect(jsonPath("$.item.point").exists()) + .andExpect(jsonPath("$.item.activity").hasJsonPath()) + .andExpect(jsonPath("$.item.activity.reviews").exists()) + .andExpect(jsonPath("$.item.activity.threads").exists()) + .andExpect(jsonPath("$.item.activity.likes").exists()) + .andDo(document("getMemberByName/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("name") + .description("회원 이름")), + responseFields( + fieldWithPath("item") + .type(JsonFieldType.OBJECT) + .description("조회 데이터"), + fieldWithPath("item.name") + .type(JsonFieldType.STRING) + .description("회원 이름"), + fieldWithPath("item.isMine") + .type(JsonFieldType.BOOLEAN) + .description("본인 여부(본인 프로필 조회시 true)"), + fieldWithPath("item.description") + .type(JsonFieldType.STRING) + .description("자기 소개"), + fieldWithPath("item.thumbnail") + .type(JsonFieldType.STRING) + .description("프로필 이미지"), + fieldWithPath("item.backgroundImage") + .type(JsonFieldType.STRING) + .description("프로필 배경 이미지"), + fieldWithPath("item.point") + .type(JsonFieldType.NUMBER) + .description("회원 포인트"), + fieldWithPath("item.activity") + .type(JsonFieldType.OBJECT) + .description("회원 활동"), + fieldWithPath("item.activity.reviews") + .type(JsonFieldType.NUMBER) + .description("작성한 리뷰 갯수"), + fieldWithPath("item.activity.threads") + .type(JsonFieldType.NUMBER) + .description("작성한 쓰레드 갯수"), + fieldWithPath("item.activity.likes") + .type(JsonFieldType.NUMBER) + .description("받은 좋아요 갯수") + ))); + } + + // TODO: 회원 프로필 조회 실패시 + } + + @Nested + @DisplayName("회원 정보 수정") + class PatchProfile { + + @DisplayName("회원 가입 성공시 201 Created 반환") + @Test + void patchProfile() throws Exception { + // given + // 회원 정보 수정에 필요한 데이터 + PatchReq body = PatchReq.builder() + .name("bob") + .description("hello, world!") + .build(); + + String content = gson.toJson(body); + + // when + ResultActions actions = mockMvc.perform( + patch("/members") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + // then + actions + .andExpect(status().isNoContent()) + .andDo(document("patchMember/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "한,영소문자, 숫자 포함 2-10자. ^[0-9A-Za-z가-힣]{2,10}$")) + .description("이름 변경시 필요한 이름"), + fieldWithPath("description") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "문자열 0-100자")) + .description("자기 소개 변경시 필요한 내용")) + )); + } + + // TODO: 회원 프로필 수정 실패시 + } +} diff --git a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java new file mode 100644 index 00000000..b9b04661 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java @@ -0,0 +1,44 @@ +package io.oduck.api.unit.member.service; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; +import io.oduck.api.domain.member.service.MemberServiceStub; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class MemberServiceTest { + @InjectMocks + private MemberServiceStub memberService; + + // TODO: 회원 가입 + + @Nested + @DisplayName("회원 프로필 조회") + class GetProfileByName { + + @DisplayName("이름으로 회원 프로필 조회 성공시 오류 없이 회원 프로필 반환") + @Test + void getMemberByName() { + // given + String name = "bob"; + + // when + MemberProfileRes res = memberService.getProfileByName(name); + + // then + assertDoesNotThrow(() -> memberService.getProfileByName(name)); // 오류 없이 회원 프로필 반환 + assertEquals(res.getName(), name); // 회원 프로필의 이름이 요청한 이름과 같은지 확인 + } + + // TODO: 회원 프로필 조회 실패시 오류 반환 + } + + // TODO: 회원 정보 수정 +} From 3ede9ce609755c6d69c8b8adb2763f13bd4bfe84 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:42:04 +0900 Subject: [PATCH 025/734] =?UTF-8?q?docs:=20RestDocs=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/docs/asciidoc/index.adoc diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc new file mode 100644 index 00000000..0baa6508 --- /dev/null +++ b/src/docs/asciidoc/index.adoc @@ -0,0 +1,67 @@ += oDuckio Documentation +:sectnums: +:toc: left +:toclevels: 4 +:toc-title: API 목록 +:source-highlighter: prettify + +== members +=== POST api/v1/members +.curl-request +include::{snippets}/postMember/success/curl-request.adoc[] + +.http-request +include::{snippets}/postMember/success/http-request.adoc[] + +.request-body +include::{snippets}/postMember/success/request-body.adoc[] + +.request-fields +include::{snippets}/postMember/success/request-fields.adoc[] +==== 성공시 + +.http-response +include::{snippets}/postMember/success/http-response.adoc[] + +==== 실패시 + +=== GET api/v1/members/:name + +.curl-request +include::{snippets}/getMemberByName/success/curl-request.adoc[] + +.http-request +include::{snippets}/getMemberByName/success/http-request.adoc[] + +.request-param +include::{snippets}/getMemberByName/success/path-parameters.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getMemberByName/success/http-response.adoc[] + +.response-body +include::{snippets}/getMemberByName/success/response-body.adoc[] + +.response-fields +include::{snippets}/getMemberByName/success/response-fields.adoc[] + +==== 실패시 + +=== PATCH api/v1/members +.curl-request +include::{snippets}/patchMember/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchMember/success/http-request.adoc[] + +.request-body +include::{snippets}/patchMember/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchMember/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchMember/success/http-response.adoc[] \ No newline at end of file From c24da4d264093d22d20ab6e228f905f64dc3a7bd Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:49:06 +0900 Subject: [PATCH 026/734] =?UTF-8?q?refactor:=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=20=EA=B2=80=EC=82=AC=20=EC=88=98=EC=A0=95=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/controller/MemberController.java | 2 +- .../java/io/oduck/api/domain/member/dto/MemberReqDto.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index 3f9bbd39..0444e79a 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -49,7 +49,7 @@ public ResponseEntity getProfileByName( // 회원 프로필 수정 @PatchMapping public ResponseEntity patchProfile( - @RequestBody PatchReq body + @RequestBody @Valid PatchReq body // TODO: 인증 정보 추가 ) { // TODO: 회원 정보 수정 로직 구현 diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java index bee94abc..01da44f7 100644 --- a/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java @@ -31,10 +31,12 @@ public static class CreateReq { public static class PatchReq { @NotBlank - @Pattern(regexp = "^[0-9A-Za-z가-힣]{2,10}$") + @Pattern(regexp = "^[0-9A-Za-z가-힣]{2,10}$", + message = "이름은 2~10자리의 한글, 영문, 숫자여야 합니다.") private String name; - @Length(min = 0, max = 100) + @Length(min = 0, max = 100, + message = "자기 소개는 100자 이내여야 합니다.") private String description; @Builder From 2d7eda0cd11f1ae50eaca4688151e6c4258c3cac Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:53:00 +0900 Subject: [PATCH 027/734] =?UTF-8?q?test:=20=EC=98=A4=ED=83=88=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/e2e/member/MemberControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index f85bfe19..35c60157 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -129,7 +129,7 @@ void getProfileByName() throws Exception { .andExpect(jsonPath("$.item.activity.reviews").exists()) .andExpect(jsonPath("$.item.activity.threads").exists()) .andExpect(jsonPath("$.item.activity.likes").exists()) - .andDo(document("getMemberByName/success", + .andDo(document("getProfileByName/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -202,7 +202,7 @@ void patchProfile() throws Exception { // then actions .andExpect(status().isNoContent()) - .andDo(document("patchMember/success", + .andDo(document("patchProfile/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestFields( From 34ee2ed361a47511cdb80210573f8ecb2914ea6a Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 17:53:26 +0900 Subject: [PATCH 028/734] =?UTF-8?q?docs:=20snippet=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 0baa6508..f979e843 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -28,40 +28,40 @@ include::{snippets}/postMember/success/http-response.adoc[] === GET api/v1/members/:name .curl-request -include::{snippets}/getMemberByName/success/curl-request.adoc[] +include::{snippets}/getProfileByName/success/curl-request.adoc[] .http-request -include::{snippets}/getMemberByName/success/http-request.adoc[] +include::{snippets}/getProfileByName/success/http-request.adoc[] .request-param -include::{snippets}/getMemberByName/success/path-parameters.adoc[] +include::{snippets}/getProfileByName/success/path-parameters.adoc[] ==== 성공시 .http-response -include::{snippets}/getMemberByName/success/http-response.adoc[] +include::{snippets}/getProfileByName/success/http-response.adoc[] .response-body -include::{snippets}/getMemberByName/success/response-body.adoc[] +include::{snippets}/getProfileByName/success/response-body.adoc[] .response-fields -include::{snippets}/getMemberByName/success/response-fields.adoc[] +include::{snippets}/getProfileByName/success/response-fields.adoc[] ==== 실패시 === PATCH api/v1/members .curl-request -include::{snippets}/patchMember/success/curl-request.adoc[] +include::{snippets}/patchProfile/success/curl-request.adoc[] .http-request -include::{snippets}/patchMember/success/http-request.adoc[] +include::{snippets}/patchProfile/success/http-request.adoc[] .request-body -include::{snippets}/patchMember/success/request-body.adoc[] +include::{snippets}/patchProfile/success/request-body.adoc[] .request-fields -include::{snippets}/patchMember/success/request-fields.adoc[] +include::{snippets}/patchProfile/success/request-fields.adoc[] ==== 성공시 .http-response -include::{snippets}/patchMember/success/http-response.adoc[] \ No newline at end of file +include::{snippets}/patchProfile/success/http-response.adoc[] \ No newline at end of file From 0fe1e2795fe21613ec0bf9a6b4fb0410e2a449e1 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 30 Sep 2023 22:34:30 +0900 Subject: [PATCH 029/734] =?UTF-8?q?test:=20Rest=20Docs=20Config=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/member/MemberControllerTest.java | 208 +++++++++--------- .../{ => global}/config/RestDocsConfig.java | 2 +- 2 files changed, 103 insertions(+), 107 deletions(-) rename src/test/java/io/oduck/api/{ => global}/config/RestDocsConfig.java (90%) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 35c60157..f7b545e2 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -1,6 +1,6 @@ package io.oduck.api.e2e.member; -import static io.oduck.api.config.RestDocsConfig.field; +import static io.oduck.api.global.config.RestDocsConfig.field; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; @@ -36,7 +36,7 @@ @AutoConfigureRestDocs @AutoConfigureMockMvc -@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) +@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class }) @SpringBootTest public class MemberControllerTest { @Autowired @@ -57,40 +57,39 @@ void postMember() throws Exception { // given // TODO: 회원 가입에 필요한 데이터 CreateReq body = CreateReq.builder() - .email("bob@gmail.com") - .password("Qwer1234!") - .build(); + .email("bob@gmail.com") + .password("Qwer1234!") + .build(); String content = gson.toJson(body); // when // 요청 실행 ResultActions actions = mockMvc.perform( - post("/members") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(content) - ); + post("/members") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content)); // then // 응답 결과 검증 후 문서화 actions - .andExpect(status().isOk()) - .andDo(document("postMember/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - requestFields( - attributes(key("title") - .value("Fields for member creation")), - fieldWithPath("email") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "이메일 형식")) - .description("회원 가입에 필요한 이메일"), - fieldWithPath("password") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "숫자, 영문대소문자, 특수문자(!@#$%^&*()-_=+)를 포함한 8~20자리")) - .description("회원 가입에 필요한 비밀 번호")) - )); + .andExpect(status().isOk()) + .andDo(document("postMember/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("email") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "이메일 형식")) + .description("회원 가입에 필요한 이메일"), + fieldWithPath("password") + .type(JsonFieldType.STRING) + .attributes(field("constraints", + "숫자, 영문대소문자, 특수문자(!@#$%^&*()-_=+)를 포함한 8~20자리")) + .description("회원 가입에 필요한 비밀 번호")))); } // TODO: 회원 가입 실패시 @@ -110,66 +109,64 @@ void getProfileByName() throws Exception { // when // 요청 실행 ResultActions actions = mockMvc.perform( - get("/members"+ "/{name}", name) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - ); + get("/members" + "/{name}", name) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)); // then // 응답 결과 검증 후 문서화 actions - .andExpect(status().isOk()) - .andExpect(jsonPath("$.item.name").exists()) - .andExpect(jsonPath("$.item.isMine").exists()) - .andExpect(jsonPath("$.item.description").exists()) - .andExpect(jsonPath("$.item.thumbnail").exists()) - .andExpect(jsonPath("$.item.backgroundImage").exists()) - .andExpect(jsonPath("$.item.point").exists()) - .andExpect(jsonPath("$.item.activity").hasJsonPath()) - .andExpect(jsonPath("$.item.activity.reviews").exists()) - .andExpect(jsonPath("$.item.activity.threads").exists()) - .andExpect(jsonPath("$.item.activity.likes").exists()) - .andDo(document("getProfileByName/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("name") - .description("회원 이름")), - responseFields( - fieldWithPath("item") - .type(JsonFieldType.OBJECT) - .description("조회 데이터"), - fieldWithPath("item.name") - .type(JsonFieldType.STRING) - .description("회원 이름"), - fieldWithPath("item.isMine") - .type(JsonFieldType.BOOLEAN) - .description("본인 여부(본인 프로필 조회시 true)"), - fieldWithPath("item.description") - .type(JsonFieldType.STRING) - .description("자기 소개"), - fieldWithPath("item.thumbnail") - .type(JsonFieldType.STRING) - .description("프로필 이미지"), - fieldWithPath("item.backgroundImage") - .type(JsonFieldType.STRING) - .description("프로필 배경 이미지"), - fieldWithPath("item.point") - .type(JsonFieldType.NUMBER) - .description("회원 포인트"), - fieldWithPath("item.activity") - .type(JsonFieldType.OBJECT) - .description("회원 활동"), - fieldWithPath("item.activity.reviews") - .type(JsonFieldType.NUMBER) - .description("작성한 리뷰 갯수"), - fieldWithPath("item.activity.threads") - .type(JsonFieldType.NUMBER) - .description("작성한 쓰레드 갯수"), - fieldWithPath("item.activity.likes") - .type(JsonFieldType.NUMBER) - .description("받은 좋아요 갯수") - ))); + .andExpect(status().isOk()) + .andExpect(jsonPath("$.item.name").exists()) + .andExpect(jsonPath("$.item.isMine").exists()) + .andExpect(jsonPath("$.item.description").exists()) + .andExpect(jsonPath("$.item.thumbnail").exists()) + .andExpect(jsonPath("$.item.backgroundImage").exists()) + .andExpect(jsonPath("$.item.point").exists()) + .andExpect(jsonPath("$.item.activity").hasJsonPath()) + .andExpect(jsonPath("$.item.activity.reviews").exists()) + .andExpect(jsonPath("$.item.activity.threads").exists()) + .andExpect(jsonPath("$.item.activity.likes").exists()) + .andDo(document("getProfileByName/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("name") + .description("회원 이름")), + responseFields( + fieldWithPath("item") + .type(JsonFieldType.OBJECT) + .description("조회 데이터"), + fieldWithPath("item.name") + .type(JsonFieldType.STRING) + .description("회원 이름"), + fieldWithPath("item.isMine") + .type(JsonFieldType.BOOLEAN) + .description("본인 여부(본인 프로필 조회시 true)"), + fieldWithPath("item.description") + .type(JsonFieldType.STRING) + .description("자기 소개"), + fieldWithPath("item.thumbnail") + .type(JsonFieldType.STRING) + .description("프로필 이미지"), + fieldWithPath("item.backgroundImage") + .type(JsonFieldType.STRING) + .description("프로필 배경 이미지"), + fieldWithPath("item.point") + .type(JsonFieldType.NUMBER) + .description("회원 포인트"), + fieldWithPath("item.activity") + .type(JsonFieldType.OBJECT) + .description("회원 활동"), + fieldWithPath("item.activity.reviews") + .type(JsonFieldType.NUMBER) + .description("작성한 리뷰 갯수"), + fieldWithPath("item.activity.threads") + .type(JsonFieldType.NUMBER) + .description("작성한 쓰레드 갯수"), + fieldWithPath("item.activity.likes") + .type(JsonFieldType.NUMBER) + .description("받은 좋아요 갯수")))); } // TODO: 회원 프로필 조회 실패시 @@ -185,38 +182,37 @@ void patchProfile() throws Exception { // given // 회원 정보 수정에 필요한 데이터 PatchReq body = PatchReq.builder() - .name("bob") - .description("hello, world!") - .build(); + .name("bob") + .description("hello, world!") + .build(); String content = gson.toJson(body); // when ResultActions actions = mockMvc.perform( - patch("/members") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(content) - ); + patch("/members") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content)); // then actions - .andExpect(status().isNoContent()) - .andDo(document("patchProfile/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - requestFields( - attributes(key("title") - .value("Fields for member creation")), - fieldWithPath("name") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "한,영소문자, 숫자 포함 2-10자. ^[0-9A-Za-z가-힣]{2,10}$")) - .description("이름 변경시 필요한 이름"), - fieldWithPath("description") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "문자열 0-100자")) - .description("자기 소개 변경시 필요한 내용")) - )); + .andExpect(status().isNoContent()) + .andDo(document("patchProfile/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes( + field("constraints", "한,영소문자, 숫자 포함 2-10자. ^[0-9A-Za-z가-힣]{2,10}$")) + .description("이름 변경시 필요한 이름"), + fieldWithPath("description") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "문자열 0-100자")) + .description("자기 소개 변경시 필요한 내용")))); } // TODO: 회원 프로필 수정 실패시 diff --git a/src/test/java/io/oduck/api/config/RestDocsConfig.java b/src/test/java/io/oduck/api/global/config/RestDocsConfig.java similarity index 90% rename from src/test/java/io/oduck/api/config/RestDocsConfig.java rename to src/test/java/io/oduck/api/global/config/RestDocsConfig.java index b3148803..a935d770 100644 --- a/src/test/java/io/oduck/api/config/RestDocsConfig.java +++ b/src/test/java/io/oduck/api/global/config/RestDocsConfig.java @@ -1,4 +1,4 @@ -package io.oduck.api.config; +package io.oduck.api.global.config; import org.springframework.boot.test.context.TestConfiguration; import static org.springframework.restdocs.snippet.Attributes.Attribute; From 100deb85f46e225fd73b0bc042daab04d354b470 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 2 Oct 2023 14:35:40 +0900 Subject: [PATCH 030/734] =?UTF-8?q?feat:=20CustomException=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/advice/ExceptionHandlerAdvice.java | 120 ++++++++++++++++++ .../global/exception/BadRequestException.java | 7 + .../global/exception/ConflictException.java | 7 + .../api/global/exception/CustomException.java | 17 +++ .../global/exception/ForbiddenException.java | 7 + .../global/exception/NotFoundException.java | 8 ++ .../exception/UnauthorizedException.java | 7 + 7 files changed, 173 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/advice/ExceptionHandlerAdvice.java create mode 100644 src/main/java/io/oduck/api/global/exception/BadRequestException.java create mode 100644 src/main/java/io/oduck/api/global/exception/ConflictException.java create mode 100644 src/main/java/io/oduck/api/global/exception/CustomException.java create mode 100644 src/main/java/io/oduck/api/global/exception/ForbiddenException.java create mode 100644 src/main/java/io/oduck/api/global/exception/NotFoundException.java create mode 100644 src/main/java/io/oduck/api/global/exception/UnauthorizedException.java diff --git a/src/main/java/io/oduck/api/global/advice/ExceptionHandlerAdvice.java b/src/main/java/io/oduck/api/global/advice/ExceptionHandlerAdvice.java new file mode 100644 index 00000000..55b793e2 --- /dev/null +++ b/src/main/java/io/oduck/api/global/advice/ExceptionHandlerAdvice.java @@ -0,0 +1,120 @@ +package io.oduck.api.global.advice; + +import io.oduck.api.global.common.ErrorResponse; +import io.oduck.api.global.exception.BadRequestException; +import io.oduck.api.global.exception.ConflictException; +import io.oduck.api.global.exception.CustomException; +import io.oduck.api.global.exception.ForbiddenException; +import io.oduck.api.global.exception.NotFoundException; +import io.oduck.api.global.exception.UnauthorizedException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingRequestCookieException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpClientErrorException.Forbidden; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +@RequiredArgsConstructor +@Slf4j +@RestControllerAdvice +public class ExceptionHandlerAdvice { + + // 요청 바디 필드 유효성 검증 예외 처리 + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleMethodArgumentNotValidException( + MethodArgumentNotValidException e) { + + return ErrorResponse.of(e.getBindingResult()); + } + + // 경로 변수 유효성 검증 예외 처리 + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleConstraintViolationException( + ConstraintViolationException e) { + + return ErrorResponse.of(e.getConstraintViolations()); + } + + // converter가 변환하지 못했을 경우(enum type에 존재하지 않는 값) + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException e) { + + return ErrorResponse.of(e.getPropertyName() + " Type Mismatched"); + } + + // url에 대해 지원하지 않는 http method 일 때 예외 처리 + @ExceptionHandler + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + public ErrorResponse handleHttpRequestMethodNotSupportedException( + HttpRequestMethodNotSupportedException e) { + + return ErrorResponse.of(HttpStatus.METHOD_NOT_ALLOWED); + } + + // HTTP Body를 제대로 파싱하지 못했을 때 예외 처리 + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleHttpMessageNotReadableException( + HttpMessageNotReadableException e) { + + // 메시지 수정 필요 + return ErrorResponse.of("Required request body is missing"); + } + + // 요청 시 쿼리 파라미터가 결여됐을 때 예외 처리 + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleMissingServletRequestParameterException( + MissingServletRequestParameterException e) { + + return ErrorResponse.of(e.getMessage()); + } + + // 요청 쿠키가 null 값이거나 빈 값일 경우 + // 수정 필요 + @ExceptionHandler(MissingRequestCookieException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ErrorResponse handleMissingRequestCookieException(MissingRequestCookieException e) { + return ErrorResponse.of(e.getMessage()); + } + + // 일반적인 예외 처리 + @ExceptionHandler({ + BadRequestException.class, + UnauthorizedException.class, + ForbiddenException.class, + NotFoundException.class, + ConflictException.class, + CustomException.class + }) + public ResponseEntity handleCustomException(CustomException e) { + final ErrorResponse response = ErrorResponse.of(e.getMessage()); + + return new ResponseEntity<>(response, HttpStatus.valueOf(e.getStatus())); + } + + // 위에서 지정한 예외 외의 서버 로직 예외에 대한 예외 처리. + // 예상하지 못한 서버 예외 + // 운영에 치명적일 수 있음. + // 반드시 로그를 기록하고, 관리자에게 알림을 줄 것. + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleException(HttpServletRequest req, Exception e) { + log.error("# handle Exception", e); + return ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/io/oduck/api/global/exception/BadRequestException.java b/src/main/java/io/oduck/api/global/exception/BadRequestException.java new file mode 100644 index 00000000..b73f419e --- /dev/null +++ b/src/main/java/io/oduck/api/global/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package io.oduck.api.global.exception; + +public class BadRequestException extends CustomException{ + public BadRequestException(String message) { + super(400, message); + } +} diff --git a/src/main/java/io/oduck/api/global/exception/ConflictException.java b/src/main/java/io/oduck/api/global/exception/ConflictException.java new file mode 100644 index 00000000..bfe2b7bd --- /dev/null +++ b/src/main/java/io/oduck/api/global/exception/ConflictException.java @@ -0,0 +1,7 @@ +package io.oduck.api.global.exception; + +public class ConflictException extends CustomException{ + public ConflictException(String value) { + super(409, "Duplicate" + value); + } +} diff --git a/src/main/java/io/oduck/api/global/exception/CustomException.java b/src/main/java/io/oduck/api/global/exception/CustomException.java new file mode 100644 index 00000000..8a95e8d8 --- /dev/null +++ b/src/main/java/io/oduck/api/global/exception/CustomException.java @@ -0,0 +1,17 @@ +package io.oduck.api.global.exception; + +import lombok.Getter; + +@Getter +public class CustomException extends RuntimeException { + private int status; + + public CustomException(int status, String message) { + super(message); + this.status = status; + } + + public CustomException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/exception/ForbiddenException.java b/src/main/java/io/oduck/api/global/exception/ForbiddenException.java new file mode 100644 index 00000000..c631c285 --- /dev/null +++ b/src/main/java/io/oduck/api/global/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package io.oduck.api.global.exception; + +public class ForbiddenException extends CustomException { + public ForbiddenException(String message) { + super(403, message); + } +} diff --git a/src/main/java/io/oduck/api/global/exception/NotFoundException.java b/src/main/java/io/oduck/api/global/exception/NotFoundException.java new file mode 100644 index 00000000..9cedf2d9 --- /dev/null +++ b/src/main/java/io/oduck/api/global/exception/NotFoundException.java @@ -0,0 +1,8 @@ +package io.oduck.api.global.exception; + +public class NotFoundException extends CustomException{ + + public NotFoundException(String value) { + super(404, value + " Not Found"); + } +} diff --git a/src/main/java/io/oduck/api/global/exception/UnauthorizedException.java b/src/main/java/io/oduck/api/global/exception/UnauthorizedException.java new file mode 100644 index 00000000..280b7583 --- /dev/null +++ b/src/main/java/io/oduck/api/global/exception/UnauthorizedException.java @@ -0,0 +1,7 @@ +package io.oduck.api.global.exception; + +public class UnauthorizedException extends CustomException{ + public UnauthorizedException(String message) { + super(401, message); + } +} From 9c26b8fbe004a03f076dc7fdff95842e626dce71 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 2 Oct 2023 14:36:41 +0900 Subject: [PATCH 031/734] =?UTF-8?q?feat:=20response=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/common/EntityBased.java | 5 + .../api/global/common/ErrorResponse.java | 105 ++++++++++++++++++ .../io/oduck/api/global/common/PageInfo.java | 13 +++ .../oduck/api/global/common/PageResponse.java | 23 ++++ .../api/global/common/SliceResponse.java | 27 +++++ 5 files changed, 173 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/common/EntityBased.java create mode 100644 src/main/java/io/oduck/api/global/common/ErrorResponse.java create mode 100644 src/main/java/io/oduck/api/global/common/PageInfo.java create mode 100644 src/main/java/io/oduck/api/global/common/PageResponse.java create mode 100644 src/main/java/io/oduck/api/global/common/SliceResponse.java diff --git a/src/main/java/io/oduck/api/global/common/EntityBased.java b/src/main/java/io/oduck/api/global/common/EntityBased.java new file mode 100644 index 00000000..e89f44e2 --- /dev/null +++ b/src/main/java/io/oduck/api/global/common/EntityBased.java @@ -0,0 +1,5 @@ +package io.oduck.api.global.common; + +public interface EntityBased { + Long getId(); +} diff --git a/src/main/java/io/oduck/api/global/common/ErrorResponse.java b/src/main/java/io/oduck/api/global/common/ErrorResponse.java new file mode 100644 index 00000000..94b4face --- /dev/null +++ b/src/main/java/io/oduck/api/global/common/ErrorResponse.java @@ -0,0 +1,105 @@ +package io.oduck.api.global.common; + +import jakarta.validation.ConstraintViolation; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindingResult; + +@Getter +public class ErrorResponse { + private String message; + private List fieldErrors; + private List violationErrors; + + /** + * http 상태 코드와 예외 메시지를 받는 생성자
+ * ErrorResponse("임시 에러") + **/ + private ErrorResponse(String message) { + this.message = message; + } + + /** + * 바디 필드 예외 및 경로 변수 예외를 받는 생성자 + * + *

+   * List fieldErrors = [
+   *    { "field: "name", "rejectedValue : "", "reason" : "이름이 공백이 아니어야 합니다." }
+   *    { "field: "email", "rejectedValue : "", "reason" : "이메일이 공백이 아니어야 합니다." }
+   * ];
+   *
+   * List violationErrors = [
+   *    { "propertyPath" : "article-id", "message" : "1 이상이어야 합니다", "invalidValue" : -1 }
+   * ];
+   * 
+ * + * ErrorResponse(fieldErrors, violationErrors) + **/ + private ErrorResponse(final List fieldErrors, + final List violationErrors) { + this.fieldErrors = fieldErrors; + this.violationErrors = violationErrors; + } + + // 바디 필드 예외에 해당하는 에러 응답 생성 메소드 + public static ErrorResponse of(BindingResult bindingResult) { + return new ErrorResponse(FieldError.of(bindingResult), null); + } + + // 경로 변수 예외에 해당하는 에러 응답 생성 메소드 + public static ErrorResponse of(Set> violations) { + return new ErrorResponse(null, ConstraintViolationError.of(violations)); + } + + // HttpStatus에 해당하는 에러 응답 생성 메소드 + public static ErrorResponse of(HttpStatus httpStatus) { + return new ErrorResponse(httpStatus.getReasonPhrase()); + } + + // HttpStatus에 커스텀 메시지를 넣는 에러 응답 생성 메소드 + public static ErrorResponse of(String message) { + return new ErrorResponse(message); + } + + // 요청 바디 필드 유효성 검증 예외 + @Getter + @AllArgsConstructor + public static class FieldError { + private String field; + private Object rejectedValue; + private String reason; + + public static List of(BindingResult bindingResult) { + final List fieldErrors = bindingResult.getFieldErrors(); + return fieldErrors.stream() + .map(error -> new FieldError( + error.getField(), + error.getRejectedValue() == null ? "" : error.getRejectedValue().toString(), + error.getDefaultMessage())) + .collect(Collectors.toList()); + } + } + + // 경로 변수 유효성 검증 예외 + @Getter + @AllArgsConstructor + public static class ConstraintViolationError { + private String propertyPath; + private Object rejectedValue; + private String reason; + + public static List of( + Set> constraintViolations) { + return constraintViolations.stream() + .map(constraintViolation -> new ConstraintViolationError( + constraintViolation.getPropertyPath().toString(), + constraintViolation.getInvalidValue().toString(), + constraintViolation.getMessage())) + .collect(Collectors.toList()); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/common/PageInfo.java b/src/main/java/io/oduck/api/global/common/PageInfo.java new file mode 100644 index 00000000..09fe3095 --- /dev/null +++ b/src/main/java/io/oduck/api/global/common/PageInfo.java @@ -0,0 +1,13 @@ +package io.oduck.api.global.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class PageInfo { + private int page; + private int size; + private long totalElements; + private int totalPages; +} diff --git a/src/main/java/io/oduck/api/global/common/PageResponse.java b/src/main/java/io/oduck/api/global/common/PageResponse.java new file mode 100644 index 00000000..38f2d126 --- /dev/null +++ b/src/main/java/io/oduck/api/global/common/PageResponse.java @@ -0,0 +1,23 @@ +package io.oduck.api.global.common; + +import java.util.List; +import lombok.Getter; +import org.springframework.data.domain.Page; + + +@Getter +public class PageResponse { + + private final List items; + private final PageInfo pageInfo; + + public PageResponse(Page page) { + this.items = page.getContent(); + this.pageInfo = new PageInfo(page.getNumber() + 1, + page.getSize(), page.getTotalElements(), page.getTotalPages()); + } + + public static PageResponse of(Page page) { + return new PageResponse<>(page); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/common/SliceResponse.java b/src/main/java/io/oduck/api/global/common/SliceResponse.java new file mode 100644 index 00000000..66876ef4 --- /dev/null +++ b/src/main/java/io/oduck/api/global/common/SliceResponse.java @@ -0,0 +1,27 @@ +package io.oduck.api.global.common; + +import java.util.List; +import lombok.Getter; +import org.springframework.data.domain.Slice; + +@Getter +public class SliceResponse { + private final List items; + private final int size; // 한 페이지에 보여줄 아이템의 개수 + private final boolean isLastPage; // 마지막 페이지일 경우, true 반환. + private Long lastId; // 마지막 아이템의 id + public SliceResponse(Slice sliceContent) { + this.items = sliceContent.getContent(); + this.size = sliceContent.getSize(); + this.isLastPage = sliceContent.isLast(); + + if (!items.isEmpty() && !isLastPage) { + T lastItem = items.get(items.size() - 1); + lastId = lastItem.getId(); + } + } + + public static SliceResponse of(Slice sliceContent) { + return new SliceResponse<>(sliceContent); + } +} \ No newline at end of file From 6fd62d229f6a91f17356d6d7b5d19186ee31f62d Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 2 Oct 2023 20:18:46 +0900 Subject: [PATCH 032/734] =?UTF-8?q?refactor:=20enum=20AttractionElement=20?= =?UTF-8?q?ART=20=EC=88=98=EC=A0=95=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클라이언트가 작화를 입덕 포인트로 선택했을 때 ART에서 DRAWING으로 변경하였습니다. --- .../api/domain/attractionPoint/entity/AttractionElement.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionElement.java b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionElement.java index 2e88a5fa..54b86613 100644 --- a/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionElement.java +++ b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionElement.java @@ -1,7 +1,7 @@ package io.oduck.api.domain.attractionPoint.entity; public enum AttractionElement { - ART, + DRAWING, STORY, MUSIC, CHARACTER, From 9097afdc28f1b22513424dc50349961c17711973 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 2 Oct 2023 20:23:01 +0900 Subject: [PATCH 033/734] =?UTF-8?q?cleanup:=20AnimeReq=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 애니 등록은 관리자에서 이루어질 것으로 예상합니다. 따라서 애니 등록 DTO를 Anime 패키지에서 삭제하였습니다. --- .../oduck/api/domain/anime/dto/AnimeReq.java | 22 --------------- .../oduck/api/domain/anime/entity/Anime.java | 28 +++---------------- 2 files changed, 4 insertions(+), 46 deletions(-) delete mode 100644 src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java deleted file mode 100644 index 4cb3feea..00000000 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.oduck.api.domain.anime.dto; - -import io.oduck.api.domain.anime.entity.BroadcastType; -import io.oduck.api.domain.anime.entity.Quarter; -import io.oduck.api.domain.anime.entity.Rating; -import io.oduck.api.domain.anime.entity.Status; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class AnimeReq { - private String title; - private String thumbnail; - private String summary; - private BroadcastType broadcastType; - private int eposideCount; - private int year; - private Quarter quarter; - private Rating rating; - private Status status; -} diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 57c05623..0a2f2906 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -3,15 +3,10 @@ import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; import io.oduck.api.domain.bookmark.entity.Bookmark; import io.oduck.api.domain.review.entity.ShortReview; +import io.oduck.api.domain.series.entity.Series; import io.oduck.api.domain.starRating.entity.StarRating; import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; -import jakarta.persistence.OneToMany; -import java.util.List; -import org.hibernate.annotations.ColumnDefault; - -import io.oduck.api.domain.anime.dto.AnimeReq; -import io.oduck.api.domain.series.entity.Series; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -22,8 +17,11 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; @Entity @Getter @@ -96,22 +94,4 @@ public class Anime extends BaseEntity { @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) private List attractionPoints; - - static public Anime creatAnime(AnimeReq req, Series series) { - Anime anime = new Anime(); - anime.broadcastType = req.getBroadcastType(); - anime.episodeCount = req.getEposideCount(); - anime.quarter = req.getQuarter(); - anime.rating = req.getRating(); - anime.status = req.getStatus(); - anime.summary = req.getSummary(); - anime.thumbnail = req.getThumbnail(); - anime.title = req.getTitle(); - anime.year = req.getYear(); - anime.isReleased = false; - anime.viewCount = 0; - anime.reviewCount = 0; - anime.series = series; - return anime; - } } From f5bdb10086b30f6d6f40e0450997c4d2ce3b3d55 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 2 Oct 2023 20:34:32 +0900 Subject: [PATCH 034/734] =?UTF-8?q?feat:=20Anime=20Stub=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anime/controller/AnimeController.java | 39 ++++++ .../oduck/api/domain/anime/dto/AnimeRes.java | 50 ++++++++ .../domain/anime/service/AnimeService.java | 13 ++ .../anime/service/AnimeServiceStub.java | 112 ++++++++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java create mode 100644 src/main/java/io/oduck/api/domain/anime/service/AnimeService.java create mode 100644 src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java diff --git a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java new file mode 100644 index 00000000..c9f70052 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java @@ -0,0 +1,39 @@ +package io.oduck.api.domain.anime.controller; + +import io.oduck.api.domain.anime.dto.AnimeRes; +import io.oduck.api.domain.anime.service.AnimeService; +import io.oduck.api.global.common.SingleResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +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.RestController; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/animes") +@Slf4j +public class AnimeController { + + private final AnimeService animeService; + + // 애니 아이디로 상세 조회 + @GetMapping("/{animeId}") + public ResponseEntity getAnimeById(@PathVariable Long animeId){ + + // TODO: 애니 조회 로직 구현 + AnimeRes res = animeService.getAnimeById(animeId); + return ResponseEntity + .ok(SingleResponse.of(res)); + } + + // TODO: 애니 검색 결과 페이징 +// @GetMapping +// public ResponseEntity getAnimesBySearchCondition(){ +// return null; +// } +} diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java index 1aaec6df..51002543 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -1,5 +1,55 @@ package io.oduck.api.domain.anime.dto; +import io.oduck.api.domain.anime.entity.BroadcastType; +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Rating; +import io.oduck.api.domain.anime.entity.Status; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder public class AnimeRes { + private Anime anime; + private Score score; + + @Getter + @Builder + public static class Anime { + private Long animeId; + private String title; + private String thumbnail; + private BroadCast broadCast; + private String summary; + private int episodeCount; + private Rating rating; + private Status status; + + private List genres; + private List originalAuthors; + private List voiceActors; + private List studios; + private long reviewCount; + private long bookmarkCount; + } + + @Getter + @Builder + public static class BroadCast { + private BroadcastType broadcastType; + private int year; + private Quarter quarter; + } + @Getter + @Builder + public static class Score { + private Double starRatingScoreAverage; + private int selectedDrawingCount; + private int selectedStoryCount; + private int selectedMusicCount; + private int selectedCharacterCount; + private int selectedVoiceActorCount; + } } diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java new file mode 100644 index 00000000..7b0f776d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java @@ -0,0 +1,13 @@ +package io.oduck.api.domain.anime.service; + +import io.oduck.api.domain.anime.dto.AnimeRes; + +public interface AnimeService { + + /** + * 애니 상세 조회 로직 + * @param animeId + * @return AnimeRes + */ + AnimeRes getAnimeById(Long animeId); +} diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java new file mode 100644 index 00000000..b44acd9e --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java @@ -0,0 +1,112 @@ +package io.oduck.api.domain.anime.service; + +import io.oduck.api.domain.anime.dto.AnimeRes; +import io.oduck.api.domain.anime.dto.AnimeRes.Anime; +import io.oduck.api.domain.anime.dto.AnimeRes.BroadCast; +import io.oduck.api.domain.anime.dto.AnimeRes.Score; +import io.oduck.api.domain.anime.entity.BroadcastType; +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Rating; +import io.oduck.api.domain.anime.entity.Status; +import java.util.ArrayList; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class AnimeServiceStub implements AnimeService{ + + @Override + public AnimeRes getAnimeById(Long animeId) { + Anime anime = createAnime(animeId); + Score score = createScore(); + + return AnimeRes.builder() + .anime(anime) + .score(score) + .build(); + } + + private Score createScore() { + return Score.builder() + .starRatingScoreAverage(9.2) + .selectedDrawingCount(174) + .selectedStoryCount(52) + .selectedMusicCount(0) + .selectedCharacterCount(31) + .selectedVoiceActorCount(77) + .build(); + } + + private Anime createAnime(Long animeId) { + return Anime.builder() + .animeId(animeId) + .title("귀멸의 칼날: 도공 마을편") + .thumbnail("https://image파일경로/uuid.jpg") + .broadCast(getBroadcast()) + .summary( + "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다. 탄지로가 검이 수리되기를 기다리는 동안, 상현 혈귀 한텐구와 쿗코가 숨겨진 마을인 ‘도공 마을'을 습격한다. 공격할 때마다 분열해서 위력이 커지는 한텐구로 인해 탄지로와 겐야는 고전을 면치 못한다. 한편, 타인에 대한 관심이 희박한 하주 토키토 무이치로는 혈귀들에게 공격당하고 있는 코테츠를 목격하는데….") + .episodeCount(11) + .rating(Rating.ADULT) + .status(Status.FINISHED) + .genres(getGenres()) + .originalAuthors(getOriginalAuthors()) + .voiceActors(getVoiceActors()) + .studios(getStudios()) + .reviewCount(172) + .bookmarkCount(72) + .build(); + } + + private BroadCast getBroadcast() { + return BroadCast.builder() + .broadcastType(BroadcastType.TVA) + .year(2023) + .quarter(Quarter.Q2) + .build(); + + } + + private List getStudios() { + List studios = new ArrayList<>(); + studios.add("ufotable"); + return studios; + } + + private List getVoiceActors() { + List voiceActors = new ArrayList<>(); + voiceActors.add("하나에 나츠키"); + voiceActors.add("키토 아카리"); + voiceActors.add("카와니시 켄고"); + voiceActors.add("하나자와 카나"); + voiceActors.add("오카모토 노부히코"); + voiceActors.add("나미카와 다이스케"); + voiceActors.add("후루카와 토시오"); + voiceActors.add("토리우미 코스케"); + voiceActors.add("타케우치 슌스케"); + voiceActors.add("우메하라 유이치로"); + voiceActors.add("사이토 소마"); + voiceActors.add("이시카와 카이토"); + voiceActors.add("야마데라 코이치"); + voiceActors.add("무라세 아유무"); + voiceActors.add("타케모토 에이지"); + voiceActors.add("야라 유사쿠"); + voiceActors.add("시모노 히로"); + voiceActors.add("마츠오카 요시츠구"); + voiceActors.add("우에다 레이나"); + voiceActors.add("에하라 유리"); + return voiceActors; + } + + private List getGenres(){ + List genres = new ArrayList<>(); + genres.add("판타지"); + genres.add("액션"); + return genres; + } + + private List getOriginalAuthors() { + List originalAuthors = new ArrayList<>(); + originalAuthors.add("고토게 코요하루"); + return originalAuthors; + } +} From 88c2670bd988b5b446b095957909da0f2a2f26e7 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 2 Oct 2023 20:37:11 +0900 Subject: [PATCH 035/734] =?UTF-8?q?test:=20Anime=20Controller,=20Anime=20S?= =?UTF-8?q?ervice=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AC=B8=EC=84=9C=ED=99=94=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/anime/AnimeControllerTest.java | 169 ++++++++++++++++++ .../unit/anime/service/AnimeServiceTest.java | 39 ++++ 2 files changed, 208 insertions(+) create mode 100644 src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java create mode 100644 src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java new file mode 100644 index 00000000..1faf7203 --- /dev/null +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -0,0 +1,169 @@ +package io.oduck.api.e2e.anime; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +@AutoConfigureRestDocs +@AutoConfigureMockMvc +@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) +@SpringBootTest +public class AnimeControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Nested + @DisplayName("애니 조회") + class GetAnimes{ + + @DisplayName("조회 성공 시 Http Status 200 반환") + @Test + void getAnimes() throws Exception { + //given + Long animeId = 1L; + + //when + ResultActions actions = mockMvc.perform( + RestDocumentationRequestBuilders.get("/animes"+"/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + //then + actions + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(jsonPath("$.item.anime.title").exists()) + .andExpect(jsonPath("$.item.anime.thumbnail").exists()) + .andExpect(jsonPath("$.item.anime.broadCast.broadcastType").exists()) + .andExpect(jsonPath("$.item.anime.broadCast.year").exists()) + .andExpect(jsonPath("$.item.anime.broadCast.quarter").exists()) + .andExpect(jsonPath("$.item.anime.summary").exists()) + .andExpect(jsonPath("$.item.anime.episodeCount").exists()) + .andExpect(jsonPath("$.item.anime.rating").exists()) + .andExpect(jsonPath("$.item.anime.status").exists()) + .andExpect(jsonPath("$.item.anime.genres").exists()) + .andExpect(jsonPath("$.item.anime.originalAuthors").exists()) + .andExpect(jsonPath("$.item.anime.voiceActors").exists()) + .andExpect(jsonPath("$.item.anime.studios").exists()) + .andExpect(jsonPath("$.item.anime.reviewCount").exists()) + .andExpect(jsonPath("$.item.anime.bookmarkCount").exists()) + .andExpect(jsonPath("$.item.score.starRatingScoreAverage").exists()) + .andExpect(jsonPath("$.item.score.selectedDrawingCount").exists()) + .andExpect(jsonPath("$.item.score.selectedStoryCount").exists()) + .andExpect(jsonPath("$.item.score.selectedMusicCount").exists()) + .andExpect(jsonPath("$.item.score.selectedCharacterCount").exists()) + .andExpect(jsonPath("$.item.score.selectedVoiceActorCount").exists()) + .andDo(document("getAnimeById/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId") + .description("애니 아이디") + ), + responseFields( + fieldWithPath("item") + .type(JsonFieldType.OBJECT) + .description("조회 데이터"), + fieldWithPath("item.anime") + .type(JsonFieldType.OBJECT) + .description("애니 정보"), + fieldWithPath("item.anime.title") + .type(JsonFieldType.STRING) + .description("애니 제목"), + fieldWithPath("item.anime.thumbnail") + .type(JsonFieldType.STRING) + .description("애니 이미지, 경로로 저장됨"), + fieldWithPath("item.anime.broadCast") + .type(JsonFieldType.OBJECT) + .description("애니 방송 관련 정보"), + fieldWithPath("item.anime.broadCast.broadcastType") + .type(JsonFieldType.STRING) + .description("작품의 출시 방식. TVA, OVA, ONA, MOV가 있음"), + fieldWithPath("item.anime.broadCast.year") + .type(JsonFieldType.NUMBER) + .description("작품의 출시 년도"), + fieldWithPath("item.anime.broadCast.quarter") + .type(JsonFieldType.STRING) + .description("작품의 출시 분기"), + fieldWithPath("item.anime.summary") + .type(JsonFieldType.STRING) + .description("애니를 소개할 때 줄거리"), + fieldWithPath("item.anime.episodeCount") + .type(JsonFieldType.NUMBER) + .description("에피소드의 숫자. 16화까지 나왔으면 16으로 표기됨."), + fieldWithPath("item.anime.rating") + .type(JsonFieldType.STRING) + .description("작품의 심의 등급. ADULT, FIFTEEN, TWELVE, ALL가 있다."), + fieldWithPath("item.anime.status") + .type(JsonFieldType.STRING) + .description("방영 상태. FINISHED, ONGOING, UPCOMING, UNKNOWN이 있다."), + fieldWithPath("item.anime.genres") + .type(JsonFieldType.ARRAY) + .description("애니의 장르."), + fieldWithPath("item.anime.originalAuthors") + .type(JsonFieldType.ARRAY) + .description("애니의 원작 작가."), + fieldWithPath("item.anime.voiceActors") + .type(JsonFieldType.ARRAY) + .description("애니의 출연 성우."), + fieldWithPath("item.anime.studios") + .type(JsonFieldType.ARRAY) + .description("애니의 제작사."), + fieldWithPath("item.anime.reviewCount") + .type(JsonFieldType.NUMBER) + .description("애니의 리뷰 개수. 리뷰 개수는 짧은 리뷰와 장문 리뷰를 합친 값"), + fieldWithPath("item.anime.bookmarkCount") + .type(JsonFieldType.NUMBER) + .description("애니의 덕후 수. 덕후는 북마크의 개념."), + fieldWithPath("item.score") + .type(JsonFieldType.OBJECT) + .description("애니 별점 및 입덕 포인트 정보"), + fieldWithPath("item.score.starRatingScoreAverage") + .type(JsonFieldType.NUMBER) + .description("애니 별점 평균"), + fieldWithPath("item.score.selectedDrawingCount") + .type(JsonFieldType.NUMBER) + .description("입덕 포인트 중 작화를 선택한 수"), + fieldWithPath("item.score.selectedStoryCount") + .type(JsonFieldType.NUMBER) + .description("입덕 포인트 중 스토리를 선택한 수"), + fieldWithPath("item.score.selectedMusicCount") + .type(JsonFieldType.NUMBER) + .description("입덕 포인트 중 음악을 선택한 수"), + fieldWithPath("item.score.selectedCharacterCount") + .type(JsonFieldType.NUMBER) + .description("입덕 포인트 중 캐릭터를 선택한 수"), + fieldWithPath("item.score.selectedVoiceActorCount") + .type(JsonFieldType.NUMBER) + .description("입덕 포인트 중 성우를 선택한 수") + ) + )); + + //TODO : 조회 실패 시 + } + } +} diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java new file mode 100644 index 00000000..8d8b1461 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -0,0 +1,39 @@ +package io.oduck.api.unit.anime.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; + +import io.oduck.api.domain.anime.dto.AnimeRes; +import io.oduck.api.domain.anime.service.AnimeServiceStub; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class AnimeServiceTest { + + @InjectMocks + private AnimeServiceStub animeService; + + @Nested + @DisplayName("애니 조회") + class GetAnime{ + + @Test + @DisplayName("애니 상세 조회") + void getAnimeById() { + //given + Long animeId = 1L; + + //when + AnimeRes response = animeService.getAnimeById(animeId); + + //then + assertThat(response.getAnime().getAnimeId()).isEqualTo(animeId); + assertThatNoException(); + } + } +} From c9266718307fdf424fe547f7fae222c2724bbc64 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Tue, 3 Oct 2023 16:21:55 +0900 Subject: [PATCH 036/734] =?UTF-8?q?test:=20restDocs=EC=9D=98=20responseFie?= =?UTF-8?q?ld=EC=97=90=20animeId=20=EC=B6=94=EA=B0=80=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/e2e/anime/AnimeControllerTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index 1faf7203..47fb66e4 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -82,7 +82,7 @@ void getAnimes() throws Exception { preprocessResponse(prettyPrint()), pathParameters( parameterWithName("animeId") - .description("애니 아이디") + .description("애니의 고유 식별자") ), responseFields( fieldWithPath("item") @@ -91,6 +91,9 @@ void getAnimes() throws Exception { fieldWithPath("item.anime") .type(JsonFieldType.OBJECT) .description("애니 정보"), + fieldWithPath("item.anime.animeId") + .type(JsonFieldType.NUMBER) + .description("애니의 고유 식별자"), fieldWithPath("item.anime.title") .type(JsonFieldType.STRING) .description("애니 제목"), From b5f2d8328cb7344a55b322bf4b2d62808844e99e Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Tue, 3 Oct 2023 16:23:39 +0900 Subject: [PATCH 037/734] =?UTF-8?q?refactor:=20AnimeRes=EC=9D=98=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=20=ED=81=B4=EB=9E=98=EC=8A=A4=20BroadCast=20?= =?UTF-8?q?->=20Broadcast=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/anime/dto/AnimeRes.java | 4 ++-- .../api/domain/anime/service/AnimeServiceStub.java | 8 ++++---- .../oduck/api/e2e/anime/AnimeControllerTest.java | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java index 51002543..03a95c68 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -20,7 +20,7 @@ public static class Anime { private Long animeId; private String title; private String thumbnail; - private BroadCast broadCast; + private Broadcast broadcast; private String summary; private int episodeCount; private Rating rating; @@ -36,7 +36,7 @@ public static class Anime { @Getter @Builder - public static class BroadCast { + public static class Broadcast { private BroadcastType broadcastType; private int year; private Quarter quarter; diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java index b44acd9e..d3482010 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java @@ -2,7 +2,7 @@ import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.dto.AnimeRes.Anime; -import io.oduck.api.domain.anime.dto.AnimeRes.BroadCast; +import io.oduck.api.domain.anime.dto.AnimeRes.Broadcast; import io.oduck.api.domain.anime.dto.AnimeRes.Score; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; @@ -42,7 +42,7 @@ private Anime createAnime(Long animeId) { .animeId(animeId) .title("귀멸의 칼날: 도공 마을편") .thumbnail("https://image파일경로/uuid.jpg") - .broadCast(getBroadcast()) + .broadcast(getBroadcast()) .summary( "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다. 탄지로가 검이 수리되기를 기다리는 동안, 상현 혈귀 한텐구와 쿗코가 숨겨진 마을인 ‘도공 마을'을 습격한다. 공격할 때마다 분열해서 위력이 커지는 한텐구로 인해 탄지로와 겐야는 고전을 면치 못한다. 한편, 타인에 대한 관심이 희박한 하주 토키토 무이치로는 혈귀들에게 공격당하고 있는 코테츠를 목격하는데….") .episodeCount(11) @@ -57,8 +57,8 @@ private Anime createAnime(Long animeId) { .build(); } - private BroadCast getBroadcast() { - return BroadCast.builder() + private Broadcast getBroadcast() { + return Broadcast.builder() .broadcastType(BroadcastType.TVA) .year(2023) .quarter(Quarter.Q2) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index 47fb66e4..d38792d4 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -58,9 +58,9 @@ void getAnimes() throws Exception { .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(jsonPath("$.item.anime.title").exists()) .andExpect(jsonPath("$.item.anime.thumbnail").exists()) - .andExpect(jsonPath("$.item.anime.broadCast.broadcastType").exists()) - .andExpect(jsonPath("$.item.anime.broadCast.year").exists()) - .andExpect(jsonPath("$.item.anime.broadCast.quarter").exists()) + .andExpect(jsonPath("$.item.anime.broadcast.broadcastType").exists()) + .andExpect(jsonPath("$.item.anime.broadcast.year").exists()) + .andExpect(jsonPath("$.item.anime.broadcast.quarter").exists()) .andExpect(jsonPath("$.item.anime.summary").exists()) .andExpect(jsonPath("$.item.anime.episodeCount").exists()) .andExpect(jsonPath("$.item.anime.rating").exists()) @@ -100,16 +100,16 @@ void getAnimes() throws Exception { fieldWithPath("item.anime.thumbnail") .type(JsonFieldType.STRING) .description("애니 이미지, 경로로 저장됨"), - fieldWithPath("item.anime.broadCast") + fieldWithPath("item.anime.broadcast") .type(JsonFieldType.OBJECT) .description("애니 방송 관련 정보"), - fieldWithPath("item.anime.broadCast.broadcastType") + fieldWithPath("item.anime.broadcast.broadcastType") .type(JsonFieldType.STRING) .description("작품의 출시 방식. TVA, OVA, ONA, MOV가 있음"), - fieldWithPath("item.anime.broadCast.year") + fieldWithPath("item.anime.broadcast.year") .type(JsonFieldType.NUMBER) .description("작품의 출시 년도"), - fieldWithPath("item.anime.broadCast.quarter") + fieldWithPath("item.anime.broadcast.quarter") .type(JsonFieldType.STRING) .description("작품의 출시 분기"), fieldWithPath("item.anime.summary") From d8ac331017f829760797fc27fa111e87da271759 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 15:40:26 +0900 Subject: [PATCH 038/734] =?UTF-8?q?refactor:=20member=20entity=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/auth}/entity/AuthLocal.java | 3 ++- .../security/auth}/entity/AuthSocial.java | 19 +++++++++++++++++-- .../security/auth}/entity/SocialType.java | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) rename src/main/java/io/oduck/api/{domain/member => global/security/auth}/entity/AuthLocal.java (89%) rename src/main/java/io/oduck/api/{domain/member => global/security/auth}/entity/AuthSocial.java (65%) rename src/main/java/io/oduck/api/{domain/member => global/security/auth}/entity/SocialType.java (50%) diff --git a/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java b/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java similarity index 89% rename from src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java rename to src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java index cec23c2d..2222bbef 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/AuthLocal.java +++ b/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java @@ -1,5 +1,6 @@ -package io.oduck.api.domain.member.entity; +package io.oduck.api.global.security.auth.entity; +import io.oduck.api.domain.member.entity.Member; import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java b/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java similarity index 65% rename from src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java rename to src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java index a7653470..14f2b088 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/AuthSocial.java +++ b/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java @@ -1,5 +1,6 @@ -package io.oduck.api.domain.member.entity; +package io.oduck.api.global.security.auth.entity; +import io.oduck.api.domain.member.entity.Member; import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -11,6 +12,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -29,9 +31,22 @@ public class AuthSocial extends BaseEntity { @Column(nullable = false, length = 100) private String email; - @Column(nullable = false, length = 50) + @Column(nullable = false, length = 50, unique = true) private String socialId; @Enumerated(EnumType.STRING) private SocialType socialType; + + @Builder + public AuthSocial(Long id, Member member, String email, String socialId, SocialType socialType) { + this.id = id; + this.member = member; + this.email = email; + this.socialId = socialId; + this.socialType = socialType; + } + + public void setMember(Member member) { + this.member = member; + } } diff --git a/src/main/java/io/oduck/api/domain/member/entity/SocialType.java b/src/main/java/io/oduck/api/global/security/auth/entity/SocialType.java similarity index 50% rename from src/main/java/io/oduck/api/domain/member/entity/SocialType.java rename to src/main/java/io/oduck/api/global/security/auth/entity/SocialType.java index 4222dcfd..1dac0db4 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/SocialType.java +++ b/src/main/java/io/oduck/api/global/security/auth/entity/SocialType.java @@ -1,4 +1,4 @@ -package io.oduck.api.domain.member.entity; +package io.oduck.api.global.security.auth.entity; public enum SocialType { GOOGLE, NAVER, KAKAO From 074c227afe6a45e1acbbd99876b17cdfefa0c4ef Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 15:41:07 +0900 Subject: [PATCH 039/734] =?UTF-8?q?refactor:=20member=20entity=20=EB=B9=8C?= =?UTF-8?q?=EB=8D=94=20=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/entity/LoginType.java | 2 +- .../api/domain/member/entity/Member.java | 13 +++++++ .../domain/member/entity/MemberProfile.java | 37 ++++++++++++++++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/entity/LoginType.java b/src/main/java/io/oduck/api/domain/member/entity/LoginType.java index 63f4012b..5a6a7f59 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/LoginType.java +++ b/src/main/java/io/oduck/api/domain/member/entity/LoginType.java @@ -1,5 +1,5 @@ package io.oduck.api.domain.member.entity; public enum LoginType { - LOCAL, GOOGLE, KAKAO, NAVER + LOCAL, SOCIAL } diff --git a/src/main/java/io/oduck/api/domain/member/entity/Member.java b/src/main/java/io/oduck/api/domain/member/entity/Member.java index bc24421a..7ddda6d3 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/Member.java +++ b/src/main/java/io/oduck/api/domain/member/entity/Member.java @@ -6,6 +6,8 @@ import io.oduck.api.domain.reviewLike.entity.ShortReviewLike; import io.oduck.api.domain.starRating.entity.StarRating; import io.oduck.api.global.audit.BaseEntity; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import io.oduck.api.global.security.auth.entity.AuthSocial; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import java.util.List; @@ -17,6 +19,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -54,4 +57,14 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST) private List attractionPoints; + + @Builder + public Member(Long id, LoginType loginType, AuthSocial authSocial, MemberProfile memberProfile) { + this.id = id; + this.loginType = loginType; + this.authSocial = authSocial; + authSocial.setMember(this); + this.memberProfile = memberProfile; + memberProfile.setMember(this); + } } diff --git a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java index 9fd20c01..0e9df9ed 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java +++ b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java @@ -11,13 +11,17 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; @Entity -@Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Getter public class MemberProfile extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -31,18 +35,39 @@ public class MemberProfile extends BaseEntity { private String name; @Column(nullable = false, length = 50) - private String info; + @Builder.Default + private String info = ""; @Enumerated(EnumType.STRING) - private Role role; + @Builder.Default + private Role role = Role.MEMBER; @Column(length = 100) - private String thumbnail; + @Builder.Default + private String thumbnail = ""; @Column(length = 100) - private String backgroundImage; + @Builder.Default + private String backgroundImage = ""; @Column(nullable = false) @ColumnDefault("0") - private Long point; + @Builder.Default + private Long point = 0L; + + @Builder + public MemberProfile(Long id, String name, String info, Role role, String thumbnail, + String backgroundImage, Long point) { + this.id = id; + this.name = name; + this.info = info; + this.role = role; + this.thumbnail = thumbnail; + this.backgroundImage = backgroundImage; + this.point = point; + } + + public void setMember(Member member) { + this.member = member; + } } From 80c133350c1be0ce13cdfe0f87bb4a624e1be359 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 15:43:21 +0900 Subject: [PATCH 040/734] =?UTF-8?q?chore:=20.gitignore=20application.yml?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src/main/resources/application.yml | 31 ------------------------------ 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 src/main/resources/application.yml diff --git a/.gitignore b/.gitignore index 9c416a72..33db90d2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ build/ *.log *.gz src/main/resources/static/docs/index.html +application-*.yml ### STS ### .apt_generated diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index ebc36069..00000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,31 +0,0 @@ -server: - servlet: - contextPath: /api/v1 - -spring: - h2: - console: - enabled: true - path: /h2 - jpa: - hibernate: - ddl-auto: create - show-sql: true - datasource: - driver-class-name: org.mariadb.jdbc.Driver - url: jdbc:mariadb://localhost:3306/testDB?useSSL=false&serverTimezone=UTC - username: ${DB_USER} - password: ${DB_PASSWORD} - -logging: - level: - root: INFO - pattern: - file: '%-5level %d{yy-MM-dd HH:mm:SSS}[%thread] %logger[%thod:%line] - %msg%n' - file: - name: ./logs/oDuck.log - logback: - rollingpolicy: - max-file-size: 100KB #default 10M - max-history: 30 #default 7 - file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz From 8045e7342644ff039e3e5316a94d80b3a69984ff Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 15:43:52 +0900 Subject: [PATCH 041/734] =?UTF-8?q?chore:=20build.gradle=20=EC=8B=9C?= =?UTF-8?q?=ED=81=90=EB=A6=AC=ED=8B=B0=EB=93=B1=20=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 49fb34bd..0d31cfaa 100644 --- a/build.gradle +++ b/build.gradle @@ -30,28 +30,35 @@ ext { dependencies { // 서버 상태 체크 implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - // implementation 'org.springframework.boot:spring-boot-starter-data-redis' - // implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - // implementation 'org.springframework.boot:spring-boot-starter-security' + + // security & oauth2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-security' + + // web implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'com.google.code.gson:gson' - compileOnly 'org.projectlombok:lombok' - - developmentOnly 'org.springframework.boot:spring-boot-devtools' + // session + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.session:spring-session-data-redis' + // database + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' - + + // dev tools + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.projectlombok:lombok' - + + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' - // testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' testImplementation 'com.h2database:h2' - + implementation 'com.google.code.gson:gson' asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' } From 6ed58cafb1e26b77775448e8c9bb2583a0bb1f7c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 15:48:41 +0900 Subject: [PATCH 042/734] =?UTF-8?q?feat:=20auth=20=EA=B4=80=EB=A0=A8=20dto?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/auth/dto/LoginUser.java | 11 +++ .../security/auth/dto/OAuthAttributes.java | 82 +++++++++++++++++++ .../global/security/auth/dto/SessionUser.java | 16 ++++ 3 files changed, 109 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/auth/dto/LoginUser.java create mode 100644 src/main/java/io/oduck/api/global/security/auth/dto/OAuthAttributes.java create mode 100644 src/main/java/io/oduck/api/global/security/auth/dto/SessionUser.java diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/LoginUser.java b/src/main/java/io/oduck/api/global/security/auth/dto/LoginUser.java new file mode 100644 index 00000000..f0b3b309 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/dto/LoginUser.java @@ -0,0 +1,11 @@ +package io.oduck.api.global.security.auth.dto; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface LoginUser { +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/OAuthAttributes.java b/src/main/java/io/oduck/api/global/security/auth/dto/OAuthAttributes.java new file mode 100644 index 00000000..e3039821 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/dto/OAuthAttributes.java @@ -0,0 +1,82 @@ +package io.oduck.api.global.security.auth.dto; + +import io.oduck.api.global.security.auth.entity.AuthSocial; +import io.oduck.api.global.security.auth.entity.SocialType; +import io.oduck.api.global.security.userInfo.GoogleUserInfo; +import io.oduck.api.global.security.userInfo.KakaoUserInfo; +import io.oduck.api.global.security.userInfo.NaverUserInfo; +import io.oduck.api.global.security.userInfo.UserInfo; +import java.util.Map; +import lombok.Builder; +import lombok.Getter; + +/** + * 각 소셜에서 받아오는 데이터가 다르므로 + * 소셜별로 데이터를 받는 데이터를 분기 처리하는 DTO 클래스 + */ +@Getter +public class OAuthAttributes { + + private String nameAttributeKey; // OAuth2 로그인 진행 시 키가 되는 필드 값, PK와 같은 의미 + private UserInfo userInfo; // 소셜 타입별 로그인 유저 정보(닉네임, 이메일, 프로필 사진 등등) + + @Builder + public OAuthAttributes(String nameAttributeKey, UserInfo userInfo) { + this.nameAttributeKey = nameAttributeKey; + this.userInfo = userInfo; + } + + /** + * SocialType에 맞는 메소드 호출하여 OAuthAttributes 객체 반환 + * 파라미터 : userNameAttributeName -> OAuth2 로그인 시 키(PK)가 되는 값 / attributes : OAuth 서비스의 유저 정보들 + * 소셜별 of 메소드(ofGoogle, ofKaKao, ofNaver)들은 각각 소셜 로그인 API에서 제공하는 + * 회원의 식별값(id), attributes, nameAttributeKey를 저장 후 build + */ + public static OAuthAttributes of(SocialType socialType, + String userNameAttributeName, Map attributes) { + + if (socialType == SocialType.NAVER) { + return ofNaver(userNameAttributeName, attributes); + } + if (socialType == SocialType.KAKAO) { + return ofKakao(userNameAttributeName, attributes); + } + return ofGoogle(userNameAttributeName, attributes); + } + + private static OAuthAttributes ofKakao(String userNameAttributeName, Map attributes) { + return OAuthAttributes.builder() + .nameAttributeKey(userNameAttributeName) + .userInfo(new KakaoUserInfo(attributes)) + .build(); + } + + public static OAuthAttributes ofGoogle(String userNameAttributeName, Map attributes) { + return OAuthAttributes.builder() + .nameAttributeKey(userNameAttributeName) + .userInfo(new GoogleUserInfo(attributes)) + .build(); + } + + public static OAuthAttributes ofNaver(String userNameAttributeName, Map attributes) { + return OAuthAttributes.builder() + .nameAttributeKey(userNameAttributeName) + .userInfo(new NaverUserInfo(attributes)) + .build(); + } + + + /** + * of메소드로 OAuthAttributes 객체가 생성되어, 유저 정보들이 담긴 OAuth2UserInfo가 소셜 타입별로 주입된 상태 + * OAuth2UserInfo에서 socialId(식별값), nickname, imageUrl을 가져와서 build + * email에는 UUID로 중복 없는 랜덤 값 생성 + * role은 GUEST로 설정 + */ + public AuthSocial toEntity(SocialType socialType, UserInfo userInfo) { + return AuthSocial.builder() + .socialType(socialType) + .socialId(userInfo.getId()) + .email(userInfo.getEmail()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/SessionUser.java b/src/main/java/io/oduck/api/global/security/auth/dto/SessionUser.java new file mode 100644 index 00000000..60eede17 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/dto/SessionUser.java @@ -0,0 +1,16 @@ +package io.oduck.api.global.security.auth.dto; + +import io.oduck.api.domain.member.entity.LoginType; +import java.io.Serializable; +import lombok.Getter; + +@Getter +public class SessionUser implements Serializable { + private Long id; + private LoginType loginType; + + public SessionUser(Long id, LoginType loginType) { + this.id = id; + this.loginType = loginType; + } +} \ No newline at end of file From 2341289a3b757ae4c628561dc64c1e593db26991 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 15:57:19 +0900 Subject: [PATCH 043/734] =?UTF-8?q?feat:=20auth=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/handler/ForbiddenHandler.java | 25 ++++++++++++++ .../security/handler/LoginFailureHandler.java | 27 +++++++++++++++ .../security/handler/LoginSuccessHandler.java | 33 +++++++++++++++++++ .../security/handler/UnauthorizedHandler.java | 25 ++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/handler/ForbiddenHandler.java create mode 100644 src/main/java/io/oduck/api/global/security/handler/LoginFailureHandler.java create mode 100644 src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java create mode 100644 src/main/java/io/oduck/api/global/security/handler/UnauthorizedHandler.java diff --git a/src/main/java/io/oduck/api/global/security/handler/ForbiddenHandler.java b/src/main/java/io/oduck/api/global/security/handler/ForbiddenHandler.java new file mode 100644 index 00000000..e6a62463 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/handler/ForbiddenHandler.java @@ -0,0 +1,25 @@ +package io.oduck.api.global.security.handler; + +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.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ForbiddenHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException, ServletException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(403); + response + .getWriter() + .write("{\"message\":\"Forbidden\"}"); + } +} diff --git a/src/main/java/io/oduck/api/global/security/handler/LoginFailureHandler.java b/src/main/java/io/oduck/api/global/security/handler/LoginFailureHandler.java new file mode 100644 index 00000000..347517ef --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/handler/LoginFailureHandler.java @@ -0,0 +1,27 @@ +package io.oduck.api.global.security.handler; + +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.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class LoginFailureHandler implements AuthenticationFailureHandler { + @Override + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception + ) throws IOException, ServletException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(401); + response + .getWriter() + .write("{\"message\":\"Unauthorized\"}"); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java b/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java new file mode 100644 index 00000000..07becd01 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java @@ -0,0 +1,33 @@ +package io.oduck.api.global.security.handler; + +import jakarta.servlet.ServletException; +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.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class LoginSuccessHandler implements AuthenticationSuccessHandler { + + @Value("${config.base.url}") + private String BASE_URL; + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { +// SecurityContextHolder.getContext().setAuthentication(authentication); + +// request.getSession().setMaxInactiveInterval(1800); + + log.info("OAuth2 Login 성공!"); + + response.sendRedirect(BASE_URL); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/security/handler/UnauthorizedHandler.java b/src/main/java/io/oduck/api/global/security/handler/UnauthorizedHandler.java new file mode 100644 index 00000000..a38c05c5 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/handler/UnauthorizedHandler.java @@ -0,0 +1,25 @@ +package io.oduck.api.global.security.handler; + +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.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class UnauthorizedHandler implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(401); + response + .getWriter() + .write("{\"message\":\"Unauthorized\"}"); + } +} From f1be19c4c66e09199e6c4db58a5c04f3dadcd17e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:03:16 +0900 Subject: [PATCH 044/734] =?UTF-8?q?feat:=20SocialUserInfo=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/userInfo/GoogleUserInfo.java | 32 +++++++++++ .../security/userInfo/KakaoUserInfo.java | 52 ++++++++++++++++++ .../security/userInfo/NaverUserInfo.java | 54 +++++++++++++++++++ .../security/userInfo/SocialUserInfo.java | 15 ++++++ 4 files changed, 153 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/userInfo/GoogleUserInfo.java create mode 100644 src/main/java/io/oduck/api/global/security/userInfo/KakaoUserInfo.java create mode 100644 src/main/java/io/oduck/api/global/security/userInfo/NaverUserInfo.java create mode 100644 src/main/java/io/oduck/api/global/security/userInfo/SocialUserInfo.java diff --git a/src/main/java/io/oduck/api/global/security/userInfo/GoogleUserInfo.java b/src/main/java/io/oduck/api/global/security/userInfo/GoogleUserInfo.java new file mode 100644 index 00000000..66c594cb --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/userInfo/GoogleUserInfo.java @@ -0,0 +1,32 @@ +package io.oduck.api.global.security.userInfo; + +import java.util.Map; + +public class GoogleUserInfo extends SocialUserInfo { + public GoogleUserInfo(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 getNickname() { +// return (String) attributes.get("name"); +// } + +// // 구글 프로필 이미지. +// @Override +// public String getImageUrl() { +// return (String) attributes.get("picture"); +// } +} diff --git a/src/main/java/io/oduck/api/global/security/userInfo/KakaoUserInfo.java b/src/main/java/io/oduck/api/global/security/userInfo/KakaoUserInfo.java new file mode 100644 index 00000000..c20bb17d --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/userInfo/KakaoUserInfo.java @@ -0,0 +1,52 @@ +package io.oduck.api.global.security.userInfo; + +import java.util.Map; + +public class KakaoUserInfo extends SocialUserInfo { + public KakaoUserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return String.valueOf(attributes.get("id")); + } + + // 카카오 닉네임 + @Override + public String getEmail() { + Map account = (Map) attributes.get("kakao_account"); + + if (account == null) { + return null; + } + + return (String) account.get("email"); + } + +// // 카카오 닉네임 +// @Override +// public String getNickname() { +// Map account = (Map) attributes.get("kakao_account"); +// Map profile = (Map) account.get("profile"); +// +// if (account == null || profile == null) { +// return null; +// } +// +// return (String) profile.get("nickname"); +// } + +// // 카카오 프로필 이미지. +// @Override +// public String getImageUrl() { +// Map account = (Map) attributes.get("kakao_account"); +// Map profile = (Map) account.get("profile"); +// +// if (account == null || profile == null) { +// return null; +// } +// +// return (String) profile.get("thumbnail_image_url"); +// } +} diff --git a/src/main/java/io/oduck/api/global/security/userInfo/NaverUserInfo.java b/src/main/java/io/oduck/api/global/security/userInfo/NaverUserInfo.java new file mode 100644 index 00000000..425c135a --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/userInfo/NaverUserInfo.java @@ -0,0 +1,54 @@ +package io.oduck.api.global.security.userInfo; + +import java.util.Map; + +public class NaverUserInfo extends SocialUserInfo { + public NaverUserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + Map response = (Map) attributes.get("response"); + + if (response == null) { + return null; + } + return (String) response.get("id"); + } + + @Override + public String getEmail() { + Map response = (Map) attributes.get("response"); + + if (response == null) { + return null; + } + + return (String) response.get("email"); + } + +// // 네이버 닉네임 +// @Override +// public String getNickname() { +// Map response = (Map) attributes.get("response"); +// +// if (response == null) { +// return null; +// } +// +// return (String) response.get("nickname"); +// } + +// // 네이버 프로필 이미지. +// @Override +// public String getImageUrl() { +// Map response = (Map) attributes.get("response"); +// +// if (response == null) { +// return null; +// } +// +// return (String) response.get("profile_image"); +// } +} diff --git a/src/main/java/io/oduck/api/global/security/userInfo/SocialUserInfo.java b/src/main/java/io/oduck/api/global/security/userInfo/SocialUserInfo.java new file mode 100644 index 00000000..7d98be92 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/userInfo/SocialUserInfo.java @@ -0,0 +1,15 @@ +package io.oduck.api.global.security.userInfo; + +import java.util.Map; + +public abstract class SocialUserInfo { + protected Map attributes; + + public SocialUserInfo(Map attributes) { + this.attributes = attributes; + } + + public abstract String getId(); //소셜 식별 값 : 구글 - "sub", 카카오 - "id", 네이버 - "id" + + public abstract String getEmail(); +} From 962658d43c5283daa044e94138afae138794aa97 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:04:05 +0900 Subject: [PATCH 045/734] =?UTF-8?q?refactor:=20OAuthAttributes=20socialUse?= =?UTF-8?q?rInfo=20=EB=B0=98=EC=98=81=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/auth/dto/OAuthAttributes.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/OAuthAttributes.java b/src/main/java/io/oduck/api/global/security/auth/dto/OAuthAttributes.java index e3039821..2822dee4 100644 --- a/src/main/java/io/oduck/api/global/security/auth/dto/OAuthAttributes.java +++ b/src/main/java/io/oduck/api/global/security/auth/dto/OAuthAttributes.java @@ -5,7 +5,7 @@ import io.oduck.api.global.security.userInfo.GoogleUserInfo; import io.oduck.api.global.security.userInfo.KakaoUserInfo; import io.oduck.api.global.security.userInfo.NaverUserInfo; -import io.oduck.api.global.security.userInfo.UserInfo; +import io.oduck.api.global.security.userInfo.SocialUserInfo; import java.util.Map; import lombok.Builder; import lombok.Getter; @@ -18,12 +18,12 @@ public class OAuthAttributes { private String nameAttributeKey; // OAuth2 로그인 진행 시 키가 되는 필드 값, PK와 같은 의미 - private UserInfo userInfo; // 소셜 타입별 로그인 유저 정보(닉네임, 이메일, 프로필 사진 등등) + private SocialUserInfo socialUserInfo; // 소셜 타입별 로그인 유저 정보(닉네임, 이메일, 프로필 사진 등등) @Builder - public OAuthAttributes(String nameAttributeKey, UserInfo userInfo) { + public OAuthAttributes(String nameAttributeKey, SocialUserInfo socialUserInfo) { this.nameAttributeKey = nameAttributeKey; - this.userInfo = userInfo; + this.socialUserInfo = socialUserInfo; } /** @@ -47,21 +47,21 @@ public static OAuthAttributes of(SocialType socialType, private static OAuthAttributes ofKakao(String userNameAttributeName, Map attributes) { return OAuthAttributes.builder() .nameAttributeKey(userNameAttributeName) - .userInfo(new KakaoUserInfo(attributes)) + .socialUserInfo(new KakaoUserInfo(attributes)) .build(); } public static OAuthAttributes ofGoogle(String userNameAttributeName, Map attributes) { return OAuthAttributes.builder() .nameAttributeKey(userNameAttributeName) - .userInfo(new GoogleUserInfo(attributes)) + .socialUserInfo(new GoogleUserInfo(attributes)) .build(); } public static OAuthAttributes ofNaver(String userNameAttributeName, Map attributes) { return OAuthAttributes.builder() .nameAttributeKey(userNameAttributeName) - .userInfo(new NaverUserInfo(attributes)) + .socialUserInfo(new NaverUserInfo(attributes)) .build(); } @@ -72,11 +72,11 @@ public static OAuthAttributes ofNaver(String userNameAttributeName, Map Date: Wed, 4 Oct 2023 16:06:26 +0900 Subject: [PATCH 046/734] =?UTF-8?q?feat:=20Member,=20Auth=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20Repository=20=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/repository/MemberProfileRepository.java | 10 ++++++++++ .../domain/member/repository/MemberRepository.java | 10 ++++++++++ .../auth/repository/AuthSocialRepository.java | 14 ++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java create mode 100644 src/main/java/io/oduck/api/domain/member/repository/MemberRepository.java create mode 100644 src/main/java/io/oduck/api/global/security/auth/repository/AuthSocialRepository.java diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java b/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java new file mode 100644 index 00000000..30ffed33 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.member.repository; + +import io.oduck.api.domain.member.entity.MemberProfile; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MemberProfileRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepository.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepository.java new file mode 100644 index 00000000..4fea38d5 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.member.repository; + +import io.oduck.api.domain.member.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MemberRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/global/security/auth/repository/AuthSocialRepository.java b/src/main/java/io/oduck/api/global/security/auth/repository/AuthSocialRepository.java new file mode 100644 index 00000000..b8f7b2ed --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/repository/AuthSocialRepository.java @@ -0,0 +1,14 @@ +package io.oduck.api.global.security.auth.repository; + +import io.oduck.api.global.security.auth.entity.AuthSocial; +import io.oduck.api.global.security.auth.entity.SocialType; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AuthSocialRepository extends JpaRepository { + + Optional findBySocialIdAndSocialType(String socialId, SocialType socialType); + Optional findByEmail(String email); +} From 4366ccff6b9f3f241da87b52180afea67995b968 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:07:51 +0900 Subject: [PATCH 047/734] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A6=84=20=EC=A1=B0?= =?UTF-8?q?=ED=95=A9=20=EC=83=9D=EC=84=B1=EA=B8=B0=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/utils/NameGenerator.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/utils/NameGenerator.java diff --git a/src/main/java/io/oduck/api/global/utils/NameGenerator.java b/src/main/java/io/oduck/api/global/utils/NameGenerator.java new file mode 100644 index 00000000..4a9feb96 --- /dev/null +++ b/src/main/java/io/oduck/api/global/utils/NameGenerator.java @@ -0,0 +1,57 @@ +package io.oduck.api.global.utils; + +import java.util.Random; + +public class NameGenerator { + private static final String[] adjectives = { + "벽력일섬쓰는", + "영역전개하는", + "에네르기파쓰는", + "이세계로간", + "트럭에치인", + "포켓몬잡는", + "건담을타는", + "마법소녀", + "열매능력자", + "해적왕", + "칠무해", + "해군", + "나뭇잎마을닌자", + "아카츠키", + "닌자연합군", + "호정13대", + "사신대행", + "아란칼", + "영령", + "마스터", + "서번트", + "최강슬라임", + "헌터" + }; + + private static final String[] nouns = { + "오리", + "오덕", + "덕후" + }; + + private static final String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + public static String generateNickname() { + String selectedAdjective = adjectives[new Random().nextInt(adjectives.length)]; + String selectedNoun = nouns[new Random().nextInt(nouns.length)]; + + return selectedAdjective + "_" + selectedNoun + "_" + generateRandomTag(); + } + + private static String generateRandomTag() { + StringBuilder tag = new StringBuilder(); + + for (int i = 0; i < 4; i++) { + int randomIndex = new Random().nextInt(characters.length()); + tag.append(characters.charAt(randomIndex)); + } + + return tag.toString(); + } +} From 87e69752afb7718849b933a5774536f0125dce1c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:08:41 +0900 Subject: [PATCH 048/734] =?UTF-8?q?feat:=20=EC=86=8C=EC=85=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/CustomOAuth2UserService.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java diff --git a/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java b/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java new file mode 100644 index 00000000..f6c8337d --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java @@ -0,0 +1,102 @@ +package io.oduck.api.global.security.auth.service; + +import static io.oduck.api.global.utils.NameGenerator.generateNickname; + +import io.oduck.api.global.security.auth.dto.SessionUser; +import io.oduck.api.global.security.auth.entity.AuthSocial; +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.global.security.auth.entity.SocialType; +import io.oduck.api.global.security.auth.repository.AuthSocialRepository; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.global.security.auth.dto.OAuthAttributes; +import jakarta.servlet.http.HttpSession; +import java.util.Collections; +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; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService implements OAuth2UserService { + + private final MemberRepository memberRepository; + private final AuthSocialRepository authSocialRepository; + private final HttpSession httpSession; + + @Override + @Transactional + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2UserService delegate = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = delegate.loadUser(userRequest); + + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + SocialType socialType = getSocialType(registrationId); + String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() + .getUserInfoEndpoint() + .getUserNameAttributeName(); + + OAuthAttributes attributes = OAuthAttributes.of( + socialType, + userNameAttributeName, + oAuth2User + .getAttributes()); + + AuthSocial authSocial = attributes.toEntity(socialType, attributes.getSocialUserInfo()); + + Member member = getOrSaveMember(authSocial); + httpSession.setAttribute("user", new SessionUser(member.getId(), LoginType.SOCIAL)); + + return new DefaultOAuth2User( + Collections.singleton(new SimpleGrantedAuthority(member.getMemberProfile().getRole().toString())), + oAuth2User.getAttributes(), + attributes.getNameAttributeKey()); + } + + private Member getOrSaveMember(AuthSocial authSocial) { + Optional optionalAuthSocial = authSocialRepository + .findBySocialIdAndSocialType( + authSocial.getSocialId(), + authSocial.getSocialType()); + + if (optionalAuthSocial.isPresent()) { + AuthSocial foundAuthSocial = optionalAuthSocial.get(); + return memberRepository.findById(foundAuthSocial.getMember().getId()).get(); + } + + MemberProfile memberProfile = MemberProfile.builder() + .name(generateNickname()) + .build(); + + Member member = Member.builder() + .loginType(LoginType.SOCIAL) + .authSocial(authSocial) + .memberProfile(memberProfile) + .build(); + + return memberRepository.save(member); + } + + private SocialType getSocialType(String registrationId) { + if (registrationId.equals("naver")) { + return SocialType.NAVER; + } + + if (registrationId.equals("kakao")) { + return SocialType.KAKAO; + } + + return SocialType.GOOGLE; + } +} \ No newline at end of file From e802acdf5c83cebf3afefde5760783923517e7d3 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:11:23 +0900 Subject: [PATCH 049/734] =?UTF-8?q?feat:=20spring=20security=20=EB=B3=B4?= =?UTF-8?q?=EC=95=88=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/config/SecurityConfig.java diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java new file mode 100644 index 00000000..426b0d96 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -0,0 +1,102 @@ +package io.oduck.api.global.security.config; + + +import static org.springframework.security.config.Customizer.withDefaults; + +import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.global.security.handler.ForbiddenHandler; +import io.oduck.api.global.security.handler.LoginFailureHandler; +import io.oduck.api.global.security.handler.LoginSuccessHandler; +import io.oduck.api.global.security.auth.service.CustomOAuth2UserService; +import io.oduck.api.global.security.handler.UnauthorizedHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; + +@RequiredArgsConstructor +@EnableWebSecurity +@Configuration +public class SecurityConfig { + private final CustomOAuth2UserService customOAuth2UserService; + private final LoginSuccessHandler loginSuccessHandler; + private final LoginFailureHandler loginFailureHandler; + private final ForbiddenHandler forbiddenHandler; + private final UnauthorizedHandler unauthorizedHandler; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // 헤더 보안 설정 + http + .headers((headers) -> headers + .contentTypeOptions(withDefaults()) + .xssProtection(withDefaults()) + .cacheControl(withDefaults()) + .httpStrictTransportSecurity(withDefaults()) + .frameOptions(withDefaults()) + ); + + // csrf 비활성, cors 기본 설정 + http + .csrf( + AbstractHttpConfigurer::disable + ) + .cors(withDefaults()); + + // form 로그인 비활성, httpBasic 비활성 + http + .formLogin( + AbstractHttpConfigurer::disable + ) + .httpBasic( + AbstractHttpConfigurer::disable + ); + + // 세션 설정 + http + .sessionManagement( + (sessionManagement) -> sessionManagement + .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)); + + // 인가 설정 + http + .authorizeRequests((authorizeRequests) -> + authorizeRequests + .requestMatchers("/auth/status").hasAuthority(Role.MEMBER.name()) + .requestMatchers("docs/index.html").hasAuthority(Role.ADMIN.name()) + .anyRequest().permitAll() + ); + + // 로그아웃 설정 + http + .logout((logout) -> logout + .logoutUrl("/auth/logout") + .logoutSuccessUrl("/") + .deleteCookies("oDuckio.sid") + .invalidateHttpSession(true) + .permitAll(false) + ); + + // OAuth2 로그인 설정 + http + .oauth2Login( + (oauth2Login) -> oauth2Login + .userInfoEndpoint((userInfoEndpoint) -> userInfoEndpoint + .userService(customOAuth2UserService) + ) + .successHandler(loginSuccessHandler) + .failureHandler(loginFailureHandler) + ) + .exceptionHandling( + (exceptionHandling) -> exceptionHandling + .authenticationEntryPoint(unauthorizedHandler) + .accessDeniedHandler(forbiddenHandler) + ); + + return http.build(); + } +} \ No newline at end of file From c624863e33fa7cc9c2b1f8590fb2d933e8dd39d2 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:12:00 +0900 Subject: [PATCH 050/734] =?UTF-8?q?feat:=20LoginUser=20argumentResolver=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/config/WebConfig.java | 19 ++++++++++ .../auth/controller/AuthController.java | 24 ++++++++++++ .../resolver/LoginUserArgumentResolver.java | 38 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/config/WebConfig.java create mode 100644 src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java create mode 100644 src/main/java/io/oduck/api/global/security/resolver/LoginUserArgumentResolver.java diff --git a/src/main/java/io/oduck/api/global/config/WebConfig.java b/src/main/java/io/oduck/api/global/config/WebConfig.java new file mode 100644 index 00000000..fb73a5e0 --- /dev/null +++ b/src/main/java/io/oduck/api/global/config/WebConfig.java @@ -0,0 +1,19 @@ +package io.oduck.api.global.config; + +import io.oduck.api.global.security.resolver.LoginUserArgumentResolver; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@RequiredArgsConstructor +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final LoginUserArgumentResolver loginUserArgumentResolver; + + @Override + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(loginUserArgumentResolver); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java new file mode 100644 index 00000000..6e514529 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java @@ -0,0 +1,24 @@ +package io.oduck.api.global.security.auth.controller; + +import io.oduck.api.global.exception.UnauthorizedException; +import io.oduck.api.global.security.auth.dto.SessionUser; +import io.oduck.api.global.security.auth.dto.LoginUser; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/auth") +public class AuthController { + @GetMapping("/status") + public String status( + @LoginUser SessionUser user + ) { + + if (user == null) { + throw new UnauthorizedException("Unauthorized"); + } + + return "success"; + } +} diff --git a/src/main/java/io/oduck/api/global/security/resolver/LoginUserArgumentResolver.java b/src/main/java/io/oduck/api/global/security/resolver/LoginUserArgumentResolver.java new file mode 100644 index 00000000..27eb4b37 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/resolver/LoginUserArgumentResolver.java @@ -0,0 +1,38 @@ +package io.oduck.api.global.security.resolver; + +import io.oduck.api.global.security.auth.dto.LoginUser; +import io.oduck.api.global.security.auth.dto.SessionUser; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@RequiredArgsConstructor +@Component +public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver { + + private final HttpSession httpSession; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null; + boolean isUserClass = SessionUser.class.equals(parameter.getParameterType()); + return isLoginUserAnnotation && isUserClass; + } + + @Override + public Object resolveArgument( + MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory + ) throws Exception { + SessionUser user = (SessionUser) httpSession.getAttribute("user"); + + return user; + } +} \ No newline at end of file From ab8b351b02e83effdd796617283010d508ac18c2 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:18:44 +0900 Subject: [PATCH 051/734] =?UTF-8?q?test:=20test=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=A0=81=EC=9A=A9=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/ApiApplicationTests.java | 2 ++ src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/test/java/io/oduck/api/ApiApplicationTests.java b/src/test/java/io/oduck/api/ApiApplicationTests.java index 36560b1b..e3db97c5 100644 --- a/src/test/java/io/oduck/api/ApiApplicationTests.java +++ b/src/test/java/io/oduck/api/ApiApplicationTests.java @@ -2,8 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest +@ActiveProfiles("test") class ApiApplicationTests { @Test diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index f7b545e2..3bfc3e04 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -30,6 +30,7 @@ import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; @@ -38,6 +39,7 @@ @AutoConfigureMockMvc @ExtendWith({ RestDocumentationExtension.class, SpringExtension.class }) @SpringBootTest +@ActiveProfiles("test") public class MemberControllerTest { @Autowired private MockMvc mockMvc; From baaab0b1f22278c71882073eb43b005d547b57e1 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:21:58 +0900 Subject: [PATCH 052/734] =?UTF-8?q?chore:=20docker=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 22 ++++++++++++++++++++++ docker.md | 21 +++++++++++++++++++++ dockerfile-dev | 5 +++++ 3 files changed, 48 insertions(+) create mode 100644 docker-compose.yml create mode 100644 docker.md create mode 100644 dockerfile-dev diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..08f6afe5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +services: + redis: + image: redis:latest + command: redis-server + container_name: oDuckio-redis + ports: + - '6379:6379' + + spring: + build: + context: . + dockerfile: ./dockerfile-dev + container_name: oDuckio-spring + depends_on: + - redis + ports: + - '8000:8000' + volumes: + # 로컬 logs < - > 컨테이너 logs 연결 + # 로컬 /logs랑 안 섞이게 /data/logs로 연결 + # 컨테이너상 서버 실행 경로 var/app/ + - ./data/logs:/logs diff --git a/docker.md b/docker.md new file mode 100644 index 00000000..132c81fd --- /dev/null +++ b/docker.md @@ -0,0 +1,21 @@ +# Docker 실행 방법 + +1. docker 설치 후 실행 +2. server 폴더에서 다음 명령어 실행 + +> ```bash +> # docker-compose 실행(redis + spring) +> docker-compose up -d --build +> +> # docker 컨테이너 확인 +> docker ps +> +> # docker 컨테이너 로그 확인 +> docker container logs oDuckio-spring +> +> # docker 컨테이너 터미널 +> docker exec -it /bin/bash +> +> # docker-compose 종료 및 컨테이너 삭제 +> docker-compose down +> ``` diff --git a/dockerfile-dev b/dockerfile-dev new file mode 100644 index 00000000..30173cd6 --- /dev/null +++ b/dockerfile-dev @@ -0,0 +1,5 @@ +FROM openjdk:17-jdk-slim +VOLUME /tmp +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-Duser.timezone=Asia/Seoul", "-jar", "/app.jar"] \ No newline at end of file From 3f9864c586e3189b20fedc2045f8a2ef10727838 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 16:26:37 +0900 Subject: [PATCH 053/734] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80,?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=B1=EA=B3=B5=EC=8B=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=A0=9C=EA=B1=B0=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/auth/service/CustomOAuth2UserService.java | 4 +++- .../api/global/security/handler/LoginSuccessHandler.java | 6 ------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java b/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java index f6c8337d..782be876 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java @@ -85,7 +85,9 @@ private Member getOrSaveMember(AuthSocial authSocial) { .memberProfile(memberProfile) .build(); - return memberRepository.save(member); + Member savedMember = memberRepository.save(member); + log.info("Member Created! {}", savedMember.getId()); + return savedMember; } private SocialType getSocialType(String registrationId) { diff --git a/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java b/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java index 07becd01..61af7f9b 100644 --- a/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java +++ b/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java @@ -22,12 +22,6 @@ public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { -// SecurityContextHolder.getContext().setAuthentication(authentication); - -// request.getSession().setMaxInactiveInterval(1800); - - log.info("OAuth2 Login 성공!"); - response.sendRedirect(BASE_URL); } } \ No newline at end of file From 07a99f0af368df5dd20085c5e1be3f9242f8a917 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 18:20:17 +0900 Subject: [PATCH 054/734] =?UTF-8?q?refactor:=20=EC=95=88=EC=93=B0=EB=8A=94?= =?UTF-8?q?=20import=20=EC=A0=9C=EA=B1=B0=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/security/handler/LoginSuccessHandler.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java b/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java index 61af7f9b..283c596f 100644 --- a/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java +++ b/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java @@ -5,23 +5,21 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; -@Slf4j @Component @RequiredArgsConstructor public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Value("${config.base.url}") private String BASE_URL; + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { + Authentication authentication) throws IOException, ServletException { response.sendRedirect(BASE_URL); } } \ No newline at end of file From dcf8d193b8ee9fba24872c2782474a6c693858bb Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 18:25:00 +0900 Subject: [PATCH 055/734] =?UTF-8?q?chore:=20dockerfile=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dockerfile-dev | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dockerfile-dev b/dockerfile-dev index 30173cd6..786e2b0c 100644 --- a/dockerfile-dev +++ b/dockerfile-dev @@ -1,5 +1,7 @@ FROM openjdk:17-jdk-slim -VOLUME /tmp -ARG JAR_FILE=build/libs/*.jar -COPY ${JAR_FILE} app.jar -ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-Duser.timezone=Asia/Seoul", "-jar", "/app.jar"] \ No newline at end of file +RUN mkdir -p /var/app +WORKDIR /var/app +COPY . . +RUN ./gradlew build +ARG JAR_FILE=./build/libs/api-0.0.1-SNAPSHOT.jar +ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-Duser.timezone=Asia/Seoul", "-jar", "./build/libs/api-0.0.1-SNAPSHOT.jar"] \ No newline at end of file From f0a4b5c217cf1a98b5ca6233758f23ae1b627556 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 18:25:22 +0900 Subject: [PATCH 056/734] =?UTF-8?q?chore:=20=20dockerignore=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..ea3adb25 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +*dockerfile* +build +data +logs +.gitignore +bin +.vscode +.idea \ No newline at end of file From 159f0ce1409d069ef56de52b89104a3516544dde Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 20:58:07 +0900 Subject: [PATCH 057/734] =?UTF-8?q?refactor:=20LogoutHandler=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 9 ++++--- .../security/handler/LogoutHandler.java | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/main/java/io/oduck/api/global/security/handler/LogoutHandler.java diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index 426b0d96..dec17b12 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -8,6 +8,7 @@ import io.oduck.api.global.security.handler.LoginFailureHandler; import io.oduck.api.global.security.handler.LoginSuccessHandler; import io.oduck.api.global.security.auth.service.CustomOAuth2UserService; +import io.oduck.api.global.security.handler.LogoutHandler; import io.oduck.api.global.security.handler.UnauthorizedHandler; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -22,6 +23,7 @@ @EnableWebSecurity @Configuration public class SecurityConfig { + private final LogoutHandler logoutHandler; private final CustomOAuth2UserService customOAuth2UserService; private final LoginSuccessHandler loginSuccessHandler; private final LoginFailureHandler loginFailureHandler; @@ -66,8 +68,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests((authorizeRequests) -> authorizeRequests - .requestMatchers("/auth/status").hasAuthority(Role.MEMBER.name()) - .requestMatchers("docs/index.html").hasAuthority(Role.ADMIN.name()) + .requestMatchers("/auth/status").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) +// .requestMatchers("docs/index.html").hasAuthority(Role.ADMIN.name()) + .requestMatchers("oduckdmin/*").hasAuthority(Role.ADMIN.name()) .anyRequest().permitAll() ); @@ -75,7 +78,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .logout((logout) -> logout .logoutUrl("/auth/logout") - .logoutSuccessUrl("/") + .logoutSuccessHandler(logoutHandler) .deleteCookies("oDuckio.sid") .invalidateHttpSession(true) .permitAll(false) diff --git a/src/main/java/io/oduck/api/global/security/handler/LogoutHandler.java b/src/main/java/io/oduck/api/global/security/handler/LogoutHandler.java new file mode 100644 index 00000000..73fdede2 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/handler/LogoutHandler.java @@ -0,0 +1,26 @@ +package io.oduck.api.global.security.handler; + +import jakarta.servlet.ServletException; +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.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class LogoutHandler implements LogoutSuccessHandler { + + @Value("${config.base.url}") + private String BASE_URL; + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + response.sendRedirect(BASE_URL); + } +} From 79585c04841ed347bf607fb2aa292652391369bd Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 4 Oct 2023 20:58:48 +0900 Subject: [PATCH 058/734] =?UTF-8?q?chore:=20tml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=ED=99=95=EC=9E=A5=EC=9E=90=20=EC=B6=94=EA=B0=80=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 33db90d2..8fce7672 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build/ */.DS_Store *.log *.gz +*.tmp src/main/resources/static/docs/index.html application-*.yml From b279385af13f79329bbc7987ce8b19bd4e7d996f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 5 Oct 2023 13:43:50 +0900 Subject: [PATCH 059/734] =?UTF-8?q?refactor:=20security=20Deprecated=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=83=88=20=EA=B1=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/security/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index dec17b12..4cca4f91 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -66,7 +66,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 인가 설정 http - .authorizeRequests((authorizeRequests) -> + .authorizeHttpRequests((authorizeRequests) -> authorizeRequests .requestMatchers("/auth/status").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) // .requestMatchers("docs/index.html").hasAuthority(Role.ADMIN.name()) From beef5a4ce1d3f5748fcc62106ff7d3e1ef08094c Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Thu, 5 Oct 2023 19:46:45 +0900 Subject: [PATCH 060/734] =?UTF-8?q?refactor:=20Anime=20Entity=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=20=EA=B4=80=EA=B3=84=20=EC=88=98=EC=A0=95=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Anime와 관련된 AnimeOriginalAuthor, AnimeStudios, AnimeVoiceActors, AnimeGenres는 batchsize 설정으로 한 번에 가져오고, Bookmark, ShortReview, StarRating, AttractionPoint는 연관 관계를 제거하였습니다. --- .../io/oduck/api/domain/anime/entity/Anime.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 0a2f2906..75a5343f 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -1,10 +1,6 @@ package io.oduck.api.domain.anime.entity; -import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; -import io.oduck.api.domain.bookmark.entity.Bookmark; -import io.oduck.api.domain.review.entity.ShortReview; import io.oduck.api.domain.series.entity.Series; -import io.oduck.api.domain.starRating.entity.StarRating; import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -82,16 +78,4 @@ public class Anime extends BaseEntity { @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) private List animeGenres; - - @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private List bookMarks; - - @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private List shortReviews; - - @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private List starRatings; - - @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private List attractionPoints; } From fbf5c43a3d1363dd71978325de13d614faa01d19 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Thu, 5 Oct 2023 19:48:23 +0900 Subject: [PATCH 061/734] =?UTF-8?q?refactor:=20Anime=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20bookmark=20count=20=EC=B6=94=EA=B0=80=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/anime/entity/Anime.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 75a5343f..86c0854f 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -63,6 +63,9 @@ public class Anime extends BaseEntity { @ColumnDefault("0") private long reviewCount; + @ColumnDefault("0") + private long bookmarkCount; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "series_id") private Series series; From 466635ebe9a511b56e209fe6593d50345cc399dd Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Thu, 5 Oct 2023 19:49:32 +0900 Subject: [PATCH 062/734] =?UTF-8?q?refactor:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EA=B0=92=EB=A7=8C=20=EB=A6=AC=ED=84=B4=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/anime/service/AnimeServiceStub.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java index d3482010..ae01a554 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java @@ -3,7 +3,6 @@ import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.dto.AnimeRes.Anime; import io.oduck.api.domain.anime.dto.AnimeRes.Broadcast; -import io.oduck.api.domain.anime.dto.AnimeRes.Score; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; import io.oduck.api.domain.anime.entity.Rating; @@ -18,22 +17,9 @@ public class AnimeServiceStub implements AnimeService{ @Override public AnimeRes getAnimeById(Long animeId) { Anime anime = createAnime(animeId); - Score score = createScore(); return AnimeRes.builder() .anime(anime) - .score(score) - .build(); - } - - private Score createScore() { - return Score.builder() - .starRatingScoreAverage(9.2) - .selectedDrawingCount(174) - .selectedStoryCount(52) - .selectedMusicCount(0) - .selectedCharacterCount(31) - .selectedVoiceActorCount(77) .build(); } From aa193fd61d29d66330b5528b678a6aacee79d0bf Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Thu, 5 Oct 2023 19:55:30 +0900 Subject: [PATCH 063/734] =?UTF-8?q?test:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EA=B0=92=EB=A7=8C=20=EB=A6=AC=ED=84=B4,=20=EC=84=A4=EB=AA=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/anime/AnimeControllerTest.java | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index d38792d4..a9256679 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -71,12 +71,6 @@ void getAnimes() throws Exception { .andExpect(jsonPath("$.item.anime.studios").exists()) .andExpect(jsonPath("$.item.anime.reviewCount").exists()) .andExpect(jsonPath("$.item.anime.bookmarkCount").exists()) - .andExpect(jsonPath("$.item.score.starRatingScoreAverage").exists()) - .andExpect(jsonPath("$.item.score.selectedDrawingCount").exists()) - .andExpect(jsonPath("$.item.score.selectedStoryCount").exists()) - .andExpect(jsonPath("$.item.score.selectedMusicCount").exists()) - .andExpect(jsonPath("$.item.score.selectedCharacterCount").exists()) - .andExpect(jsonPath("$.item.score.selectedVoiceActorCount").exists()) .andDo(document("getAnimeById/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -138,31 +132,10 @@ void getAnimes() throws Exception { .description("애니의 제작사."), fieldWithPath("item.anime.reviewCount") .type(JsonFieldType.NUMBER) - .description("애니의 리뷰 개수. 리뷰 개수는 짧은 리뷰와 장문 리뷰를 합친 값"), + .description("애니의 리뷰 개수."), fieldWithPath("item.anime.bookmarkCount") .type(JsonFieldType.NUMBER) - .description("애니의 덕후 수. 덕후는 북마크의 개념."), - fieldWithPath("item.score") - .type(JsonFieldType.OBJECT) - .description("애니 별점 및 입덕 포인트 정보"), - fieldWithPath("item.score.starRatingScoreAverage") - .type(JsonFieldType.NUMBER) - .description("애니 별점 평균"), - fieldWithPath("item.score.selectedDrawingCount") - .type(JsonFieldType.NUMBER) - .description("입덕 포인트 중 작화를 선택한 수"), - fieldWithPath("item.score.selectedStoryCount") - .type(JsonFieldType.NUMBER) - .description("입덕 포인트 중 스토리를 선택한 수"), - fieldWithPath("item.score.selectedMusicCount") - .type(JsonFieldType.NUMBER) - .description("입덕 포인트 중 음악을 선택한 수"), - fieldWithPath("item.score.selectedCharacterCount") - .type(JsonFieldType.NUMBER) - .description("입덕 포인트 중 캐릭터를 선택한 수"), - fieldWithPath("item.score.selectedVoiceActorCount") - .type(JsonFieldType.NUMBER) - .description("입덕 포인트 중 성우를 선택한 수") + .description("애니의 덕후 수.") ) )); From 9b844f7327c39f6e709d1048d6d9fd5ff91eafa6 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Thu, 5 Oct 2023 19:56:02 +0900 Subject: [PATCH 064/734] =?UTF-8?q?docs:=20index.adoc=EC=9D=98=20anime=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index f979e843..c556b5d3 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -64,4 +64,26 @@ include::{snippets}/patchProfile/success/request-fields.adoc[] ==== 성공시 .http-response -include::{snippets}/patchProfile/success/http-response.adoc[] \ No newline at end of file +include::{snippets}/patchProfile/success/http-response.adoc[] + +== animes +=== GET api/v1/animes/:id + +.curl-request +include::{snippets}/getAnimeById/success/curl-request.adoc[] + +.http-request +include::{snippets}/getAnimeById/success/http-request.adoc[] + +include::{snippets}/getAnimeById/success/path-parameters.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getAnimeById/success/http-response.adoc[] + +.response-body +include::{snippets}/getAnimeById/success/response-body.adoc[] + +.response-fields +include::{snippets}/getAnimeById/success/response-fields.adoc[] +==== 실패 시 \ No newline at end of file From 31b2c1e2bd6313e16c350fac093a482ad6ac7397 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 6 Oct 2023 17:31:37 +0900 Subject: [PATCH 065/734] =?UTF-8?q?refactor:=20=EB=A1=B4=EB=9F=AC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - email 유니크 제약조건 추가 - 빌더 및 멤버 setter 추가 --- .../api/global/security/auth/entity/AuthLocal.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java b/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java index 2222bbef..c8bf5ef7 100644 --- a/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java +++ b/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java @@ -10,6 +10,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -25,9 +26,20 @@ public class AuthLocal extends BaseEntity { @JoinColumn(name = "member_id") private Member member; - @Column(nullable = false, length = 100) + @Column(nullable = false, length = 100, unique = true) private String email; @Column(nullable = false, length = 75) private String password; + + @Builder + public AuthLocal(Member member, String email, String password) { + this.member = member; + this.email = email; + this.password = password; + } + + public void setMember(Member member) { + this.member = member; + } } From 36b7f6f9bd8bc991b4285967d365bac94427e746 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 6 Oct 2023 17:31:50 +0900 Subject: [PATCH 066/734] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20repository=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/repository/AuthLocalRepository.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/auth/repository/AuthLocalRepository.java diff --git a/src/main/java/io/oduck/api/global/security/auth/repository/AuthLocalRepository.java b/src/main/java/io/oduck/api/global/security/auth/repository/AuthLocalRepository.java new file mode 100644 index 00000000..0370fd60 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/repository/AuthLocalRepository.java @@ -0,0 +1,12 @@ +package io.oduck.api.global.security.auth.repository; + + +import io.oduck.api.global.security.auth.entity.AuthLocal; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AuthLocalRepository extends JpaRepository { + + boolean existsByEmail(String email); +} From 8cd8c866c9a162a2e03afa592a74fae7434c587f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 6 Oct 2023 17:44:16 +0900 Subject: [PATCH 067/734] =?UTF-8?q?refactor:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로컬 회원가입시 해시화를 위한 passwordEncoder 추가 - 로그아웃 요청 POST method로 변경을 위한 코드 주석 추가 --- .../security/config/SecurityConfig.java | 85 +++++++++++-------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index 4cca4f91..effd13e7 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -1,6 +1,5 @@ package io.oduck.api.global.security.config; - import static org.springframework.security.config.Customizer.withDefaults; import io.oduck.api.domain.member.entity.Role; @@ -13,16 +12,21 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @RequiredArgsConstructor @EnableWebSecurity @Configuration public class SecurityConfig { + private final LogoutHandler logoutHandler; private final CustomOAuth2UserService customOAuth2UserService; private final LoginSuccessHandler loginSuccessHandler; @@ -34,72 +38,81 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 헤더 보안 설정 http - .headers((headers) -> headers - .contentTypeOptions(withDefaults()) - .xssProtection(withDefaults()) - .cacheControl(withDefaults()) - .httpStrictTransportSecurity(withDefaults()) - .frameOptions(withDefaults()) + .headers((headers) -> + headers + .contentTypeOptions(withDefaults()) + .xssProtection(withDefaults()) + .cacheControl(withDefaults()) + .httpStrictTransportSecurity(withDefaults()) + .frameOptions(withDefaults()) ); // csrf 비활성, cors 기본 설정 http - .csrf( - AbstractHttpConfigurer::disable - ) + .csrf(AbstractHttpConfigurer::disable) .cors(withDefaults()); // form 로그인 비활성, httpBasic 비활성 http - .formLogin( - AbstractHttpConfigurer::disable - ) - .httpBasic( - AbstractHttpConfigurer::disable - ); + .formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable); // 세션 설정 http - .sessionManagement( - (sessionManagement) -> sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)); + .sessionManagement((sessionManagement) -> + sessionManagement + .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) + ); // 인가 설정 http .authorizeHttpRequests((authorizeRequests) -> authorizeRequests .requestMatchers("/auth/status").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) -// .requestMatchers("docs/index.html").hasAuthority(Role.ADMIN.name()) + // .requestMatchers("docs/index.html").hasAuthority(Role.ADMIN.name()) .requestMatchers("oduckdmin/*").hasAuthority(Role.ADMIN.name()) .anyRequest().permitAll() ); // 로그아웃 설정 http - .logout((logout) -> logout - .logoutUrl("/auth/logout") - .logoutSuccessHandler(logoutHandler) - .deleteCookies("oDuckio.sid") - .invalidateHttpSession(true) - .permitAll(false) + .logout((logout) -> + logout + .logoutUrl("/auth/logout") + // .logoutRequestMatcher(new AntPathRequestMatcher("/auth/logout", "POST")) + .logoutSuccessHandler(logoutHandler).deleteCookies("oDuckio.sid") + .invalidateHttpSession(true).permitAll(false) ); // OAuth2 로그인 설정 http - .oauth2Login( - (oauth2Login) -> oauth2Login - .userInfoEndpoint((userInfoEndpoint) -> userInfoEndpoint + .oauth2Login((oauth2Login) -> + oauth2Login + .userInfoEndpoint((userInfoEndpoint) -> + userInfoEndpoint .userService(customOAuth2UserService) - ) - .successHandler(loginSuccessHandler) - .failureHandler(loginFailureHandler) - ) - .exceptionHandling( - (exceptionHandling) -> exceptionHandling + ) + .successHandler(loginSuccessHandler) + .failureHandler(loginFailureHandler) + ); + + http + .exceptionHandling((exceptionHandling) -> + exceptionHandling .authenticationEntryPoint(unauthorizedHandler) .accessDeniedHandler(forbiddenHandler) ); return http.build(); } -} \ No newline at end of file + + // PasswordEncoder Bean 등록 + @Bean + public PasswordEncoder passwordEncoder() { + // Spring Security 에서 지원하는 PasswordEncoder 구현 객체를 생성해주는 컴포넌트. + // DelegatingPasswordEncoder를 통해 애플리케이션에서 사용할 PasswordEncoder 를 결정하고, + // 결정된 PasswordEncoder로 사용자가 입력한 패스워드를 단방향으로 암호화 해준다. + // 기본 BCrypt + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } +} From dad6da35731cd0483f3769f94706f38c7bcefa1e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 6 Oct 2023 17:44:50 +0900 Subject: [PATCH 068/734] =?UTF-8?q?refactor:=20ConflictException=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/global/exception/ConflictException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/global/exception/ConflictException.java b/src/main/java/io/oduck/api/global/exception/ConflictException.java index bfe2b7bd..24ff9776 100644 --- a/src/main/java/io/oduck/api/global/exception/ConflictException.java +++ b/src/main/java/io/oduck/api/global/exception/ConflictException.java @@ -2,6 +2,6 @@ public class ConflictException extends CustomException{ public ConflictException(String value) { - super(409, "Duplicate" + value); + super(409, value + " Already Exists"); } } From f1ead87c977fa0d8a7f76bea58322b1de0896b2f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 6 Oct 2023 17:45:22 +0900 Subject: [PATCH 069/734] =?UTF-8?q?refactor:=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EB=B9=8C=EB=8D=94=20null=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/member/entity/Member.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/entity/Member.java b/src/main/java/io/oduck/api/domain/member/entity/Member.java index 7ddda6d3..f72dbf0f 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/Member.java +++ b/src/main/java/io/oduck/api/domain/member/entity/Member.java @@ -59,11 +59,21 @@ public class Member extends BaseEntity { private List attractionPoints; @Builder - public Member(Long id, LoginType loginType, AuthSocial authSocial, MemberProfile memberProfile) { + public Member(Long id, LoginType loginType, AuthSocial authSocial, AuthLocal authLocal, MemberProfile memberProfile) { this.id = id; this.loginType = loginType; this.authSocial = authSocial; - authSocial.setMember(this); + + if (authSocial != null) { + authSocial.setMember(this); + } + + this.authLocal = authLocal; + + if (authLocal != null) { + authLocal.setMember(this); + } + this.memberProfile = memberProfile; memberProfile.setMember(this); } From 441c7c8837cba315f81f44ce2bd8b6fe58528b73 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 6 Oct 2023 18:03:22 +0900 Subject: [PATCH 070/734] =?UTF-8?q?feat:=20memberService=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/service/MemberService.java | 3 +- .../member/service/MemberServiceStub.java | 59 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberService.java b/src/main/java/io/oduck/api/domain/member/service/MemberService.java index fd6e595f..7cc076fb 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberService.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberService.java @@ -1,10 +1,11 @@ package io.oduck.api.domain.member.service; +import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; public interface MemberService { // 로컬 회원가입 로직 - // Member signUpByLocal(Member member); + void signUpByLocal(CreateReq createReq); // 이름으로 회원 프로필 조회 로직 MemberProfileRes getProfileByName(String name); diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java index e7c1cf2e..4cd9d47c 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java @@ -1,12 +1,69 @@ package io.oduck.api.domain.member.service; +import static io.oduck.api.global.utils.NameGenerator.generateNickname; + +import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; import io.oduck.api.domain.member.dto.MemberResDto.Activity; import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.repository.MemberProfileRepository; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.global.exception.BadRequestException; +import io.oduck.api.global.exception.ConflictException; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import io.oduck.api.global.security.auth.repository.AuthLocalRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service -public class MemberServiceStub implements MemberService{ +@RequiredArgsConstructor +public class MemberServiceStub implements MemberService { + private final PasswordEncoder passwordEncoder; + private final AuthLocalRepository authLocalRepository; + private final MemberRepository memberRepository; + private final MemberProfileRepository memberProfileRepository; + + @Transactional + @Override + public void signUpByLocal(CreateReq createReq) { + String email = createReq.getEmail(); + boolean isExistMember = authLocalRepository.existsByEmail(email); + + if (isExistMember) { + throw new ConflictException("Email"); + } + + try { + String encryptedPassword = passwordEncoder.encode(createReq.getPassword()); + AuthLocal authLocal = AuthLocal.builder() + .email(createReq.getEmail()) + .password(encryptedPassword) + .build(); + + MemberProfile memberProfile = MemberProfile.builder() + .name(generateNickname()) + .build(); + + Member member = Member.builder() + .loginType(LoginType.LOCAL) + .authLocal(authLocal) + .memberProfile(memberProfile) + .build(); + + Member savedMember = memberRepository.save(member); + log.info("Member Created! {}", savedMember.getId()); + } catch (Exception e) { + log.error(e.getMessage()); + throw new BadRequestException("Try Again Plz."); + } + } @Override public MemberProfileRes getProfileByName(String name) { From cdac0347f759ddbdfc101d4179c23f51e2037060 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 6 Oct 2023 18:03:45 +0900 Subject: [PATCH 071/734] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=91=EB=8B=B5=20status=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/member/controller/MemberController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index 0444e79a..2f09938f 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -29,9 +29,9 @@ public class MemberController { public ResponseEntity PostMember( @RequestBody @Valid CreateReq body) { // TODO: 회원 가입 로직 구현 - // memberService.signUpByLocal(body); + memberService.signUpByLocal(body); - return ResponseEntity.ok().build(); + return ResponseEntity.created(null).build(); } // 이름으로 회원 프로필 조회 From f463e8e3e90d5dba9675dcbd1ef4929fe92c5ade Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 6 Oct 2023 18:04:01 +0900 Subject: [PATCH 072/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9D=91=EB=8B=B5=20status=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 3bfc3e04..2bf59b91 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -76,7 +76,7 @@ void postMember() throws Exception { // then // 응답 결과 검증 후 문서화 actions - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andDo(document("postMember/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), From aad1f4e9914e36e75bbe80d0817de1765be799a5 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Fri, 6 Oct 2023 19:23:59 +0900 Subject: [PATCH 073/734] =?UTF-8?q?refactor:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EA=B0=92=EB=A7=8C=20=EB=A6=AC=ED=84=B4=20#?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/anime/dto/AnimeRes.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java index 03a95c68..12c1f321 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -12,7 +12,6 @@ @Builder public class AnimeRes { private Anime anime; - private Score score; @Getter @Builder @@ -41,15 +40,4 @@ public static class Broadcast { private int year; private Quarter quarter; } - - @Getter - @Builder - public static class Score { - private Double starRatingScoreAverage; - private int selectedDrawingCount; - private int selectedStoryCount; - private int selectedMusicCount; - private int selectedCharacterCount; - private int selectedVoiceActorCount; - } } From 5a54facead55069cf1f4b7ac30c9bc3a6cfb4ae0 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 11:23:00 +0900 Subject: [PATCH 074/734] feat: Role, MemberProfile -> Member #13 --- .../java/io/oduck/api/domain/member/entity/Member.java | 10 +++++++++- .../oduck/api/domain/member/entity/MemberProfile.java | 5 ----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/entity/Member.java b/src/main/java/io/oduck/api/domain/member/entity/Member.java index f72dbf0f..881eb76c 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/Member.java +++ b/src/main/java/io/oduck/api/domain/member/entity/Member.java @@ -19,6 +19,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -26,6 +27,8 @@ @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder public class Member extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -34,6 +37,10 @@ public class Member extends BaseEntity { @Enumerated(EnumType.STRING) private LoginType loginType; + @Enumerated(EnumType.STRING) + @Builder.Default + private Role role = Role.MEMBER; + @OneToOne(mappedBy = "member", cascade = CascadeType.PERSIST) private AuthSocial authSocial; @@ -59,8 +66,9 @@ public class Member extends BaseEntity { private List attractionPoints; @Builder - public Member(Long id, LoginType loginType, AuthSocial authSocial, AuthLocal authLocal, MemberProfile memberProfile) { + public Member(Long id, Role role, LoginType loginType, AuthSocial authSocial, AuthLocal authLocal, MemberProfile memberProfile) { this.id = id; + this.role = role; this.loginType = loginType; this.authSocial = authSocial; diff --git a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java index 0e9df9ed..a76e1bdf 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java +++ b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java @@ -38,10 +38,6 @@ public class MemberProfile extends BaseEntity { @Builder.Default private String info = ""; - @Enumerated(EnumType.STRING) - @Builder.Default - private Role role = Role.MEMBER; - @Column(length = 100) @Builder.Default private String thumbnail = ""; @@ -61,7 +57,6 @@ public MemberProfile(Long id, String name, String info, Role role, String thumbn this.id = id; this.name = name; this.info = info; - this.role = role; this.thumbnail = thumbnail; this.backgroundImage = backgroundImage; this.point = point; From f79c9cbdc1964bb258b8c4098d8c1cdffe65fc6b Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 11:56:44 +0900 Subject: [PATCH 075/734] =?UTF-8?q?feat:=20Fetch=20Type=20LAZY=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EC=95=88=20=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=84=EC=8B=9C=20=EC=A0=9C=EA=B1=B0=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 리팩토링 필요 --- .../io/oduck/api/global/security/auth/entity/AuthLocal.java | 3 ++- .../io/oduck/api/global/security/auth/entity/AuthSocial.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java b/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java index c8bf5ef7..357a4c6e 100644 --- a/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java +++ b/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java @@ -22,7 +22,8 @@ public class AuthLocal extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @OneToOne(fetch = FetchType.LAZY) + // TODO + @OneToOne @JoinColumn(name = "member_id") private Member member; diff --git a/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java b/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java index 14f2b088..bbe9bd61 100644 --- a/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java +++ b/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java @@ -24,7 +24,8 @@ public class AuthSocial extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @OneToOne(fetch = FetchType.LAZY) + // TODO + @OneToOne @JoinColumn(name = "member_id") private Member member; From fc86637009d137c34a4c7d5ef088b4b2a8b42678 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 11:57:56 +0900 Subject: [PATCH 076/734] =?UTF-8?q?feat:=20Member=20Profile=20repository?= =?UTF-8?q?=20=EC=B6=94=EB=8B=A4=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/repository/MemberProfileRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java b/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java index 30ffed33..2c0bc622 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java @@ -1,10 +1,13 @@ package io.oduck.api.domain.member.repository; import io.oduck.api.domain.member.entity.MemberProfile; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface MemberProfileRepository extends JpaRepository { + Optional findByMemberId(Long memberId); + boolean existsByMemberId(Long memberId); } From 8edce3bb6c8309790c0730f86a8ea75a25838119 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 11:58:26 +0900 Subject: [PATCH 077/734] =?UTF-8?q?feat:=20Auth=20Local=20repository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/auth/repository/AuthLocalRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/io/oduck/api/global/security/auth/repository/AuthLocalRepository.java b/src/main/java/io/oduck/api/global/security/auth/repository/AuthLocalRepository.java index 0370fd60..7583e758 100644 --- a/src/main/java/io/oduck/api/global/security/auth/repository/AuthLocalRepository.java +++ b/src/main/java/io/oduck/api/global/security/auth/repository/AuthLocalRepository.java @@ -2,6 +2,7 @@ import io.oduck.api.global.security.auth.entity.AuthLocal; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -9,4 +10,6 @@ public interface AuthLocalRepository extends JpaRepository { boolean existsByEmail(String email); + + Optional findByEmail(String email); } From 60c921cd03a4955024b2e46b3f2b1e81cd433e68 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 11:59:01 +0900 Subject: [PATCH 078/734] =?UTF-8?q?feat:=20local=20login=EC=97=90=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20DTO=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/security/auth/dto/LocalAuthDto.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/auth/dto/LocalAuthDto.java diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/LocalAuthDto.java b/src/main/java/io/oduck/api/global/security/auth/dto/LocalAuthDto.java new file mode 100644 index 00000000..4a06dc30 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/dto/LocalAuthDto.java @@ -0,0 +1,9 @@ +package io.oduck.api.global.security.auth.dto; + +import lombok.Getter; + +@Getter +public class LocalAuthDto { + private String email; + private String password; +} From 8f3140874307a220214e348e7ccaf0ddc575e3de Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 12:00:07 +0900 Subject: [PATCH 079/734] =?UTF-8?q?refactor:=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ustomOAuth2UserService.java => SocialLoginService.java} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename src/main/java/io/oduck/api/global/security/auth/service/{CustomOAuth2UserService.java => SocialLoginService.java} (95%) diff --git a/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java b/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java similarity index 95% rename from src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java rename to src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java index 782be876..671c8e32 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java @@ -29,7 +29,7 @@ @Slf4j @Service @RequiredArgsConstructor -public class CustomOAuth2UserService implements OAuth2UserService { +public class SocialLoginService implements OAuth2UserService { private final MemberRepository memberRepository; private final AuthSocialRepository authSocialRepository; @@ -59,9 +59,10 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic httpSession.setAttribute("user", new SessionUser(member.getId(), LoginType.SOCIAL)); return new DefaultOAuth2User( - Collections.singleton(new SimpleGrantedAuthority(member.getMemberProfile().getRole().toString())), + Collections.singleton(new SimpleGrantedAuthority(member.getRole().toString())), oAuth2User.getAttributes(), - attributes.getNameAttributeKey()); + attributes.getNameAttributeKey() + ); } private Member getOrSaveMember(AuthSocial authSocial) { From dc17ef07d3ec0e4a2dc3626e9813711a3dffc330 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 12:02:14 +0900 Subject: [PATCH 080/734] =?UTF-8?q?feat:=20local=20login=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=ED=95=84=ED=84=B0=20=EB=B0=8F=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/auth/dto/CustomUserDetails.java | 43 ++++++++++++++++ .../auth/service/LocalLoginService.java | 39 ++++++++++++++ .../filter/LocalAuthenticationFilter.java | 51 +++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java create mode 100644 src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java create mode 100644 src/main/java/io/oduck/api/global/security/filter/LocalAuthenticationFilter.java diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java b/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java new file mode 100644 index 00000000..1b4486b0 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java @@ -0,0 +1,43 @@ +package io.oduck.api.global.security.auth.dto; + +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.Role; +import java.util.Collection; +import java.util.Collections; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; + +@Getter +public class CustomUserDetails extends User { + private Long id; + public CustomUserDetails(Long id, String username, String password, Role role) { + super(username, password, Collections.singletonList(new SimpleGrantedAuthority( + role.toString()))); + this.id = id; + } + + // 아래에서부터는 사용하지 않을 것이므로 전부 true 반환 + @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/io/oduck/api/global/security/auth/service/LocalLoginService.java b/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java new file mode 100644 index 00000000..37c9f52b --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java @@ -0,0 +1,39 @@ +package io.oduck.api.global.security.auth.service; + +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.global.exception.UnauthorizedException; +import io.oduck.api.global.security.auth.dto.CustomUserDetails; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import io.oduck.api.global.security.auth.repository.AuthLocalRepository; +import java.util.Optional; +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 LocalLoginService implements UserDetailsService { + private final AuthLocalRepository authLocalRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + Optional optionalAuthLocal = authLocalRepository.findByEmail(email); + + if (!optionalAuthLocal.isPresent()) { + throw new UnauthorizedException("Unauthorized"); + } + + AuthLocal authLocal = optionalAuthLocal.get(); + + Member member = authLocal.getMember(); + + return new CustomUserDetails( + member.getId(), + authLocal.getEmail(), + authLocal.getPassword(), + member.getRole() + ); + } +} diff --git a/src/main/java/io/oduck/api/global/security/filter/LocalAuthenticationFilter.java b/src/main/java/io/oduck/api/global/security/filter/LocalAuthenticationFilter.java new file mode 100644 index 00000000..9cf30f00 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/filter/LocalAuthenticationFilter.java @@ -0,0 +1,51 @@ +package io.oduck.api.global.security.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.global.security.auth.dto.CustomUserDetails; +import io.oduck.api.global.security.auth.dto.LocalAuthDto; +import io.oduck.api.global.security.auth.dto.SessionUser; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@RequiredArgsConstructor +public class LocalAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + private static final String CONTENT_TYPE = "application/json"; + private final AuthenticationManager authenticationManager; + private final ObjectMapper objectMapper; + + @SneakyThrows + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { + if(request.getContentType() == null || !request.getContentType().equals(CONTENT_TYPE) ) { + throw new AuthenticationServiceException("Authentication Content-Type not supported: " + request.getContentType()); + } + + // objectMapper.readValue(request.getInputStream(), LoginDto.class)를 통해 ServletInputStream 을 LoginDto 클래스의 객체로 역직렬화(Deserialization) + LocalAuthDto loginAuthDto = objectMapper.readValue(request.getInputStream(), LocalAuthDto.class); + UsernamePasswordAuthenticationToken authRequest = + new UsernamePasswordAuthenticationToken(loginAuthDto.getEmail(), loginAuthDto.getPassword()); + + return this.authenticationManager.authenticate(authRequest); + } + + @Override + public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { + CustomUserDetails userDetails = (CustomUserDetails) authResult.getPrincipal(); + SessionUser sessionUser = new SessionUser(userDetails.getId(), LoginType.LOCAL); + HttpSession session = request.getSession(); + session.setAttribute("user", sessionUser); + super.successfulAuthentication(request, response, chain, authResult); + } +} \ No newline at end of file From 7495fedecd6bc77ffeb5f12b32206b8331b87fb0 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 12:03:59 +0900 Subject: [PATCH 081/734] =?UTF-8?q?refactor:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EC=84=A4=EC=A0=95=EC=97=90=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=95=84=ED=84=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index effd13e7..cf74c62c 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -2,17 +2,20 @@ import static org.springframework.security.config.Customizer.withDefaults; +import com.fasterxml.jackson.databind.ObjectMapper; import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.global.security.filter.LocalAuthenticationFilter; import io.oduck.api.global.security.handler.ForbiddenHandler; import io.oduck.api.global.security.handler.LoginFailureHandler; import io.oduck.api.global.security.handler.LoginSuccessHandler; -import io.oduck.api.global.security.auth.service.CustomOAuth2UserService; +import io.oduck.api.global.security.auth.service.SocialLoginService; import io.oduck.api.global.security.handler.LogoutHandler; import io.oduck.api.global.security.handler.UnauthorizedHandler; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -20,6 +23,8 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @RequiredArgsConstructor @@ -27,8 +32,9 @@ @Configuration public class SecurityConfig { + private final ObjectMapper objectMapper; private final LogoutHandler logoutHandler; - private final CustomOAuth2UserService customOAuth2UserService; + private final SocialLoginService socialLoginService; private final LoginSuccessHandler loginSuccessHandler; private final LoginFailureHandler loginFailureHandler; private final ForbiddenHandler forbiddenHandler; @@ -84,18 +90,24 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .invalidateHttpSession(true).permitAll(false) ); + // Custom Configurer : LocalAuthenticationFilter 를 등록하는 역할 + http + .apply(new CustomFilterConfigurer()); + // OAuth2 로그인 설정 http .oauth2Login((oauth2Login) -> oauth2Login .userInfoEndpoint((userInfoEndpoint) -> userInfoEndpoint - .userService(customOAuth2UserService) + .userService(socialLoginService) ) .successHandler(loginSuccessHandler) .failureHandler(loginFailureHandler) ); + // 예외 처리 설정 + // 인증 인가 예외 처리 http .exceptionHandling((exceptionHandling) -> exceptionHandling @@ -111,8 +123,50 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public PasswordEncoder passwordEncoder() { // Spring Security 에서 지원하는 PasswordEncoder 구현 객체를 생성해주는 컴포넌트. // DelegatingPasswordEncoder를 통해 애플리케이션에서 사용할 PasswordEncoder 를 결정하고, - // 결정된 PasswordEncoder로 사용자가 입력한 패스워드를 단방향으로 암호화 해준다. + // 결정된 PasswordEncoder로 사용자가 입력한 패스워드를 단방향으로 암호화 해줌. // 기본 BCrypt return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } + + @Bean + public AuthenticationManager authenticationManager( + AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + +// // Custom Configurer : CustomFilterConfigurer 는 직접 구현한 필터인 JwtAuthenticationFilter 를 등록하는 역할 + public class CustomFilterConfigurer extends AbstractHttpConfigurer { + // AbstractHttpConfigurer 를 상속하여 구현 + // AbstractHttpConfigurer 을 지정 + + // configure() 메서드를 오버라이드해서 Configuration 을 커스터마이징 + @Override + public void configure(HttpSecurity builder) throws Exception { + + // getSharedObject() 를 통해서 Spring Security 의 설정을 구성하는 SecurityConfigurer 간에 공유되는 객체를 획득가능 + // getSharedObject(AuthenticationManager.class)를 통해 AuthenticationManager 객체 획득 + AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); + + // LocalAuthenticationFilter 생성 + LocalAuthenticationFilter localAuthenticationFilter = new LocalAuthenticationFilter(authenticationManager, objectMapper); + + // url과 httpMethod 를 통해 요청을 필터링하는데 사용할 RequestMatcher 를 지정 + localAuthenticationFilter.setRequiresAuthenticationRequestMatcher( + new AntPathRequestMatcher("/auth/login", "POST") + ); + + // 인증에 성공하면 SecurityContext 에 Authentication 객체를 저장하고 로드하는데, + // 이러한 역할을 SecurityContextRepository 클래스에서 담당. + SecurityContextRepository contextRepository = new HttpSessionSecurityContextRepository(); + localAuthenticationFilter.setSecurityContextRepository(contextRepository); + + // 추가 처리 Custom Handler 를 필터에 등록 + localAuthenticationFilter.setAuthenticationSuccessHandler(loginSuccessHandler); + localAuthenticationFilter.setAuthenticationFailureHandler(loginFailureHandler); + + // Spring Security Filter Chain에 추가 + builder + .addFilter(localAuthenticationFilter); + } + } } From 952f43401aae4caa2a8f534a9f8add8f048910a2 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 12:07:21 +0900 Subject: [PATCH 082/734] =?UTF-8?q?refactor:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EC=9D=B8=EC=A6=9D=20=ED=95=84=ED=84=B0=20=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EC=9D=B8=EC=A6=9D=20=EA=B5=AC=ED=98=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/auth/dto/CustomUserDetails.java | 86 +++++++-------- .../auth/service/LocalLoginService.java | 78 +++++++------- .../security/config/SecurityConfig.java | 74 ++++++------- .../filter/LocalAuthenticationFilter.java | 102 +++++++++--------- 4 files changed, 170 insertions(+), 170 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java b/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java index 1b4486b0..f040c8e9 100644 --- a/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java +++ b/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java @@ -1,43 +1,43 @@ -package io.oduck.api.global.security.auth.dto; - -import io.oduck.api.domain.member.entity.Member; -import io.oduck.api.domain.member.entity.Role; -import java.util.Collection; -import java.util.Collections; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; - -@Getter -public class CustomUserDetails extends User { - private Long id; - public CustomUserDetails(Long id, String username, String password, Role role) { - super(username, password, Collections.singletonList(new SimpleGrantedAuthority( - role.toString()))); - this.id = id; - } - - // 아래에서부터는 사용하지 않을 것이므로 전부 true 반환 - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } -} +//package io.oduck.api.global.security.auth.dto; +// +//import io.oduck.api.domain.member.entity.Member; +//import io.oduck.api.domain.member.entity.Role; +//import java.util.Collection; +//import java.util.Collections; +//import lombok.Builder; +//import lombok.Getter; +//import lombok.NoArgsConstructor; +//import org.springframework.security.core.GrantedAuthority; +//import org.springframework.security.core.authority.SimpleGrantedAuthority; +//import org.springframework.security.core.userdetails.User; +// +//@Getter +//public class CustomUserDetails extends User { +// private Long id; +// public CustomUserDetails(Long id, String username, String password, Role role) { +// super(username, password, Collections.singletonList(new SimpleGrantedAuthority( +// role.toString()))); +// this.id = id; +// } +// +// // 아래에서부터는 사용하지 않을 것이므로 전부 true 반환 +// @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/io/oduck/api/global/security/auth/service/LocalLoginService.java b/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java index 37c9f52b..9b40f88d 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java @@ -1,39 +1,39 @@ -package io.oduck.api.global.security.auth.service; - -import io.oduck.api.domain.member.entity.Member; -import io.oduck.api.global.exception.UnauthorizedException; -import io.oduck.api.global.security.auth.dto.CustomUserDetails; -import io.oduck.api.global.security.auth.entity.AuthLocal; -import io.oduck.api.global.security.auth.repository.AuthLocalRepository; -import java.util.Optional; -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 LocalLoginService implements UserDetailsService { - private final AuthLocalRepository authLocalRepository; - - @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - Optional optionalAuthLocal = authLocalRepository.findByEmail(email); - - if (!optionalAuthLocal.isPresent()) { - throw new UnauthorizedException("Unauthorized"); - } - - AuthLocal authLocal = optionalAuthLocal.get(); - - Member member = authLocal.getMember(); - - return new CustomUserDetails( - member.getId(), - authLocal.getEmail(), - authLocal.getPassword(), - member.getRole() - ); - } -} +//package io.oduck.api.global.security.auth.service; +// +//import io.oduck.api.domain.member.entity.Member; +//import io.oduck.api.global.exception.UnauthorizedException; +//import io.oduck.api.global.security.auth.dto.CustomUserDetails; +//import io.oduck.api.global.security.auth.entity.AuthLocal; +//import io.oduck.api.global.security.auth.repository.AuthLocalRepository; +//import java.util.Optional; +//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 LocalLoginService implements UserDetailsService { +// private final AuthLocalRepository authLocalRepository; +// +// @Override +// public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { +// Optional optionalAuthLocal = authLocalRepository.findByEmail(email); +// +// if (!optionalAuthLocal.isPresent()) { +// throw new UnauthorizedException("Unauthorized"); +// } +// +// AuthLocal authLocal = optionalAuthLocal.get(); +// +// Member member = authLocal.getMember(); +// +// return new CustomUserDetails( +// member.getId(), +// authLocal.getEmail(), +// authLocal.getPassword(), +// member.getRole() +// ); +// } +//} diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index cf74c62c..358f7287 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.oduck.api.domain.member.entity.Role; -import io.oduck.api.global.security.filter.LocalAuthenticationFilter; +//import io.oduck.api.global.security.filter.LocalAuthenticationFilter; import io.oduck.api.global.security.handler.ForbiddenHandler; import io.oduck.api.global.security.handler.LoginFailureHandler; import io.oduck.api.global.security.handler.LoginSuccessHandler; @@ -23,16 +23,16 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +//import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +//import org.springframework.security.web.context.SecurityContextRepository; +//import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @RequiredArgsConstructor @EnableWebSecurity @Configuration public class SecurityConfig { - private final ObjectMapper objectMapper; +// private final ObjectMapper objectMapper; private final LogoutHandler logoutHandler; private final SocialLoginService socialLoginService; private final LoginSuccessHandler loginSuccessHandler; @@ -91,8 +91,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ); // Custom Configurer : LocalAuthenticationFilter 를 등록하는 역할 - http - .apply(new CustomFilterConfigurer()); +// http +// .apply(new CustomFilterConfigurer()); // OAuth2 로그인 설정 http @@ -135,38 +135,38 @@ public AuthenticationManager authenticationManager( } // // Custom Configurer : CustomFilterConfigurer 는 직접 구현한 필터인 JwtAuthenticationFilter 를 등록하는 역할 - public class CustomFilterConfigurer extends AbstractHttpConfigurer { +// public class CustomFilterConfigurer extends AbstractHttpConfigurer { // AbstractHttpConfigurer 를 상속하여 구현 // AbstractHttpConfigurer 을 지정 // configure() 메서드를 오버라이드해서 Configuration 을 커스터마이징 - @Override - public void configure(HttpSecurity builder) throws Exception { - - // getSharedObject() 를 통해서 Spring Security 의 설정을 구성하는 SecurityConfigurer 간에 공유되는 객체를 획득가능 - // getSharedObject(AuthenticationManager.class)를 통해 AuthenticationManager 객체 획득 - AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); - - // LocalAuthenticationFilter 생성 - LocalAuthenticationFilter localAuthenticationFilter = new LocalAuthenticationFilter(authenticationManager, objectMapper); - - // url과 httpMethod 를 통해 요청을 필터링하는데 사용할 RequestMatcher 를 지정 - localAuthenticationFilter.setRequiresAuthenticationRequestMatcher( - new AntPathRequestMatcher("/auth/login", "POST") - ); - - // 인증에 성공하면 SecurityContext 에 Authentication 객체를 저장하고 로드하는데, - // 이러한 역할을 SecurityContextRepository 클래스에서 담당. - SecurityContextRepository contextRepository = new HttpSessionSecurityContextRepository(); - localAuthenticationFilter.setSecurityContextRepository(contextRepository); - - // 추가 처리 Custom Handler 를 필터에 등록 - localAuthenticationFilter.setAuthenticationSuccessHandler(loginSuccessHandler); - localAuthenticationFilter.setAuthenticationFailureHandler(loginFailureHandler); - - // Spring Security Filter Chain에 추가 - builder - .addFilter(localAuthenticationFilter); - } - } +// @Override +// public void configure(HttpSecurity builder) throws Exception { +// +// // getSharedObject() 를 통해서 Spring Security 의 설정을 구성하는 SecurityConfigurer 간에 공유되는 객체를 획득가능 +// // getSharedObject(AuthenticationManager.class)를 통해 AuthenticationManager 객체 획득 +// AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); +// +// // LocalAuthenticationFilter 생성 +// LocalAuthenticationFilter localAuthenticationFilter = new LocalAuthenticationFilter(authenticationManager, objectMapper); +// +// // url과 httpMethod 를 통해 요청을 필터링하는데 사용할 RequestMatcher 를 지정 +// localAuthenticationFilter.setRequiresAuthenticationRequestMatcher( +// new AntPathRequestMatcher("/auth/login", "POST") +// ); +// +// // 인증에 성공하면 SecurityContext 에 Authentication 객체를 저장하고 로드하는데, +// // 이러한 역할을 SecurityContextRepository 클래스에서 담당. +// SecurityContextRepository contextRepository = new HttpSessionSecurityContextRepository(); +// localAuthenticationFilter.setSecurityContextRepository(contextRepository); +// +// // 추가 처리 Custom Handler 를 필터에 등록 +// localAuthenticationFilter.setAuthenticationSuccessHandler(loginSuccessHandler); +// localAuthenticationFilter.setAuthenticationFailureHandler(loginFailureHandler); +// +// // Spring Security Filter Chain에 추가 +// builder +// .addFilter(localAuthenticationFilter); +// } +// } } diff --git a/src/main/java/io/oduck/api/global/security/filter/LocalAuthenticationFilter.java b/src/main/java/io/oduck/api/global/security/filter/LocalAuthenticationFilter.java index 9cf30f00..9bc96c40 100644 --- a/src/main/java/io/oduck/api/global/security/filter/LocalAuthenticationFilter.java +++ b/src/main/java/io/oduck/api/global/security/filter/LocalAuthenticationFilter.java @@ -1,51 +1,51 @@ -package io.oduck.api.global.security.filter; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.oduck.api.domain.member.entity.LoginType; -import io.oduck.api.global.security.auth.dto.CustomUserDetails; -import io.oduck.api.global.security.auth.dto.LocalAuthDto; -import io.oduck.api.global.security.auth.dto.SessionUser; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import java.io.IOException; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - -@RequiredArgsConstructor -public class LocalAuthenticationFilter extends UsernamePasswordAuthenticationFilter { - private static final String CONTENT_TYPE = "application/json"; - private final AuthenticationManager authenticationManager; - private final ObjectMapper objectMapper; - - @SneakyThrows - @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { - if(request.getContentType() == null || !request.getContentType().equals(CONTENT_TYPE) ) { - throw new AuthenticationServiceException("Authentication Content-Type not supported: " + request.getContentType()); - } - - // objectMapper.readValue(request.getInputStream(), LoginDto.class)를 통해 ServletInputStream 을 LoginDto 클래스의 객체로 역직렬화(Deserialization) - LocalAuthDto loginAuthDto = objectMapper.readValue(request.getInputStream(), LocalAuthDto.class); - UsernamePasswordAuthenticationToken authRequest = - new UsernamePasswordAuthenticationToken(loginAuthDto.getEmail(), loginAuthDto.getPassword()); - - return this.authenticationManager.authenticate(authRequest); - } - - @Override - public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { - CustomUserDetails userDetails = (CustomUserDetails) authResult.getPrincipal(); - SessionUser sessionUser = new SessionUser(userDetails.getId(), LoginType.LOCAL); - HttpSession session = request.getSession(); - session.setAttribute("user", sessionUser); - super.successfulAuthentication(request, response, chain, authResult); - } -} \ No newline at end of file +//package io.oduck.api.global.security.filter; +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import io.oduck.api.domain.member.entity.LoginType; +//import io.oduck.api.global.security.auth.dto.CustomUserDetails; +//import io.oduck.api.global.security.auth.dto.LocalAuthDto; +//import io.oduck.api.global.security.auth.dto.SessionUser; +//import jakarta.servlet.FilterChain; +//import jakarta.servlet.ServletException; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import jakarta.servlet.http.HttpSession; +//import java.io.IOException; +//import lombok.RequiredArgsConstructor; +//import lombok.SneakyThrows; +//import org.springframework.security.authentication.AuthenticationManager; +//import org.springframework.security.authentication.AuthenticationServiceException; +//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +//import org.springframework.security.core.Authentication; +//import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +// +//@RequiredArgsConstructor +//public class LocalAuthenticationFilter extends UsernamePasswordAuthenticationFilter { +// private static final String CONTENT_TYPE = "application/json"; +// private final AuthenticationManager authenticationManager; +// private final ObjectMapper objectMapper; +// +// @SneakyThrows +// @Override +// public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { +// if(request.getContentType() == null || !request.getContentType().equals(CONTENT_TYPE) ) { +// throw new AuthenticationServiceException("Authentication Content-Type not supported: " + request.getContentType()); +// } +// +// // objectMapper.readValue(request.getInputStream(), LoginDto.class)를 통해 ServletInputStream 을 LoginDto 클래스의 객체로 역직렬화(Deserialization) +// LocalAuthDto loginAuthDto = objectMapper.readValue(request.getInputStream(), LocalAuthDto.class); +// UsernamePasswordAuthenticationToken authRequest = +// new UsernamePasswordAuthenticationToken(loginAuthDto.getEmail(), loginAuthDto.getPassword()); +// +// return this.authenticationManager.authenticate(authRequest); +// } +// +// @Override +// public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { +// CustomUserDetails userDetails = (CustomUserDetails) authResult.getPrincipal(); +// SessionUser sessionUser = new SessionUser(userDetails.getId(), LoginType.LOCAL); +// HttpSession session = request.getSession(); +// session.setAttribute("user", sessionUser); +// super.successfulAuthentication(request, response, chain, authResult); +// } +//} \ No newline at end of file From 0a3ac4d2f229e5959ca8dc7fa9c282cdf711ca25 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 12:09:39 +0900 Subject: [PATCH 083/734] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=B0=8F=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 21 ++++++ .../security/auth/service/AuthService.java | 70 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/auth/service/AuthService.java diff --git a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java index 6e514529..d1610e92 100644 --- a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java +++ b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java @@ -1,15 +1,36 @@ package io.oduck.api.global.security.auth.controller; +import io.oduck.api.global.common.SingleResponse; import io.oduck.api.global.exception.UnauthorizedException; +import io.oduck.api.global.security.auth.dto.AuthResDto.Status; +import io.oduck.api.global.security.auth.dto.LocalAuthDto; import io.oduck.api.global.security.auth.dto.SessionUser; import io.oduck.api.global.security.auth.dto.LoginUser; +import io.oduck.api.global.security.auth.service.AuthService; +import java.net.URI; +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.RestController; @RestController @RequestMapping("/auth") +@RequiredArgsConstructor public class AuthController { + private final AuthService authService; + + @PostMapping("/login") + public ResponseEntity login( + @RequestBody LocalAuthDto localAuthDto + ) { + + authService.login(localAuthDto); + return ResponseEntity.status(302).location(URI.create("http://localhost:5173")).build(); + } + @GetMapping("/status") public String status( @LoginUser SessionUser user diff --git a/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java b/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java new file mode 100644 index 00000000..7a8d16fd --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java @@ -0,0 +1,70 @@ +package io.oduck.api.global.security.auth.service; + +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.domain.member.repository.MemberProfileRepository; +import io.oduck.api.global.exception.NotFoundException; +import io.oduck.api.global.exception.UnauthorizedException; +import io.oduck.api.global.security.auth.dto.AuthResDto.Status; +import io.oduck.api.global.security.auth.dto.LocalAuthDto; +import io.oduck.api.global.security.auth.dto.SessionUser; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import io.oduck.api.global.security.auth.entity.AuthSocial; +import io.oduck.api.global.security.auth.entity.SocialType; +import io.oduck.api.global.security.auth.repository.AuthLocalRepository; +import io.oduck.api.global.security.auth.repository.AuthSocialRepository; +import jakarta.servlet.http.HttpSession; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthService { + private final AuthLocalRepository authLocalRepository; + private final AuthenticationManager authenticationManager; + private final HttpSession httpSession; + + public AuthLocal getAuthLocal(String email) { + return authLocalRepository.findByEmail(email) + .orElseThrow(() -> new NotFoundException("Member")); + } + + public void login(LocalAuthDto localAuthDto) { + String username = localAuthDto.getEmail(); + + + AuthLocal authLocal = getAuthLocal(username); + Role role = authLocal.getMember().getRole(); + + String password = extractPasswordIfAdmin(role, localAuthDto.getPassword()); + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); + Authentication authentication = authenticationManager.authenticate(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + httpSession.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + SecurityContextHolder.getContext()); + httpSession.setAttribute("user", new SessionUser(authLocal.getMember().getId(), LoginType.LOCAL)); + } + + private String extractPasswordIfAdmin(Role role, String password) { + if (role.equals(Role.MEMBER)) return password; + + LocalTime now = LocalTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmm"); + + String currentTime = password.substring(0, 4); + if (!now.format(formatter).equals(currentTime)) { + throw new UnauthorizedException("Unauthorized"); + } + + return password.substring(4); + } +} From 7aa9dd1862f2f362e55b57b0758ff6748877b9e3 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 12:10:38 +0900 Subject: [PATCH 084/734] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20dto=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/security/auth/dto/AuthResDto.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/security/auth/dto/AuthResDto.java diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/AuthResDto.java b/src/main/java/io/oduck/api/global/security/auth/dto/AuthResDto.java new file mode 100644 index 00000000..dad11383 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/dto/AuthResDto.java @@ -0,0 +1,21 @@ +package io.oduck.api.global.security.auth.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +public class AuthResDto { + @Builder + @NoArgsConstructor(access = AccessLevel.PROTECTED) + @AllArgsConstructor + @Getter + public static class Status { + private String name; + private String description; + private String thumbnail; + private Long point; + } +} From f0ae96af29fb89605b770b63e07da370438383af Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 9 Oct 2023 12:11:02 +0900 Subject: [PATCH 085/734] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20api=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/auth/controller/AuthController.java | 9 +++------ .../global/security/auth/service/AuthService.java | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java index d1610e92..23d08eb0 100644 --- a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java +++ b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java @@ -1,7 +1,6 @@ package io.oduck.api.global.security.auth.controller; import io.oduck.api.global.common.SingleResponse; -import io.oduck.api.global.exception.UnauthorizedException; import io.oduck.api.global.security.auth.dto.AuthResDto.Status; import io.oduck.api.global.security.auth.dto.LocalAuthDto; import io.oduck.api.global.security.auth.dto.SessionUser; @@ -32,14 +31,12 @@ public ResponseEntity login( } @GetMapping("/status") - public String status( + public ResponseEntity status( @LoginUser SessionUser user ) { - if (user == null) { - throw new UnauthorizedException("Unauthorized"); - } + Status res = authService.getStatus(user.getId()); - return "success"; + return ResponseEntity.ok(SingleResponse.of(res)); } } diff --git a/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java b/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java index 7a8d16fd..efb6b8b6 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java @@ -10,8 +10,6 @@ import io.oduck.api.global.security.auth.dto.LocalAuthDto; import io.oduck.api.global.security.auth.dto.SessionUser; import io.oduck.api.global.security.auth.entity.AuthLocal; -import io.oduck.api.global.security.auth.entity.AuthSocial; -import io.oduck.api.global.security.auth.entity.SocialType; import io.oduck.api.global.security.auth.repository.AuthLocalRepository; import io.oduck.api.global.security.auth.repository.AuthSocialRepository; import jakarta.servlet.http.HttpSession; @@ -30,8 +28,21 @@ public class AuthService { private final AuthLocalRepository authLocalRepository; private final AuthenticationManager authenticationManager; + private final MemberProfileRepository memberProfileRepository; private final HttpSession httpSession; + public Status getStatus(Long memberId) { + MemberProfile memberProfile = memberProfileRepository.findByMemberId(memberId) + .orElseThrow(() -> new NotFoundException("Member")); + + return Status.builder() + .name(memberProfile.getName()) + .description(memberProfile.getInfo()) + .thumbnail(memberProfile.getThumbnail()) + .point(memberProfile.getPoint()) + .build(); + } + public AuthLocal getAuthLocal(String email) { return authLocalRepository.findByEmail(email) .orElseThrow(() -> new NotFoundException("Member")); From f43144641dbe8b8d272894aef47dab517f813313 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Mon, 9 Oct 2023 20:34:13 +0900 Subject: [PATCH 086/734] =?UTF-8?q?cleanup:=20createAt=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EB=B0=8F=20BaseEntity=20=EC=B6=94=EA=B0=80#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/reviewLike/entity/ShortReviewLike.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java index bd1b9db0..fa43497b 100644 --- a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java +++ b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java @@ -2,7 +2,7 @@ import io.oduck.api.domain.member.entity.Member; import io.oduck.api.domain.review.entity.ShortReview; -import jakarta.persistence.Column; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -10,15 +10,13 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.CreationTimestamp; @Entity @Getter @NoArgsConstructor -public class ShortReviewLike { +public class ShortReviewLike extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -31,8 +29,4 @@ public class ShortReviewLike { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; - - @CreationTimestamp - @Column(nullable = false, updatable = false) - protected LocalDateTime createdAt; } From ccec2e87c286ccb2154f748b52885362f1f41961 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Mon, 9 Oct 2023 22:09:43 +0900 Subject: [PATCH 087/734] =?UTF-8?q?refactor:=20has=5Fspoiler=20->=20hasSpo?= =?UTF-8?q?iler=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=85=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/review/entity/ShortReview.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java index 26601b4f..27a35492 100644 --- a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java +++ b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java @@ -38,7 +38,7 @@ public class ShortReview extends BaseEntity { private String content; @Column(nullable = false) - private boolean has_spoiler; + private boolean hasSpoiler; @OneToMany(mappedBy = "shortReview", cascade = CascadeType.PERSIST) private List shortReviewLikes; From a012384df8560f305f437171ef44004d3146e473 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:17:14 +0900 Subject: [PATCH 088/734] refactor: SessionUser -> AuthUser #13 --- .../security/auth/dto/{SessionUser.java => AuthUser.java} | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) rename src/main/java/io/oduck/api/global/security/auth/dto/{SessionUser.java => AuthUser.java} (67%) diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/SessionUser.java b/src/main/java/io/oduck/api/global/security/auth/dto/AuthUser.java similarity index 67% rename from src/main/java/io/oduck/api/global/security/auth/dto/SessionUser.java rename to src/main/java/io/oduck/api/global/security/auth/dto/AuthUser.java index 60eede17..e36eb6dd 100644 --- a/src/main/java/io/oduck/api/global/security/auth/dto/SessionUser.java +++ b/src/main/java/io/oduck/api/global/security/auth/dto/AuthUser.java @@ -2,14 +2,16 @@ import io.oduck.api.domain.member.entity.LoginType; import java.io.Serializable; +import lombok.Builder; import lombok.Getter; @Getter -public class SessionUser implements Serializable { +public class AuthUser implements Serializable { private Long id; private LoginType loginType; - public SessionUser(Long id, LoginType loginType) { + @Builder + public AuthUser(Long id, LoginType loginType) { this.id = id; this.loginType = loginType; } From d30537473a26483523bca426e2b62fe8964a195b Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:17:47 +0900 Subject: [PATCH 089/734] =?UTF-8?q?refactor:=20=EC=84=B8=EC=85=98=EC=9D=B4?= =?UTF-8?q?=20=EC=95=84=EB=8B=8C=20Authentication=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=A0=95=EB=B3=B4=20=ED=9A=8D=EB=93=9D=20?= =?UTF-8?q?#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resolver/LoginUserArgumentResolver.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/resolver/LoginUserArgumentResolver.java b/src/main/java/io/oduck/api/global/security/resolver/LoginUserArgumentResolver.java index 27eb4b37..4b907366 100644 --- a/src/main/java/io/oduck/api/global/security/resolver/LoginUserArgumentResolver.java +++ b/src/main/java/io/oduck/api/global/security/resolver/LoginUserArgumentResolver.java @@ -1,10 +1,13 @@ package io.oduck.api.global.security.resolver; +import io.oduck.api.global.security.auth.dto.CustomOAuth2User; +import io.oduck.api.global.security.auth.dto.CustomUserDetails; import io.oduck.api.global.security.auth.dto.LoginUser; -import io.oduck.api.global.security.auth.dto.SessionUser; +import io.oduck.api.global.security.auth.dto.AuthUser; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; @@ -20,7 +23,7 @@ public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver @Override public boolean supportsParameter(MethodParameter parameter) { boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null; - boolean isUserClass = SessionUser.class.equals(parameter.getParameterType()); + boolean isUserClass = AuthUser.class.equals(parameter.getParameterType()); return isLoginUserAnnotation && isUserClass; } @@ -31,8 +34,44 @@ public Object resolveArgument( NativeWebRequest webRequest, WebDataBinderFactory binderFactory ) throws Exception { - SessionUser user = (SessionUser) httpSession.getAttribute("user"); +// Object user = httpSession.getAttribute("user"); + + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + + if (principal == "anonymousUser" || principal == null) { + return null; + } + + AuthUser user; + + if (principal.getClass().getSimpleName().equals("CustomUserDetails")) { + user = getAuthUserFromCustomUserDetails(principal); + } else { + user = getAuthUserFromOAuth2User(principal); + } return user; } + + private AuthUser getAuthUserFromOAuth2User(Object principal) { + return AuthUser.builder() + .id( + ((CustomOAuth2User)principal).getId() + ) + .loginType( + ((CustomOAuth2User)principal).getLoginType() + ) + .build(); + } + + private AuthUser getAuthUserFromCustomUserDetails(Object principal) { + return AuthUser.builder() + .id( + ((CustomUserDetails)principal).getId() + ) + .loginType( + ((CustomUserDetails)principal).getLoginType() + ) + .build(); + } } \ No newline at end of file From 73169fc47cd79636b747f519ad06a0995704563f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:23:39 +0900 Subject: [PATCH 090/734] =?UTF-8?q?refactor:=20Member=20Entity=20setter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/entity/Member.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/entity/Member.java b/src/main/java/io/oduck/api/domain/member/entity/Member.java index 881eb76c..d9c95ee8 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/Member.java +++ b/src/main/java/io/oduck/api/domain/member/entity/Member.java @@ -26,9 +26,9 @@ @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) @AllArgsConstructor -@Builder public class Member extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -71,18 +71,29 @@ public Member(Long id, Role role, LoginType loginType, AuthSocial authSocial, A this.role = role; this.loginType = loginType; this.authSocial = authSocial; + this.authLocal = authLocal; + this.memberProfile = memberProfile; + } - if (authSocial != null) { + // TODO: set 말고 다른 이름으로 변경하기 + public void setAuthSocial(AuthSocial authSocial) { + this.authSocial = authSocial; + if(authSocial != null) { authSocial.setMember(this); } + } + public void setAuthLocal(AuthLocal authLocal) { this.authLocal = authLocal; - - if (authLocal != null) { + if(authLocal != null) { authLocal.setMember(this); } + } + public void setMemberProfile(MemberProfile memberProfile) { this.memberProfile = memberProfile; - memberProfile.setMember(this); + if(memberProfile != null) { + memberProfile.setMember(this); + } } } From 107d1906fbd145a017cfbaa1f2b038295d4aeaf7 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:27:42 +0900 Subject: [PATCH 091/734] =?UTF-8?q?refactor:=20SessionUser=20->=20AuthUser?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=EB=94=B0=EB=A5=B8=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=88=98=EC=A0=95=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/security/auth/controller/AuthController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java index 23d08eb0..5d5e1fe8 100644 --- a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java +++ b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java @@ -3,7 +3,7 @@ import io.oduck.api.global.common.SingleResponse; import io.oduck.api.global.security.auth.dto.AuthResDto.Status; import io.oduck.api.global.security.auth.dto.LocalAuthDto; -import io.oduck.api.global.security.auth.dto.SessionUser; +import io.oduck.api.global.security.auth.dto.AuthUser; import io.oduck.api.global.security.auth.dto.LoginUser; import io.oduck.api.global.security.auth.service.AuthService; import java.net.URI; @@ -32,7 +32,7 @@ public ResponseEntity login( @GetMapping("/status") public ResponseEntity status( - @LoginUser SessionUser user + @LoginUser AuthUser user ) { Status res = authService.getStatus(user.getId()); From db126af2c1a9ea00c5da5109d189799b8869c02e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:27:59 +0900 Subject: [PATCH 092/734] =?UTF-8?q?refactor:=20setter=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/member/service/MemberServiceStub.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java index 4cd9d47c..671b8bca 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java @@ -53,10 +53,11 @@ public void signUpByLocal(CreateReq createReq) { Member member = Member.builder() .loginType(LoginType.LOCAL) - .authLocal(authLocal) - .memberProfile(memberProfile) .build(); + member.setAuthLocal(authLocal); + member.setMemberProfile(memberProfile); + Member savedMember = memberRepository.save(member); log.info("Member Created! {}", savedMember.getId()); } catch (Exception e) { From 88efff92e550445d03134d3719acf8f060dd2d0f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:29:25 +0900 Subject: [PATCH 093/734] =?UTF-8?q?feat:=20Authentication=EC=97=90=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=88=20OAuthUser=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/auth/dto/CustomOAuth2User.java | 32 +++++++ .../security/auth/dto/CustomUserDetails.java | 84 +++++++++---------- 2 files changed, 73 insertions(+), 43 deletions(-) create mode 100644 src/main/java/io/oduck/api/global/security/auth/dto/CustomOAuth2User.java diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/CustomOAuth2User.java b/src/main/java/io/oduck/api/global/security/auth/dto/CustomOAuth2User.java new file mode 100644 index 00000000..c0bb1523 --- /dev/null +++ b/src/main/java/io/oduck/api/global/security/auth/dto/CustomOAuth2User.java @@ -0,0 +1,32 @@ +package io.oduck.api.global.security.auth.dto; + +import io.oduck.api.domain.member.entity.LoginType; +import java.util.Collection; +import java.util.Map; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; + +@Getter +public class CustomOAuth2User extends DefaultOAuth2User { + private long id; + private LoginType loginType; + + /** + * Constructs a {@code DefaultOAuth2User} using the provided parameters. + * + * @param authorities the authorities granted to the user + * @param attributes the attributes about the user + * @param nameAttributeKey the key used to access the user's "name" from + * {@link #getAttributes()} + */ + public CustomOAuth2User( + long id, + Collection authorities, + Map attributes, String nameAttributeKey + ) { + super(authorities, attributes, nameAttributeKey); + this.id = id; + this.loginType = LoginType.SOCIAL; + } +} diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java b/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java index f040c8e9..d8959907 100644 --- a/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java +++ b/src/main/java/io/oduck/api/global/security/auth/dto/CustomUserDetails.java @@ -1,43 +1,41 @@ -//package io.oduck.api.global.security.auth.dto; -// -//import io.oduck.api.domain.member.entity.Member; -//import io.oduck.api.domain.member.entity.Role; -//import java.util.Collection; -//import java.util.Collections; -//import lombok.Builder; -//import lombok.Getter; -//import lombok.NoArgsConstructor; -//import org.springframework.security.core.GrantedAuthority; -//import org.springframework.security.core.authority.SimpleGrantedAuthority; -//import org.springframework.security.core.userdetails.User; -// -//@Getter -//public class CustomUserDetails extends User { -// private Long id; -// public CustomUserDetails(Long id, String username, String password, Role role) { -// super(username, password, Collections.singletonList(new SimpleGrantedAuthority( -// role.toString()))); -// this.id = id; -// } -// -// // 아래에서부터는 사용하지 않을 것이므로 전부 true 반환 -// @Override -// public boolean isAccountNonExpired() { -// return true; -// } -// -// @Override -// public boolean isAccountNonLocked() { -// return true; -// } -// -// @Override -// public boolean isCredentialsNonExpired() { -// return true; -// } -// -// @Override -// public boolean isEnabled() { -// return true; -// } -//} +package io.oduck.api.global.security.auth.dto; + +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.domain.member.entity.Role; +import java.util.Collections; +import lombok.Getter; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; + +@Getter +public class CustomUserDetails extends User { + private Long id; + private LoginType loginType; + public CustomUserDetails(Long id, String username, String password, Role role) { + super(username, password, Collections.singletonList(new SimpleGrantedAuthority( + role.toString()))); + this.id = id; + this.loginType = LoginType.LOCAL; + } + + // 아래에서부터는 사용하지 않을 것이므로 전부 true 반환 + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} From eeceeee3e2edc8aef6d1648bc515a490e8fda7a3 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:30:17 +0900 Subject: [PATCH 094/734] =?UTF-8?q?refactor:=20Test=EC=97=90=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20Builder=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/security/auth/dto/LocalAuthDto.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/io/oduck/api/global/security/auth/dto/LocalAuthDto.java b/src/main/java/io/oduck/api/global/security/auth/dto/LocalAuthDto.java index 4a06dc30..9a777dd0 100644 --- a/src/main/java/io/oduck/api/global/security/auth/dto/LocalAuthDto.java +++ b/src/main/java/io/oduck/api/global/security/auth/dto/LocalAuthDto.java @@ -1,9 +1,16 @@ package io.oduck.api.global.security.auth.dto; +import lombok.Builder; import lombok.Getter; @Getter public class LocalAuthDto { private String email; private String password; + + @Builder + public LocalAuthDto(String email, String password) { + this.email = email; + this.password = password; + } } From 5ae720aff82e1b893bab1f241b2cc3140e8c44ec Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:32:09 +0900 Subject: [PATCH 095/734] =?UTF-8?q?refactor:=20LocalLogin=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20Service=20=EC=A3=BC=EC=84=9D=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/LocalLoginService.java | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java b/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java index 9b40f88d..37c9f52b 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/LocalLoginService.java @@ -1,39 +1,39 @@ -//package io.oduck.api.global.security.auth.service; -// -//import io.oduck.api.domain.member.entity.Member; -//import io.oduck.api.global.exception.UnauthorizedException; -//import io.oduck.api.global.security.auth.dto.CustomUserDetails; -//import io.oduck.api.global.security.auth.entity.AuthLocal; -//import io.oduck.api.global.security.auth.repository.AuthLocalRepository; -//import java.util.Optional; -//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 LocalLoginService implements UserDetailsService { -// private final AuthLocalRepository authLocalRepository; -// -// @Override -// public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { -// Optional optionalAuthLocal = authLocalRepository.findByEmail(email); -// -// if (!optionalAuthLocal.isPresent()) { -// throw new UnauthorizedException("Unauthorized"); -// } -// -// AuthLocal authLocal = optionalAuthLocal.get(); -// -// Member member = authLocal.getMember(); -// -// return new CustomUserDetails( -// member.getId(), -// authLocal.getEmail(), -// authLocal.getPassword(), -// member.getRole() -// ); -// } -//} +package io.oduck.api.global.security.auth.service; + +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.global.exception.UnauthorizedException; +import io.oduck.api.global.security.auth.dto.CustomUserDetails; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import io.oduck.api.global.security.auth.repository.AuthLocalRepository; +import java.util.Optional; +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 LocalLoginService implements UserDetailsService { + private final AuthLocalRepository authLocalRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + Optional optionalAuthLocal = authLocalRepository.findByEmail(email); + + if (!optionalAuthLocal.isPresent()) { + throw new UnauthorizedException("Unauthorized"); + } + + AuthLocal authLocal = optionalAuthLocal.get(); + + Member member = authLocal.getMember(); + + return new CustomUserDetails( + member.getId(), + authLocal.getEmail(), + authLocal.getPassword(), + member.getRole() + ); + } +} From e78faf1a3a340e8b2c854c772322e4b636968b8b Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:34:11 +0900 Subject: [PATCH 096/734] =?UTF-8?q?refactor:=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Authentication에 등록될 CustomOAuth2User로 변경 - Setter 추가 --- .../auth/service/SocialLoginService.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java b/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java index 671c8e32..61b8d226 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java @@ -2,7 +2,8 @@ import static io.oduck.api.global.utils.NameGenerator.generateNickname; -import io.oduck.api.global.security.auth.dto.SessionUser; +import io.oduck.api.global.security.auth.dto.AuthUser; +import io.oduck.api.global.security.auth.dto.CustomOAuth2User; import io.oduck.api.global.security.auth.entity.AuthSocial; import io.oduck.api.domain.member.entity.LoginType; import io.oduck.api.domain.member.entity.Member; @@ -56,9 +57,10 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic AuthSocial authSocial = attributes.toEntity(socialType, attributes.getSocialUserInfo()); Member member = getOrSaveMember(authSocial); - httpSession.setAttribute("user", new SessionUser(member.getId(), LoginType.SOCIAL)); + httpSession.setAttribute("user", new AuthUser(member.getId(), LoginType.SOCIAL)); - return new DefaultOAuth2User( + return new CustomOAuth2User( + member.getId(), Collections.singleton(new SimpleGrantedAuthority(member.getRole().toString())), oAuth2User.getAttributes(), attributes.getNameAttributeKey() @@ -81,10 +83,11 @@ private Member getOrSaveMember(AuthSocial authSocial) { .build(); Member member = Member.builder() - .loginType(LoginType.SOCIAL) - .authSocial(authSocial) - .memberProfile(memberProfile) - .build(); + .loginType(LoginType.SOCIAL) + .build(); + + member.setAuthSocial(authSocial); + member.setMemberProfile(memberProfile); Member savedMember = memberRepository.save(member); log.info("Member Created! {}", savedMember.getId()); From d3c53a9ddeee948db5d195f22fe0de6d35b1ea4d Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:37:47 +0900 Subject: [PATCH 097/734] =?UTF-8?q?test:=20=EC=9D=B8=EC=A6=9D=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9C=84=ED=95=9C=20CustomMockUser=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MockMember/WithCustomMockMember.java | 19 ++++++++ ...thMemberDetailsSecurityContextFactory.java | 46 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/test/java/io/oduck/api/global/MockMember/WithCustomMockMember.java create mode 100644 src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java diff --git a/src/test/java/io/oduck/api/global/MockMember/WithCustomMockMember.java b/src/test/java/io/oduck/api/global/MockMember/WithCustomMockMember.java new file mode 100644 index 00000000..049c5fc1 --- /dev/null +++ b/src/test/java/io/oduck/api/global/MockMember/WithCustomMockMember.java @@ -0,0 +1,19 @@ +package io.oduck.api.global.MockMember; + +import io.oduck.api.domain.member.entity.Role; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.springframework.security.test.context.support.TestExecutionEvent; +import org.springframework.security.test.context.support.WithSecurityContext; + +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = WithMemberDetailsSecurityContextFactory.class, setupBefore = TestExecutionEvent.TEST_EXECUTION) +public @interface WithCustomMockMember { + long id() default 1L; + + String email() default "bob@gmail.com"; + + String password() default "Qwer!234"; + + Role role() default Role.MEMBER; +} \ No newline at end of file diff --git a/src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java b/src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java new file mode 100644 index 00000000..09b2e751 --- /dev/null +++ b/src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java @@ -0,0 +1,46 @@ +package io.oduck.api.global.MockMember; + +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.global.security.auth.dto.CustomUserDetails; +import io.oduck.api.global.security.auth.dto.AuthUser; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpSession; +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.context.SecurityContextHolderStrategy; +import org.springframework.security.test.context.support.WithSecurityContextFactory; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +public class WithMemberDetailsSecurityContextFactory implements + WithSecurityContextFactory { + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + protected MockHttpSession session; + MockHttpServletRequest servletRequest; + + @Override + public SecurityContext createSecurityContext( + WithCustomMockMember customUser) { + + CustomUserDetails principal = new CustomUserDetails(customUser.id(), customUser.email(), + customUser.password(), customUser.role()); + + Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal, + principal.getPassword(), principal.getAuthorities()); + SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); + context.setAuthentication(authentication); + + session = new MockHttpSession(); + AuthUser user = new AuthUser(1L, LoginType.LOCAL); + servletRequest = new MockHttpServletRequest(); + servletRequest.setSession(session); + + session.setAttribute("user", user); + + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest)); + return context; + } +} \ No newline at end of file From 7e39c6acd2de1070b116fd09b6cbccc2bd9bfdc0 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:38:16 +0900 Subject: [PATCH 098/734] =?UTF-8?q?test:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=9C=84=ED=95=9C=20DataInitiali?= =?UTF-8?q?zer=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../initializer/TestDataInitializer.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java diff --git a/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java b/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java new file mode 100644 index 00000000..7a24b402 --- /dev/null +++ b/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java @@ -0,0 +1,49 @@ +package io.oduck.api.global.initializer; + +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.test.context.ActiveProfiles; + +@Component +@ActiveProfiles("test") +public class TestDataInitializer { + + private final MemberRepository memberRepository; + + @Autowired + public TestDataInitializer(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + // TODO: Test Data Helper로 분리하기 + public Member saveTestMember() { + Member member = Member.builder() + .loginType(LoginType.LOCAL) + .role(Role.MEMBER) + .build(); + + AuthLocal authLocal = AuthLocal.builder() + .email("bob@gmail.com") + .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") + .build(); + + MemberProfile memberProfile = MemberProfile.builder() + .name("admin") + .info("bob info") + .thumbnail("bob thumbnail") + .point(0L) + .build(); + + member.setAuthLocal(authLocal); + member.setMemberProfile(memberProfile); + + Member savedMember = memberRepository.save(member); + return savedMember; + } +} From e9f63232479836cc0be5a13b7513b17bf8de9806 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:39:52 +0900 Subject: [PATCH 099/734] =?UTF-8?q?test:=20MemberRepository=20Test=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberRepositoryTest.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java diff --git a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java new file mode 100644 index 00000000..f7d84f09 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java @@ -0,0 +1,61 @@ +package io.oduck.api.unit.member.repository; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; + +@DataJpaTest +@TestInstance(Lifecycle.PER_CLASS) +@ActiveProfiles("test") +public class MemberRepositoryTest { + @Autowired + MemberRepository memberRepository; + + @DisplayName("회원 저장") + @Test + void saveMember() { + // given + Member member = Member.builder() + .role(Role.ADMIN) + .loginType(LoginType.LOCAL) + .build(); + + AuthLocal authLocal = AuthLocal.builder() + .email("bob@gmail.com") + .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") + .build(); + + MemberProfile memberProfile = MemberProfile.builder() + .name("bob") + .info("bob info") + .thumbnail("bob thumbnail") + .point(0L) + .build(); + + member.setAuthLocal(authLocal); + member.setMemberProfile(memberProfile); + + // when + Member savedMember = memberRepository.save(member); + + // then + assertNotNull(savedMember); // savedMember가 null이면 안 됨. + assertEquals(member.getId(), savedMember.getId()); + assertEquals(member.getRole(), savedMember.getRole()); + assertEquals(member.getAuthLocal().getEmail(), savedMember.getAuthLocal().getEmail()); + assertEquals(member.getMemberProfile().getName(), savedMember.getMemberProfile().getName()); + } +} From bae818dfdefe7a10c0cbfac7c0d7e44f39c66327 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:40:09 +0900 Subject: [PATCH 100/734] =?UTF-8?q?test:=20AuthControler=20Test=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/auth/AuthControllerTest.java | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java diff --git a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java new file mode 100644 index 00000000..ce9b136d --- /dev/null +++ b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java @@ -0,0 +1,132 @@ +package io.oduck.api.e2e.auth; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.google.gson.Gson; +import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.global.initializer.TestDataInitializer; +import io.oduck.api.global.MockMember.WithCustomMockMember; +import io.oduck.api.global.security.auth.dto.LocalAuthDto; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@AutoConfigureRestDocs +@AutoConfigureMockMvc +@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) +@SpringBootTest +@ActiveProfiles("test") +public class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + private final String BASE_URL = "/auth"; + + @BeforeAll + public static void setUp( + @Autowired TestDataInitializer testDataInitializer) { + testDataInitializer.saveTestMember(); + } + + @Nested + @DisplayName("로컬 회원 인증") + class PostLogin { + + @DisplayName("회원 인증 성공시 302 Redirect 및 세션 생성") + @Test + void postLogin() throws Exception { + // given + LocalAuthDto localAuthDto = LocalAuthDto.builder() + .email("bob@gmail.com") + .password("Qwer!234") + .build(); + + String content = gson.toJson(localAuthDto); + + // when + ResultActions actions = mockMvc.perform(post(BASE_URL + "/login") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + // then + actions + .andExpect(status().is3xxRedirection()) + .andDo( + document("postLogin/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + ) + ); + } + } + + @Nested + @DisplayName("현재 인증 상태") + class GetStatus { + + @DisplayName("인증된 회원일시 200 OK 및 회원 정보 반환") + @Test + @WithCustomMockMember(id = 1L, email = "bob", password = "Qwer!234", role = Role.MEMBER) + void getAuthStatus() throws Exception { + // given + + // when + ResultActions actions = mockMvc.perform(get(BASE_URL + "/status") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + actions.andExpect(status().isOk()) + .andDo( + document("getStatus/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("item") + .type(JsonFieldType.OBJECT) + .description("조회 데이터"), + fieldWithPath("item.name") + .type(JsonFieldType.STRING) + .description("회원 이름"), + fieldWithPath("item.description") + .type(JsonFieldType.STRING) + .description("자기 소개"), + fieldWithPath("item.thumbnail") + .type(JsonFieldType.STRING) + .description("프로필 이미지"), + fieldWithPath("item.point") + .type(JsonFieldType.NUMBER) + .description("회원 포인트") + ) + ) + ); + } + } +} From 3fde0dc3546a341a26f060807f47a33bbf712642 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:40:28 +0900 Subject: [PATCH 101/734] Refactor: SessionUser -> AuthUser #13 --- .../oduck/api/global/security/auth/service/AuthService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java b/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java index efb6b8b6..6793b3c6 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java @@ -7,11 +7,10 @@ import io.oduck.api.global.exception.NotFoundException; import io.oduck.api.global.exception.UnauthorizedException; import io.oduck.api.global.security.auth.dto.AuthResDto.Status; +import io.oduck.api.global.security.auth.dto.AuthUser; import io.oduck.api.global.security.auth.dto.LocalAuthDto; -import io.oduck.api.global.security.auth.dto.SessionUser; import io.oduck.api.global.security.auth.entity.AuthLocal; import io.oduck.api.global.security.auth.repository.AuthLocalRepository; -import io.oduck.api.global.security.auth.repository.AuthSocialRepository; import jakarta.servlet.http.HttpSession; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -62,7 +61,7 @@ public void login(LocalAuthDto localAuthDto) { SecurityContextHolder.getContext().setAuthentication(authentication); httpSession.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); - httpSession.setAttribute("user", new SessionUser(authLocal.getMember().getId(), LoginType.LOCAL)); + httpSession.setAttribute("user", new AuthUser(authLocal.getMember().getId(), LoginType.LOCAL)); } private String extractPasswordIfAdmin(Role role, String password) { From 8530e02420abe58128a3296d2e50ce3355fc3e68 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 15:46:08 +0900 Subject: [PATCH 102/734] =?UTF-8?q?refactor:=20AnimeController=20Test=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=8C=8C=EC=9D=BC=20=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index a9256679..89ba7b69 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -22,6 +22,7 @@ import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; @@ -31,6 +32,7 @@ @AutoConfigureMockMvc @ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) @SpringBootTest +@ActiveProfiles("test") public class AnimeControllerTest { @Autowired From 1643ec1cfd96a613876a6d6110fe48131d44affc Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 16:20:04 +0900 Subject: [PATCH 103/734] =?UTF-8?q?test:=20authController=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ReaqurestFields 추가 - requestHeader 추가 --- .../api/e2e/auth/AuthControllerTest.java | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java index ce9b136d..e9ec8a94 100644 --- a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java @@ -1,29 +1,41 @@ package io.oduck.api.e2e.auth; +import static io.oduck.api.global.config.RestDocsConfig.field; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.google.gson.Gson; +import io.oduck.api.domain.member.entity.LoginType; import io.oduck.api.domain.member.entity.Role; import io.oduck.api.global.initializer.TestDataInitializer; import io.oduck.api.global.MockMember.WithCustomMockMember; +import io.oduck.api.global.security.auth.dto.AuthUser; import io.oduck.api.global.security.auth.dto.LocalAuthDto; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpSession; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.context.ActiveProfiles; @@ -44,6 +56,9 @@ public class AuthControllerTest { @Autowired private Gson gson; + @Mock + MockHttpSession httpsession; + private final String BASE_URL = "/auth"; @BeforeAll @@ -80,7 +95,25 @@ void postLogin() throws Exception { .andDo( document("postLogin/success", preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()) + preprocessResponse(prettyPrint()), + requestFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("email") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "이메일 형식")) + .description("로그인에 필요한 이메일"), + fieldWithPath("password") + .type(JsonFieldType.STRING) + .attributes(field("constraints", + "숫자, 영문대소문자, 특수문자(!@#$%^&*()-_=+)를 포함한 8~20자리")) + .description("로그인에 필요한 비밀 번호")), + responseHeaders( + headerWithName(HttpHeaders.LOCATION) // 헤더 이름 + .description("Header Location, 리소스의 URL"), + headerWithName(HttpHeaders.SET_COOKIE) // 헤더 이름 + .description("Header Set-Cookie, 세션 쿠키") + ) ) ); } @@ -97,9 +130,11 @@ void getAuthStatus() throws Exception { // given // when - ResultActions actions = mockMvc.perform(get(BASE_URL + "/status") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) + ResultActions actions = mockMvc.perform( + get(BASE_URL + "/status") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") ); // then @@ -108,6 +143,11 @@ void getAuthStatus() throws Exception { document("getStatus/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .description("Header Cookie, 세션 쿠키") + ), responseFields( fieldWithPath("item") .type(JsonFieldType.OBJECT) From 41b3ebbed792e109b7d46174281133976caa1ce3 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 16:20:15 +0900 Subject: [PATCH 104/734] =?UTF-8?q?docs:=20Auth=20API=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index c556b5d3..d8289e39 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -5,6 +5,51 @@ :toc-title: API 목록 :source-highlighter: prettify +== auth +=== POST api/v1/auth/login +.curl-request +include::{snippets}/postLogin/success/curl-request.adoc[] + +.http-request +include::{snippets}/postLogin/success/http-request.adoc[] + +.request-body +include::{snippets}/postLogin/success/request-body.adoc[] + +.request-fields +include::{snippets}/postLogin/success/request-fields.adoc[] + +==== 성공시 +.http-response +include::{snippets}/postLogin/success/http-response.adoc[] + +.response-headers +include::{snippets}/postLogin/success/response-headers.adoc[] + +==== 실패시 + +=== GET api/v1/auth/status +.curl-request +include::{snippets}/getStatus/success/curl-request.adoc[] + +.http-request +include::{snippets}/getStatus/success/http-request.adoc[] + +.request-headers +include::{snippets}/getStatus/success/request-headers.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getStatus/success/http-response.adoc[] + +.response-body +include::{snippets}/getStatus/success/response-body.adoc[] + +.response-fields +include::{snippets}/getStatus/success/response-fields.adoc[] + +==== 실패시 + == members === POST api/v1/members .curl-request @@ -66,6 +111,8 @@ include::{snippets}/patchProfile/success/request-fields.adoc[] .http-response include::{snippets}/patchProfile/success/http-response.adoc[] +==== 실패시 + == animes === GET api/v1/animes/:id From 321cea8a3fe07ba07984a3d0441a256c1c1cead0 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 16:40:52 +0900 Subject: [PATCH 105/734] =?UTF-8?q?test:=20MockUser=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=8B=9C=20Context=EC=97=90=20=EC=84=B8=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WithMemberDetailsSecurityContextFactory.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java b/src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java index 09b2e751..34298fb0 100644 --- a/src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java +++ b/src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java @@ -33,14 +33,14 @@ public SecurityContext createSecurityContext( SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authentication); - session = new MockHttpSession(); - AuthUser user = new AuthUser(1L, LoginType.LOCAL); - servletRequest = new MockHttpServletRequest(); - servletRequest.setSession(session); - - session.setAttribute("user", user); - - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest)); +// session = new MockHttpSession(); +// AuthUser user = new AuthUser(1L, LoginType.LOCAL); +// servletRequest = new MockHttpServletRequest(); +// servletRequest.setSession(session); +// +// session.setAttribute("user", user); +// +// RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest)); return context; } } \ No newline at end of file From 44e14a3d0824ea1f0a930a671bcbb15a8243f3eb Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 16:41:15 +0900 Subject: [PATCH 106/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 2bf59b91..81d9bc40 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -59,7 +59,7 @@ void postMember() throws Exception { // given // TODO: 회원 가입에 필요한 데이터 CreateReq body = CreateReq.builder() - .email("bob@gmail.com") + .email("john@gmail.com") .password("Qwer1234!") .build(); From d9c54c2de6d630d6eb4e305a1c84d6299f33d2de Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 16:51:39 +0900 Subject: [PATCH 107/734] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20Log?= =?UTF-8?q?inUser=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/controller/MemberController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index 2f09938f..d3eb45ca 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -5,6 +5,8 @@ import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; import io.oduck.api.domain.member.service.MemberService; import io.oduck.api.global.common.SingleResponse; +import io.oduck.api.global.security.auth.dto.AuthUser; +import io.oduck.api.global.security.auth.dto.LoginUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -37,8 +39,8 @@ public ResponseEntity PostMember( // 이름으로 회원 프로필 조회 @GetMapping("/{name}") public ResponseEntity getProfileByName( - @PathVariable("name") String name - // TODO: 인증 정보 추가 + @PathVariable("name") String name, + @LoginUser AuthUser user ) { // TODO: 회원 프로필 조회 로직 구현 MemberProfileRes res = memberService.getProfileByName(name); From 1d107889f415e28fa3d665e29ff83afcd44afa66 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 16:51:58 +0900 Subject: [PATCH 108/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=8B=9C=20=EC=84=B8=EC=85=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/member/MemberControllerTest.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 81d9bc40..d1e674b4 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -1,6 +1,8 @@ package io.oduck.api.e2e.member; import static io.oduck.api.global.config.RestDocsConfig.field; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; @@ -27,6 +29,7 @@ import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.payload.JsonFieldType; @@ -71,7 +74,8 @@ void postMember() throws Exception { post("/members") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) - .content(content)); + .content(content) + ); // then // 응답 결과 검증 후 문서화 @@ -112,8 +116,10 @@ void getProfileByName() throws Exception { // 요청 실행 ResultActions actions = mockMvc.perform( get("/members" + "/{name}", name) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)); + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") + ); // then // 응답 결과 검증 후 문서화 @@ -135,6 +141,12 @@ void getProfileByName() throws Exception { pathParameters( parameterWithName("name") .description("회원 이름")), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .optional() + .description("Header Cookie, 세션 쿠키") + ), responseFields( fieldWithPath("item") .type(JsonFieldType.OBJECT) From f1c8f31a4c22ece46c1341c55f1237d28ca73a5e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 16:52:24 +0900 Subject: [PATCH 109/734] =?UTF-8?q?docs:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20api=20=20req=20header?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index d8289e39..6098edf4 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -81,6 +81,9 @@ include::{snippets}/getProfileByName/success/http-request.adoc[] .request-param include::{snippets}/getProfileByName/success/path-parameters.adoc[] +.request-headers +include::{snippets}/getProfileByName/success/request-headers.adoc[] + ==== 성공시 .http-response include::{snippets}/getProfileByName/success/http-response.adoc[] From 86d076ebe03988ce1ff6f2c0e096806d5a0fcb32 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 17:16:09 +0900 Subject: [PATCH 110/734] =?UTF-8?q?test:=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1=ED=9B=84=20db=EC=97=90=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=BD=EC=9E=85=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/initializer/TestDataInitializer.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java b/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java index 7a24b402..b5358063 100644 --- a/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java +++ b/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java @@ -6,6 +6,7 @@ import io.oduck.api.domain.member.entity.Role; import io.oduck.api.domain.member.repository.MemberRepository; import io.oduck.api.global.security.auth.entity.AuthLocal; +import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.test.context.ActiveProfiles; @@ -21,8 +22,9 @@ public TestDataInitializer(MemberRepository memberRepository) { this.memberRepository = memberRepository; } - // TODO: Test Data Helper로 분리하기 - public Member saveTestMember() { + @PostConstruct + public void saveTestMember() { + // TODO: Test Data들 Helper로 분리하기 Member member = Member.builder() .loginType(LoginType.LOCAL) .role(Role.MEMBER) @@ -43,7 +45,6 @@ public Member saveTestMember() { member.setAuthLocal(authLocal); member.setMemberProfile(memberProfile); - Member savedMember = memberRepository.save(member); - return savedMember; + memberRepository.save(member); } } From b4d8e8c398f87f8f422c7b1bf89968fd987e664b Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 17:16:18 +0900 Subject: [PATCH 111/734] =?UTF-8?q?test:=20beforeAll=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java index e9ec8a94..6d7fb2d6 100644 --- a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java @@ -61,12 +61,6 @@ public class AuthControllerTest { private final String BASE_URL = "/auth"; - @BeforeAll - public static void setUp( - @Autowired TestDataInitializer testDataInitializer) { - testDataInitializer.saveTestMember(); - } - @Nested @DisplayName("로컬 회원 인증") class PostLogin { From aa17d42eeea0b36965f8fa692e78d5055fa48ce4 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 17:17:14 +0900 Subject: [PATCH 112/734] =?UTF-8?q?test:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20httpSession=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java index 6d7fb2d6..d6555547 100644 --- a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java @@ -56,9 +56,6 @@ public class AuthControllerTest { @Autowired private Gson gson; - @Mock - MockHttpSession httpsession; - private final String BASE_URL = "/auth"; @Nested From 0544a0573bc94bb15829bd98956084a9320c4d1f Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 10 Oct 2023 21:57:45 +0900 Subject: [PATCH 113/734] =?UTF-8?q?feat:=20ShortReviewResDto,=20service=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/review/dto/ShortReviewResDto.java | 32 +++++++++++++++++++ .../review/service/ShortReviewService.java | 12 +++++++ 2 files changed, 44 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java create mode 100644 src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java new file mode 100644 index 00000000..ce443fa3 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java @@ -0,0 +1,32 @@ +package io.oduck.api.domain.review.dto; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.member.entity.MemberProfile; +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ShortReviewResDto { + private List shortReview; + + @Getter + @Builder + public static class ShortReview{ + private Long animeId; + private String content; + private boolean hasSpoiler; + private int score; + private int shortReviewLikeCount; + private MemberProfile member; + } + + @Builder + @Getter + public static class MemberProfile{ + private String name; + private String thumbnail; + } +} diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java new file mode 100644 index 00000000..d2f098e9 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java @@ -0,0 +1,12 @@ +package io.oduck.api.domain.review.service; + +import io.oduck.api.domain.review.dto.ShortReviewReqDto; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewResDto; +import io.oduck.api.domain.review.entity.ShortReview; + +public interface ShortReviewService { + + //애니 짧은 리뷰 조회 + ShortReviewResDto getShortReviews(Long anime); +} \ No newline at end of file From 06f1c26c64369a4a293be8583fccd5b2e9c1490b Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 10 Oct 2023 21:58:16 +0900 Subject: [PATCH 114/734] =?UTF-8?q?refactor:=20/auth/logout=20POST=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/security/config/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index 358f7287..0c4f1c4b 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -23,6 +23,7 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; //import org.springframework.security.web.context.HttpSessionSecurityContextRepository; //import org.springframework.security.web.context.SecurityContextRepository; //import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -85,7 +86,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .logout((logout) -> logout .logoutUrl("/auth/logout") - // .logoutRequestMatcher(new AntPathRequestMatcher("/auth/logout", "POST")) + .logoutRequestMatcher(new AntPathRequestMatcher("/auth/logout", "POST")) .logoutSuccessHandler(logoutHandler).deleteCookies("oDuckio.sid") .invalidateHttpSession(true).permitAll(false) ); From 3eae60d9d209ca75e1905d4a1cced90fa3142064 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 10 Oct 2023 21:58:21 +0900 Subject: [PATCH 115/734] =?UTF-8?q?feat:=20ShortReviewController=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ShortReviewController.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java diff --git a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java new file mode 100644 index 00000000..280bccb8 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java @@ -0,0 +1,31 @@ +package io.oduck.api.domain.review.controller; + +import io.oduck.api.domain.review.dto.ShortReviewResDto; +import io.oduck.api.domain.review.service.ShortReviewService; +import io.oduck.api.global.common.SingleResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +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.RestController; + +@Validated +@RequestMapping("/short-reviews") +@RestController +@RequiredArgsConstructor + +public class ShortReviewController { + + private final ShortReviewService shortReviewService; + + //애니의 짧은 리뷰 조회 + @GetMapping("/animeId/{animeId}") + public ResponseEntity getShortReviews( + @PathVariable Long animeId) { + //TODO: 애니에 따른 짧은 리뷰 조회 + ShortReviewResDto reviewResDto = shortReviewService.getShortReviews(animeId); + return ResponseEntity.ok(SingleResponse.of(reviewResDto)); + } +} \ No newline at end of file From b9823de47acbc380c9117386eba18d2c6ab6344b Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 10 Oct 2023 22:08:34 +0900 Subject: [PATCH 116/734] =?UTF-8?q?feat:=20ShortReviewServiceStub=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ShortReviewServiceStub.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java new file mode 100644 index 00000000..682e73b6 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java @@ -0,0 +1,43 @@ +package io.oduck.api.domain.review.service; + +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewResDto; +import io.oduck.api.domain.review.dto.ShortReviewResDto.MemberProfile; +import io.oduck.api.domain.review.dto.ShortReviewResDto.ShortReview; +import java.util.ArrayList; +import org.springframework.stereotype.Service; + +@Service +public class ShortReviewServiceStub implements ShortReviewService{ + + @Override + public ShortReviewResDto getShortReviews(Long animeId) { + ArrayList list = new ArrayList<>(); + ShortReview shortReview = createReview(animeId); + list.add(shortReview); + return ShortReviewResDto + .builder() + .shortReview(list) + .build(); + } + + private ShortReview createReview(Long animeId){ + + return ShortReview + .builder() + .animeId(1L) + .content("최고의 반전의 반전") + .score(5) + .hasSpoiler(false) + .shortReviewLikeCount(100) + .member(getMemberProfile()) + .build(); + } + + private MemberProfile getMemberProfile(){ + return MemberProfile.builder() + .name("오덕12") + .thumbnail("사진 url") + .build(); + } +} From ca8e3cc7d231427627b89b974f3027a6b8bb705f Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 10 Oct 2023 22:14:26 +0900 Subject: [PATCH 117/734] =?UTF-8?q?test:=20ShortReviewController,=20Servic?= =?UTF-8?q?e=20Test=20=EB=B0=8F=20=EB=AC=B8=EC=84=9C=ED=99=94=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 30 ++- .../ShortReviewControllerTest.java | 174 ++++++++++++++++++ .../service/ShortReviewServiceTest.java | 45 +++++ 3 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java create mode 100644 src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index c556b5d3..cf59fdcc 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -86,4 +86,32 @@ include::{snippets}/getAnimeById/success/response-body.adoc[] .response-fields include::{snippets}/getAnimeById/success/response-fields.adoc[] -==== 실패 시 \ No newline at end of file +==== 실패 시 + + +== shortReview + +=== GET api/v1/short-review/animeId/1 + +.curl-request +include::{snippets}/getShortReviews/success/curl-request.adoc[] + +.http-request +include::{snippets}/getShortReviews/success/http-request.adoc[] + +.request-param +include::{snippets}/getShortReviews/success/path-parameters.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getShortReviews/success/response-fields.adoc[] + +.response-body +include::{snippets}/getShortReviews/success/response-body.adoc[] + +.response-fields +include::{snippets}/getShortReviews/success/response-fields.adoc[] + +.http-response +include::{snippets}/getShortReviews/success/http-response.adoc[] +==== 실패시 \ No newline at end of file diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java new file mode 100644 index 00000000..af141aea --- /dev/null +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -0,0 +1,174 @@ +package io.oduck.api.e2e.shortReview; + +import static io.oduck.api.global.config.RestDocsConfig.field; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; + + +import com.google.gson.Gson; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + + +@AutoConfigureRestDocs +@AutoConfigureMockMvc +@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class }) +@SpringBootTest +@ActiveProfiles("test") +public class ShortReviewControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + @Nested + @DisplayName("짧은 리뷰 조회") + class GetShortReviews{ + + @DisplayName("조회 성공 시 Http Status 200 반환") + @Test + void getShortReviews() throws Exception{ + //given + Long animeId = 1L; + + //when + ResultActions actions = mockMvc.perform( + get("/short-reviews" + "/animeId/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + //then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.item.shortReview[0].animeId").exists()) + .andExpect(jsonPath("$.item.shortReview[0].content").exists()) + .andExpect(jsonPath("$.item.shortReview[0].hasSpoiler").exists()) + .andExpect(jsonPath("$.item.shortReview[0].score").exists()) + .andExpect(jsonPath("$.item.shortReview[0].shortReviewLikeCount").exists()) + .andExpect(jsonPath("$.item.shortReview[0].member.name").exists()) + .andExpect(jsonPath("$.item.shortReview[0].member.thumbnail").exists()) + .andDo(document("getShortReviews/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId") + .description("애니 아이디") + ), + responseFields( + fieldWithPath("item") + .type(JsonFieldType.OBJECT) + .description("조회 데이터"), + fieldWithPath("item.shortReview") + .type(JsonFieldType.ARRAY) + .description("리뷰 데이터"), + fieldWithPath("item.shortReview[0].animeId") + .type(JsonFieldType.NUMBER) + .description("애니 고유 식별자"), + fieldWithPath("item.shortReview[0].content") + .type(JsonFieldType.STRING) + .description("짧은 리뷰 내용"), + fieldWithPath("item.shortReview[0].hasSpoiler") + .type(JsonFieldType.BOOLEAN) + .description("스포일러 유무"), + fieldWithPath("item.shortReview[0].score") + .type(JsonFieldType.NUMBER) + .description("평점"), + fieldWithPath("item.shortReview[0].shortReviewLikeCount") + .type(JsonFieldType.NUMBER) + .description("리뷰 좋아요 수") + ,fieldWithPath("item.shortReview[0].member") + .type(JsonFieldType.OBJECT) + .description("회원 관련 데이터"), + fieldWithPath("item.shortReview[0].member.name") + .type(JsonFieldType.STRING) + .description("회원 이름"), + fieldWithPath("item.shortReview[0].member.thumbnail") + .type(JsonFieldType.STRING) + .description("회원 이미지 사진") + ) + )); + //TODO : 조회 실패 시 + } + } + + +// @Nested +// @DisplayName("짧은 리뷰 작성") +// class PostShortReviews{ +// +// @DisplayName(("리뷰 작성 성공시 200 ok 반환")) +// @Test +// void postShortReview() throws Exception{ +// //given +// PostShortReviewReq req = PostShortReviewReq +// .builder() +// .memberId(2L) +// .animeId(1L) +// .content("최고의 반전 올해 최고의 애니!!!") +// .hasSpoiler(false) +// .build(); +// +// String content = gson.toJson(req); +// +// //when +// ResultActions actions = mockMvc.perform( +// post("/short-reviews") +// .contentType(MediaType.APPLICATION_JSON) +// .accept(MediaType.APPLICATION_JSON) +// .content(content)); +// +// //then +// actions +// .andExpect(status().isOk()) +// .andDo(document("postShortReview/success", +// preprocessRequest(prettyPrint()), +// preprocessResponse(prettyPrint()), +// requestFields(attributes(key("title").value("Fields for shortReview creation")), +// fieldWithPath("memberId") +// .type(JsonFieldType.NUMBER) +// .description("리뷰를 등록할 회원의 고유 식별 번호"), +// fieldWithPath("animeId") +// .type(JsonFieldType.NUMBER) +// .description("리뷰를 등록할 애니 고유 식별 번호"), +// fieldWithPath("content") +// .type(JsonFieldType.STRING) +// .attributes( +// field("constraints", "최소 10에서 100자 까지 입력 가능합니다.")) +// .description("짧은 리뷰 내용"), +// fieldWithPath("hasSpoiler") +// .type(JsonFieldType.BOOLEAN) +// .description("스포일러 유무")) +// ) +// ); +// } +// //TODO: 리뷰 작성 실패 시 +// } +} \ No newline at end of file diff --git a/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java b/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java new file mode 100644 index 00000000..7fc5109a --- /dev/null +++ b/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java @@ -0,0 +1,45 @@ +package io.oduck.api.unit.shortReview.service; + +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewResDto; +import io.oduck.api.domain.review.service.ShortReviewServiceStub; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +public class ShortReviewServiceTest { + + @InjectMocks + private ShortReviewServiceStub shortReviewService; + + @Nested + @DisplayName("짧은 리뷰 조회") + class GetShortReviews{ + + @Test + @DisplayName("짧은 리뷰 조회") + void getShortReviews(){ + //given + Long animeId = 1L; + + //when + ShortReviewResDto response = shortReviewService.getShortReviews(animeId); + + //then + assertDoesNotThrow(() -> shortReviewService.getShortReviews(animeId)); + assertEquals(response.getShortReview().get(0).getAnimeId(),animeId); + } + } + + +} \ No newline at end of file From bab5b16cb5011f6279f74ddce7fe3f9dc66009b6 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 10 Oct 2023 22:33:27 +0900 Subject: [PATCH 118/734] =?UTF-8?q?refactor:=20ShortReviewController=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShortReviewControllerTest.java | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index af141aea..6357060f 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -118,57 +118,4 @@ void getShortReviews() throws Exception{ //TODO : 조회 실패 시 } } - - -// @Nested -// @DisplayName("짧은 리뷰 작성") -// class PostShortReviews{ -// -// @DisplayName(("리뷰 작성 성공시 200 ok 반환")) -// @Test -// void postShortReview() throws Exception{ -// //given -// PostShortReviewReq req = PostShortReviewReq -// .builder() -// .memberId(2L) -// .animeId(1L) -// .content("최고의 반전 올해 최고의 애니!!!") -// .hasSpoiler(false) -// .build(); -// -// String content = gson.toJson(req); -// -// //when -// ResultActions actions = mockMvc.perform( -// post("/short-reviews") -// .contentType(MediaType.APPLICATION_JSON) -// .accept(MediaType.APPLICATION_JSON) -// .content(content)); -// -// //then -// actions -// .andExpect(status().isOk()) -// .andDo(document("postShortReview/success", -// preprocessRequest(prettyPrint()), -// preprocessResponse(prettyPrint()), -// requestFields(attributes(key("title").value("Fields for shortReview creation")), -// fieldWithPath("memberId") -// .type(JsonFieldType.NUMBER) -// .description("리뷰를 등록할 회원의 고유 식별 번호"), -// fieldWithPath("animeId") -// .type(JsonFieldType.NUMBER) -// .description("리뷰를 등록할 애니 고유 식별 번호"), -// fieldWithPath("content") -// .type(JsonFieldType.STRING) -// .attributes( -// field("constraints", "최소 10에서 100자 까지 입력 가능합니다.")) -// .description("짧은 리뷰 내용"), -// fieldWithPath("hasSpoiler") -// .type(JsonFieldType.BOOLEAN) -// .description("스포일러 유무")) -// ) -// ); -// } -// //TODO: 리뷰 작성 실패 시 -// } } \ No newline at end of file From e21e36cca79f0549c4047712a197a3d5827cc826 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Wed, 11 Oct 2023 15:03:53 +0900 Subject: [PATCH 119/734] =?UTF-8?q?cleanup:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=20=EC=82=AD=EC=A0=9C=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/review/dto/ShortReviewResDto.java | 3 --- .../oduck/api/domain/review/service/ShortReviewService.java | 3 --- .../api/domain/review/service/ShortReviewServiceStub.java | 1 - .../oduck/api/e2e/shortReview/ShortReviewControllerTest.java | 5 ----- .../api/unit/shortReview/service/ShortReviewServiceTest.java | 4 ---- 5 files changed, 16 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java index ce443fa3..23461d58 100644 --- a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java @@ -1,8 +1,5 @@ package io.oduck.api.domain.review.dto; -import io.oduck.api.domain.anime.entity.Anime; -import io.oduck.api.domain.member.entity.MemberProfile; -import java.util.ArrayList; import java.util.List; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java index d2f098e9..be2dff1e 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java @@ -1,9 +1,6 @@ package io.oduck.api.domain.review.service; -import io.oduck.api.domain.review.dto.ShortReviewReqDto; -import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; import io.oduck.api.domain.review.dto.ShortReviewResDto; -import io.oduck.api.domain.review.entity.ShortReview; public interface ShortReviewService { diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java index 682e73b6..028cbbd7 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java @@ -1,6 +1,5 @@ package io.oduck.api.domain.review.service; -import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; import io.oduck.api.domain.review.dto.ShortReviewResDto; import io.oduck.api.domain.review.dto.ShortReviewResDto.MemberProfile; import io.oduck.api.domain.review.dto.ShortReviewResDto.ShortReview; diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index 6357060f..56623e02 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -1,24 +1,19 @@ package io.oduck.api.e2e.shortReview; -import static io.oduck.api.global.config.RestDocsConfig.field; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.restdocs.snippet.Attributes.attributes; -import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import com.google.gson.Gson; -import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; diff --git a/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java b/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java index 7fc5109a..a0dae6d5 100644 --- a/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java +++ b/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java @@ -1,9 +1,7 @@ package io.oduck.api.unit.shortReview.service; -import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; import io.oduck.api.domain.review.dto.ShortReviewResDto; import io.oduck.api.domain.review.service.ShortReviewServiceStub; -import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -11,8 +9,6 @@ import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; From 32359ca990a9817ce56facea1aac1bb98e116e29 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Wed, 11 Oct 2023 19:37:22 +0900 Subject: [PATCH 120/734] =?UTF-8?q?refactor:=20@ActiveProfiles("test")?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index a9256679..89ba7b69 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -22,6 +22,7 @@ import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; @@ -31,6 +32,7 @@ @AutoConfigureMockMvc @ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) @SpringBootTest +@ActiveProfiles("test") public class AnimeControllerTest { @Autowired From 607e4206140d9c58958d9e45bd0ad1af6ac11ed4 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 11 Oct 2023 19:40:54 +0900 Subject: [PATCH 121/734] =?UTF-8?q?chore:=20docker=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 1 - docker-compose.yml | 4 +--- docker.md | 19 +++++++++++++++++++ dockerfile-dev | 9 +++------ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.dockerignore b/.dockerignore index ea3adb25..367b6aaa 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,5 @@ .git *dockerfile* -build data logs .gitignore diff --git a/docker-compose.yml b/docker-compose.yml index 08f6afe5..ecbc3266 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,7 @@ services: - '6379:6379' spring: - build: - context: . - dockerfile: ./dockerfile-dev + image: fabeejoo/oduckio-spring container_name: oDuckio-spring depends_on: - redis diff --git a/docker.md b/docker.md index 132c81fd..e4b0beb0 100644 --- a/docker.md +++ b/docker.md @@ -19,3 +19,22 @@ > # docker-compose 종료 및 컨테이너 삭제 > docker-compose down > ``` + +# Dockerfile 빌드 및 푸시 방법 + +> ```bash +> # 도커 빌드 +> # docker build -f {dockerfile} -t {dockerHubName}/{imageName}:{tag} {path} +> +> docker build -f dockerfile-dev -t fabeejoo/oduckio-spring . +> +> # 도커 컨테이터 실행 +> # docker run -p {hostPort}:{containerPort} {dockerHubName}/{imageName}:{tag} +> +> docker run -d -p 8000:8000 fabeejoo/oduckio-spring +> +> # 도커 푸시 +> # docker push {dockerHubName}/{imageName}:{tag} +> +> docker push fabeejoo/oduckio-spring +> ``` diff --git a/dockerfile-dev b/dockerfile-dev index 786e2b0c..4a88da3d 100644 --- a/dockerfile-dev +++ b/dockerfile-dev @@ -1,7 +1,4 @@ FROM openjdk:17-jdk-slim -RUN mkdir -p /var/app -WORKDIR /var/app -COPY . . -RUN ./gradlew build -ARG JAR_FILE=./build/libs/api-0.0.1-SNAPSHOT.jar -ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-Duser.timezone=Asia/Seoul", "-jar", "./build/libs/api-0.0.1-SNAPSHOT.jar"] \ No newline at end of file +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-Duser.timezone=Asia/Seoul", "-jar", "./app.jar"] \ No newline at end of file From c32a1b142b5c8e021cc2349d57fbb6fd2a4e2d9a Mon Sep 17 00:00:00 2001 From: hanyMK Date: Thu, 12 Oct 2023 15:15:26 +0900 Subject: [PATCH 122/734] =?UTF-8?q?cleanup:=20@ActiveProfiles("test")?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index 89ba7b69..9e2d0b0c 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -32,7 +32,6 @@ @AutoConfigureMockMvc @ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) @SpringBootTest -@ActiveProfiles("test") public class AnimeControllerTest { @Autowired From 5b53868ef878e798fbd492e449c9e3095aa6f437 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 15:16:15 +0900 Subject: [PATCH 123/734] =?UTF-8?q?refactor:=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=9D=91=EB=8B=B5=EC=9D=80=20res?= =?UTF-8?q?ponse=EB=A1=9C=20=08=EC=95=88=20=EA=B0=90=EC=8B=B8=EA=B3=A0=20d?= =?UTF-8?q?to=20=EB=B0=98=ED=99=98=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/member/controller/MemberController.java | 4 ++-- .../api/global/security/auth/controller/AuthController.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index d3eb45ca..3194d36e 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -38,14 +38,14 @@ public ResponseEntity PostMember( // 이름으로 회원 프로필 조회 @GetMapping("/{name}") - public ResponseEntity getProfileByName( + public ResponseEntity getProfileByName( @PathVariable("name") String name, @LoginUser AuthUser user ) { // TODO: 회원 프로필 조회 로직 구현 MemberProfileRes res = memberService.getProfileByName(name); - return ResponseEntity.ok(SingleResponse.of(res)); + return ResponseEntity.ok(res); } // 회원 프로필 수정 diff --git a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java index 5d5e1fe8..ac01e77c 100644 --- a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java +++ b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java @@ -31,12 +31,12 @@ public ResponseEntity login( } @GetMapping("/status") - public ResponseEntity status( + public ResponseEntity status( @LoginUser AuthUser user ) { Status res = authService.getStatus(user.getId()); - return ResponseEntity.ok(SingleResponse.of(res)); + return ResponseEntity.ok(res); } } From 1ead945404803abb56c1aca3296e534e05cd57fc Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 15:16:30 +0900 Subject: [PATCH 124/734] =?UTF-8?q?test:=20=EB=8B=A8=EC=9D=BC=20dto=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=A0=81=EC=9A=A9=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/auth/AuthControllerTest.java | 59 ++++++++++--------- .../api/e2e/member/MemberControllerTest.java | 43 +++++++------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java index d6555547..73ba9646 100644 --- a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java @@ -14,6 +14,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.google.gson.Gson; @@ -129,35 +130,37 @@ void getAuthStatus() throws Exception { ); // then - actions.andExpect(status().isOk()) - .andDo( - document("getStatus/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - requestHeaders( - headerWithName(HttpHeaders.COOKIE) - .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) - .description("Header Cookie, 세션 쿠키") - ), - responseFields( - fieldWithPath("item") - .type(JsonFieldType.OBJECT) - .description("조회 데이터"), - fieldWithPath("item.name") - .type(JsonFieldType.STRING) - .description("회원 이름"), - fieldWithPath("item.description") - .type(JsonFieldType.STRING) - .description("자기 소개"), - fieldWithPath("item.thumbnail") - .type(JsonFieldType.STRING) - .description("프로필 이미지"), - fieldWithPath("item.point") - .type(JsonFieldType.NUMBER) - .description("회원 포인트") - ) + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").exists()) + .andExpect(jsonPath("$.description").exists()) + .andExpect(jsonPath("$.thumbnail").exists()) + .andExpect(jsonPath("$.point").exists()) + .andDo( + document("getStatus/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .description("Header Cookie, 세션 쿠키") + ), + responseFields( + fieldWithPath("name") + .type(JsonFieldType.STRING) + .description("회원 이름"), + fieldWithPath("description") + .type(JsonFieldType.STRING) + .description("자기 소개"), + fieldWithPath("thumbnail") + .type(JsonFieldType.STRING) + .description("프로필 이미지"), + fieldWithPath("point") + .type(JsonFieldType.NUMBER) + .description("회원 포인트") ) - ); + ) + ); } } } diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index d1e674b4..0c95e4f5 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -125,16 +125,16 @@ void getProfileByName() throws Exception { // 응답 결과 검증 후 문서화 actions .andExpect(status().isOk()) - .andExpect(jsonPath("$.item.name").exists()) - .andExpect(jsonPath("$.item.isMine").exists()) - .andExpect(jsonPath("$.item.description").exists()) - .andExpect(jsonPath("$.item.thumbnail").exists()) - .andExpect(jsonPath("$.item.backgroundImage").exists()) - .andExpect(jsonPath("$.item.point").exists()) - .andExpect(jsonPath("$.item.activity").hasJsonPath()) - .andExpect(jsonPath("$.item.activity.reviews").exists()) - .andExpect(jsonPath("$.item.activity.threads").exists()) - .andExpect(jsonPath("$.item.activity.likes").exists()) + .andExpect(jsonPath("$.name").exists()) + .andExpect(jsonPath("$.isMine").exists()) + .andExpect(jsonPath("$.description").exists()) + .andExpect(jsonPath("$.thumbnail").exists()) + .andExpect(jsonPath("$.backgroundImage").exists()) + .andExpect(jsonPath("$.point").exists()) + .andExpect(jsonPath("$.activity").hasJsonPath()) + .andExpect(jsonPath("$.activity.reviews").exists()) + .andExpect(jsonPath("$.activity.threads").exists()) + .andExpect(jsonPath("$.activity.likes").exists()) .andDo(document("getProfileByName/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -148,37 +148,34 @@ void getProfileByName() throws Exception { .description("Header Cookie, 세션 쿠키") ), responseFields( - fieldWithPath("item") - .type(JsonFieldType.OBJECT) - .description("조회 데이터"), - fieldWithPath("item.name") + fieldWithPath("name") .type(JsonFieldType.STRING) .description("회원 이름"), - fieldWithPath("item.isMine") + fieldWithPath("isMine") .type(JsonFieldType.BOOLEAN) .description("본인 여부(본인 프로필 조회시 true)"), - fieldWithPath("item.description") + fieldWithPath("description") .type(JsonFieldType.STRING) .description("자기 소개"), - fieldWithPath("item.thumbnail") + fieldWithPath("thumbnail") .type(JsonFieldType.STRING) .description("프로필 이미지"), - fieldWithPath("item.backgroundImage") + fieldWithPath("backgroundImage") .type(JsonFieldType.STRING) .description("프로필 배경 이미지"), - fieldWithPath("item.point") + fieldWithPath("point") .type(JsonFieldType.NUMBER) .description("회원 포인트"), - fieldWithPath("item.activity") + fieldWithPath("activity") .type(JsonFieldType.OBJECT) .description("회원 활동"), - fieldWithPath("item.activity.reviews") + fieldWithPath("activity.reviews") .type(JsonFieldType.NUMBER) .description("작성한 리뷰 갯수"), - fieldWithPath("item.activity.threads") + fieldWithPath("activity.threads") .type(JsonFieldType.NUMBER) .description("작성한 쓰레드 갯수"), - fieldWithPath("item.activity.likes") + fieldWithPath("activity.likes") .type(JsonFieldType.NUMBER) .description("받은 좋아요 갯수")))); } From f98eda0674aee51b547bc5b02d0e9f2ecf1c98ec Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 15:31:47 +0900 Subject: [PATCH 125/734] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=8B=9C=20redirect=20url=20=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/security/auth/controller/AuthController.java | 6 +++++- .../api/global/security/handler/LoginSuccessHandler.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java index ac01e77c..fad96b7d 100644 --- a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java +++ b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java @@ -8,6 +8,7 @@ import io.oduck.api.global.security.auth.service.AuthService; import java.net.URI; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -21,13 +22,16 @@ public class AuthController { private final AuthService authService; + @Value("${config.base.url}") + private String BASE_URL; + @PostMapping("/login") public ResponseEntity login( @RequestBody LocalAuthDto localAuthDto ) { authService.login(localAuthDto); - return ResponseEntity.status(302).location(URI.create("http://localhost:5173")).build(); + return ResponseEntity.status(302).location(URI.create(BASE_URL + "/auth/callback")).build(); } @GetMapping("/status") diff --git a/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java b/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java index 283c596f..97e85c66 100644 --- a/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java +++ b/src/main/java/io/oduck/api/global/security/handler/LoginSuccessHandler.java @@ -20,6 +20,6 @@ public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - response.sendRedirect(BASE_URL); + response.sendRedirect(BASE_URL + "/auth/callback"); } } \ No newline at end of file From 2106a6d14b6a927e184ac552844e969b2cac0588 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 16:38:58 +0900 Subject: [PATCH 126/734] =?UTF-8?q?chore:=20QueryDSL=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20#12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/build.gradle b/build.gradle index 0d31cfaa..882f602f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,9 @@ +buildscript { + ext { + queryDslVersion = "5.0.0" + } +} + plugins { id 'java' id 'org.springframework.boot' version '3.1.4' @@ -47,6 +53,12 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + // QueryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // dev tools compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' @@ -62,6 +74,24 @@ dependencies { asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' } +// querydsl 시작 +def generated = "$buildDir/generated/querydsl" + +// querydsl QClass 파일 생성 위치를 지정 +tasks.withType(JavaCompile).configureEach { + options.getGeneratedSourceOutputDirectory().set(file(generated)) +} + +// java source set 에 querydsl QClass 위치 추가 +sourceSets { + main.java.srcDirs += [ generated ] +} + +// gradle clean 시에 QClass 디렉토리 삭제 +clean { + delete file(generated) +} + test { outputs.dir snippetsDir useJUnitPlatform() From 3f06f56349e6929be45e08cf3c44203e0c7936a7 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 18:08:17 +0900 Subject: [PATCH 127/734] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20401=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/security/auth/service/AuthService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java b/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java index 6793b3c6..3a4ed0b1 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/AuthService.java @@ -4,7 +4,6 @@ import io.oduck.api.domain.member.entity.MemberProfile; import io.oduck.api.domain.member.entity.Role; import io.oduck.api.domain.member.repository.MemberProfileRepository; -import io.oduck.api.global.exception.NotFoundException; import io.oduck.api.global.exception.UnauthorizedException; import io.oduck.api.global.security.auth.dto.AuthResDto.Status; import io.oduck.api.global.security.auth.dto.AuthUser; @@ -32,7 +31,7 @@ public class AuthService { public Status getStatus(Long memberId) { MemberProfile memberProfile = memberProfileRepository.findByMemberId(memberId) - .orElseThrow(() -> new NotFoundException("Member")); + .orElseThrow(() -> new UnauthorizedException("UnAuthorized")); return Status.builder() .name(memberProfile.getName()) @@ -44,13 +43,12 @@ public Status getStatus(Long memberId) { public AuthLocal getAuthLocal(String email) { return authLocalRepository.findByEmail(email) - .orElseThrow(() -> new NotFoundException("Member")); + .orElseThrow(() -> new UnauthorizedException("UnAuthorized")); } public void login(LocalAuthDto localAuthDto) { String username = localAuthDto.getEmail(); - AuthLocal authLocal = getAuthLocal(username); Role role = authLocal.getMember().getRole(); From 3c448b0a8ae53936f0155ee06939a9e7e2967704 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 18:10:21 +0900 Subject: [PATCH 128/734] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EB=A0=89=ED=8A=B8=EA=B0=80=20=EC=95=84=EB=8B=88=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/security/auth/controller/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java index fad96b7d..f78ce357 100644 --- a/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java +++ b/src/main/java/io/oduck/api/global/security/auth/controller/AuthController.java @@ -31,7 +31,7 @@ public ResponseEntity login( ) { authService.login(localAuthDto); - return ResponseEntity.status(302).location(URI.create(BASE_URL + "/auth/callback")).build(); + return ResponseEntity.created(null).build(); } @GetMapping("/status") From a37882ece74e8e312bea09921d0f46eac5288206 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 18:10:59 +0900 Subject: [PATCH 129/734] =?UTF-8?q?test:=20login=20test=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=B3=80=EA=B2=BD=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java index 73ba9646..62c21aca 100644 --- a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java @@ -36,7 +36,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpSession; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.context.ActiveProfiles; @@ -83,7 +82,7 @@ void postLogin() throws Exception { // then actions - .andExpect(status().is3xxRedirection()) + .andExpect(status().isCreated()) .andDo( document("postLogin/success", preprocessRequest(prettyPrint()), @@ -101,8 +100,6 @@ void postLogin() throws Exception { "숫자, 영문대소문자, 특수문자(!@#$%^&*()-_=+)를 포함한 8~20자리")) .description("로그인에 필요한 비밀 번호")), responseHeaders( - headerWithName(HttpHeaders.LOCATION) // 헤더 이름 - .description("Header Location, 리소스의 URL"), headerWithName(HttpHeaders.SET_COOKIE) // 헤더 이름 .description("Header Set-Cookie, 세션 쿠키") ) From 09ea64732679b954c04678018889b81b93129815 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 18:11:44 +0900 Subject: [PATCH 130/734] =?UTF-8?q?feat:=20QueryDsl=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/config/QueryDslConfig.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/config/QueryDslConfig.java diff --git a/src/main/java/io/oduck/api/global/config/QueryDslConfig.java b/src/main/java/io/oduck/api/global/config/QueryDslConfig.java new file mode 100644 index 00000000..7f81ed28 --- /dev/null +++ b/src/main/java/io/oduck/api/global/config/QueryDslConfig.java @@ -0,0 +1,17 @@ +package io.oduck.api.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QueryDslConfig { + @PersistenceContext + private EntityManager em; + @Bean + public JPAQueryFactory jpaQueryFactory(){ + return new JPAQueryFactory(em); + } +} From 29bbcc4d3fef310be00c301072cb6c6bb71083d2 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 18:12:53 +0900 Subject: [PATCH 131/734] =?UTF-8?q?feat:=20QueryDsl=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9C=A0=ED=8B=B8=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/utils/QueryDslUtils.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/utils/QueryDslUtils.java diff --git a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java new file mode 100644 index 00000000..05dca60d --- /dev/null +++ b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java @@ -0,0 +1,43 @@ +package io.oduck.api.global.utils; + +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberPath; +import java.util.ArrayList; +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; + +// QueryDsl 정렬 기준 생성용 클래스 +@Component +public class QueryDslUtils { + // pageable에서 sort 객체로 OrderSpecifier를 생성하여 반환. + public OrderSpecifier[] getAllOrderSpecifiers(Pageable pageable, Path path) { + List orders = convertToDslOrder(pageable, path); + return orders.toArray(OrderSpecifier[]::new); + } + + // Sort 객체에 Order가 존재할 때 QueryDSL Order로 변환 + private static List convertToDslOrder(Pageable pageable, Path path) { + List orders = new ArrayList<>(); + if (!pageable.getSort().isEmpty()) { + for (Sort.Order order : pageable.getSort()) { + Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; + + OrderSpecifier orderBy = createOrderSpecifier(direction, path, order.getProperty()); + orders.add(orderBy); + } + } + return orders; + } + + private static OrderSpecifier createOrderSpecifier(Order order, Path parent, String fieldName) { + // 일반 컬럼을 기준으로 할때의 OrderSpecifier + Path fieldPath = Expressions.path(Object.class, parent, fieldName); + + return new OrderSpecifier(order, fieldPath); + } +} \ No newline at end of file From b16e0e2b3130247c1c20119103a058398ec2b919 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 12 Oct 2023 18:13:16 +0900 Subject: [PATCH 132/734] =?UTF-8?q?test:=20QueryDsl=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/config/QueryDslConfig.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/test/java/io/oduck/api/global/config/QueryDslConfig.java diff --git a/src/test/java/io/oduck/api/global/config/QueryDslConfig.java b/src/test/java/io/oduck/api/global/config/QueryDslConfig.java new file mode 100644 index 00000000..ba043705 --- /dev/null +++ b/src/test/java/io/oduck/api/global/config/QueryDslConfig.java @@ -0,0 +1,19 @@ +package io.oduck.api.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +public class QueryDslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} \ No newline at end of file From 71cd6032ccfeaa3983c9db8f5ec4fc43d9f96212 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Fri, 13 Oct 2023 12:18:50 +0900 Subject: [PATCH 133/734] =?UTF-8?q?refactor:=20GetMapping=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=20/animeId/{animeId}=20->/{animeId}=EB=B3=80=EA=B2=BD?= =?UTF-8?q?,=20SingleResponse=20->=20SliceResponse=EB=B3=80=EA=B2=BD=20=20?= =?UTF-8?q?#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/review/controller/ShortReviewController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java index 280bccb8..da9f05a8 100644 --- a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java +++ b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java @@ -2,7 +2,7 @@ import io.oduck.api.domain.review.dto.ShortReviewResDto; import io.oduck.api.domain.review.service.ShortReviewService; -import io.oduck.api.global.common.SingleResponse; +import io.oduck.api.global.common.SliceResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -21,11 +21,11 @@ public class ShortReviewController { private final ShortReviewService shortReviewService; //애니의 짧은 리뷰 조회 - @GetMapping("/animeId/{animeId}") + @GetMapping("/{animeId}") public ResponseEntity getShortReviews( @PathVariable Long animeId) { //TODO: 애니에 따른 짧은 리뷰 조회 ShortReviewResDto reviewResDto = shortReviewService.getShortReviews(animeId); - return ResponseEntity.ok(SingleResponse.of(reviewResDto)); + return ResponseEntity.ok(SliceResponse.of(reviewResDto.getShortReviews())); } } \ No newline at end of file From c3028fa83367297e13f867d21663b2d828388f08 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Fri, 13 Oct 2023 12:20:22 +0900 Subject: [PATCH 134/734] =?UTF-8?q?refactor:=20slice=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20implements?= =?UTF-8?q?=20EntityBased=20=EC=B6=94=EA=B0=80=20=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/review/dto/ShortReviewResDto.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java index 23461d58..5f5a7a5c 100644 --- a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java @@ -1,23 +1,29 @@ package io.oduck.api.domain.review.dto; -import java.util.List; +import io.oduck.api.global.common.EntityBased; import lombok.Builder; import lombok.Getter; +import org.springframework.data.domain.Slice; @Getter @Builder public class ShortReviewResDto { - private List shortReview; + private Slice shortReviews; @Getter @Builder - public static class ShortReview{ + public static class ShortReview implements EntityBased { private Long animeId; private String content; private boolean hasSpoiler; private int score; private int shortReviewLikeCount; private MemberProfile member; + + @Override + public Long getId() { + return this.getAnimeId(); + } } @Builder From ecd6003d44b6180b6f4b4c5304852a5957a57f11 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Fri, 13 Oct 2023 12:27:19 +0900 Subject: [PATCH 135/734] =?UTF-8?q?refactor:=20slice=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20shortReview=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=B0=98=EB=B3=B5?= =?UTF-8?q?=EB=AC=B8=20=20=EC=B6=94=EA=B0=80=20=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ShortReviewServiceStub.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java index 028cbbd7..382e71df 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java @@ -4,6 +4,10 @@ import io.oduck.api.domain.review.dto.ShortReviewResDto.MemberProfile; import io.oduck.api.domain.review.dto.ShortReviewResDto.ShortReview; import java.util.ArrayList; +import java.util.List; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Service; @Service @@ -11,12 +15,19 @@ public class ShortReviewServiceStub implements ShortReviewService{ @Override public ShortReviewResDto getShortReviews(Long animeId) { - ArrayList list = new ArrayList<>(); - ShortReview shortReview = createReview(animeId); - list.add(shortReview); + List shortReviewList = new ArrayList<>(); + + for(int i = 0; i < 10; i++){ + ShortReview shortReview = createReview(animeId); + shortReviewList.add(shortReview); + } + //slice객체 만들기 + PageRequest pageable = PageRequest.of(0,10); + Slice slice = new SliceImpl<>(shortReviewList,pageable,true); + return ShortReviewResDto .builder() - .shortReview(list) + .shortReviews(slice) .build(); } @@ -27,7 +38,7 @@ private ShortReview createReview(Long animeId){ .animeId(1L) .content("최고의 반전의 반전") .score(5) - .hasSpoiler(false) + .hasSpoiler(true) .shortReviewLikeCount(100) .member(getMemberProfile()) .build(); From dee685af7ff112b95c1e9ae2e2398eaa593c3db4 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Fri, 13 Oct 2023 12:29:51 +0900 Subject: [PATCH 136/734] =?UTF-8?q?test:=20=20=EB=A6=AC=ED=84=B4=20?= =?UTF-8?q?=EA=B0=92=20=EB=B0=8F=20=EC=84=A4=EB=AA=85=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShortReviewControllerTest.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index 56623e02..82d38ec3 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -55,7 +55,7 @@ void getShortReviews() throws Exception{ //when ResultActions actions = mockMvc.perform( - get("/short-reviews" + "/animeId/{animeId}", animeId) + get("/short-reviews" + "/{animeId}", animeId) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) ); @@ -63,13 +63,13 @@ void getShortReviews() throws Exception{ //then actions .andExpect(status().isOk()) - .andExpect(jsonPath("$.item.shortReview[0].animeId").exists()) - .andExpect(jsonPath("$.item.shortReview[0].content").exists()) - .andExpect(jsonPath("$.item.shortReview[0].hasSpoiler").exists()) - .andExpect(jsonPath("$.item.shortReview[0].score").exists()) - .andExpect(jsonPath("$.item.shortReview[0].shortReviewLikeCount").exists()) - .andExpect(jsonPath("$.item.shortReview[0].member.name").exists()) - .andExpect(jsonPath("$.item.shortReview[0].member.thumbnail").exists()) + .andExpect(jsonPath("$.items[0].animeId").exists()) + .andExpect(jsonPath("$.items[0].content").exists()) + .andExpect(jsonPath("$.items[0].hasSpoiler").exists()) + .andExpect(jsonPath("$.items[0].score").exists()) + .andExpect(jsonPath("$.items[0].shortReviewLikeCount").exists()) + .andExpect(jsonPath("$.items[0].member.name").exists()) + .andExpect(jsonPath("$.items[0].member.thumbnail").exists()) .andDo(document("getShortReviews/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -78,36 +78,46 @@ void getShortReviews() throws Exception{ .description("애니 아이디") ), responseFields( - fieldWithPath("item") - .type(JsonFieldType.OBJECT) - .description("조회 데이터"), - fieldWithPath("item.shortReview") + fieldWithPath("items") .type(JsonFieldType.ARRAY) - .description("리뷰 데이터"), - fieldWithPath("item.shortReview[0].animeId") + .description("조회 데이터"), + fieldWithPath("items[].animeId") .type(JsonFieldType.NUMBER) .description("애니 고유 식별자"), - fieldWithPath("item.shortReview[0].content") + fieldWithPath("items[].content") .type(JsonFieldType.STRING) .description("짧은 리뷰 내용"), - fieldWithPath("item.shortReview[0].hasSpoiler") + fieldWithPath("items[].hasSpoiler") .type(JsonFieldType.BOOLEAN) .description("스포일러 유무"), - fieldWithPath("item.shortReview[0].score") + fieldWithPath("items[].score") .type(JsonFieldType.NUMBER) .description("평점"), - fieldWithPath("item.shortReview[0].shortReviewLikeCount") + fieldWithPath("items[].shortReviewLikeCount") .type(JsonFieldType.NUMBER) .description("리뷰 좋아요 수") - ,fieldWithPath("item.shortReview[0].member") + ,fieldWithPath("items[].member") .type(JsonFieldType.OBJECT) .description("회원 관련 데이터"), - fieldWithPath("item.shortReview[0].member.name") + fieldWithPath("items[].member.name") .type(JsonFieldType.STRING) .description("회원 이름"), - fieldWithPath("item.shortReview[0].member.thumbnail") + fieldWithPath("items[].member.thumbnail") .type(JsonFieldType.STRING) - .description("회원 이미지 사진") + .description("회원 이미지 사진"), + fieldWithPath("items[].id") + .type(JsonFieldType.NUMBER) + .description("애니 아이디"), + fieldWithPath("size") + .type(JsonFieldType.NUMBER) + .description("한 페이지에 보여줄 아이템의 개수"), + fieldWithPath("lastId") + .type(JsonFieldType.NUMBER) + .description("마지막 아이템의 id"), + fieldWithPath("lastPage") + .type(JsonFieldType.BOOLEAN) + .description("마지막 페이지일 경우, true 반환.") + ) )); //TODO : 조회 실패 시 From ffc2845efa5861ed545493d72824bbee8b0113a4 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Fri, 13 Oct 2023 12:31:01 +0900 Subject: [PATCH 137/734] =?UTF-8?q?test:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20r?= =?UTF-8?q?esponse=EA=B0=92=20=EC=88=98=EC=A0=95=20#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/unit/shortReview/service/ShortReviewServiceTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java b/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java index a0dae6d5..e75910f0 100644 --- a/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java +++ b/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java @@ -33,9 +33,7 @@ void getShortReviews(){ //then assertDoesNotThrow(() -> shortReviewService.getShortReviews(animeId)); - assertEquals(response.getShortReview().get(0).getAnimeId(),animeId); + assertEquals(response.getShortReviews().getContent().get(0).getAnimeId(),animeId); } } - - } \ No newline at end of file From ea0bf5dbc1dddc129b68b25005d8a1f7d7b96143 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 14 Oct 2023 21:54:45 +0900 Subject: [PATCH 138/734] =?UTF-8?q?refactor:=20Anime=20Controller=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=B3=80=EA=B2=BD=20#17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dto를 SingleResponse에 감싸서 return하는 것이 아닌, 바로 return하도록 변경하였습니다. animeId를 id로 변경하였습니다. Dto의 Broadcast 클래스를 삭제하였습니다. --- .../anime/controller/AnimeController.java | 3 +-- .../oduck/api/domain/anime/dto/AnimeRes.java | 14 ++++--------- .../anime/service/AnimeServiceStub.java | 20 ++++++++----------- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java index c9f70052..7d3c6fb9 100644 --- a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java +++ b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java @@ -2,7 +2,6 @@ import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.service.AnimeService; -import io.oduck.api.global.common.SingleResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -28,7 +27,7 @@ public ResponseEntity getAnimeById(@PathVariable Long animeId){ // TODO: 애니 조회 로직 구현 AnimeRes res = animeService.getAnimeById(animeId); return ResponseEntity - .ok(SingleResponse.of(res)); + .ok(res); } // TODO: 애니 검색 결과 페이징 diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java index 12c1f321..a0d84a60 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -16,10 +16,12 @@ public class AnimeRes { @Getter @Builder public static class Anime { - private Long animeId; + private Long id; private String title; private String thumbnail; - private Broadcast broadcast; + private BroadcastType broadcastType; + private int year; + private Quarter quarter; private String summary; private int episodeCount; private Rating rating; @@ -32,12 +34,4 @@ public static class Anime { private long reviewCount; private long bookmarkCount; } - - @Getter - @Builder - public static class Broadcast { - private BroadcastType broadcastType; - private int year; - private Quarter quarter; - } } diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java index ae01a554..eba7d573 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java @@ -2,7 +2,10 @@ import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.dto.AnimeRes.Anime; -import io.oduck.api.domain.anime.dto.AnimeRes.Broadcast; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; import io.oduck.api.domain.anime.entity.Rating; @@ -25,10 +28,12 @@ public AnimeRes getAnimeById(Long animeId) { private Anime createAnime(Long animeId) { return Anime.builder() - .animeId(animeId) + .id(animeId) .title("귀멸의 칼날: 도공 마을편") .thumbnail("https://image파일경로/uuid.jpg") - .broadcast(getBroadcast()) + .broadcastType(BroadcastType.TVA) + .year(2023) + .quarter(Quarter.Q2) .summary( "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다. 탄지로가 검이 수리되기를 기다리는 동안, 상현 혈귀 한텐구와 쿗코가 숨겨진 마을인 ‘도공 마을'을 습격한다. 공격할 때마다 분열해서 위력이 커지는 한텐구로 인해 탄지로와 겐야는 고전을 면치 못한다. 한편, 타인에 대한 관심이 희박한 하주 토키토 무이치로는 혈귀들에게 공격당하고 있는 코테츠를 목격하는데….") .episodeCount(11) @@ -43,15 +48,6 @@ private Anime createAnime(Long animeId) { .build(); } - private Broadcast getBroadcast() { - return Broadcast.builder() - .broadcastType(BroadcastType.TVA) - .year(2023) - .quarter(Quarter.Q2) - .build(); - - } - private List getStudios() { List studios = new ArrayList<>(); studios.add("ufotable"); From 30458db3209132a268d0e06b6d0d91c55771c36d Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 14 Oct 2023 21:55:12 +0900 Subject: [PATCH 139/734] =?UTF-8?q?test:=20Anime=20Controller=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=80=EA=B2=BD=20#17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/anime/AnimeControllerTest.java | 75 +++++++++---------- .../unit/anime/service/AnimeServiceTest.java | 2 +- 2 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index 89ba7b69..479a8bac 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -9,6 +9,7 @@ import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -26,7 +27,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @AutoConfigureRestDocs @AutoConfigureMockMvc @@ -57,22 +57,23 @@ void getAnimes() throws Exception { //then actions - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(jsonPath("$.item.anime.title").exists()) - .andExpect(jsonPath("$.item.anime.thumbnail").exists()) - .andExpect(jsonPath("$.item.anime.broadcast.broadcastType").exists()) - .andExpect(jsonPath("$.item.anime.broadcast.year").exists()) - .andExpect(jsonPath("$.item.anime.broadcast.quarter").exists()) - .andExpect(jsonPath("$.item.anime.summary").exists()) - .andExpect(jsonPath("$.item.anime.episodeCount").exists()) - .andExpect(jsonPath("$.item.anime.rating").exists()) - .andExpect(jsonPath("$.item.anime.status").exists()) - .andExpect(jsonPath("$.item.anime.genres").exists()) - .andExpect(jsonPath("$.item.anime.originalAuthors").exists()) - .andExpect(jsonPath("$.item.anime.voiceActors").exists()) - .andExpect(jsonPath("$.item.anime.studios").exists()) - .andExpect(jsonPath("$.item.anime.reviewCount").exists()) - .andExpect(jsonPath("$.item.anime.bookmarkCount").exists()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.anime.id").exists()) + .andExpect(jsonPath("$.anime.title").exists()) + .andExpect(jsonPath("$.anime.thumbnail").exists()) + .andExpect(jsonPath("$.anime.broadcastType").exists()) + .andExpect(jsonPath("$.anime.year").exists()) + .andExpect(jsonPath("$.anime.quarter").exists()) + .andExpect(jsonPath("$.anime.summary").exists()) + .andExpect(jsonPath("$.anime.episodeCount").exists()) + .andExpect(jsonPath("$.anime.rating").exists()) + .andExpect(jsonPath("$.anime.status").exists()) + .andExpect(jsonPath("$.anime.genres").exists()) + .andExpect(jsonPath("$.anime.originalAuthors").exists()) + .andExpect(jsonPath("$.anime.voiceActors").exists()) + .andExpect(jsonPath("$.anime.studios").exists()) + .andExpect(jsonPath("$.anime.reviewCount").exists()) + .andExpect(jsonPath("$.anime.bookmarkCount").exists()) .andDo(document("getAnimeById/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -81,61 +82,55 @@ void getAnimes() throws Exception { .description("애니의 고유 식별자") ), responseFields( - fieldWithPath("item") - .type(JsonFieldType.OBJECT) - .description("조회 데이터"), - fieldWithPath("item.anime") + fieldWithPath("anime") .type(JsonFieldType.OBJECT) .description("애니 정보"), - fieldWithPath("item.anime.animeId") + fieldWithPath("anime.id") .type(JsonFieldType.NUMBER) .description("애니의 고유 식별자"), - fieldWithPath("item.anime.title") + fieldWithPath("anime.title") .type(JsonFieldType.STRING) .description("애니 제목"), - fieldWithPath("item.anime.thumbnail") + fieldWithPath("anime.thumbnail") .type(JsonFieldType.STRING) .description("애니 이미지, 경로로 저장됨"), - fieldWithPath("item.anime.broadcast") - .type(JsonFieldType.OBJECT) - .description("애니 방송 관련 정보"), - fieldWithPath("item.anime.broadcast.broadcastType") + fieldWithPath("anime.broadcastType") .type(JsonFieldType.STRING) .description("작품의 출시 방식. TVA, OVA, ONA, MOV가 있음"), - fieldWithPath("item.anime.broadcast.year") + fieldWithPath("anime.year") .type(JsonFieldType.NUMBER) .description("작품의 출시 년도"), - fieldWithPath("item.anime.broadcast.quarter") + fieldWithPath("anime.quarter") .type(JsonFieldType.STRING) .description("작품의 출시 분기"), - fieldWithPath("item.anime.summary") + fieldWithPath("anime.summary") .type(JsonFieldType.STRING) .description("애니를 소개할 때 줄거리"), - fieldWithPath("item.anime.episodeCount") + fieldWithPath("anime.episodeCount") .type(JsonFieldType.NUMBER) .description("에피소드의 숫자. 16화까지 나왔으면 16으로 표기됨."), - fieldWithPath("item.anime.rating") + fieldWithPath("anime.rating") .type(JsonFieldType.STRING) .description("작품의 심의 등급. ADULT, FIFTEEN, TWELVE, ALL가 있다."), - fieldWithPath("item.anime.status") + fieldWithPath("anime.status") .type(JsonFieldType.STRING) .description("방영 상태. FINISHED, ONGOING, UPCOMING, UNKNOWN이 있다."), - fieldWithPath("item.anime.genres") + fieldWithPath("anime.genres") .type(JsonFieldType.ARRAY) .description("애니의 장르."), - fieldWithPath("item.anime.originalAuthors") + fieldWithPath("anime.originalAuthors") .type(JsonFieldType.ARRAY) .description("애니의 원작 작가."), - fieldWithPath("item.anime.voiceActors") + fieldWithPath("anime.voiceActors") .type(JsonFieldType.ARRAY) .description("애니의 출연 성우."), - fieldWithPath("item.anime.studios") + fieldWithPath("anime.studios") .type(JsonFieldType.ARRAY) .description("애니의 제작사."), - fieldWithPath("item.anime.reviewCount") + fieldWithPath("anime.reviewCount") .type(JsonFieldType.NUMBER) .description("애니의 리뷰 개수."), - fieldWithPath("item.anime.bookmarkCount") + fieldWithPath("anime.bookmarkCount") .type(JsonFieldType.NUMBER) .description("애니의 덕후 수.") ) diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index 8d8b1461..357d9dd5 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -32,7 +32,7 @@ void getAnimeById() { AnimeRes response = animeService.getAnimeById(animeId); //then - assertThat(response.getAnime().getAnimeId()).isEqualTo(animeId); + assertThat(response.getAnime().getId()).isEqualTo(animeId); assertThatNoException(); } } From 2be55a61ee2094009710db1fe7b34a5bbad24bfa Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 14 Oct 2023 21:58:34 +0900 Subject: [PATCH 140/734] =?UTF-8?q?feat:=20Admin=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=B6=94=EA=B0=80,=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D,=20=EC=88=98=EC=A0=95=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 154 ++++++++++++++ .../oduck/api/domain/anime/dto/AnimeReq.java | 200 ++++++++++++++++++ .../domain/anime/service/AnimeService.java | 56 +++++ .../anime/service/AnimeServiceStub.java | 38 ++++ 4 files changed, 448 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/admin/controller/AdminController.java create mode 100644 src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java diff --git a/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java new file mode 100644 index 00000000..6f9503d7 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java @@ -0,0 +1,154 @@ +package io.oduck.api.domain.admin.controller; + +import static io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.PostReq; + +import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.domain.anime.service.AnimeService; +import io.oduck.api.domain.series.entity.Series; +import jakarta.validation.Valid; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +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.RestController; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/oduckdmin") +@Slf4j +public class AdminController { + + private final AnimeService animeService; + + //애니 등록 + @PostMapping("/animes") + public ResponseEntity postAnime(@RequestBody @Valid PostReq req){ + // TODO: 애니 추가 로직 구현 + animeService.save(req); + + return ResponseEntity.ok().build(); + } + + //애니 관련 수정 + @PatchMapping("/animes/{animeId}") + public ResponseEntity patchAnime( + @PathVariable Long animeId, @RequestBody @Valid PatchAnimeReq req + ){ + //TODO: 애니 수정 로직 구현 + animeService.update(animeId, req); + + return ResponseEntity.noContent().build(); + } + + // 애니의 원작 작가 수정 + @PatchMapping("/animes/{animeId}/original-authors") + public ResponseEntity patchAnimeOriginalAuthors( + @PathVariable Long animeId, @RequestBody PatchOriginalAuthorIdsReq req + ){ + List originalAuthorIds = req.getOriginalAuthorIds(); + //TODO: 컨트롤러에서 애니 아이디와 원작 작가 아이디로 AnimeOriginalAuthor들을 찾아야 함. + List animeOriginalAuthors = new ArrayList<>(); + + //TODO: 애니 수정 로직 구현 + animeService.updateAnimeOriginalAuthors(animeId, animeOriginalAuthors); + + return ResponseEntity.noContent().build(); + } + + // 애니의 스튜디오 수정 + @PatchMapping("/animes/{animeId}/studios") + public ResponseEntity patchAnimeStudios( + @PathVariable Long animeId, @RequestBody PatchStudioIdsReq req + ){ + List originalAuthorIds = req.getStudioIds(); + //TODO: 컨트롤러에서 애니 아이디와 원작 작가 아이디로 AnimeOriginalAuthor들을 찾아야 함. + List animeStudios = new ArrayList<>(); + + //TODO: 애니 수정 로직 구현 + animeService.updateAnimeStudios(animeId, animeStudios); + + return ResponseEntity.noContent().build(); + } + // 애니의 성우 수정 + @PatchMapping("/animes/{animeId}/voice-actors") + public ResponseEntity patchAnimeVoiceActors( + @PathVariable Long animeId, @RequestBody PatchVoiceActorIdsReq req + ){ + List voiceActorIds = req.getVoiceActorIds(); + //TODO: 컨트롤러에서 애니 아이디와 성우 아이디로 AnimeOriginalAuthor들을 찾아야 함. + List animeVoiceActors = new ArrayList<>(); + + //TODO: 애니 수정 로직 구현 + animeService.updateAnimeVoiceActors(animeId, animeVoiceActors); + + return ResponseEntity.noContent().build(); + } + + // 애니의 장르 수정 + @PatchMapping("/animes/{animeId}/genres") + public ResponseEntity patchAnimeGenres( + @PathVariable Long animeId, @RequestBody PatchGenreIdsReq req + ){ + List genreIds = req.getGenreIds(); + //TODO: 컨트롤러에서 애니 아이디와 장르 아이디로 AnimeGenre들을 찾아야 함. + List animeGenres = new ArrayList<>(); + + //TODO: 애니 수정 로직 구현 + animeService.updateAnimeGenres(animeId, animeGenres); + + return ResponseEntity.noContent().build(); + } + + // 애니의 시리즈 수정 + @PatchMapping("/animes/{animeId}/series") + public ResponseEntity patchAnimeSeries( + @PathVariable Long animeId, @RequestBody PatchSeriesIdReq req + ){ + Long seriesId = req.getSeriesId(); + //TODO: 컨트롤러에서 시리즈 아이디로 시리즈를 찾아야 함. + Series series = null; + + //TODO: 애니 수정 로직 구현 + animeService.update(animeId, series); + + return ResponseEntity.noContent().build(); + } + + // 애니 삭제 +// @DeleteMapping("/animes/{animeId}") +// public ResponseEntity deleteAnime(@PathVariable Long animeId){ +// +// //TODO: 애니 삭제 로직 구현. (애니를 실제로 삭제하지 않음) +// animeService.delete(animeId); +// return ResponseEntity.noContent().build(); +// } + +// 관리자 애니 조회 (isReleased = false도 조회) +// @GetMapping("/animes") +// public ResponseEntity getAnimes( +// Pageable pageable, String query, SearchCondition condition +// ){ +// +// //TODO: 애니 조회 로직 구현. +// Page res = adminAnimeService.getAnimes(pageable, query, condition); +// return ResponseEntity +// .ok(PageResponse.of(null)); +// } +} diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java new file mode 100644 index 00000000..a0ce1168 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java @@ -0,0 +1,200 @@ +package io.oduck.api.domain.anime.dto; + +import io.oduck.api.domain.anime.entity.BroadcastType; +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Rating; +import io.oduck.api.domain.anime.entity.Status; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class AnimeReq { + + @Getter + public static class PostReq { + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String title; + + @NotBlank + @Length(min = 1, max = 255, + message = "글자 수는 0~255를 허용합니다.") + private String summary; + + private BroadcastType broadcastType; + + @Min(value = 0, + message = "음수가 올 수 없습니다.") + private int episodeCount; + + @NotBlank( + message = "섬네일을 입력하세요.") + private String thumbnail; + + @Min(value = 1900, + message = "1900년대 이상을 입력하세요.") + private int year; + + private Quarter quarter; + + private Rating rating; + + private Status status; + + private List originalAuthorIds; + + private List studioIds; + + private List voiceActorIds; + + private List genreIds; + + private Long seriesId; + + public PostReq (String title, String summary, BroadcastType broadcastType, + int episodeCount, String thumbnail, int year, Quarter quarter, Rating rating, Status status, + List originalAuthorIds, List studioIds, List voiceActorIds, + List genreIds, Long seriesId) { + + this.title = title; + this.summary = summary; + this.broadcastType = broadcastType; + this.episodeCount = episodeCount; + this.thumbnail = thumbnail; + this.year = year; + this.quarter = quarter; + this.rating = rating; + this.status = status; + + if(originalAuthorIds == null){ + this.originalAuthorIds = new ArrayList<>(); + }else { + this.originalAuthorIds = originalAuthorIds; + } + + if(studioIds == null){ + this.studioIds = new ArrayList<>(); + }else{ + this.studioIds = studioIds; + } + + if(voiceActorIds == null) { + this.voiceActorIds = new ArrayList<>(); + }else { + this.voiceActorIds = voiceActorIds; + } + + if(genreIds == null) { + this.genreIds = new ArrayList<>(); + }else { + this.genreIds = genreIds; + } + + this.seriesId = seriesId; + } + } + + @Getter + @AllArgsConstructor + public static class PatchAnimeReq{ + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String title; + + @NotBlank + @Length(min = 1, max = 255, + message = "글자 수는 0~255를 허용합니다.") + private String summary; + + private BroadcastType broadcastType; + + @Min(value = 0, + message = "음수가 올 수 없습니다.") + private int episodeCount; + + @NotBlank( + message = "섬네일을 입력하세요.") + private String thumbnail; + + @Min(value = 1900, + message = "1900년대 이상을 입력하세요.") + private int year; + + private Quarter quarter; + + private Rating rating; + + private Status status; + } + + @Getter + @NoArgsConstructor + public static class PatchOriginalAuthorIdsReq { + private List originalAuthorIds; + + public PatchOriginalAuthorIdsReq(List originalAuthorIds) { + if(originalAuthorIds == null){ + this.originalAuthorIds = new ArrayList<>(); + }else{ + this.originalAuthorIds = originalAuthorIds; + } + } + } + + @Getter + @NoArgsConstructor + public static class PatchStudioIdsReq { + private List studioIds; + + public PatchStudioIdsReq(List studioIds) { + if(studioIds == null){ + this.studioIds = new ArrayList<>(); + }else{ + this.studioIds = studioIds; + } + } + + } + + @Getter + @NoArgsConstructor + public static class PatchVoiceActorIdsReq { + private List voiceActorIds; + + public PatchVoiceActorIdsReq(List voiceActorIds) { + if(voiceActorIds == null){ + this.voiceActorIds = new ArrayList<>(); + }else{ + this.voiceActorIds = voiceActorIds; + } + } + } + + @Getter + @NoArgsConstructor + public static class PatchGenreIdsReq { + private List genreIds; + + public PatchGenreIdsReq(List genreIds) { + if(genreIds == null){ + this.genreIds = new ArrayList<>(); + }else{ + this.genreIds = genreIds; + } + } + } + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class PatchSeriesIdReq{ + private Long seriesId; + } +} diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java index 7b0f776d..e2c35c91 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java @@ -1,6 +1,13 @@ package io.oduck.api.domain.anime.service; +import io.oduck.api.domain.anime.dto.AnimeReq.*; import io.oduck.api.domain.anime.dto.AnimeRes; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.domain.series.entity.Series; +import java.util.List; public interface AnimeService { @@ -10,4 +17,53 @@ public interface AnimeService { * @return AnimeRes */ AnimeRes getAnimeById(Long animeId); + + /** + * 애니 저장 로직 + * @param req + * @return AnimeId + */ + Long save(PostReq req); + + /** + * 애니 수정 로직 + * @param animeId + * @param req + */ + void update(Long animeId, PatchAnimeReq req); + + /** + * 애니 원작 작가 수정 로직 + * @param animeId + * @param originalAuthors + */ + void updateAnimeOriginalAuthors(Long animeId, List originalAuthors); + + /** + * 애니 스튜디오 수정 로직 + * @param animeId + * @param animeStudios + */ + void updateAnimeStudios(Long animeId, List animeStudios); + + /** + * 애니 성우 수정 로직 + * @param animeId + * @param animeVoiceActors + */ + void updateAnimeVoiceActors(Long animeId, List animeVoiceActors); + + /** + * 애니 장르 수정 로직 + * @param animeId + * @param animeGenres + */ + void updateAnimeGenres(Long animeId, List animeGenres); + + /** + * 애니 시리즈 수정 로직 + * @param animeId + * @param series + */ + void update(Long animeId, Series series); } diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java index eba7d573..9118a856 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java @@ -1,5 +1,7 @@ package io.oduck.api.domain.anime.service; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.dto.AnimeRes.Anime; import io.oduck.api.domain.anime.entity.AnimeGenre; @@ -10,6 +12,7 @@ import io.oduck.api.domain.anime.entity.Quarter; import io.oduck.api.domain.anime.entity.Rating; import io.oduck.api.domain.anime.entity.Status; +import io.oduck.api.domain.series.entity.Series; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; @@ -17,6 +20,11 @@ @Service public class AnimeServiceStub implements AnimeService{ + @Override + public Long save(PostReq req) { + return 1L; + } + @Override public AnimeRes getAnimeById(Long animeId) { Anime anime = createAnime(animeId); @@ -26,6 +34,36 @@ public AnimeRes getAnimeById(Long animeId) { .build(); } + @Override + public void update(Long animeId, PatchAnimeReq req) { + + } + + @Override + public void updateAnimeOriginalAuthors(Long animeId, List originalAuthors) { + + } + + @Override + public void updateAnimeStudios(Long animeId, List animeStudios) { + + } + + @Override + public void updateAnimeVoiceActors(Long animeId, List animeVoiceActors) { + + } + + @Override + public void updateAnimeGenres(Long animeId, List animeGenres) { + + } + + @Override + public void update(Long animeId, Series series) { + + } + private Anime createAnime(Long animeId) { return Anime.builder() .id(animeId) From 1c15b7d45fba6ac79d232c3666c994a004bb540d Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 14 Oct 2023 22:18:01 +0900 Subject: [PATCH 141/734] =?UTF-8?q?test:=20=EC=95=A0=EB=8B=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D,=20=EC=88=98=EC=A0=95=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20#17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/admin/AdminControllerTest.java | 410 ++++++++++++++++++ .../api/global/utils/AnimeTestUtils.java | 92 ++++ .../unit/anime/service/AnimeServiceTest.java | 17 + 3 files changed, 519 insertions(+) create mode 100644 src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java create mode 100644 src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java diff --git a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java new file mode 100644 index 00000000..ec5f5023 --- /dev/null +++ b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java @@ -0,0 +1,410 @@ +package io.oduck.api.e2e.admin; + +import static io.oduck.api.global.config.RestDocsConfig.field; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.google.gson.Gson; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import io.oduck.api.global.utils.AnimeTestUtils; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@AutoConfigureRestDocs +@AutoConfigureMockMvc +@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) +@SpringBootTest +@ActiveProfiles("test") +public class AdminControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + private final static String ADMIN_URL = "/oduckdmin"; + + @Nested + @DisplayName("애니 등록") + class PostAnime{ + + @Test + @DisplayName("등록 성공 시 Http Status 200 반환") + void postAnime() throws Exception { + //given + PostReq request = AnimeTestUtils.createPostAnimeRequest(); + String content = gson.toJson(request); + + //when + ResultActions actions = mockMvc.perform( + post(ADMIN_URL+"/animes") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andExpect(status().isOk()) + .andDo(document("postAnime/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("title") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("애니의 제목"), + fieldWithPath("summary") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~255자를 허용합니다.")) + .description("애니 요약 설명"), + fieldWithPath("broadcastType") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "TVA,ONA,OVA,MOV만 허용합니다.")) + .description("애니 방송사"), + fieldWithPath("episodeCount") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "0 이상의 숫자만 허용합니다.")) + .description("애피소드 화수"), + fieldWithPath("thumbnail") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다.")) + .description("애피소드 화수"), + fieldWithPath("year") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "1900 이상의 숫자만 허용합니다.")) + .description("애니 발행 년도"), + fieldWithPath("quarter") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "Q1,Q2,Q3,Q4만 허용합니다.")) + .description("애니 발행 분기"), + fieldWithPath("rating") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ADULT,FIFTEEN,TWELVE,ALL만 허용합니다.")) + .description("애니 등급"), + fieldWithPath("status") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ONGOING,FINISHED,UPCOMING,UNKNOWN만 허용합니다.")) + .description("애니 상태"), + fieldWithPath("originalAuthorIds") + .type(JsonFieldType.ARRAY) + .description("원작 작가들 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("studioIds") + .type(JsonFieldType.ARRAY) + .description("애니 스튜디오 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("voiceActorIds") + .type(JsonFieldType.ARRAY) + .description("애니 성우 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("genreIds") + .type(JsonFieldType.ARRAY) + .description("애니 장르 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("seriesId") + .type(JsonFieldType.NUMBER) + .description("애니 시리즈 아이디") + .attributes(field("constraints", "숫자만 허용합니다.")) + .optional() + ) + )); + } + //TODO : 애니 등록 실패 시 + } + + @Nested + @DisplayName("애니 수정") + class PatchAnime{ + + @Test + @DisplayName("애니 수정 성공 시 Http Status 204 반환") + void patchAnime() throws Exception { + //given + Long animeId = 1L; + PatchAnimeReq req = AnimeTestUtils.createPatchAnimeRequest(); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch(ADMIN_URL+"/animes/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andExpect(status().isNoContent()) + .andDo(document("patchAnime/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("title") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("애니의 제목"), + fieldWithPath("summary") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~255자를 허용합니다.")) + .description("애니 요약 설명"), + fieldWithPath("broadcastType") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "TVA,ONA,OVA,MOV만 허용합니다.")) + .description("애니 방송사"), + fieldWithPath("episodeCount") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "0 이상의 숫자만 허용합니다.")) + .description("애피소드 화수"), + fieldWithPath("thumbnail") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다.")) + .description("애피소드 화수"), + fieldWithPath("year") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "1900 이상의 숫자만 허용합니다.")) + .description("애니 발행 년도"), + fieldWithPath("quarter") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "Q1,Q2,Q3,Q4만 허용합니다.")) + .description("애니 발행 분기"), + fieldWithPath("rating") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ADULT,FIFTEEN,TWELVE,ALL만 허용합니다.")) + .description("애니 등급"), + fieldWithPath("status") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ONGOING,FINISHED,UPCOMING,UNKNOWN만 허용합니다.")) + .description("애니 상태") + ) + )); + + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니 원작 작가 수정 성공 시 Http Status 204 반환") + void patchAnimeOriginalAuthors() throws Exception { + //given + Long animeId = 1L; + List originalAuthorIds = AnimeTestUtils.getOriginalAuthorIds(); + + PatchOriginalAuthorIdsReq req = new PatchOriginalAuthorIdsReq(originalAuthorIds); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch(ADMIN_URL+"/animes/{animeId}/original-authors", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andDo(document("patchAnimeOriginalAuthors/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("originalAuthorIds") + .type(JsonFieldType.ARRAY) + .description("원작 작가들 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); + + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니 원작 작가 수정 성공 시 Http Status 204 반환") + void patchAnimeStudios() throws Exception { + //given + Long animeId = 1L; + List studioIds = AnimeTestUtils.getStudioIds(); + + PatchStudioIdsReq req = new PatchStudioIdsReq(studioIds); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch(ADMIN_URL+"/animes/{animeId}/studios", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andDo(document("patchAnimeStudios/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("studioIds") + .type(JsonFieldType.ARRAY) + .description("스튜디오 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); + + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니 성우 수정 성공 시 Http Status 204 반환") + void patchAnimeVoiceActors() throws Exception { + //given + Long animeId = 1L; + List voiceActorIds = AnimeTestUtils.getVoiceActorIds(); + + PatchVoiceActorIdsReq req = new PatchVoiceActorIdsReq(voiceActorIds); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + put(ADMIN_URL+"/animes/{animeId}/voice-actors", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andDo(document("patchAnimeVoiceActors/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("voiceActorIds") + .type(JsonFieldType.ARRAY) + .description("애니 성우 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니 장르 수정 성공 시 Http Status 204 반환") + void patchAnimeGenres() throws Exception { + //given + Long animeId = 1L; + List genreIds = AnimeTestUtils.getGenreIds(); + + PatchGenreIdsReq req = new PatchGenreIdsReq(genreIds); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + put(ADMIN_URL+"/animes/{animeId}/genres", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andDo(document("patchAnimeGenres/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("genreIds") + .type(JsonFieldType.ARRAY) + .description("애니 장르 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니의 시리즈 수정 성공 시 Http Status 204 반환") + void patchAnimeSeries() throws Exception { + //given + Long animeId = 1L; + PatchSeriesIdReq req = new PatchSeriesIdReq(AnimeTestUtils.getSeriesId()); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch(ADMIN_URL+"/animes/{animeId}/series", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andExpect(status().isNoContent()) + .andDo(document("patchAnimeSeries/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("seriesId") + .type(JsonFieldType.NUMBER) + .description("애니 시리즈 아이디") + .attributes(field("constraints", "숫자만 허용합니다.")) + .optional() + ) + )); + } + //TODO: 수정 실패 시 + } +} diff --git a/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java new file mode 100644 index 00000000..d3995b88 --- /dev/null +++ b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java @@ -0,0 +1,92 @@ +package io.oduck.api.global.utils; + +import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import io.oduck.api.domain.anime.entity.BroadcastType; +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Rating; +import io.oduck.api.domain.anime.entity.Status; +import java.util.ArrayList; +import java.util.List; + +public class AnimeTestUtils { + public static PostReq createPostAnimeRequest(){ + return new PostReq( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), + getThumbnail(), getYear(), getQuarter(), getRating(), getStatus(), + getOriginalAuthorIds(), getStudioIds(), getVoiceActorIds(), getGenreIds(), getSeriesId()); + } + + public static PatchAnimeReq createPatchAnimeRequest(){ + return new PatchAnimeReq( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), + getThumbnail(), getYear(), getQuarter(), getRating(), getStatus()); + } + + public static long getSeriesId() { + return 1L; + } + + public static Status getStatus() { + return Status.FINISHED; + } + + public static Rating getRating() { + return Rating.ADULT; + } + + public static Quarter getQuarter() { + return Quarter.Q2; + } + + public static int getYear() { + return 2023; + } + + public static String getThumbnail() { + return "/uuid.jpg"; + } + + public static int getEpisodeCount() { + return 11; + } + + public static BroadcastType getBroadcastType() { + return BroadcastType.TVA; + } + + public static String getSummary() { + return "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다."; + } + + public static String getTitle() { + return "귀멸의 칼날: 도공 마을편"; + } + + + public static List getGenreIds() { + List genreIds = new ArrayList(); + genreIds.add(4L); + genreIds.add(5L); + return genreIds; + } + + public static List getVoiceActorIds() { + List voiceActorIds = getStudioIds(); + voiceActorIds.add(2L); + voiceActorIds.add(4L); + return voiceActorIds; + } + + public static List getStudioIds() { + List studioIds = new ArrayList(); + studioIds.add(1L); + return studioIds; + } + + public static List getOriginalAuthorIds() { + List originalAuthorIds = new ArrayList(); + originalAuthorIds.add(5L); + return originalAuthorIds; + } +} diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index 357d9dd5..2ad167bf 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -3,8 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.service.AnimeServiceStub; +import io.oduck.api.global.utils.AnimeTestUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -36,4 +38,19 @@ void getAnimeById() { assertThatNoException(); } } + + @Nested + @DisplayName("애니 등록") + class PostAnime{ + + @Test + @DisplayName("애니 등록 성공") + void postAnime(){ + PostReq req = AnimeTestUtils.createPostAnimeRequest(); + + Long animeId = animeService.save(req); + + assertThat(animeId).isEqualTo(1L); + } + } } From ac1f4b060ef38fc9718b349b5fa5536dc8d47321 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 15:39:52 +0900 Subject: [PATCH 142/734] =?UTF-8?q?test:=20=ED=8F=B4=EB=8D=94=20=EB=AA=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/{QueryDslConfig.java => QueryDslTestConfig.java} | 0 .../global/{MockMember => mockMember}/WithCustomMockMember.java | 0 .../WithMemberDetailsSecurityContextFactory.java | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/test/java/io/oduck/api/global/config/{QueryDslConfig.java => QueryDslTestConfig.java} (100%) rename src/test/java/io/oduck/api/global/{MockMember => mockMember}/WithCustomMockMember.java (100%) rename src/test/java/io/oduck/api/global/{MockMember => mockMember}/WithMemberDetailsSecurityContextFactory.java (100%) diff --git a/src/test/java/io/oduck/api/global/config/QueryDslConfig.java b/src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java similarity index 100% rename from src/test/java/io/oduck/api/global/config/QueryDslConfig.java rename to src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java diff --git a/src/test/java/io/oduck/api/global/MockMember/WithCustomMockMember.java b/src/test/java/io/oduck/api/global/mockMember/WithCustomMockMember.java similarity index 100% rename from src/test/java/io/oduck/api/global/MockMember/WithCustomMockMember.java rename to src/test/java/io/oduck/api/global/mockMember/WithCustomMockMember.java diff --git a/src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java b/src/test/java/io/oduck/api/global/mockMember/WithMemberDetailsSecurityContextFactory.java similarity index 100% rename from src/test/java/io/oduck/api/global/MockMember/WithMemberDetailsSecurityContextFactory.java rename to src/test/java/io/oduck/api/global/mockMember/WithMemberDetailsSecurityContextFactory.java From 01e74f2bd55b3c2a2dd850249507c972fca011ec Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 15:40:19 +0900 Subject: [PATCH 143/734] =?UTF-8?q?chore:=20queryDsl=20=EB=B9=8C=EB=93=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 882f602f..b5bc130f 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ dependencies { runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' // QueryDSL - implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + implementation "com.querydsl:querydsl-jpa:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" From 43a07d4b43a4571806f518c433826cb799949f37 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 15:42:55 +0900 Subject: [PATCH 144/734] =?UTF-8?q?refactor:=20memberResDsl=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/member/dto/MemberResDto.java | 4 ++-- .../io/oduck/api/domain/member/service/MemberServiceStub.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java index bf6ebf64..4fa2c4c1 100644 --- a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java @@ -14,11 +14,11 @@ public static class MemberProfileRes { private String thumbnail; private String backgroundImage; private Activity activity; - private int point; + private Long point; @Builder public MemberProfileRes(boolean isMine, String name, String description, String thumbnail, - String backgroundImage, Activity activity, int point) { + String backgroundImage, Activity activity, Long point) { this.isMine = isMine; this.name = name; this.description = description; diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java index 671b8bca..f8bf65d4 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java @@ -82,7 +82,7 @@ public MemberProfileRes getProfileByName(String name) { .thumbnail("썸네일") .backgroundImage("배경 이미지") .activity(activity) - .point(0) + .point(0L) .build(); return memberProfile; From dddc57547a14edaec6a8338a9c2aec1190b67a64 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 15:44:23 +0900 Subject: [PATCH 145/734] =?UTF-8?q?test:=20queryDsl=20Config=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=AA=85=20=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/global/config/QueryDslTestConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java b/src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java index ba043705..66d4e947 100644 --- a/src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java +++ b/src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java @@ -7,7 +7,7 @@ import org.springframework.context.annotation.Bean; @TestConfiguration -public class QueryDslConfig { +public class QueryDslTestConfig { @PersistenceContext private EntityManager entityManager; From 13c10f24fda874cfab0181dc3a2a3e7ead88ab66 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 15:44:57 +0900 Subject: [PATCH 146/734] =?UTF-8?q?test:=20=ED=8F=B4=EB=8D=94=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/mockMember/WithCustomMockMember.java | 2 +- .../mockMember/WithMemberDetailsSecurityContextFactory.java | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/oduck/api/global/mockMember/WithCustomMockMember.java b/src/test/java/io/oduck/api/global/mockMember/WithCustomMockMember.java index 049c5fc1..36115c87 100644 --- a/src/test/java/io/oduck/api/global/mockMember/WithCustomMockMember.java +++ b/src/test/java/io/oduck/api/global/mockMember/WithCustomMockMember.java @@ -1,4 +1,4 @@ -package io.oduck.api.global.MockMember; +package io.oduck.api.global.mockMember; import io.oduck.api.domain.member.entity.Role; import java.lang.annotation.Retention; diff --git a/src/test/java/io/oduck/api/global/mockMember/WithMemberDetailsSecurityContextFactory.java b/src/test/java/io/oduck/api/global/mockMember/WithMemberDetailsSecurityContextFactory.java index 34298fb0..48ebbce5 100644 --- a/src/test/java/io/oduck/api/global/mockMember/WithMemberDetailsSecurityContextFactory.java +++ b/src/test/java/io/oduck/api/global/mockMember/WithMemberDetailsSecurityContextFactory.java @@ -1,8 +1,6 @@ -package io.oduck.api.global.MockMember; +package io.oduck.api.global.mockMember; -import io.oduck.api.domain.member.entity.LoginType; import io.oduck.api.global.security.auth.dto.CustomUserDetails; -import io.oduck.api.global.security.auth.dto.AuthUser; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -11,8 +9,6 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.test.context.support.WithSecurityContextFactory; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; public class WithMemberDetailsSecurityContextFactory implements WithSecurityContextFactory { From 9378f7e41f7a4370f7c0bbdf630b745009c5130b Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 15:55:22 +0900 Subject: [PATCH 147/734] =?UTF-8?q?feat:=20DslDto=20=EC=B6=94=EA=B0=80=20#?= =?UTF-8?q?22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/dto/MemberDslDto.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/member/dto/MemberDslDto.java diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberDslDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberDslDto.java new file mode 100644 index 00000000..74107061 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberDslDto.java @@ -0,0 +1,43 @@ +package io.oduck.api.domain.member.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +// TODO: VO로 변경 할지 논의 필요 +public class MemberDslDto { + @Getter + @NoArgsConstructor + public static class ProfileWithoutActivity { + private Long memberId; + private String name; + private String description; + private String thumbnail; + private String backgroundImage; + private Long point; + + @Builder + public ProfileWithoutActivity(Long memberId, String name, String description, String thumbnail, + String backgroundImage, Long point) { + this.memberId = memberId; + this.name = name; + this.description = description; + this.thumbnail = thumbnail; + this.backgroundImage = backgroundImage; + this.point = point; + } + } + + @Getter + @NoArgsConstructor + public static class MemberActivity { + private Long reviews; + private Long likes; + + @Builder + public MemberActivity(Long reviews, Long likes) { + this.reviews = reviews; + this.likes = likes; + } + } +} From a2a645d80670dc4b09b4eb746f1b1110a16a02d2 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 15:56:20 +0900 Subject: [PATCH 148/734] =?UTF-8?q?feat:=20MemberRepositoryCustom=20&=20Im?= =?UTF-8?q?pl=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/repository/MemberRepository.java | 2 +- .../repository/MemberRepositoryCustom.java | 10 +++ .../repository/MemberRepositoryImpl.java | 65 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java create mode 100644 src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepository.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepository.java index 4fea38d5..63dd92a0 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepository.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepository.java @@ -5,6 +5,6 @@ import org.springframework.stereotype.Repository; @Repository -public interface MemberRepository extends JpaRepository { +public interface MemberRepository extends JpaRepository, MemberRepositoryCustom{ } diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java new file mode 100644 index 00000000..fdf9237f --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.member.repository; + +import io.oduck.api.domain.member.dto.MemberDslDto.MemberActivity; +import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; + +public interface MemberRepositoryCustom { + ProfileWithoutActivity selectProfileByName(String name); + + MemberActivity selectActivityByMemberId(Long id); +} diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java new file mode 100644 index 00000000..98f2b4ca --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java @@ -0,0 +1,65 @@ +package io.oduck.api.domain.member.repository; + +import static io.oduck.api.domain.member.entity.QMemberProfile.memberProfile; +import static io.oduck.api.domain.review.entity.QShortReview.shortReview; +import static io.oduck.api.domain.reviewLike.entity.QShortReviewLike.shortReviewLike; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.*; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.JPQLQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import io.oduck.api.domain.member.dto.MemberDslDto.MemberActivity; +import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; +import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; +import io.oduck.api.domain.member.entity.QMemberProfile; +import io.oduck.api.domain.review.entity.QShortReview; +import io.oduck.api.global.utils.QueryDslUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +@RequiredArgsConstructor +class MemberRepositoryImpl implements MemberRepositoryCustom { + private final JPAQueryFactory query; +// private final QueryDslUtils queryDslUtils; + + + @Override + public ProfileWithoutActivity selectProfileByName(String name) { + ProfileWithoutActivity memberProfileRes = query + .select( + Projections.constructor( + ProfileWithoutActivity.class, + memberProfile.member.id, + memberProfile.name, + memberProfile.info, + memberProfile.thumbnail, + memberProfile.backgroundImage, + memberProfile.point + ) + ) + .from(memberProfile) + .where(memberProfile.name.eq(name)) + .fetchOne(); + return memberProfileRes; + } + + @Override + public MemberActivity selectActivityByMemberId(Long id) { + NumberExpression reviewCount = shortReview.id.count(); + NumberExpression reviewLikeCount = shortReviewLike.id.count(); + + MemberActivity activity = (MemberActivity) query + .select(reviewCount.as("reviews"), reviewLikeCount.sum().as("likes")) + .from(shortReview) + .join(shortReviewLike).on(shortReviewLike.shortReview.id.eq(shortReview.id)) + .where(shortReview.member.id.eq(id)) + .groupBy(shortReview.id) + .fetchOne(); + return activity; + } +} From 81c9603d411795949741f5ae6cab898af7ac3967 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 17:08:31 +0900 Subject: [PATCH 149/734] =?UTF-8?q?refactor:=20=EC=9E=91=EC=84=B1=ED=95=9C?= =?UTF-8?q?=20=EB=A6=AC=EB=B7=B0=20=EC=88=98,=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EB=B0=9B=EC=9D=80=20=EC=A2=8B=EC=95=84=EC=9A=94=EC=88=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberRepositoryCustom.java | 3 +-- .../repository/MemberRepositoryImpl.java | 23 ++++--------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java index fdf9237f..980fcc5e 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java @@ -1,10 +1,9 @@ package io.oduck.api.domain.member.repository; -import io.oduck.api.domain.member.dto.MemberDslDto.MemberActivity; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; public interface MemberRepositoryCustom { ProfileWithoutActivity selectProfileByName(String name); - MemberActivity selectActivityByMemberId(Long id); + Long countLikesByMemberId(Long id); } diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java index 98f2b4ca..7a80b423 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java @@ -4,18 +4,9 @@ import static io.oduck.api.domain.review.entity.QShortReview.shortReview; import static io.oduck.api.domain.reviewLike.entity.QShortReviewLike.shortReviewLike; -import com.querydsl.core.Tuple; import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.*; -import com.querydsl.jpa.JPAExpressions; -import com.querydsl.jpa.JPQLQuery; import com.querydsl.jpa.impl.JPAQueryFactory; -import io.oduck.api.domain.member.dto.MemberDslDto.MemberActivity; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; -import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; -import io.oduck.api.domain.member.entity.QMemberProfile; -import io.oduck.api.domain.review.entity.QShortReview; -import io.oduck.api.global.utils.QueryDslUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; @@ -49,17 +40,13 @@ public ProfileWithoutActivity selectProfileByName(String name) { } @Override - public MemberActivity selectActivityByMemberId(Long id) { - NumberExpression reviewCount = shortReview.id.count(); - NumberExpression reviewLikeCount = shortReviewLike.id.count(); - - MemberActivity activity = (MemberActivity) query - .select(reviewCount.as("reviews"), reviewLikeCount.sum().as("likes")) + public Long countLikesByMemberId(Long id) { + Long likesCount = query + .select(shortReviewLike.id.count()) .from(shortReview) - .join(shortReviewLike).on(shortReviewLike.shortReview.id.eq(shortReview.id)) + .join(shortReviewLike).on(shortReview.id.eq(shortReviewLike.shortReview.id)) .where(shortReview.member.id.eq(id)) - .groupBy(shortReview.id) .fetchOne(); - return activity; + return likesCount; } } From b06c86feb56221a4b50884746ce6c88e3e5ca744 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 17:09:42 +0900 Subject: [PATCH 150/734] =?UTF-8?q?test:=20AuthControllerTest=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/e2e/auth/AuthControllerTest.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java index 62c21aca..d6e7598e 100644 --- a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java @@ -18,18 +18,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.google.gson.Gson; -import io.oduck.api.domain.member.entity.LoginType; import io.oduck.api.domain.member.entity.Role; -import io.oduck.api.global.initializer.TestDataInitializer; -import io.oduck.api.global.MockMember.WithCustomMockMember; -import io.oduck.api.global.security.auth.dto.AuthUser; +import io.oduck.api.global.mockMember.WithCustomMockMember; import io.oduck.api.global.security.auth.dto.LocalAuthDto; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -67,7 +62,7 @@ class PostLogin { void postLogin() throws Exception { // given LocalAuthDto localAuthDto = LocalAuthDto.builder() - .email("bob@gmail.com") + .email("oduckdmin@gmail.com") .password("Qwer!234") .build(); From 884af02186bdc49754e35460021e3ba6efecd06c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 17:10:03 +0900 Subject: [PATCH 151/734] =?UTF-8?q?test:=20MemberControllerTest=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 0c95e4f5..4460bcc0 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -62,7 +62,7 @@ void postMember() throws Exception { // given // TODO: 회원 가입에 필요한 데이터 CreateReq body = CreateReq.builder() - .email("john@gmail.com") + .email("bob@gmail.com") .password("Qwer1234!") .build(); From f48df2ded1398d8c9ce58ad62712873be7ac9eae Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 17:10:54 +0900 Subject: [PATCH 152/734] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=85=81=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../initializer/TestDataInitializer.java | 32 ++----- .../io/oduck/api/global/stub/MemberStub.java | 84 +++++++++++++++++++ 2 files changed, 89 insertions(+), 27 deletions(-) create mode 100644 src/test/java/io/oduck/api/global/stub/MemberStub.java diff --git a/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java b/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java index b5358063..bd41fdd5 100644 --- a/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java +++ b/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java @@ -1,11 +1,7 @@ package io.oduck.api.global.initializer; -import io.oduck.api.domain.member.entity.LoginType; -import io.oduck.api.domain.member.entity.Member; -import io.oduck.api.domain.member.entity.MemberProfile; -import io.oduck.api.domain.member.entity.Role; import io.oduck.api.domain.member.repository.MemberRepository; -import io.oduck.api.global.security.auth.entity.AuthLocal; +import io.oduck.api.global.stub.MemberStub; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -24,27 +20,9 @@ public TestDataInitializer(MemberRepository memberRepository) { @PostConstruct public void saveTestMember() { - // TODO: Test Data들 Helper로 분리하기 - Member member = Member.builder() - .loginType(LoginType.LOCAL) - .role(Role.MEMBER) - .build(); - - AuthLocal authLocal = AuthLocal.builder() - .email("bob@gmail.com") - .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") - .build(); - - MemberProfile memberProfile = MemberProfile.builder() - .name("admin") - .info("bob info") - .thumbnail("bob thumbnail") - .point(0L) - .build(); - - member.setAuthLocal(authLocal); - member.setMemberProfile(memberProfile); - - memberRepository.save(member); + // 회원 데이터 초기화 + MemberStub memberstub = new MemberStub(); + memberstub.init(); + memberRepository.saveAll(memberstub.getMembers()); } } diff --git a/src/test/java/io/oduck/api/global/stub/MemberStub.java b/src/test/java/io/oduck/api/global/stub/MemberStub.java new file mode 100644 index 00000000..0b56f873 --- /dev/null +++ b/src/test/java/io/oduck/api/global/stub/MemberStub.java @@ -0,0 +1,84 @@ +package io.oduck.api.global.stub; + +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import java.util.ArrayList; +import java.util.List; + +public class MemberStub { + List members = new ArrayList<>(); + + public List getMembers() { + return members; + } + + public void init() { + // Member1 + Member member1 = Member.builder() + .loginType(LoginType.LOCAL) + .role(Role.MEMBER) + .build(); + + AuthLocal authLocal1 = AuthLocal.builder() + .email("oduckdmin@gmail.com") + .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") + .build(); + + MemberProfile memberProfile1 = MemberProfile.builder() + .name("admin") + .info("admin info") + .thumbnail("admin thumbnail") + .point(0L) + .build(); + + member1.setAuthLocal(authLocal1); + member1.setMemberProfile(memberProfile1); + + // member2 + Member member2 = Member.builder() + .loginType(LoginType.LOCAL) + .role(Role.MEMBER) + .build(); + + AuthLocal authLocal2 = AuthLocal.builder() + .email("john@gmail.com") + .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") + .build(); + + MemberProfile memberProfile2 = MemberProfile.builder() + .name("john") + .info("john info") + .thumbnail("john thumbnail") + .point(0L) + .build(); + + member2.setAuthLocal(authLocal2); + member2.setMemberProfile(memberProfile2); + + // member3 + Member member3 = Member.builder() + .loginType(LoginType.LOCAL) + .role(Role.MEMBER) + .build(); + + AuthLocal authLocal3 = AuthLocal.builder() + .email("david@gmail.com") + .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") + .build(); + + MemberProfile memberProfile3 = MemberProfile.builder() + .name("david") + .info("david info") + .thumbnail("david thumbnail") + .point(0L) + .build(); + + member3.setAuthLocal(authLocal3); + member3.setMemberProfile(memberProfile3); + + members.addAll(List.of(member1, member2, member3)); + } +} From d990cb22617e5cab6437cc117c46e2aae570b763 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 17:20:32 +0900 Subject: [PATCH 153/734] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=9C=BC=EB=A1=9C=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberRepositoryTest.java | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java index f7d84f09..7b60e192 100644 --- a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java @@ -3,33 +3,55 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; import io.oduck.api.domain.member.entity.LoginType; import io.oduck.api.domain.member.entity.Member; import io.oduck.api.domain.member.entity.MemberProfile; import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.domain.member.repository.MemberProfileRepository; import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.global.config.QueryDslTestConfig; +import io.oduck.api.global.initializer.TestDataInitializer; import io.oduck.api.global.security.auth.entity.AuthLocal; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; @DataJpaTest @TestInstance(Lifecycle.PER_CLASS) @ActiveProfiles("test") +// QueryDSL repository 테스트를 위한 설정 불러오기 +@Import(QueryDslTestConfig.class) public class MemberRepositoryTest { + @Autowired MemberRepository memberRepository; + @Autowired + MemberProfileRepository memberProfileRepository; + + // 테스트 데이터 초기화 + // SpringBootTest는 기본적으로 테스트마다 새로운 ApplicationContext를 생성하는데, + // 유닛 테스트에서는 SpringBootTest를 사용하지 않고, DataJpaTest를 사용하기 때문에 + // 테스트 데이터를 초기화하기 위해 @BeforeAll을 사용해야 함. + @BeforeAll + void setUp() { + TestDataInitializer testDataInitializer = new TestDataInitializer(memberRepository); + testDataInitializer.saveTestMember(); + } + @DisplayName("회원 저장") @Test void saveMember() { // given Member member = Member.builder() - .role(Role.ADMIN) + .role(Role.MEMBER) .loginType(LoginType.LOCAL) .build(); @@ -58,4 +80,18 @@ void saveMember() { assertEquals(member.getAuthLocal().getEmail(), savedMember.getAuthLocal().getEmail()); assertEquals(member.getMemberProfile().getName(), savedMember.getMemberProfile().getName()); } + + @DisplayName("회원 이름으로 프로필 조회") + @Test + void selectProfileByName() { + // given + String name = "admin"; + + // when + ProfileWithoutActivity memberProfile = memberRepository.selectProfileByName(name); + + // then + assertNotNull(memberProfile); + assertEquals(name, memberProfile.getName()); + } } From 37983263870a062b52bad78f811a8801aa0c2696 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 15 Oct 2023 17:53:58 +0900 Subject: [PATCH 154/734] =?UTF-8?q?docs:=20index.adoc=EC=9D=98=20admin=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80=20#17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 189 ++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 6098edf4..4c2dcbef 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -136,4 +136,191 @@ include::{snippets}/getAnimeById/success/response-body.adoc[] .response-fields include::{snippets}/getAnimeById/success/response-fields.adoc[] -==== 실패 시 \ No newline at end of file +==== 실패 시 + + +== shortReview + +=== GET api/v1/short-review/animeId/1 + +.curl-request +include::{snippets}/getShortReviews/success/curl-request.adoc[] + +.http-request +include::{snippets}/getShortReviews/success/http-request.adoc[] + +.request-param +include::{snippets}/getShortReviews/success/path-parameters.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getShortReviews/success/response-fields.adoc[] + +.response-body +include::{snippets}/getShortReviews/success/response-body.adoc[] + +.response-fields +include::{snippets}/getShortReviews/success/response-fields.adoc[] + +.http-response +include::{snippets}/getShortReviews/success/http-response.adoc[] +==== 실패시 + +== admin + +=== POST api/v1/oduckdmin/animes +.curl-request +include::{snippets}/postAnime/success/curl-request.adoc[] + +.http-request +include::{snippets}/postAnime/success/http-request.adoc[] + +.request-body +include::{snippets}/postAnime/success/request-body.adoc[] + +.request-fields +include::{snippets}/postAnime/success/request-fields.adoc[] + +==== 성공시 +.http-response +include::{snippets}/postAnime/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id +.curl-request +include::{snippets}/patchAnime/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnime/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnime/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnime/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnime/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnime/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/original-authors +.curl-request +include::{snippets}/patchAnimeOriginalAuthors/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeOriginalAuthors/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeOriginalAuthors/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeOriginalAuthors/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeOriginalAuthors/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeOriginalAuthors/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/studios +.curl-request +include::{snippets}/patchAnimeStudios/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeStudios/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeStudios/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeStudios/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeStudios/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeStudios/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/voice-actors +.curl-request +include::{snippets}/patchAnimeVoiceActors/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeVoiceActors/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeVoiceActors/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeVoiceActors/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeVoiceActors/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeVoiceActors/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/genres +.curl-request +include::{snippets}/patchAnimeGenres/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeGenres/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeGenres/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeGenres/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeGenres/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeGenres/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/series +.curl-request +include::{snippets}/patchAnimeSeries/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeSeries/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeSeries/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeSeries/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeSeries/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeSeries/success/http-response.adoc[] + +==== 실패시 \ No newline at end of file From 40cce0077709b429d358113e0cc247ebe75d99e7 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 19:34:14 +0900 Subject: [PATCH 155/734] =?UTF-8?q?test:=20Initializer=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/initializer/MemberInitializer.java | 23 +++++++++++++++ .../initializer/TestDataInitializer.java | 28 ------------------- 2 files changed, 23 insertions(+), 28 deletions(-) create mode 100644 src/test/java/io/oduck/api/global/initializer/MemberInitializer.java delete mode 100644 src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java diff --git a/src/test/java/io/oduck/api/global/initializer/MemberInitializer.java b/src/test/java/io/oduck/api/global/initializer/MemberInitializer.java new file mode 100644 index 00000000..a9e34b5d --- /dev/null +++ b/src/test/java/io/oduck/api/global/initializer/MemberInitializer.java @@ -0,0 +1,23 @@ +package io.oduck.api.global.initializer; + +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.global.stub.MemberStub; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import org.springframework.test.context.ActiveProfiles; + +@Component +@ActiveProfiles("test") +public class MemberInitializer implements ApplicationRunner { + + @Autowired + MemberRepository memberRepository; + + @Override + public void run(ApplicationArguments args) throws Exception { + MemberStub memberStub = new MemberStub(); + memberRepository.saveAll(memberStub.getMembers()); + } +} diff --git a/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java b/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java deleted file mode 100644 index bd41fdd5..00000000 --- a/src/test/java/io/oduck/api/global/initializer/TestDataInitializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.oduck.api.global.initializer; - -import io.oduck.api.domain.member.repository.MemberRepository; -import io.oduck.api.global.stub.MemberStub; -import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.test.context.ActiveProfiles; - -@Component -@ActiveProfiles("test") -public class TestDataInitializer { - - private final MemberRepository memberRepository; - - @Autowired - public TestDataInitializer(MemberRepository memberRepository) { - this.memberRepository = memberRepository; - } - - @PostConstruct - public void saveTestMember() { - // 회원 데이터 초기화 - MemberStub memberstub = new MemberStub(); - memberstub.init(); - memberRepository.saveAll(memberstub.getMembers()); - } -} From 19c2274e9b7f7bb900eb9c600ceacb901e282c76 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 19:34:51 +0900 Subject: [PATCH 156/734] =?UTF-8?q?test:=20=EB=A9=A4=ED=84=B0=20=EC=8A=A4?= =?UTF-8?q?=ED=85=81=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20->=20=EC=83=9D=EC=84=B1=EC=9E=90=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/global/stub/MemberStub.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/global/stub/MemberStub.java b/src/test/java/io/oduck/api/global/stub/MemberStub.java index 0b56f873..7cf4d8a3 100644 --- a/src/test/java/io/oduck/api/global/stub/MemberStub.java +++ b/src/test/java/io/oduck/api/global/stub/MemberStub.java @@ -15,7 +15,7 @@ public List getMembers() { return members; } - public void init() { + public MemberStub() { // Member1 Member member1 = Member.builder() .loginType(LoginType.LOCAL) From 9e075314890b4eb52d90c0277e94c9bf70be02dc Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 19:36:48 +0900 Subject: [PATCH 157/734] =?UTF-8?q?test:=20QueryDslTestConfig=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/config/QueryDslTestConfig.java | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java diff --git a/src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java b/src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java deleted file mode 100644 index 66d4e947..00000000 --- a/src/test/java/io/oduck/api/global/config/QueryDslTestConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.oduck.api.global.config; - -import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; - -@TestConfiguration -public class QueryDslTestConfig { - - @PersistenceContext - private EntityManager entityManager; - - @Bean - public JPAQueryFactory jpaQueryFactory() { - return new JPAQueryFactory(entityManager); - } -} \ No newline at end of file From 75d89aa495ce86e19539457fae2e0f0631453343 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 19:43:08 +0900 Subject: [PATCH 158/734] =?UTF-8?q?test:=20DataJpaTest=20->=20SpringBootTe?= =?UTF-8?q?st=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberRepositoryTest.java | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java index 7b60e192..26f42425 100644 --- a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java @@ -3,48 +3,34 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import io.oduck.api.domain.anime.repository.AnimeRepository; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; import io.oduck.api.domain.member.entity.LoginType; import io.oduck.api.domain.member.entity.Member; import io.oduck.api.domain.member.entity.MemberProfile; import io.oduck.api.domain.member.entity.Role; -import io.oduck.api.domain.member.repository.MemberProfileRepository; import io.oduck.api.domain.member.repository.MemberRepository; -import io.oduck.api.global.config.QueryDslTestConfig; -import io.oduck.api.global.initializer.TestDataInitializer; +import io.oduck.api.domain.review.repository.ShortReviewRepository; import io.oduck.api.global.security.auth.entity.AuthLocal; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -@DataJpaTest +@SpringBootTest @TestInstance(Lifecycle.PER_CLASS) @ActiveProfiles("test") -// QueryDSL repository 테스트를 위한 설정 불러오기 -@Import(QueryDslTestConfig.class) public class MemberRepositoryTest { @Autowired MemberRepository memberRepository; - @Autowired - MemberProfileRepository memberProfileRepository; - - // 테스트 데이터 초기화 - // SpringBootTest는 기본적으로 테스트마다 새로운 ApplicationContext를 생성하는데, - // 유닛 테스트에서는 SpringBootTest를 사용하지 않고, DataJpaTest를 사용하기 때문에 - // 테스트 데이터를 초기화하기 위해 @BeforeAll을 사용해야 함. - @BeforeAll - void setUp() { - TestDataInitializer testDataInitializer = new TestDataInitializer(memberRepository); - testDataInitializer.saveTestMember(); - } + AnimeRepository animeRepository; + @Autowired + ShortReviewRepository shortReviewRepository; @DisplayName("회원 저장") @Test From 046dd0ab88f98876deebe47e2d6cd06671d04539 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 15 Oct 2023 21:17:07 +0900 Subject: [PATCH 159/734] =?UTF-8?q?refactor:=20anime=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EB=B3=80=EA=B2=BD=20#17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit year 컬럼을 release_year로 변경하였고, 엔티티에서는 year로 사용하도록 변경하였습니다. summary 글자수를 255에서 600으로 늘렸습니다. --- src/main/java/io/oduck/api/domain/anime/entity/Anime.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 86c0854f..1c1a17b8 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -31,7 +31,7 @@ public class Anime extends BaseEntity { @Column(nullable = false, length = 50) private String title; - @Column(nullable = false, length = 255) + @Column(nullable = false, length = 600) private String summary; @Enumerated(EnumType.STRING) @@ -43,6 +43,7 @@ public class Anime extends BaseEntity { @Column(nullable = true, length = 500) private String thumbnail; + @Column(name = "release_year") private int year; @Enumerated(EnumType.STRING) From cbf70c4bbf5d23458ad7e904373175c799a12f13 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 21:35:59 +0900 Subject: [PATCH 160/734] =?UTF-8?q?test:=20stub=20thumbnail=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/global/stub/MemberStub.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/oduck/api/global/stub/MemberStub.java b/src/test/java/io/oduck/api/global/stub/MemberStub.java index 7cf4d8a3..ab954684 100644 --- a/src/test/java/io/oduck/api/global/stub/MemberStub.java +++ b/src/test/java/io/oduck/api/global/stub/MemberStub.java @@ -30,7 +30,7 @@ public MemberStub() { MemberProfile memberProfile1 = MemberProfile.builder() .name("admin") .info("admin info") - .thumbnail("admin thumbnail") + .thumbnail("http://thumbnail.com") .point(0L) .build(); @@ -51,7 +51,7 @@ public MemberStub() { MemberProfile memberProfile2 = MemberProfile.builder() .name("john") .info("john info") - .thumbnail("john thumbnail") + .thumbnail("http://thumbnail.com") .point(0L) .build(); @@ -72,7 +72,7 @@ public MemberStub() { MemberProfile memberProfile3 = MemberProfile.builder() .name("david") .info("david info") - .thumbnail("david thumbnail") + .thumbnail("http://thumbnail.com") .point(0L) .build(); From 0cfe792c41cfec60488ce3e8e7827346156837cd Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 23:04:36 +0900 Subject: [PATCH 161/734] =?UTF-8?q?refactor:=20=EA=B0=81=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=EB=B3=84=20Builder=20=EC=B6=94=EA=B0=80=20#2?= =?UTF-8?q?4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/anime/entity/Anime.java | 4 ++++ .../java/io/oduck/api/domain/bookmark/entity/Bookmark.java | 4 ++++ .../java/io/oduck/api/domain/review/entity/ShortReview.java | 4 ++++ .../oduck/api/domain/reviewLike/entity/ShortReviewLike.java | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 86c0854f..6df0714b 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -15,13 +15,17 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor public class Anime extends BaseEntity { @Id diff --git a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java index c317bb65..bba90efa 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java +++ b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java @@ -11,13 +11,17 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor public class Bookmark { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java index 27a35492..cf73543c 100644 --- a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java +++ b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java @@ -15,12 +15,16 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor public class ShortReview extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java index fa43497b..b613d3d5 100644 --- a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java +++ b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java @@ -10,12 +10,16 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter +@Builder @NoArgsConstructor +@AllArgsConstructor public class ShortReviewLike extends BaseEntity { @Id From 390d8dd73d5ffd086d349acadafe689e895b556e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 15 Oct 2023 23:04:53 +0900 Subject: [PATCH 162/734] =?UTF-8?q?feat:=20=EA=B0=81=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EB=B3=84=20repository=20=EC=B6=94=EA=B0=80=20#24?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/anime/repository/AnimeRepository.java | 10 ++++++++++ .../domain/bookmark/repository/BookmarkRepository.java | 10 ++++++++++ .../review/repository/ShortReviewRepository.java | 10 ++++++++++ .../repository/ShortReviewLikeRepository.java | 10 ++++++++++ 4 files changed, 40 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java create mode 100644 src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java create mode 100644 src/main/java/io/oduck/api/domain/review/repository/ShortReviewRepository.java create mode 100644 src/main/java/io/oduck/api/domain/reviewLike/repository/ShortReviewLikeRepository.java diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java new file mode 100644 index 00000000..ec192c9d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.anime.repository; + +import io.oduck.api.domain.anime.entity.Anime; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AnimeRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java new file mode 100644 index 00000000..4769d2f1 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.bookmark.repository; + +import io.oduck.api.domain.bookmark.entity.Bookmark; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BookmarkRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/review/repository/ShortReviewRepository.java b/src/main/java/io/oduck/api/domain/review/repository/ShortReviewRepository.java new file mode 100644 index 00000000..37c7658b --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/repository/ShortReviewRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.review.repository; + +import io.oduck.api.domain.review.entity.ShortReview; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ShortReviewRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/reviewLike/repository/ShortReviewLikeRepository.java b/src/main/java/io/oduck/api/domain/reviewLike/repository/ShortReviewLikeRepository.java new file mode 100644 index 00000000..ed886384 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/reviewLike/repository/ShortReviewLikeRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.reviewLike.repository; + +import io.oduck.api.domain.reviewLike.entity.ShortReviewLike; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ShortReviewLikeRepository extends JpaRepository { + +} From 255e5cc58837f5b44ed6589ecc4b61ad1ce9d177 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:08:47 +0900 Subject: [PATCH 163/734] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20Optional=20=EB=B0=8F?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=ED=95=9C=20=EB=A6=AC=EB=B7=B0=20=EC=88=98?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberRepositoryCustom.java | 5 +++- .../repository/MemberRepositoryImpl.java | 23 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java index 980fcc5e..0c9736da 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java @@ -1,9 +1,12 @@ package io.oduck.api.domain.member.repository; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; +import java.util.Optional; public interface MemberRepositoryCustom { - ProfileWithoutActivity selectProfileByName(String name); + Optional selectProfileByName(String name); Long countLikesByMemberId(Long id); + + Long countReviewsByMemberId(Long id); } diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java index 7a80b423..1606c3c1 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java @@ -7,6 +7,7 @@ import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; @@ -20,8 +21,8 @@ class MemberRepositoryImpl implements MemberRepositoryCustom { @Override - public ProfileWithoutActivity selectProfileByName(String name) { - ProfileWithoutActivity memberProfileRes = query + public Optional selectProfileByName(String name) { + return Optional.ofNullable(query .select( Projections.constructor( ProfileWithoutActivity.class, @@ -35,18 +36,28 @@ public ProfileWithoutActivity selectProfileByName(String name) { ) .from(memberProfile) .where(memberProfile.name.eq(name)) - .fetchOne(); - return memberProfileRes; + .fetchOne()); } @Override public Long countLikesByMemberId(Long id) { - Long likesCount = query + Long likeCount = query .select(shortReviewLike.id.count()) .from(shortReview) .join(shortReviewLike).on(shortReview.id.eq(shortReviewLike.shortReview.id)) .where(shortReview.member.id.eq(id)) .fetchOne(); - return likesCount; + return likeCount; + } + + @Override + public Long countReviewsByMemberId(Long id) { + Long reviewCount = query + .select(shortReview.id.count()) + .from(shortReview) + .where(shortReview.member.id.eq(id)) + .fetchOne(); + + return reviewCount; } } From e03b7713d5bfa2b353ea05c5ae34dd5a9994f8ce Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:33:23 +0900 Subject: [PATCH 164/734] =?UTF-8?q?refactor:=20=EB=B3=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=ED=99=95=EC=9D=B8=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EB=A7=A4?= =?UTF-8?q?=EA=B0=9C=EB=B3=80=EC=88=98=20=EC=88=98=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/member/service/MemberService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberService.java b/src/main/java/io/oduck/api/domain/member/service/MemberService.java index 7cc076fb..5ae00eff 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberService.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberService.java @@ -8,7 +8,7 @@ public interface MemberService { void signUpByLocal(CreateReq createReq); // 이름으로 회원 프로필 조회 로직 - MemberProfileRes getProfileByName(String name); + MemberProfileRes getProfileByName(String name, Long memberId); // 회원 정보 수정 로직 // void updateProfile(PatchReq body); From 2c5537b71f016c73ef1a914b85301e49900120c4 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:33:38 +0900 Subject: [PATCH 165/734] =?UTF-8?q?feat:=20MemberServiceImpl=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberServiceImpl.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java new file mode 100644 index 00000000..14a0d7ca --- /dev/null +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java @@ -0,0 +1,103 @@ +package io.oduck.api.domain.member.service; + +import static io.oduck.api.global.utils.NameGenerator.generateNickname; + +import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; +import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; +import io.oduck.api.domain.member.dto.MemberResDto.Activity; +import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; +import io.oduck.api.domain.member.entity.LoginType; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.global.exception.BadRequestException; +import io.oduck.api.global.exception.ConflictException; +import io.oduck.api.global.exception.NotFoundException; +import io.oduck.api.global.security.auth.entity.AuthLocal; +import io.oduck.api.global.security.auth.repository.AuthLocalRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class MemberServiceImpl implements MemberService{ + private final PasswordEncoder passwordEncoder; + private final AuthLocalRepository authLocalRepository; + private final MemberRepository memberRepository; + + @Transactional + @Override + public void signUpByLocal(CreateReq createReq) { + String email = createReq.getEmail(); + boolean isExistMember = authLocalRepository.existsByEmail(email); + + if (isExistMember) { + throw new ConflictException("Email"); + } + + try { + String encryptedPassword = passwordEncoder.encode(createReq.getPassword()); + + AuthLocal authLocal = AuthLocal.builder() + .email(createReq.getEmail()) + .password(encryptedPassword) + .build(); + + MemberProfile memberProfile = MemberProfile.builder() + .name(generateNickname()) + .build(); + + Member member = Member.builder() + .loginType(LoginType.LOCAL) + .build(); + + member.setAuthLocal(authLocal); + member.setMemberProfile(memberProfile); + + Member savedMember = memberRepository.save(member); + log.info("Member Created! {}", savedMember.getId()); + } catch (Exception e) { + log.error(e.getMessage()); + throw new BadRequestException("Try Again Plz."); + } + } + + @Override + public MemberProfileRes getProfileByName(String name, Long memberId) { + ProfileWithoutActivity memberProfile = getProfileWithoutActivity(name); + if (memberProfile == null) { + throw new NotFoundException("Member"); + } + Long likesCount = memberRepository.countLikesByMemberId(memberProfile.getMemberId()); + Long reviewsCount = memberRepository.countReviewsByMemberId(memberProfile.getMemberId()); + Activity activity = Activity.builder() + .reviews(reviewsCount) + .threads(0L) + .likes(likesCount) + .build(); + + MemberProfileRes memberProfileRes = MemberProfileRes. builder() + .isMine(memberProfile.getMemberId().equals(memberId)) + .name (memberProfile.getName()) + .description(memberProfile.getDescription()) + .thumbnail(memberProfile.getThumbnail()) + .backgroundImage (memberProfile.getBackgroundImage()) + .point(memberProfile.getPoint()) + .activity(activity) + .build(); + + return memberProfileRes; + } + + private ProfileWithoutActivity getProfileWithoutActivity(String name) { + return memberRepository.selectProfileByName(name) + .orElseThrow( + () -> new NotFoundException("Member") + ); + } +} From a15c31dc25f9c60f141dba28a11bfce14ac1f182 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:34:06 +0900 Subject: [PATCH 166/734] =?UTF-8?q?refactor:=20=ED=83=80=EC=9E=85=20int=20?= =?UTF-8?q?->=20long=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/member/dto/MemberResDto.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java index 4fa2c4c1..31e55437 100644 --- a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java @@ -36,12 +36,12 @@ public boolean getIsMine() { @Getter @NoArgsConstructor public static class Activity { - private int reviews; - private int threads; - private int likes; + private long reviews; + private long threads; + private long likes; @Builder - public Activity(int reviews, int threads, int likes) { + public Activity(long reviews, long threads, long likes) { this.reviews = reviews; this.threads = threads; this.likes = likes; From ebe54a151248ad2312a6d72b9fd2ead1fa289de1 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:35:19 +0900 Subject: [PATCH 167/734] =?UTF-8?q?refactor:=20AuthUser=20null=EC=8B=9C=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/member/controller/MemberController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index 3194d36e..fb54571e 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -43,7 +43,7 @@ public ResponseEntity getProfileByName( @LoginUser AuthUser user ) { // TODO: 회원 프로필 조회 로직 구현 - MemberProfileRes res = memberService.getProfileByName(name); + MemberProfileRes res = memberService.getProfileByName(name, user == null ? 0L : user.getId()); return ResponseEntity.ok(res); } From 945352be9bddb1d9b9f512524f6a2fce74e268ca Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:37:36 +0900 Subject: [PATCH 168/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C,=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=ED=95=9C=20=EB=A6=AC=EB=B7=B0=EC=88=98,=20=EB=B0=9B=EC=9D=80?= =?UTF-8?q?=20=EC=A2=8B=EC=95=84=EC=9A=94=EC=88=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberRepositoryTest.java | 149 +++++++++++++----- 1 file changed, 106 insertions(+), 43 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java index 26f42425..39679d38 100644 --- a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import io.oduck.api.domain.anime.repository.AnimeRepository; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; @@ -13,6 +14,7 @@ import io.oduck.api.domain.review.repository.ShortReviewRepository; import io.oduck.api.global.security.auth.entity.AuthLocal; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -32,52 +34,113 @@ public class MemberRepositoryTest { @Autowired ShortReviewRepository shortReviewRepository; - @DisplayName("회원 저장") - @Test - void saveMember() { - // given - Member member = Member.builder() - .role(Role.MEMBER) - .loginType(LoginType.LOCAL) - .build(); - - AuthLocal authLocal = AuthLocal.builder() - .email("bob@gmail.com") - .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") - .build(); - - MemberProfile memberProfile = MemberProfile.builder() - .name("bob") - .info("bob info") - .thumbnail("bob thumbnail") - .point(0L) - .build(); - - member.setAuthLocal(authLocal); - member.setMemberProfile(memberProfile); - - // when - Member savedMember = memberRepository.save(member); - - // then - assertNotNull(savedMember); // savedMember가 null이면 안 됨. - assertEquals(member.getId(), savedMember.getId()); - assertEquals(member.getRole(), savedMember.getRole()); - assertEquals(member.getAuthLocal().getEmail(), savedMember.getAuthLocal().getEmail()); - assertEquals(member.getMemberProfile().getName(), savedMember.getMemberProfile().getName()); + @DisplayName("로컬 회원 저장") + @Nested + class saveMemberByLocal { + @DisplayName("로컬 회원 저장 성공") + @Test + void saveMemberByLocalSuccess() { + // given + Member member = Member.builder() + .role(Role.MEMBER) + .loginType(LoginType.LOCAL) + .build(); + + AuthLocal authLocal = AuthLocal.builder() + .email("bob@gmail.com") + .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") + .build(); + + MemberProfile memberProfile = MemberProfile.builder() + .name("bob") + .info("bob info") + .thumbnail("bob thumbnail") + .point(0L) + .build(); + + member.setAuthLocal(authLocal); + member.setMemberProfile(memberProfile); + + // when + Member savedMember = memberRepository.save(member); + + // then + assertNotNull(savedMember); // savedMember가 null이면 안 됨. + assertEquals(member.getId(), savedMember.getId()); + assertEquals(member.getRole(), savedMember.getRole()); + assertEquals(member.getAuthLocal().getEmail(), savedMember.getAuthLocal().getEmail()); + assertEquals(member.getMemberProfile().getName(), savedMember.getMemberProfile().getName()); + } } - @DisplayName("회원 이름으로 프로필 조회") - @Test - void selectProfileByName() { - // given - String name = "admin"; + @DisplayName("회원 프로필 조회") + @Nested + class selectProfileByName { + @DisplayName("회원 이름으로 프로필 조회 성공") + @Test + void selectProfileByNameSuccess() { + // given + String name = "admin"; - // when - ProfileWithoutActivity memberProfile = memberRepository.selectProfileByName(name); + // when + ProfileWithoutActivity memberProfile = memberRepository.selectProfileByName(name).orElse(null); - // then - assertNotNull(memberProfile); - assertEquals(name, memberProfile.getName()); + // then + assertNotNull(memberProfile); + assertEquals(name, memberProfile.getName()); + } + + @DisplayName("회원 이름으로 프로필 조회 실패") + @Test + void selectProfileByNameFailure() { + // given + String notExistName = "notExistName"; + + // when + ProfileWithoutActivity memberProfile = memberRepository.selectProfileByName(notExistName).orElse(null); + + // then + assertNull(memberProfile); + } } + + @DisplayName("내가 받은 짧은 리뷰 좋아요 수 조회") + @Nested + class countLikesByMemberId { + @DisplayName("회원 ID로 회원이 받은 짧은 리뷰 좋아요 수 조회") + @Test + void countLikesByMemberIdSuccess() { + // given + Long memberId = 1L; + + // when + Long likesCount = memberRepository.countLikesByMemberId(memberId); + + // then + assertNotNull(likesCount); + } + } + + @DisplayName("내가 작성한 리뷰 수 조회") + @Nested + class countReviewByMemberId { + @DisplayName("회원 ID로 회원이 작성한 리뷰 수 조회 성공") + @Test + void countReviewByMemberIdSuccess() { + // given + Long memberId = 1L; + + // when + Long likesCount = memberRepository.countLikesByMemberId(memberId); + + // then + assertNotNull(likesCount); + } + } + + // TODO: 회원 프로필 수정 + + // TODO: 회원이 작성한 리뷰 목록 + + // TODO: 회원 북마크 애니 목록 } From 6d6d62969482815ad42faa9a8197900e0230a567 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:39:09 +0900 Subject: [PATCH 169/734] =?UTF-8?q?test:=20MemberStub=20get=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/global/stub/MemberStub.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/io/oduck/api/global/stub/MemberStub.java b/src/test/java/io/oduck/api/global/stub/MemberStub.java index ab954684..346131cc 100644 --- a/src/test/java/io/oduck/api/global/stub/MemberStub.java +++ b/src/test/java/io/oduck/api/global/stub/MemberStub.java @@ -81,4 +81,8 @@ public MemberStub() { members.addAll(List.of(member1, member2, member3)); } + + public Member getMember() { + return members.get(0); + } } From 30228351ac23834be0a5d94c9a349d167cbb3a68 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:39:26 +0900 Subject: [PATCH 170/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberServiceTest.java | 87 +++++++++++++++++-- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java index b9b04661..94bf38b7 100644 --- a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java +++ b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java @@ -2,43 +2,114 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; -import io.oduck.api.domain.member.service.MemberServiceStub; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.domain.member.service.MemberServiceImpl; +import io.oduck.api.global.exception.NotFoundException; +import io.oduck.api.global.stub.MemberStub; +import java.util.Optional; +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.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class MemberServiceTest { @InjectMocks - private MemberServiceStub memberService; + private MemberServiceImpl memberService; + + @Mock + MemberRepository memberRepository; // TODO: 회원 가입 + // TODO: 회원 가입 실패(이메일 중복, 비밀번호 유효성 등) @Nested @DisplayName("회원 프로필 조회") class GetProfileByName { + // given + Member member = new MemberStub().getMember(); + MemberProfile memberProfile = member.getMemberProfile(); + String name = memberProfile.getName(); + + ProfileWithoutActivity profileWithoutActivity = ProfileWithoutActivity.builder() + .memberId(1L) + .name(name) + .description(memberProfile.getInfo()) + .thumbnail(memberProfile.getThumbnail()) + .backgroundImage(memberProfile.getBackgroundImage()) + .build(); - @DisplayName("이름으로 회원 프로필 조회 성공시 오류 없이 회원 프로필 반환") + @DisplayName("본인 프로필 조회 성공시 오류 없이 회원 프로필 반환") @Test - void getMemberByName() { + void getMemberByNameIfMine() { // given - String name = "bob"; + given(memberRepository.selectProfileByName(anyString())).willReturn(Optional.ofNullable(profileWithoutActivity)); + given(memberRepository.countReviewsByMemberId(anyLong())).willReturn(1L); + given(memberRepository.countLikesByMemberId(anyLong())).willReturn(1L); // when - MemberProfileRes res = memberService.getProfileByName(name); + MemberProfileRes res = memberService.getProfileByName(name, 1L); // then - assertDoesNotThrow(() -> memberService.getProfileByName(name)); // 오류 없이 회원 프로필 반환 + assertDoesNotThrow(() -> memberService.getProfileByName(name, 1L)); // 오류 없이 회원 프로필 반환 assertEquals(res.getName(), name); // 회원 프로필의 이름이 요청한 이름과 같은지 확인 + assertTrue(res.getIsMine()); + assertNotNull(res.getActivity()); // 회원 프로필의 활동 정보가 null이 아닌지 확인 } - // TODO: 회원 프로필 조회 실패시 오류 반환 + @DisplayName("타 회원 프로필 조회 성공시 오류 없이 회원 프로필 반환, isMine = false") + @Test + void getMemberByNameIfOthers() { + // given + given(memberRepository.selectProfileByName(anyString())).willReturn(Optional.ofNullable(profileWithoutActivity)); + given(memberRepository.countReviewsByMemberId(anyLong())).willReturn(1L); + given(memberRepository.countLikesByMemberId(anyLong())).willReturn(1L); + + // when + MemberProfileRes res = memberService.getProfileByName(name, 2L); + + // then + assertDoesNotThrow(() -> memberService.getProfileByName(name, 1L)); // 오류 없이 회원 프로필 반환 + assertEquals(res.getName(), name); // 회원 프로필의 이름이 요청한 이름과 같은지 확인 + assertFalse(res.getIsMine()); + assertNotNull(res.getActivity()); // 회원 프로필의 활동 정보가 null이 아닌지 확인 + } + + @DisplayName("존재하지 않는 회원 프로필 조회시 NotFoundException 발생") + @Test + void getMemberByNameIfNotFound() { + // given + String notExistName = "notExistName"; + given(memberRepository.selectProfileByName(anyString())).willReturn(Optional.empty()); + + // when + // then + assertThrows(NotFoundException.class, + () -> memberService.getProfileByName(notExistName, 1L) + ); + } } // TODO: 회원 정보 수정 + // TODO: 회원 정보 수정 실패(닉네임 중복 등) + + // TODO: 회원이 작성한 리뷰 목록 + + // TODO: 회원 북마크 애니 목록 } From 764956d00dc52adf23e79cf47a410518fb4a9b66 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 16:40:10 +0900 Subject: [PATCH 171/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20api=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=A0=20=ED=9A=8C=EC=9B=90=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/e2e/member/MemberControllerTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 4460bcc0..9b2f23fc 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -110,7 +110,7 @@ class GetProfileByName { void getProfileByName() throws Exception { // given // 회원 프로필 조회에 필요한 데이터 - String name = "이름"; + String name = "john"; // when // 요청 실행 @@ -228,4 +228,8 @@ void patchProfile() throws Exception { // TODO: 회원 프로필 수정 실패시 } + + // TODO: 회원이 작성한 리뷰 목록 + + // TODO: 회원 북마크 애니 목록 } From 727a2fc0d3f049ed6b9a96bbcdf5cecaaca0cc6c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 17:23:37 +0900 Subject: [PATCH 172/734] =?UTF-8?q?test:=20=EC=9D=B8=EC=A6=9D=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=A1=B0=ED=9A=8C=20api=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20mockUser=20stub=EA=B3=BC=20=ED=86=B5=EC=9D=BC=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java index d6e7598e..c9af0302 100644 --- a/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/auth/AuthControllerTest.java @@ -109,7 +109,7 @@ class GetStatus { @DisplayName("인증된 회원일시 200 OK 및 회원 정보 반환") @Test - @WithCustomMockMember(id = 1L, email = "bob", password = "Qwer!234", role = Role.MEMBER) + @WithCustomMockMember(id = 1L, email = "admin", password = "Qwer!234", role = Role.MEMBER) void getAuthStatus() throws Exception { // given From c1c4f17e90d89aa38b340fb75b25f4b6cb5cfc5d Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 17:24:01 +0900 Subject: [PATCH 173/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/member/MemberControllerTest.java | 139 +++++++++++++++++- 1 file changed, 135 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 9b2f23fc..a8ade1cb 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -1,6 +1,7 @@ package io.oduck.api.e2e.member; import static io.oduck.api.global.config.RestDocsConfig.field; +import static org.hamcrest.Matchers.equalTo; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -21,6 +22,8 @@ import com.google.gson.Gson; import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; import io.oduck.api.domain.member.dto.MemberReqDto.PatchReq; +import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.global.mockMember.WithCustomMockMember; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -105,9 +108,10 @@ void postMember() throws Exception { @DisplayName("회원 프로필 조회") class GetProfileByName { - @DisplayName("회원 이름으로 프로필 조회 성공시 200 OK 반환") + @DisplayName("본인 프로필 조회 성공시 200 OK 반환, isMine = true") @Test - void getProfileByName() throws Exception { + @WithCustomMockMember(id = 2L, email = "john", password = "Qwer!234", role = Role.MEMBER) + void getProfileByNameIfMine() throws Exception { // given // 회원 프로필 조회에 필요한 데이터 String name = "john"; @@ -126,7 +130,7 @@ void getProfileByName() throws Exception { actions .andExpect(status().isOk()) .andExpect(jsonPath("$.name").exists()) - .andExpect(jsonPath("$.isMine").exists()) + .andExpect(jsonPath("$.isMine").value(true)) .andExpect(jsonPath("$.description").exists()) .andExpect(jsonPath("$.thumbnail").exists()) .andExpect(jsonPath("$.backgroundImage").exists()) @@ -135,7 +139,7 @@ void getProfileByName() throws Exception { .andExpect(jsonPath("$.activity.reviews").exists()) .andExpect(jsonPath("$.activity.threads").exists()) .andExpect(jsonPath("$.activity.likes").exists()) - .andDo(document("getProfileByName/success", + .andDo(document("getProfileByName/successIfMine", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -180,7 +184,134 @@ void getProfileByName() throws Exception { .description("받은 좋아요 갯수")))); } + @DisplayName("타인 프로필 조회 성공시 200 OK 반환, isMine = false") + @Test + @WithCustomMockMember(id = 2L, email = "john", password = "Qwer!234", role = Role.MEMBER) + void getProfileByNameIfOthers() throws Exception { + // given + // 회원 프로필 조회에 필요한 데이터 + String name = "david"; + + // when + // 요청 실행 + ResultActions actions = mockMvc.perform( + get("/members" + "/{name}", name) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") + ); + + // then + // 응답 결과 검증 후 문서화 + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").exists()) + .andExpect(jsonPath("$.isMine").value(false)) + .andExpect(jsonPath("$.description").exists()) + .andExpect(jsonPath("$.thumbnail").exists()) + .andExpect(jsonPath("$.backgroundImage").exists()) + .andExpect(jsonPath("$.point").exists()) + .andExpect(jsonPath("$.activity").hasJsonPath()) + .andExpect(jsonPath("$.activity.reviews").exists()) + .andExpect(jsonPath("$.activity.threads").exists()) + .andExpect(jsonPath("$.activity.likes").exists()) + .andDo(document("getProfileByName/successIfOthers", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("name") + .description("회원 이름")), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .optional() + .description("Header Cookie, 세션 쿠키") + ), + responseFields( + fieldWithPath("name") + .type(JsonFieldType.STRING) + .description("회원 이름"), + fieldWithPath("isMine") + .type(JsonFieldType.BOOLEAN) + .description("본인 여부(본인 프로필 조회시 true)"), + fieldWithPath("description") + .type(JsonFieldType.STRING) + .description("자기 소개"), + fieldWithPath("thumbnail") + .type(JsonFieldType.STRING) + .description("프로필 이미지"), + fieldWithPath("backgroundImage") + .type(JsonFieldType.STRING) + .description("프로필 배경 이미지"), + fieldWithPath("point") + .type(JsonFieldType.NUMBER) + .description("회원 포인트"), + fieldWithPath("activity") + .type(JsonFieldType.OBJECT) + .description("회원 활동"), + fieldWithPath("activity.reviews") + .type(JsonFieldType.NUMBER) + .description("작성한 리뷰 갯수"), + fieldWithPath("activity.threads") + .type(JsonFieldType.NUMBER) + .description("작성한 쓰레드 갯수"), + fieldWithPath("activity.likes") + .type(JsonFieldType.NUMBER) + .description("받은 좋아요 갯수")))); + } + // TODO: 회원 프로필 조회 실패시 + + @DisplayName("존재하지 않는 회원 프로필 조회 성공시 404 NotFound 반환") + @Test + @WithCustomMockMember(id = 2L, email = "john", password = "Qwer!234", role = Role.MEMBER) + void getProfileByName() throws Exception { + // given + // 회원 프로필 조회에 필요한 데이터 + String name = "notExistName"; + + // when + // 요청 실행 + ResultActions actions = mockMvc.perform( + get("/members" + "/{name}", name) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") + ); + + // then + // 응답 결과 검증 후 문서화 + actions + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.message").exists()) + .andExpect(jsonPath("$.fieldErrors").value(equalTo(null))) + .andExpect(jsonPath("$.violationErrors").value(equalTo(null))) + .andDo(document("getProfileByName/failure", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("name") + .description("회원 이름")), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .optional() + .description("Header Cookie, 세션 쿠키") + ), + responseFields( + fieldWithPath("message") + .type(JsonFieldType.STRING) + .description("응답 메시지"), + fieldWithPath("fieldErrors") + .type(JsonFieldType.NULL) + .description("api 요청 필드 오류"), + fieldWithPath("violationErrors") + .type(JsonFieldType.NULL) + .description("api 요청 규칙 위반 오류") + ) + ) + ); + } } @Nested From c8fcb566f4a065270966a70a07e3039d180f3c19 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 16 Oct 2023 17:24:36 +0900 Subject: [PATCH 174/734] =?UTF-8?q?docs:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9A=94=EC=B2=AD=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80=20=EB=AC=B8=EC=84=9C=ED=99=94=20?= =?UTF-8?q?#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index b8b2227e..2d7b8b05 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -73,28 +73,46 @@ include::{snippets}/postMember/success/http-response.adoc[] === GET api/v1/members/:name .curl-request -include::{snippets}/getProfileByName/success/curl-request.adoc[] +include::{snippets}/getProfileByName/successIfMine/curl-request.adoc[] .http-request -include::{snippets}/getProfileByName/success/http-request.adoc[] +include::{snippets}/getProfileByName/successIfMine/http-request.adoc[] .request-param -include::{snippets}/getProfileByName/success/path-parameters.adoc[] +include::{snippets}/getProfileByName/successIfMine/path-parameters.adoc[] .request-headers -include::{snippets}/getProfileByName/success/request-headers.adoc[] +include::{snippets}/getProfileByName/successIfMine/request-headers.adoc[] -==== 성공시 +==== 성공시(본인 프로필 조회) .http-response -include::{snippets}/getProfileByName/success/http-response.adoc[] +include::{snippets}/getProfileByName/successIfMine/http-response.adoc[] .response-body -include::{snippets}/getProfileByName/success/response-body.adoc[] +include::{snippets}/getProfileByName/successIfMine/response-body.adoc[] .response-fields -include::{snippets}/getProfileByName/success/response-fields.adoc[] +include::{snippets}/getProfileByName/successIfMine/response-fields.adoc[] -==== 실패시 +==== 성공시(타인 프로필 조회) +.http-response +include::{snippets}/getProfileByName/successIfOthers/http-response.adoc[] + +.response-body +include::{snippets}/getProfileByName/successIfOthers/response-body.adoc[] + +.response-fields +include::{snippets}/getProfileByName/successIfOthers/response-fields.adoc[] + +==== 실패시(존재하지 않는 회원 조회) +.http-response +include::{snippets}/getProfileByName/failure/http-response.adoc[] + +.response-body +include::{snippets}/getProfileByName/failure/response-body.adoc[] + +.response-fields +include::{snippets}/getProfileByName/failure/response-fields.adoc[] === PATCH api/v1/members .curl-request From 5f4bcc6a50705ae6f59ba883f84ebfe897c2d007 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 16 Oct 2023 22:14:04 +0900 Subject: [PATCH 175/734] =?UTF-8?q?refactor:=20=EC=9D=BC=EB=8C=80=EB=8B=A4?= =?UTF-8?q?=EC=9D=BC=20=EC=8B=9C=20=EC=97=B0=EA=B4=80=20=EA=B4=80=EA=B3=84?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EB=B9=8C=EB=8D=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 일대다 관계인 엔티티를 참조하고 있는 경우 제거하였습니다. 생성 시 빌더로 생성하도록 추가하였습니다. --- .../java/io/oduck/api/domain/genre/entity/Genre.java | 11 ++++------- .../domain/originalAuthor/entity/OriginalAuthor.java | 11 ++++------- .../io/oduck/api/domain/series/entity/Series.java | 12 ++++-------- .../io/oduck/api/domain/studio/entity/Studio.java | 11 ++++------- .../api/domain/voiceActor/entity/VoiceActor.java | 11 ++++------- 5 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/genre/entity/Genre.java b/src/main/java/io/oduck/api/domain/genre/entity/Genre.java index 278d0b89..e7b83a26 100644 --- a/src/main/java/io/oduck/api/domain/genre/entity/Genre.java +++ b/src/main/java/io/oduck/api/domain/genre/entity/Genre.java @@ -1,21 +1,21 @@ package io.oduck.api.domain.genre.entity; -import io.oduck.api.domain.anime.entity.AnimeGenre; import io.oduck.api.global.audit.BaseEntity; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder public class Genre extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -23,7 +23,4 @@ public class Genre extends BaseEntity { @Column(nullable = false, length = 15) private String name; - - @OneToMany(mappedBy = "genre", cascade = CascadeType.PERSIST) - private List animeGenres; } \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java b/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java index 65c5ecf0..adb8ed51 100644 --- a/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java +++ b/src/main/java/io/oduck/api/domain/originalAuthor/entity/OriginalAuthor.java @@ -1,21 +1,21 @@ package io.oduck.api.domain.originalAuthor.entity; -import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; import io.oduck.api.global.audit.BaseEntity; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder public class OriginalAuthor extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -23,7 +23,4 @@ public class OriginalAuthor extends BaseEntity { @Column(nullable = false, length = 50) private String name; - - @OneToMany(mappedBy = "originalAuthor", cascade = CascadeType.PERSIST) - private List animeOriginalAuthors; } diff --git a/src/main/java/io/oduck/api/domain/series/entity/Series.java b/src/main/java/io/oduck/api/domain/series/entity/Series.java index 84d46c2e..46dd28a4 100644 --- a/src/main/java/io/oduck/api/domain/series/entity/Series.java +++ b/src/main/java/io/oduck/api/domain/series/entity/Series.java @@ -1,22 +1,21 @@ package io.oduck.api.domain.series.entity; -import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.global.audit.BaseEntity; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import java.util.List; - +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder public class Series extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -24,7 +23,4 @@ public class Series extends BaseEntity { @Column(nullable = false, length = 50) private String title; - - @OneToMany(mappedBy = "series", cascade = CascadeType.PERSIST) - private List animes; } diff --git a/src/main/java/io/oduck/api/domain/studio/entity/Studio.java b/src/main/java/io/oduck/api/domain/studio/entity/Studio.java index 995fec0e..bd873b49 100644 --- a/src/main/java/io/oduck/api/domain/studio/entity/Studio.java +++ b/src/main/java/io/oduck/api/domain/studio/entity/Studio.java @@ -1,21 +1,21 @@ package io.oduck.api.domain.studio.entity; -import io.oduck.api.domain.anime.entity.AnimeStudio; import io.oduck.api.global.audit.BaseEntity; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder public class Studio extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -23,7 +23,4 @@ public class Studio extends BaseEntity { @Column(nullable = false, length = 50) private String name; - - @OneToMany(mappedBy = "studio", cascade = CascadeType.PERSIST) - private List animeStudios; } diff --git a/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java b/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java index b530f9bf..6ce08248 100644 --- a/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java +++ b/src/main/java/io/oduck/api/domain/voiceActor/entity/VoiceActor.java @@ -1,21 +1,21 @@ package io.oduck.api.domain.voiceActor.entity; -import io.oduck.api.domain.anime.entity.AnimeVoiceActor; import io.oduck.api.global.audit.BaseEntity; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder public class VoiceActor extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -23,7 +23,4 @@ public class VoiceActor extends BaseEntity { @Column(nullable = false, length = 50) private String name; - - @OneToMany(mappedBy = "voiceActor", cascade = CascadeType.PERSIST) - private List animeVoiceActors; } \ No newline at end of file From 3de691dc87ad65db8001adf0e0986a9ff1bb2d60 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 16 Oct 2023 22:16:31 +0900 Subject: [PATCH 176/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 애니와 관련 있는 도메인들의 리포지토리를 추가하였습니다. --- .../api/domain/anime/repository/AnimeGenreRepository.java | 8 ++++++++ .../anime/repository/AnimeOriginalAuthorRepository.java | 8 ++++++++ .../domain/anime/repository/AnimeStudioRepository.java | 8 ++++++++ .../anime/repository/AnimeVoiceActorRepository.java | 8 ++++++++ .../api/domain/genre/repository/GenreRepository.java | 8 ++++++++ .../repository/OriginalAuthorRepository.java | 8 ++++++++ .../api/domain/series/repository/SeriesRepository.java | 8 ++++++++ .../api/domain/studio/repository/StudioRepository.java | 8 ++++++++ .../voiceActor/repository/VoiceActorRepository.java | 8 ++++++++ 9 files changed, 72 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java create mode 100644 src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java create mode 100644 src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java create mode 100644 src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java create mode 100644 src/main/java/io/oduck/api/domain/genre/repository/GenreRepository.java create mode 100644 src/main/java/io/oduck/api/domain/originalAuthor/repository/OriginalAuthorRepository.java create mode 100644 src/main/java/io/oduck/api/domain/series/repository/SeriesRepository.java create mode 100644 src/main/java/io/oduck/api/domain/studio/repository/StudioRepository.java create mode 100644 src/main/java/io/oduck/api/domain/voiceActor/repository/VoiceActorRepository.java diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java new file mode 100644 index 00000000..cd150448 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.anime.repository; + +import io.oduck.api.domain.anime.entity.AnimeGenre; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AnimeGenreRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java new file mode 100644 index 00000000..6f389854 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.anime.repository; + +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AnimeOriginalAuthorRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java new file mode 100644 index 00000000..a8840c7b --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.anime.repository; + +import io.oduck.api.domain.anime.entity.AnimeStudio; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AnimeStudioRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java new file mode 100644 index 00000000..4bea8ee8 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.anime.repository; + +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AnimeVoiceActorRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/genre/repository/GenreRepository.java b/src/main/java/io/oduck/api/domain/genre/repository/GenreRepository.java new file mode 100644 index 00000000..7b9a7585 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/genre/repository/GenreRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.genre.repository; + +import io.oduck.api.domain.genre.entity.Genre; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GenreRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/repository/OriginalAuthorRepository.java b/src/main/java/io/oduck/api/domain/originalAuthor/repository/OriginalAuthorRepository.java new file mode 100644 index 00000000..0bd95791 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/originalAuthor/repository/OriginalAuthorRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.originalAuthor.repository; + +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OriginalAuthorRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/series/repository/SeriesRepository.java b/src/main/java/io/oduck/api/domain/series/repository/SeriesRepository.java new file mode 100644 index 00000000..6ce62424 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/series/repository/SeriesRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.series.repository; + +import io.oduck.api.domain.series.entity.Series; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SeriesRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/studio/repository/StudioRepository.java b/src/main/java/io/oduck/api/domain/studio/repository/StudioRepository.java new file mode 100644 index 00000000..b67b8232 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/studio/repository/StudioRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.studio.repository; + +import io.oduck.api.domain.studio.entity.Studio; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StudioRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/voiceActor/repository/VoiceActorRepository.java b/src/main/java/io/oduck/api/domain/voiceActor/repository/VoiceActorRepository.java new file mode 100644 index 00000000..d2919572 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/voiceActor/repository/VoiceActorRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.voiceActor.repository; + +import io.oduck.api.domain.voiceActor.entity.VoiceActor; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VoiceActorRepository extends JpaRepository { + +} From d193c6b71e2a2f0fd3048d8ca2ae7c3f22b752cf Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 16 Oct 2023 22:29:53 +0900 Subject: [PATCH 177/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=EC=9D=98?= =?UTF-8?q?=20=EC=A1=B0=EC=9D=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=86=8C=EB=93=9C,=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=20=EA=B4=80=EA=B3=84=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 애니와 M:N 관계에 있는 테이블의 연결 엔티티에서 기본 생성자를 생성하지 못하도록 protected로 변경하였습니다. 또 생성 메소드를 구현하였습니다. --- .../api/domain/anime/entity/AnimeGenre.java | 17 ++++++++++++- .../anime/entity/AnimeOriginalAuthor.java | 22 +++++++++++++---- .../api/domain/anime/entity/AnimeStudio.java | 21 +++++++++++++--- .../domain/anime/entity/AnimeVoiceActor.java | 24 +++++++++++++++---- 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java index 0d1e9d10..39307ca9 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java @@ -8,12 +8,13 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class AnimeGenre { @Id @@ -27,4 +28,18 @@ public class AnimeGenre { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "genre_id") private Genre genre; + + public void setAnime(Anime anime) { + this.anime = anime; + } + + /** + * M:N 관계 연결 테이블(연결 엔티티) 생성 시 빌더를 사용하면 애니의 연관 관계도 생성할 때 추가할 수 있어 + * 생성 메소드로 구현 + */ + public static AnimeGenre createAnimeGenre(Genre genre) { + AnimeGenre animeGenre = new AnimeGenre(); + animeGenre.genre = genre; + return animeGenre; + } } \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java index b98a0c81..7fa5b7e0 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java @@ -1,8 +1,5 @@ package io.oduck.api.domain.anime.entity; -import lombok.Getter; -import lombok.NoArgsConstructor; - import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -11,10 +8,13 @@ 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 = lombok.AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class AnimeOriginalAuthor { @Id @@ -28,4 +28,18 @@ public class AnimeOriginalAuthor { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "original_author_id") private OriginalAuthor originalAuthor; + + public void setAnime(Anime anime) { + this.anime = anime; + } + + /** + * M:N 관계 연결 테이블(연결 엔티티) 생성 시 빌더를 사용하면 애니의 연관 관계도 생성할 때 추가할 수 있어 + * 생성 메소드로 구현 + */ + public static AnimeOriginalAuthor createAnimeOriginalAuthor(OriginalAuthor originalAuthor){ + AnimeOriginalAuthor animeOriginalAuthor = new AnimeOriginalAuthor(); + animeOriginalAuthor.originalAuthor = originalAuthor; + return animeOriginalAuthor; + } } \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java index a08c8616..3c9a1685 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java @@ -1,7 +1,5 @@ package io.oduck.api.domain.anime.entity; -import lombok.Getter; -import lombok.NoArgsConstructor; import io.oduck.api.domain.studio.entity.Studio; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -10,10 +8,13 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class AnimeStudio { @Id @@ -27,4 +28,18 @@ public class AnimeStudio { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "studio_id") private Studio studio; + + public void setAnime(Anime anime) { + this.anime = anime; + } + + /** + * M:N 관계 연결 테이블(연결 엔티티) 생성 시 빌더를 사용하면 애니의 연관 관계도 생성할 때 추가할 수 있어 + * 생성 메소드로 구현 + */ + public static AnimeStudio createAnimeStudio(Studio studio) { + AnimeStudio animeStudio = new AnimeStudio(); + animeStudio.studio = studio; + return animeStudio; + } } \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java index 42a5609f..fdab5a07 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java @@ -1,9 +1,7 @@ package io.oduck.api.domain.anime.entity; -import jakarta.persistence.Column; -import lombok.Getter; -import lombok.NoArgsConstructor; import io.oduck.api.domain.voiceActor.entity.VoiceActor; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -11,10 +9,13 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class AnimeVoiceActor { @Id @@ -31,4 +32,19 @@ public class AnimeVoiceActor { @Column(nullable = false, length = 100) private String part; + + public void setAnime(Anime anime) { + this.anime = anime; + } + + /** + * M:N 관계 연결 테이블(연결 엔티티) 생성 시 빌더를 사용하면 애니의 연관 관계도 생성할 때 추가할 수 있어 + * 생성 메소드로 구현 + */ + public static AnimeVoiceActor createAnimeVoiceActor(String part, VoiceActor voiceActor) { + AnimeVoiceActor animeVoiceActor = new AnimeVoiceActor(); + animeVoiceActor.part = part; + animeVoiceActor.voiceActor = voiceActor; + return animeVoiceActor; + } } \ No newline at end of file From 90e03043528474a5be40c7d648a7aea363685b77 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 17 Oct 2023 02:27:28 +0900 Subject: [PATCH 178/734] =?UTF-8?q?refactor=20:=20ShortReview=20reviewId?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=20#20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/review/dto/ShortReviewResDto.java | 3 ++- .../api/domain/review/service/ShortReviewServiceStub.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java index 5f5a7a5c..3e3e8caf 100644 --- a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java @@ -13,6 +13,7 @@ public class ShortReviewResDto { @Getter @Builder public static class ShortReview implements EntityBased { + private Long reviewId; private Long animeId; private String content; private boolean hasSpoiler; @@ -22,7 +23,7 @@ public static class ShortReview implements EntityBased { @Override public Long getId() { - return this.getAnimeId(); + return this.getReviewId(); } } diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java index 382e71df..a222ced9 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java @@ -35,6 +35,7 @@ private ShortReview createReview(Long animeId){ return ShortReview .builder() + .reviewId(1L) .animeId(1L) .content("최고의 반전의 반전") .score(5) From bf4df300e39ada4652f8801b2a73d9dff4451496 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 17 Oct 2023 02:30:05 +0900 Subject: [PATCH 179/734] =?UTF-8?q?test:=20reviewId=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20test=20=EB=B3=80=EA=B2=BD=20#2?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../e2e/shortReview/ShortReviewControllerTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index 82d38ec3..5c83d409 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -1,19 +1,26 @@ package io.oduck.api.e2e.shortReview; +import static io.oduck.api.global.config.RestDocsConfig.field; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import com.google.gson.Gson; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PatchShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; +import io.oduck.api.global.utils.ShortReviewTestUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -63,6 +70,7 @@ void getShortReviews() throws Exception{ //then actions .andExpect(status().isOk()) + .andExpect(jsonPath("$.items[0].reviewId").exists()) .andExpect(jsonPath("$.items[0].animeId").exists()) .andExpect(jsonPath("$.items[0].content").exists()) .andExpect(jsonPath("$.items[0].hasSpoiler").exists()) @@ -81,6 +89,9 @@ void getShortReviews() throws Exception{ fieldWithPath("items") .type(JsonFieldType.ARRAY) .description("조회 데이터"), + fieldWithPath("items[].reviewId") + .type(JsonFieldType.NUMBER) + .description("리뷰 고유 식별자"), fieldWithPath("items[].animeId") .type(JsonFieldType.NUMBER) .description("애니 고유 식별자"), From 33dfc67d3b513dab56ab2a8fbd8992ae25f7b738 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 17 Oct 2023 02:33:07 +0900 Subject: [PATCH 180/734] =?UTF-8?q?feat:=20ShortReview=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D,=20=EC=88=98=EC=A0=95=20=EC=B6=94=EA=B0=80=20#20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ShortReviewController.java | 20 ++++++++++ .../domain/review/dto/ShortReviewReqDto.java | 37 +++++++++++++++++++ .../review/service/ShortReviewService.java | 11 ++++++ .../service/ShortReviewServiceStub.java | 12 ++++++ 4 files changed, 80 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java diff --git a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java index da9f05a8..799e4954 100644 --- a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java +++ b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java @@ -1,13 +1,18 @@ package io.oduck.api.domain.review.controller; +import io.oduck.api.domain.review.dto.ShortReviewReqDto; import io.oduck.api.domain.review.dto.ShortReviewResDto; import io.oduck.api.domain.review.service.ShortReviewService; import io.oduck.api.global.common.SliceResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; 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.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -28,4 +33,19 @@ public ResponseEntity getShortReviews( ShortReviewResDto reviewResDto = shortReviewService.getShortReviews(animeId); return ResponseEntity.ok(SliceResponse.of(reviewResDto.getShortReviews())); } + @PostMapping + public ResponseEntity postShortReview( + @RequestBody @Valid ShortReviewReqDto.PostShortReviewReq req) { + //TODO : 짧은 리뷰 작성 + shortReviewService.save(req); + return ResponseEntity.ok().build(); + } + + @PatchMapping("/{reviewId}") + public ResponseEntity patchShortReview( + @PathVariable Long reviewId, @RequestBody @Valid ShortReviewReqDto.PatchShortReviewReq req) { + //TODO : 짧은 리뷰 수정 + shortReviewService.update(reviewId, req); + return ResponseEntity.noContent().build(); + } } \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java new file mode 100644 index 00000000..1925c2e5 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java @@ -0,0 +1,37 @@ +package io.oduck.api.domain.review.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class ShortReviewReqDto { + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class PostShortReviewReq{ + private Long memberId; + private Long animeId; + private boolean hasSpoiler; + @NotBlank + @Length(min = 10, max = 100, + message = "최소 10에서 100자 까지 입력 가능합니다.") + private String content; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class PatchShortReviewReq{ + private boolean hasSpoiler; + @NotBlank + @Length(min = 10, max = 100, + message = "최소 10에서 100자 까지 입력 가능합니다.") + private String content; + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java index be2dff1e..9a521276 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java @@ -1,9 +1,20 @@ package io.oduck.api.domain.review.service; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PatchShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; import io.oduck.api.domain.review.dto.ShortReviewResDto; public interface ShortReviewService { //애니 짧은 리뷰 조회 ShortReviewResDto getShortReviews(Long anime); + + //애니 리뷰 작성 + void save(PostShortReviewReq shortReviewReq); + + //애니 리뷰 수정 + void update(Long reviewId, PatchShortReviewReq req); + + //애니 리뷰 삭제 + } \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java index a222ced9..b152120c 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java @@ -1,5 +1,7 @@ package io.oduck.api.domain.review.service; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PatchShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; import io.oduck.api.domain.review.dto.ShortReviewResDto; import io.oduck.api.domain.review.dto.ShortReviewResDto.MemberProfile; import io.oduck.api.domain.review.dto.ShortReviewResDto.ShortReview; @@ -31,6 +33,16 @@ public ShortReviewResDto getShortReviews(Long animeId) { .build(); } + @Override + public void save(PostShortReviewReq shortReviewReq) { + + } + + @Override + public void update(Long reviewId, PatchShortReviewReq req) { + + } + private ShortReview createReview(Long animeId){ return ShortReview From 26caf55037358158f9ae105a584e224e898fb330 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 17 Oct 2023 10:49:20 +0900 Subject: [PATCH 181/734] =?UTF-8?q?test:=20ShortReview=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D,=20=EC=88=98=EC=A0=95=20test=EC=B6=94=EA=B0=80=20#20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShortReviewControllerTest.java | 90 +++++++++++++++++++ .../global/utils/ShortReviewTestUtils.java | 34 +++++++ 2 files changed, 124 insertions(+) create mode 100644 src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index 5c83d409..f8623276 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -134,4 +134,94 @@ void getShortReviews() throws Exception{ //TODO : 조회 실패 시 } } + + @Nested + @DisplayName("짧은 리뷰 작성") + class PostShortReviews{ + + @DisplayName("리뷰 작성 성공시 Http Status 200 ok 반환") + @Test + void postShortReview() throws Exception{ + //given + PostShortReviewReq req = ShortReviewTestUtils.createPostShoreReviewReq(); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + post("/short-reviews") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content)); + + //then + actions + .andExpect(status().isOk()) + .andDo(document("postShortReview/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields(attributes(key("title").value("Fields for shortReview creation")), + fieldWithPath("memberId") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "숫자만 가능합니다.")) + .description("리뷰를 등록할 회원의 고유 식별 번호"), + fieldWithPath("animeId") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "숫자만 가능합니다.")) + .description("리뷰를 등록할 애니 고유 식별 번호"), + fieldWithPath("hasSpoiler") + .type(JsonFieldType.BOOLEAN) + .attributes(field("constraints", "true 또는 false.")) + .description("스포일러 유무"), + fieldWithPath("content") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "최소 10에서 100자 까지 입력 가능합니다.")) + .description("짧은 리뷰 내용")) + ) + ); + } + //TODO: 리뷰 작성 실패 시 + } + + @Nested + @DisplayName("짧은 리뷰 수정") + class PatchShortReview{ + + @DisplayName("짧은 리뷰 수정 성공시 Http Status 204 반환") + @Test + void patchShortReview() throws Exception{ + //given + Long reviewId = 1L; + PatchShortReviewReq req = ShortReviewTestUtils.createPatchShortReview(); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch("/short-reviews/{reviewId}", reviewId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andExpect(status().isNoContent()) + .andDo(document("patchShortReviewReq/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("reviewId") + .description("리뷰 식별자") + ), + requestFields(attributes(key("title").value("Fields for shortreview creation")), + fieldWithPath("hasSpoiler") + .type(JsonFieldType.BOOLEAN) + .attributes(field("constraints", "true 또는 false.")) + .description("스포일러 유무"), + fieldWithPath("content") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "최소 10에서 100자 까지 입력 가능합니다.")) + .description("짧은 리뷰 내용")) + )); + } + } } \ No newline at end of file diff --git a/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java b/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java new file mode 100644 index 00000000..a46f7d2f --- /dev/null +++ b/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java @@ -0,0 +1,34 @@ +package io.oduck.api.global.utils; + +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PatchShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; + +public class ShortReviewTestUtils { + + public static PostShortReviewReq createPostShoreReviewReq(){ + return new PostShortReviewReq( + getMemberId(),getAnimeId(), + isHasSpoiler(),getContent() + ); + } + + public static PatchShortReviewReq createPatchShortReview(){ + return new PatchShortReviewReq( + isHasSpoiler(),getContent() + ); + } + + public static Long getMemberId(){ + return 1L; + } + + public static Long getAnimeId(){ + return 1L; + } + public static boolean isHasSpoiler(){ + return false; + } + public static String getContent(){ + return "최고의 반전의 반전의 반전"; + } +} From c4da0b8702cc8bf4c3cdad2d3f54d33d66118701 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 17 Oct 2023 11:11:18 +0900 Subject: [PATCH 182/734] =?UTF-8?q?docs:=20index.adoc=20=EC=97=90=20ShortR?= =?UTF-8?q?eview=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A3=BC=EC=86=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 44 ++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index b8b2227e..a895935f 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -141,7 +141,7 @@ include::{snippets}/getAnimeById/success/response-fields.adoc[] == shortReview -=== GET api/v1/short-review/animeId/1 +=== GET api/v1/short-reviews/animeId/1 .curl-request include::{snippets}/getShortReviews/success/curl-request.adoc[] @@ -149,7 +149,7 @@ include::{snippets}/getShortReviews/success/curl-request.adoc[] .http-request include::{snippets}/getShortReviews/success/http-request.adoc[] -.request-param +.path-parameters include::{snippets}/getShortReviews/success/path-parameters.adoc[] ==== 성공시 @@ -164,4 +164,44 @@ include::{snippets}/getShortReviews/success/response-fields.adoc[] .http-response include::{snippets}/getShortReviews/success/http-response.adoc[] +==== 실패시 + +=== POST api/v1/short-reviews/1 + +.curl-request +include::{snippets}/postShortReview/success/curl-request.adoc[] + +.http-request +include::{snippets}/postShortReview/success/http-request.adoc[] + +.httpie-request +include::{snippets}/postShortReview/success/httpie-request.adoc[] + +==== 성공시 +.http-response +include::{snippets}/postShortReview/success/http-response.adoc[] + +.request-fields +include::{snippets}/postShortReview/success/request-fields.adoc[] + +==== 실패시 + +=== PATCH api/v1/short-reviews/1 + +.curl-request +include::{snippets}/patchShortReviewReq/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchShortReviewReq/success/http-request.adoc[] + +.httpie-request +include::{snippets}/patchShortReviewReq/success/httpie-request.adoc[] + +==== 성공시 +.http-response +include::{snippets}/patchShortReviewReq/success/http-response.adoc[] + +.request-fields +include::{snippets}/patchShortReviewReq/success/request-fields.adoc[] + ==== 실패시 \ No newline at end of file From fe8e6c21a64c66e6e80a67d3c0663d22bf81a2f6 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 17 Oct 2023 11:39:55 +0900 Subject: [PATCH 183/734] Merge branch 'develop' into feature/20 --- src/docs/asciidoc/index.adoc | 178 +++++++- .../admin/controller/AdminController.java | 154 +++++++ .../anime/controller/AnimeController.java | 3 +- .../oduck/api/domain/anime/dto/AnimeReq.java | 200 +++++++++ .../oduck/api/domain/anime/dto/AnimeRes.java | 14 +- .../oduck/api/domain/anime/entity/Anime.java | 7 +- .../anime/repository/AnimeRepository.java | 10 + .../domain/anime/service/AnimeService.java | 56 +++ .../anime/service/AnimeServiceStub.java | 58 ++- .../api/domain/bookmark/entity/Bookmark.java | 4 + .../repository/BookmarkRepository.java | 10 + .../api/domain/review/entity/ShortReview.java | 4 + .../repository/ShortReviewRepository.java | 10 + .../reviewLike/entity/ShortReviewLike.java | 4 + .../repository/ShortReviewLikeRepository.java | 10 + .../api/e2e/admin/AdminControllerTest.java | 410 ++++++++++++++++++ .../api/e2e/anime/AnimeControllerTest.java | 75 ++-- .../api/global/utils/AnimeTestUtils.java | 92 ++++ .../unit/anime/service/AnimeServiceTest.java | 19 +- 19 files changed, 1244 insertions(+), 74 deletions(-) create mode 100644 src/main/java/io/oduck/api/domain/admin/controller/AdminController.java create mode 100644 src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java create mode 100644 src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java create mode 100644 src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java create mode 100644 src/main/java/io/oduck/api/domain/review/repository/ShortReviewRepository.java create mode 100644 src/main/java/io/oduck/api/domain/reviewLike/repository/ShortReviewLikeRepository.java create mode 100644 src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java create mode 100644 src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index a895935f..2093467b 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -174,16 +174,16 @@ include::{snippets}/postShortReview/success/curl-request.adoc[] .http-request include::{snippets}/postShortReview/success/http-request.adoc[] -.httpie-request -include::{snippets}/postShortReview/success/httpie-request.adoc[] +.request-body +include::{snippets}/postShortReview/success/request-body.adoc[] + +.request-fields +include::{snippets}/postShortReview/success/request-fields.adoc[] ==== 성공시 .http-response include::{snippets}/postShortReview/success/http-response.adoc[] -.request-fields -include::{snippets}/postShortReview/success/request-fields.adoc[] - ==== 실패시 === PATCH api/v1/short-reviews/1 @@ -194,14 +194,176 @@ include::{snippets}/patchShortReviewReq/success/curl-request.adoc[] .http-request include::{snippets}/patchShortReviewReq/success/http-request.adoc[] -.httpie-request -include::{snippets}/patchShortReviewReq/success/httpie-request.adoc[] +.request-param +include::{snippets}/patchShortReviewReq/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchShortReviewReq/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchShortReviewReq/success/request-fields.adoc[] ==== 성공시 .http-response include::{snippets}/patchShortReviewReq/success/http-response.adoc[] +==== 실패시 + +== admin + +=== POST api/v1/oduckdmin/animes +.curl-request +include::{snippets}/postAnime/success/curl-request.adoc[] + +.http-request +include::{snippets}/postAnime/success/http-request.adoc[] + +.request-body +include::{snippets}/postAnime/success/request-body.adoc[] + .request-fields -include::{snippets}/patchShortReviewReq/success/request-fields.adoc[] +include::{snippets}/postAnime/success/request-fields.adoc[] + +==== 성공시 +.http-response +include::{snippets}/postAnime/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id +.curl-request +include::{snippets}/patchAnime/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnime/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnime/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnime/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnime/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnime/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/original-authors +.curl-request +include::{snippets}/patchAnimeOriginalAuthors/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeOriginalAuthors/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeOriginalAuthors/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeOriginalAuthors/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeOriginalAuthors/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeOriginalAuthors/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/studios +.curl-request +include::{snippets}/patchAnimeStudios/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeStudios/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeStudios/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeStudios/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeStudios/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeStudios/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/voice-actors +.curl-request +include::{snippets}/patchAnimeVoiceActors/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeVoiceActors/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeVoiceActors/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeVoiceActors/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeVoiceActors/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeVoiceActors/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/genres +.curl-request +include::{snippets}/patchAnimeGenres/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeGenres/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeGenres/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeGenres/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeGenres/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeGenres/success/http-response.adoc[] + +==== 실패시 + +=== PATCH api/v1/oduckdmin/animes/:id/series +.curl-request +include::{snippets}/patchAnimeSeries/success/curl-request.adoc[] + +.http-request +include::{snippets}/patchAnimeSeries/success/http-request.adoc[] + +.request-param +include::{snippets}/patchAnimeSeries/success/path-parameters.adoc[] + +.request-body +include::{snippets}/patchAnimeSeries/success/request-body.adoc[] + +.request-fields +include::{snippets}/patchAnimeSeries/success/request-fields.adoc[] + +==== 성공시 + +.http-response +include::{snippets}/patchAnimeSeries/success/http-response.adoc[] ==== 실패시 \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java new file mode 100644 index 00000000..6f9503d7 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java @@ -0,0 +1,154 @@ +package io.oduck.api.domain.admin.controller; + +import static io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import static io.oduck.api.domain.anime.dto.AnimeReq.PostReq; + +import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.domain.anime.service.AnimeService; +import io.oduck.api.domain.series.entity.Series; +import jakarta.validation.Valid; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +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.RestController; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/oduckdmin") +@Slf4j +public class AdminController { + + private final AnimeService animeService; + + //애니 등록 + @PostMapping("/animes") + public ResponseEntity postAnime(@RequestBody @Valid PostReq req){ + // TODO: 애니 추가 로직 구현 + animeService.save(req); + + return ResponseEntity.ok().build(); + } + + //애니 관련 수정 + @PatchMapping("/animes/{animeId}") + public ResponseEntity patchAnime( + @PathVariable Long animeId, @RequestBody @Valid PatchAnimeReq req + ){ + //TODO: 애니 수정 로직 구현 + animeService.update(animeId, req); + + return ResponseEntity.noContent().build(); + } + + // 애니의 원작 작가 수정 + @PatchMapping("/animes/{animeId}/original-authors") + public ResponseEntity patchAnimeOriginalAuthors( + @PathVariable Long animeId, @RequestBody PatchOriginalAuthorIdsReq req + ){ + List originalAuthorIds = req.getOriginalAuthorIds(); + //TODO: 컨트롤러에서 애니 아이디와 원작 작가 아이디로 AnimeOriginalAuthor들을 찾아야 함. + List animeOriginalAuthors = new ArrayList<>(); + + //TODO: 애니 수정 로직 구현 + animeService.updateAnimeOriginalAuthors(animeId, animeOriginalAuthors); + + return ResponseEntity.noContent().build(); + } + + // 애니의 스튜디오 수정 + @PatchMapping("/animes/{animeId}/studios") + public ResponseEntity patchAnimeStudios( + @PathVariable Long animeId, @RequestBody PatchStudioIdsReq req + ){ + List originalAuthorIds = req.getStudioIds(); + //TODO: 컨트롤러에서 애니 아이디와 원작 작가 아이디로 AnimeOriginalAuthor들을 찾아야 함. + List animeStudios = new ArrayList<>(); + + //TODO: 애니 수정 로직 구현 + animeService.updateAnimeStudios(animeId, animeStudios); + + return ResponseEntity.noContent().build(); + } + // 애니의 성우 수정 + @PatchMapping("/animes/{animeId}/voice-actors") + public ResponseEntity patchAnimeVoiceActors( + @PathVariable Long animeId, @RequestBody PatchVoiceActorIdsReq req + ){ + List voiceActorIds = req.getVoiceActorIds(); + //TODO: 컨트롤러에서 애니 아이디와 성우 아이디로 AnimeOriginalAuthor들을 찾아야 함. + List animeVoiceActors = new ArrayList<>(); + + //TODO: 애니 수정 로직 구현 + animeService.updateAnimeVoiceActors(animeId, animeVoiceActors); + + return ResponseEntity.noContent().build(); + } + + // 애니의 장르 수정 + @PatchMapping("/animes/{animeId}/genres") + public ResponseEntity patchAnimeGenres( + @PathVariable Long animeId, @RequestBody PatchGenreIdsReq req + ){ + List genreIds = req.getGenreIds(); + //TODO: 컨트롤러에서 애니 아이디와 장르 아이디로 AnimeGenre들을 찾아야 함. + List animeGenres = new ArrayList<>(); + + //TODO: 애니 수정 로직 구현 + animeService.updateAnimeGenres(animeId, animeGenres); + + return ResponseEntity.noContent().build(); + } + + // 애니의 시리즈 수정 + @PatchMapping("/animes/{animeId}/series") + public ResponseEntity patchAnimeSeries( + @PathVariable Long animeId, @RequestBody PatchSeriesIdReq req + ){ + Long seriesId = req.getSeriesId(); + //TODO: 컨트롤러에서 시리즈 아이디로 시리즈를 찾아야 함. + Series series = null; + + //TODO: 애니 수정 로직 구현 + animeService.update(animeId, series); + + return ResponseEntity.noContent().build(); + } + + // 애니 삭제 +// @DeleteMapping("/animes/{animeId}") +// public ResponseEntity deleteAnime(@PathVariable Long animeId){ +// +// //TODO: 애니 삭제 로직 구현. (애니를 실제로 삭제하지 않음) +// animeService.delete(animeId); +// return ResponseEntity.noContent().build(); +// } + +// 관리자 애니 조회 (isReleased = false도 조회) +// @GetMapping("/animes") +// public ResponseEntity getAnimes( +// Pageable pageable, String query, SearchCondition condition +// ){ +// +// //TODO: 애니 조회 로직 구현. +// Page res = adminAnimeService.getAnimes(pageable, query, condition); +// return ResponseEntity +// .ok(PageResponse.of(null)); +// } +} diff --git a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java index c9f70052..7d3c6fb9 100644 --- a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java +++ b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java @@ -2,7 +2,6 @@ import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.service.AnimeService; -import io.oduck.api.global.common.SingleResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -28,7 +27,7 @@ public ResponseEntity getAnimeById(@PathVariable Long animeId){ // TODO: 애니 조회 로직 구현 AnimeRes res = animeService.getAnimeById(animeId); return ResponseEntity - .ok(SingleResponse.of(res)); + .ok(res); } // TODO: 애니 검색 결과 페이징 diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java new file mode 100644 index 00000000..a0ce1168 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java @@ -0,0 +1,200 @@ +package io.oduck.api.domain.anime.dto; + +import io.oduck.api.domain.anime.entity.BroadcastType; +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Rating; +import io.oduck.api.domain.anime.entity.Status; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class AnimeReq { + + @Getter + public static class PostReq { + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String title; + + @NotBlank + @Length(min = 1, max = 255, + message = "글자 수는 0~255를 허용합니다.") + private String summary; + + private BroadcastType broadcastType; + + @Min(value = 0, + message = "음수가 올 수 없습니다.") + private int episodeCount; + + @NotBlank( + message = "섬네일을 입력하세요.") + private String thumbnail; + + @Min(value = 1900, + message = "1900년대 이상을 입력하세요.") + private int year; + + private Quarter quarter; + + private Rating rating; + + private Status status; + + private List originalAuthorIds; + + private List studioIds; + + private List voiceActorIds; + + private List genreIds; + + private Long seriesId; + + public PostReq (String title, String summary, BroadcastType broadcastType, + int episodeCount, String thumbnail, int year, Quarter quarter, Rating rating, Status status, + List originalAuthorIds, List studioIds, List voiceActorIds, + List genreIds, Long seriesId) { + + this.title = title; + this.summary = summary; + this.broadcastType = broadcastType; + this.episodeCount = episodeCount; + this.thumbnail = thumbnail; + this.year = year; + this.quarter = quarter; + this.rating = rating; + this.status = status; + + if(originalAuthorIds == null){ + this.originalAuthorIds = new ArrayList<>(); + }else { + this.originalAuthorIds = originalAuthorIds; + } + + if(studioIds == null){ + this.studioIds = new ArrayList<>(); + }else{ + this.studioIds = studioIds; + } + + if(voiceActorIds == null) { + this.voiceActorIds = new ArrayList<>(); + }else { + this.voiceActorIds = voiceActorIds; + } + + if(genreIds == null) { + this.genreIds = new ArrayList<>(); + }else { + this.genreIds = genreIds; + } + + this.seriesId = seriesId; + } + } + + @Getter + @AllArgsConstructor + public static class PatchAnimeReq{ + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String title; + + @NotBlank + @Length(min = 1, max = 255, + message = "글자 수는 0~255를 허용합니다.") + private String summary; + + private BroadcastType broadcastType; + + @Min(value = 0, + message = "음수가 올 수 없습니다.") + private int episodeCount; + + @NotBlank( + message = "섬네일을 입력하세요.") + private String thumbnail; + + @Min(value = 1900, + message = "1900년대 이상을 입력하세요.") + private int year; + + private Quarter quarter; + + private Rating rating; + + private Status status; + } + + @Getter + @NoArgsConstructor + public static class PatchOriginalAuthorIdsReq { + private List originalAuthorIds; + + public PatchOriginalAuthorIdsReq(List originalAuthorIds) { + if(originalAuthorIds == null){ + this.originalAuthorIds = new ArrayList<>(); + }else{ + this.originalAuthorIds = originalAuthorIds; + } + } + } + + @Getter + @NoArgsConstructor + public static class PatchStudioIdsReq { + private List studioIds; + + public PatchStudioIdsReq(List studioIds) { + if(studioIds == null){ + this.studioIds = new ArrayList<>(); + }else{ + this.studioIds = studioIds; + } + } + + } + + @Getter + @NoArgsConstructor + public static class PatchVoiceActorIdsReq { + private List voiceActorIds; + + public PatchVoiceActorIdsReq(List voiceActorIds) { + if(voiceActorIds == null){ + this.voiceActorIds = new ArrayList<>(); + }else{ + this.voiceActorIds = voiceActorIds; + } + } + } + + @Getter + @NoArgsConstructor + public static class PatchGenreIdsReq { + private List genreIds; + + public PatchGenreIdsReq(List genreIds) { + if(genreIds == null){ + this.genreIds = new ArrayList<>(); + }else{ + this.genreIds = genreIds; + } + } + } + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class PatchSeriesIdReq{ + private Long seriesId; + } +} diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java index 12c1f321..a0d84a60 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -16,10 +16,12 @@ public class AnimeRes { @Getter @Builder public static class Anime { - private Long animeId; + private Long id; private String title; private String thumbnail; - private Broadcast broadcast; + private BroadcastType broadcastType; + private int year; + private Quarter quarter; private String summary; private int episodeCount; private Rating rating; @@ -32,12 +34,4 @@ public static class Anime { private long reviewCount; private long bookmarkCount; } - - @Getter - @Builder - public static class Broadcast { - private BroadcastType broadcastType; - private int year; - private Quarter quarter; - } } diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 86c0854f..1b347b0f 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -15,13 +15,17 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor public class Anime extends BaseEntity { @Id @@ -31,7 +35,7 @@ public class Anime extends BaseEntity { @Column(nullable = false, length = 50) private String title; - @Column(nullable = false, length = 255) + @Column(nullable = false, length = 600) private String summary; @Enumerated(EnumType.STRING) @@ -43,6 +47,7 @@ public class Anime extends BaseEntity { @Column(nullable = true, length = 500) private String thumbnail; + @Column(name = "release_year") private int year; @Enumerated(EnumType.STRING) diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java new file mode 100644 index 00000000..ec192c9d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.anime.repository; + +import io.oduck.api.domain.anime.entity.Anime; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AnimeRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java index 7b0f776d..e2c35c91 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java @@ -1,6 +1,13 @@ package io.oduck.api.domain.anime.service; +import io.oduck.api.domain.anime.dto.AnimeReq.*; import io.oduck.api.domain.anime.dto.AnimeRes; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.domain.series.entity.Series; +import java.util.List; public interface AnimeService { @@ -10,4 +17,53 @@ public interface AnimeService { * @return AnimeRes */ AnimeRes getAnimeById(Long animeId); + + /** + * 애니 저장 로직 + * @param req + * @return AnimeId + */ + Long save(PostReq req); + + /** + * 애니 수정 로직 + * @param animeId + * @param req + */ + void update(Long animeId, PatchAnimeReq req); + + /** + * 애니 원작 작가 수정 로직 + * @param animeId + * @param originalAuthors + */ + void updateAnimeOriginalAuthors(Long animeId, List originalAuthors); + + /** + * 애니 스튜디오 수정 로직 + * @param animeId + * @param animeStudios + */ + void updateAnimeStudios(Long animeId, List animeStudios); + + /** + * 애니 성우 수정 로직 + * @param animeId + * @param animeVoiceActors + */ + void updateAnimeVoiceActors(Long animeId, List animeVoiceActors); + + /** + * 애니 장르 수정 로직 + * @param animeId + * @param animeGenres + */ + void updateAnimeGenres(Long animeId, List animeGenres); + + /** + * 애니 시리즈 수정 로직 + * @param animeId + * @param series + */ + void update(Long animeId, Series series); } diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java index ae01a554..9118a856 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java @@ -1,12 +1,18 @@ package io.oduck.api.domain.anime.service; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.dto.AnimeRes.Anime; -import io.oduck.api.domain.anime.dto.AnimeRes.Broadcast; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; import io.oduck.api.domain.anime.entity.Rating; import io.oduck.api.domain.anime.entity.Status; +import io.oduck.api.domain.series.entity.Series; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; @@ -14,6 +20,11 @@ @Service public class AnimeServiceStub implements AnimeService{ + @Override + public Long save(PostReq req) { + return 1L; + } + @Override public AnimeRes getAnimeById(Long animeId) { Anime anime = createAnime(animeId); @@ -23,12 +34,44 @@ public AnimeRes getAnimeById(Long animeId) { .build(); } + @Override + public void update(Long animeId, PatchAnimeReq req) { + + } + + @Override + public void updateAnimeOriginalAuthors(Long animeId, List originalAuthors) { + + } + + @Override + public void updateAnimeStudios(Long animeId, List animeStudios) { + + } + + @Override + public void updateAnimeVoiceActors(Long animeId, List animeVoiceActors) { + + } + + @Override + public void updateAnimeGenres(Long animeId, List animeGenres) { + + } + + @Override + public void update(Long animeId, Series series) { + + } + private Anime createAnime(Long animeId) { return Anime.builder() - .animeId(animeId) + .id(animeId) .title("귀멸의 칼날: 도공 마을편") .thumbnail("https://image파일경로/uuid.jpg") - .broadcast(getBroadcast()) + .broadcastType(BroadcastType.TVA) + .year(2023) + .quarter(Quarter.Q2) .summary( "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다. 탄지로가 검이 수리되기를 기다리는 동안, 상현 혈귀 한텐구와 쿗코가 숨겨진 마을인 ‘도공 마을'을 습격한다. 공격할 때마다 분열해서 위력이 커지는 한텐구로 인해 탄지로와 겐야는 고전을 면치 못한다. 한편, 타인에 대한 관심이 희박한 하주 토키토 무이치로는 혈귀들에게 공격당하고 있는 코테츠를 목격하는데….") .episodeCount(11) @@ -43,15 +86,6 @@ private Anime createAnime(Long animeId) { .build(); } - private Broadcast getBroadcast() { - return Broadcast.builder() - .broadcastType(BroadcastType.TVA) - .year(2023) - .quarter(Quarter.Q2) - .build(); - - } - private List getStudios() { List studios = new ArrayList<>(); studios.add("ufotable"); diff --git a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java index c317bb65..bba90efa 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java +++ b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java @@ -11,13 +11,17 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor public class Bookmark { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java new file mode 100644 index 00000000..4769d2f1 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.bookmark.repository; + +import io.oduck.api.domain.bookmark.entity.Bookmark; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BookmarkRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java index 27a35492..cf73543c 100644 --- a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java +++ b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java @@ -15,12 +15,16 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor public class ShortReview extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/io/oduck/api/domain/review/repository/ShortReviewRepository.java b/src/main/java/io/oduck/api/domain/review/repository/ShortReviewRepository.java new file mode 100644 index 00000000..37c7658b --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/repository/ShortReviewRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.review.repository; + +import io.oduck.api.domain.review.entity.ShortReview; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ShortReviewRepository extends JpaRepository { + +} diff --git a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java index fa43497b..b613d3d5 100644 --- a/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java +++ b/src/main/java/io/oduck/api/domain/reviewLike/entity/ShortReviewLike.java @@ -10,12 +10,16 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter +@Builder @NoArgsConstructor +@AllArgsConstructor public class ShortReviewLike extends BaseEntity { @Id diff --git a/src/main/java/io/oduck/api/domain/reviewLike/repository/ShortReviewLikeRepository.java b/src/main/java/io/oduck/api/domain/reviewLike/repository/ShortReviewLikeRepository.java new file mode 100644 index 00000000..ed886384 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/reviewLike/repository/ShortReviewLikeRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.reviewLike.repository; + +import io.oduck.api.domain.reviewLike.entity.ShortReviewLike; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ShortReviewLikeRepository extends JpaRepository { + +} diff --git a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java new file mode 100644 index 00000000..ec5f5023 --- /dev/null +++ b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java @@ -0,0 +1,410 @@ +package io.oduck.api.e2e.admin; + +import static io.oduck.api.global.config.RestDocsConfig.field; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.google.gson.Gson; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import io.oduck.api.global.utils.AnimeTestUtils; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@AutoConfigureRestDocs +@AutoConfigureMockMvc +@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) +@SpringBootTest +@ActiveProfiles("test") +public class AdminControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + private final static String ADMIN_URL = "/oduckdmin"; + + @Nested + @DisplayName("애니 등록") + class PostAnime{ + + @Test + @DisplayName("등록 성공 시 Http Status 200 반환") + void postAnime() throws Exception { + //given + PostReq request = AnimeTestUtils.createPostAnimeRequest(); + String content = gson.toJson(request); + + //when + ResultActions actions = mockMvc.perform( + post(ADMIN_URL+"/animes") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andExpect(status().isOk()) + .andDo(document("postAnime/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("title") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("애니의 제목"), + fieldWithPath("summary") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~255자를 허용합니다.")) + .description("애니 요약 설명"), + fieldWithPath("broadcastType") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "TVA,ONA,OVA,MOV만 허용합니다.")) + .description("애니 방송사"), + fieldWithPath("episodeCount") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "0 이상의 숫자만 허용합니다.")) + .description("애피소드 화수"), + fieldWithPath("thumbnail") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다.")) + .description("애피소드 화수"), + fieldWithPath("year") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "1900 이상의 숫자만 허용합니다.")) + .description("애니 발행 년도"), + fieldWithPath("quarter") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "Q1,Q2,Q3,Q4만 허용합니다.")) + .description("애니 발행 분기"), + fieldWithPath("rating") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ADULT,FIFTEEN,TWELVE,ALL만 허용합니다.")) + .description("애니 등급"), + fieldWithPath("status") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ONGOING,FINISHED,UPCOMING,UNKNOWN만 허용합니다.")) + .description("애니 상태"), + fieldWithPath("originalAuthorIds") + .type(JsonFieldType.ARRAY) + .description("원작 작가들 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("studioIds") + .type(JsonFieldType.ARRAY) + .description("애니 스튜디오 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("voiceActorIds") + .type(JsonFieldType.ARRAY) + .description("애니 성우 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("genreIds") + .type(JsonFieldType.ARRAY) + .description("애니 장르 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("seriesId") + .type(JsonFieldType.NUMBER) + .description("애니 시리즈 아이디") + .attributes(field("constraints", "숫자만 허용합니다.")) + .optional() + ) + )); + } + //TODO : 애니 등록 실패 시 + } + + @Nested + @DisplayName("애니 수정") + class PatchAnime{ + + @Test + @DisplayName("애니 수정 성공 시 Http Status 204 반환") + void patchAnime() throws Exception { + //given + Long animeId = 1L; + PatchAnimeReq req = AnimeTestUtils.createPatchAnimeRequest(); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch(ADMIN_URL+"/animes/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andExpect(status().isNoContent()) + .andDo(document("patchAnime/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("title") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("애니의 제목"), + fieldWithPath("summary") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~255자를 허용합니다.")) + .description("애니 요약 설명"), + fieldWithPath("broadcastType") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "TVA,ONA,OVA,MOV만 허용합니다.")) + .description("애니 방송사"), + fieldWithPath("episodeCount") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "0 이상의 숫자만 허용합니다.")) + .description("애피소드 화수"), + fieldWithPath("thumbnail") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다.")) + .description("애피소드 화수"), + fieldWithPath("year") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "1900 이상의 숫자만 허용합니다.")) + .description("애니 발행 년도"), + fieldWithPath("quarter") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "Q1,Q2,Q3,Q4만 허용합니다.")) + .description("애니 발행 분기"), + fieldWithPath("rating") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ADULT,FIFTEEN,TWELVE,ALL만 허용합니다.")) + .description("애니 등급"), + fieldWithPath("status") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ONGOING,FINISHED,UPCOMING,UNKNOWN만 허용합니다.")) + .description("애니 상태") + ) + )); + + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니 원작 작가 수정 성공 시 Http Status 204 반환") + void patchAnimeOriginalAuthors() throws Exception { + //given + Long animeId = 1L; + List originalAuthorIds = AnimeTestUtils.getOriginalAuthorIds(); + + PatchOriginalAuthorIdsReq req = new PatchOriginalAuthorIdsReq(originalAuthorIds); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch(ADMIN_URL+"/animes/{animeId}/original-authors", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andDo(document("patchAnimeOriginalAuthors/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("originalAuthorIds") + .type(JsonFieldType.ARRAY) + .description("원작 작가들 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); + + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니 원작 작가 수정 성공 시 Http Status 204 반환") + void patchAnimeStudios() throws Exception { + //given + Long animeId = 1L; + List studioIds = AnimeTestUtils.getStudioIds(); + + PatchStudioIdsReq req = new PatchStudioIdsReq(studioIds); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch(ADMIN_URL+"/animes/{animeId}/studios", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andDo(document("patchAnimeStudios/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("studioIds") + .type(JsonFieldType.ARRAY) + .description("스튜디오 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); + + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니 성우 수정 성공 시 Http Status 204 반환") + void patchAnimeVoiceActors() throws Exception { + //given + Long animeId = 1L; + List voiceActorIds = AnimeTestUtils.getVoiceActorIds(); + + PatchVoiceActorIdsReq req = new PatchVoiceActorIdsReq(voiceActorIds); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + put(ADMIN_URL+"/animes/{animeId}/voice-actors", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andDo(document("patchAnimeVoiceActors/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("voiceActorIds") + .type(JsonFieldType.ARRAY) + .description("애니 성우 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니 장르 수정 성공 시 Http Status 204 반환") + void patchAnimeGenres() throws Exception { + //given + Long animeId = 1L; + List genreIds = AnimeTestUtils.getGenreIds(); + + PatchGenreIdsReq req = new PatchGenreIdsReq(genreIds); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + put(ADMIN_URL+"/animes/{animeId}/genres", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andDo(document("patchAnimeGenres/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("genreIds") + .type(JsonFieldType.ARRAY) + .description("애니 장르 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); + } + //TODO: 수정 실패 시 + + @Test + @DisplayName("애니의 시리즈 수정 성공 시 Http Status 204 반환") + void patchAnimeSeries() throws Exception { + //given + Long animeId = 1L; + PatchSeriesIdReq req = new PatchSeriesIdReq(AnimeTestUtils.getSeriesId()); + String content = gson.toJson(req); + + //when + ResultActions actions = mockMvc.perform( + patch(ADMIN_URL+"/animes/{animeId}/series", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + //then + actions + .andExpect(status().isNoContent()) + .andDo(document("patchAnimeSeries/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("seriesId") + .type(JsonFieldType.NUMBER) + .description("애니 시리즈 아이디") + .attributes(field("constraints", "숫자만 허용합니다.")) + .optional() + ) + )); + } + //TODO: 수정 실패 시 + } +} diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index 89ba7b69..479a8bac 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -9,6 +9,7 @@ import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -26,7 +27,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @AutoConfigureRestDocs @AutoConfigureMockMvc @@ -57,22 +57,23 @@ void getAnimes() throws Exception { //then actions - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(jsonPath("$.item.anime.title").exists()) - .andExpect(jsonPath("$.item.anime.thumbnail").exists()) - .andExpect(jsonPath("$.item.anime.broadcast.broadcastType").exists()) - .andExpect(jsonPath("$.item.anime.broadcast.year").exists()) - .andExpect(jsonPath("$.item.anime.broadcast.quarter").exists()) - .andExpect(jsonPath("$.item.anime.summary").exists()) - .andExpect(jsonPath("$.item.anime.episodeCount").exists()) - .andExpect(jsonPath("$.item.anime.rating").exists()) - .andExpect(jsonPath("$.item.anime.status").exists()) - .andExpect(jsonPath("$.item.anime.genres").exists()) - .andExpect(jsonPath("$.item.anime.originalAuthors").exists()) - .andExpect(jsonPath("$.item.anime.voiceActors").exists()) - .andExpect(jsonPath("$.item.anime.studios").exists()) - .andExpect(jsonPath("$.item.anime.reviewCount").exists()) - .andExpect(jsonPath("$.item.anime.bookmarkCount").exists()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.anime.id").exists()) + .andExpect(jsonPath("$.anime.title").exists()) + .andExpect(jsonPath("$.anime.thumbnail").exists()) + .andExpect(jsonPath("$.anime.broadcastType").exists()) + .andExpect(jsonPath("$.anime.year").exists()) + .andExpect(jsonPath("$.anime.quarter").exists()) + .andExpect(jsonPath("$.anime.summary").exists()) + .andExpect(jsonPath("$.anime.episodeCount").exists()) + .andExpect(jsonPath("$.anime.rating").exists()) + .andExpect(jsonPath("$.anime.status").exists()) + .andExpect(jsonPath("$.anime.genres").exists()) + .andExpect(jsonPath("$.anime.originalAuthors").exists()) + .andExpect(jsonPath("$.anime.voiceActors").exists()) + .andExpect(jsonPath("$.anime.studios").exists()) + .andExpect(jsonPath("$.anime.reviewCount").exists()) + .andExpect(jsonPath("$.anime.bookmarkCount").exists()) .andDo(document("getAnimeById/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -81,61 +82,55 @@ void getAnimes() throws Exception { .description("애니의 고유 식별자") ), responseFields( - fieldWithPath("item") - .type(JsonFieldType.OBJECT) - .description("조회 데이터"), - fieldWithPath("item.anime") + fieldWithPath("anime") .type(JsonFieldType.OBJECT) .description("애니 정보"), - fieldWithPath("item.anime.animeId") + fieldWithPath("anime.id") .type(JsonFieldType.NUMBER) .description("애니의 고유 식별자"), - fieldWithPath("item.anime.title") + fieldWithPath("anime.title") .type(JsonFieldType.STRING) .description("애니 제목"), - fieldWithPath("item.anime.thumbnail") + fieldWithPath("anime.thumbnail") .type(JsonFieldType.STRING) .description("애니 이미지, 경로로 저장됨"), - fieldWithPath("item.anime.broadcast") - .type(JsonFieldType.OBJECT) - .description("애니 방송 관련 정보"), - fieldWithPath("item.anime.broadcast.broadcastType") + fieldWithPath("anime.broadcastType") .type(JsonFieldType.STRING) .description("작품의 출시 방식. TVA, OVA, ONA, MOV가 있음"), - fieldWithPath("item.anime.broadcast.year") + fieldWithPath("anime.year") .type(JsonFieldType.NUMBER) .description("작품의 출시 년도"), - fieldWithPath("item.anime.broadcast.quarter") + fieldWithPath("anime.quarter") .type(JsonFieldType.STRING) .description("작품의 출시 분기"), - fieldWithPath("item.anime.summary") + fieldWithPath("anime.summary") .type(JsonFieldType.STRING) .description("애니를 소개할 때 줄거리"), - fieldWithPath("item.anime.episodeCount") + fieldWithPath("anime.episodeCount") .type(JsonFieldType.NUMBER) .description("에피소드의 숫자. 16화까지 나왔으면 16으로 표기됨."), - fieldWithPath("item.anime.rating") + fieldWithPath("anime.rating") .type(JsonFieldType.STRING) .description("작품의 심의 등급. ADULT, FIFTEEN, TWELVE, ALL가 있다."), - fieldWithPath("item.anime.status") + fieldWithPath("anime.status") .type(JsonFieldType.STRING) .description("방영 상태. FINISHED, ONGOING, UPCOMING, UNKNOWN이 있다."), - fieldWithPath("item.anime.genres") + fieldWithPath("anime.genres") .type(JsonFieldType.ARRAY) .description("애니의 장르."), - fieldWithPath("item.anime.originalAuthors") + fieldWithPath("anime.originalAuthors") .type(JsonFieldType.ARRAY) .description("애니의 원작 작가."), - fieldWithPath("item.anime.voiceActors") + fieldWithPath("anime.voiceActors") .type(JsonFieldType.ARRAY) .description("애니의 출연 성우."), - fieldWithPath("item.anime.studios") + fieldWithPath("anime.studios") .type(JsonFieldType.ARRAY) .description("애니의 제작사."), - fieldWithPath("item.anime.reviewCount") + fieldWithPath("anime.reviewCount") .type(JsonFieldType.NUMBER) .description("애니의 리뷰 개수."), - fieldWithPath("item.anime.bookmarkCount") + fieldWithPath("anime.bookmarkCount") .type(JsonFieldType.NUMBER) .description("애니의 덕후 수.") ) diff --git a/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java new file mode 100644 index 00000000..d3995b88 --- /dev/null +++ b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java @@ -0,0 +1,92 @@ +package io.oduck.api.global.utils; + +import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import io.oduck.api.domain.anime.entity.BroadcastType; +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Rating; +import io.oduck.api.domain.anime.entity.Status; +import java.util.ArrayList; +import java.util.List; + +public class AnimeTestUtils { + public static PostReq createPostAnimeRequest(){ + return new PostReq( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), + getThumbnail(), getYear(), getQuarter(), getRating(), getStatus(), + getOriginalAuthorIds(), getStudioIds(), getVoiceActorIds(), getGenreIds(), getSeriesId()); + } + + public static PatchAnimeReq createPatchAnimeRequest(){ + return new PatchAnimeReq( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), + getThumbnail(), getYear(), getQuarter(), getRating(), getStatus()); + } + + public static long getSeriesId() { + return 1L; + } + + public static Status getStatus() { + return Status.FINISHED; + } + + public static Rating getRating() { + return Rating.ADULT; + } + + public static Quarter getQuarter() { + return Quarter.Q2; + } + + public static int getYear() { + return 2023; + } + + public static String getThumbnail() { + return "/uuid.jpg"; + } + + public static int getEpisodeCount() { + return 11; + } + + public static BroadcastType getBroadcastType() { + return BroadcastType.TVA; + } + + public static String getSummary() { + return "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다."; + } + + public static String getTitle() { + return "귀멸의 칼날: 도공 마을편"; + } + + + public static List getGenreIds() { + List genreIds = new ArrayList(); + genreIds.add(4L); + genreIds.add(5L); + return genreIds; + } + + public static List getVoiceActorIds() { + List voiceActorIds = getStudioIds(); + voiceActorIds.add(2L); + voiceActorIds.add(4L); + return voiceActorIds; + } + + public static List getStudioIds() { + List studioIds = new ArrayList(); + studioIds.add(1L); + return studioIds; + } + + public static List getOriginalAuthorIds() { + List originalAuthorIds = new ArrayList(); + originalAuthorIds.add(5L); + return originalAuthorIds; + } +} diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index 8d8b1461..2ad167bf 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -3,8 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.service.AnimeServiceStub; +import io.oduck.api.global.utils.AnimeTestUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -32,8 +34,23 @@ void getAnimeById() { AnimeRes response = animeService.getAnimeById(animeId); //then - assertThat(response.getAnime().getAnimeId()).isEqualTo(animeId); + assertThat(response.getAnime().getId()).isEqualTo(animeId); assertThatNoException(); } } + + @Nested + @DisplayName("애니 등록") + class PostAnime{ + + @Test + @DisplayName("애니 등록 성공") + void postAnime(){ + PostReq req = AnimeTestUtils.createPostAnimeRequest(); + + Long animeId = animeService.save(req); + + assertThat(animeId).isEqualTo(1L); + } + } } From 24d9e30fef7f6661ad0aa202584c5e8c68f8ab03 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 14:09:50 +0900 Subject: [PATCH 184/734] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=91=EB=8B=B5=20threads=20->?= =?UTF-8?q?=20bookmarks=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/member/dto/MemberResDto.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java index 31e55437..cc6638d7 100644 --- a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java @@ -37,13 +37,13 @@ public boolean getIsMine() { @NoArgsConstructor public static class Activity { private long reviews; - private long threads; + private long bookmarks; private long likes; @Builder - public Activity(long reviews, long threads, long likes) { + public Activity(long reviews, long bookmarks, long likes) { this.reviews = reviews; - this.threads = threads; + this.bookmarks = bookmarks; this.likes = likes; } } From 006b8d7a168ae906083c3a2b5d476fd30f2b99a7 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 14:10:40 +0900 Subject: [PATCH 185/734] =?UTF-8?q?feat:=20MemberRepository=20bookmarks=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/repository/MemberRepositoryCustom.java | 5 +++-- .../member/repository/MemberRepositoryImpl.java | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java index 0c9736da..c11c1192 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java @@ -6,7 +6,8 @@ public interface MemberRepositoryCustom { Optional selectProfileByName(String name); - Long countLikesByMemberId(Long id); - Long countReviewsByMemberId(Long id); + Long countBookmarksByMemberId(Long id); + + Long countLikesByMemberId(Long id); } diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java index 1606c3c1..92fd22a0 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java @@ -1,11 +1,13 @@ package io.oduck.api.domain.member.repository; +import static io.oduck.api.domain.bookmark.entity.QBookmark.bookmark; import static io.oduck.api.domain.member.entity.QMemberProfile.memberProfile; import static io.oduck.api.domain.review.entity.QShortReview.shortReview; import static io.oduck.api.domain.reviewLike.entity.QShortReviewLike.shortReviewLike; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; +import io.oduck.api.domain.bookmark.entity.QBookmark; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -50,6 +52,17 @@ public Long countLikesByMemberId(Long id) { return likeCount; } + @Override + public Long countBookmarksByMemberId(Long id) { + Long bookmarkCount = query + .select(bookmark.id.count()) + .from(bookmark) + .where(bookmark.member.id.eq(id)) + .fetchOne(); + + return bookmarkCount; + } + @Override public Long countReviewsByMemberId(Long id) { Long reviewCount = query From eb80b4b02af9b042460a7aa5645af99be6a6cca9 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 14:11:05 +0900 Subject: [PATCH 186/734] =?UTF-8?q?refactor:=20MemberService=20bookmarks?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberServiceImpl.java | 7 +- .../member/service/MemberServiceStub.java | 180 +++++++++--------- 2 files changed, 95 insertions(+), 92 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java index 14a0d7ca..e859ed26 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java @@ -73,11 +73,14 @@ public MemberProfileRes getProfileByName(String name, Long memberId) { if (memberProfile == null) { throw new NotFoundException("Member"); } - Long likesCount = memberRepository.countLikesByMemberId(memberProfile.getMemberId()); Long reviewsCount = memberRepository.countReviewsByMemberId(memberProfile.getMemberId()); + Long bookmarksCount = memberRepository.countBookmarksByMemberId(memberProfile.getMemberId()); + Long likesCount = memberRepository.countLikesByMemberId(memberProfile.getMemberId()); + + Activity activity = Activity.builder() .reviews(reviewsCount) - .threads(0L) + .bookmarks(bookmarksCount) .likes(likesCount) .build(); diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java index f8bf65d4..789f13c6 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceStub.java @@ -1,90 +1,90 @@ -package io.oduck.api.domain.member.service; - -import static io.oduck.api.global.utils.NameGenerator.generateNickname; - -import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; -import io.oduck.api.domain.member.dto.MemberResDto.Activity; -import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; -import io.oduck.api.domain.member.entity.LoginType; -import io.oduck.api.domain.member.entity.Member; -import io.oduck.api.domain.member.entity.MemberProfile; -import io.oduck.api.domain.member.repository.MemberProfileRepository; -import io.oduck.api.domain.member.repository.MemberRepository; -import io.oduck.api.global.exception.BadRequestException; -import io.oduck.api.global.exception.ConflictException; -import io.oduck.api.global.security.auth.entity.AuthLocal; -import io.oduck.api.global.security.auth.repository.AuthLocalRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@RequiredArgsConstructor -public class MemberServiceStub implements MemberService { - private final PasswordEncoder passwordEncoder; - private final AuthLocalRepository authLocalRepository; - private final MemberRepository memberRepository; - private final MemberProfileRepository memberProfileRepository; - - @Transactional - @Override - public void signUpByLocal(CreateReq createReq) { - String email = createReq.getEmail(); - boolean isExistMember = authLocalRepository.existsByEmail(email); - - if (isExistMember) { - throw new ConflictException("Email"); - } - - try { - String encryptedPassword = passwordEncoder.encode(createReq.getPassword()); - - AuthLocal authLocal = AuthLocal.builder() - .email(createReq.getEmail()) - .password(encryptedPassword) - .build(); - - MemberProfile memberProfile = MemberProfile.builder() - .name(generateNickname()) - .build(); - - Member member = Member.builder() - .loginType(LoginType.LOCAL) - .build(); - - member.setAuthLocal(authLocal); - member.setMemberProfile(memberProfile); - - Member savedMember = memberRepository.save(member); - log.info("Member Created! {}", savedMember.getId()); - } catch (Exception e) { - log.error(e.getMessage()); - throw new BadRequestException("Try Again Plz."); - } - } - - @Override - public MemberProfileRes getProfileByName(String name) { - - Activity activity = Activity.builder() - .reviews(0) - .threads(0) - .likes(0) - .build(); - - MemberProfileRes memberProfile = MemberProfileRes.builder() - .isMine(true) - .name(name) - .description("자기소개") - .thumbnail("썸네일") - .backgroundImage("배경 이미지") - .activity(activity) - .point(0L) - .build(); - - return memberProfile; - } -} +//package io.oduck.api.domain.member.service; +// +//import static io.oduck.api.global.utils.NameGenerator.generateNickname; +// +//import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; +//import io.oduck.api.domain.member.dto.MemberResDto.Activity; +//import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; +//import io.oduck.api.domain.member.entity.LoginType; +//import io.oduck.api.domain.member.entity.Member; +//import io.oduck.api.domain.member.entity.MemberProfile; +//import io.oduck.api.domain.member.repository.MemberProfileRepository; +//import io.oduck.api.domain.member.repository.MemberRepository; +//import io.oduck.api.global.exception.BadRequestException; +//import io.oduck.api.global.exception.ConflictException; +//import io.oduck.api.global.security.auth.entity.AuthLocal; +//import io.oduck.api.global.security.auth.repository.AuthLocalRepository; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.security.crypto.password.PasswordEncoder; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +// +//@Slf4j +//@Service +//@RequiredArgsConstructor +//public class MemberServiceStub implements MemberService { +// private final PasswordEncoder passwordEncoder; +// private final AuthLocalRepository authLocalRepository; +// private final MemberRepository memberRepository; +// private final MemberProfileRepository memberProfileRepository; +// +// @Transactional +// @Override +// public void signUpByLocal(CreateReq createReq) { +// String email = createReq.getEmail(); +// boolean isExistMember = authLocalRepository.existsByEmail(email); +// +// if (isExistMember) { +// throw new ConflictException("Email"); +// } +// +// try { +// String encryptedPassword = passwordEncoder.encode(createReq.getPassword()); +// +// AuthLocal authLocal = AuthLocal.builder() +// .email(createReq.getEmail()) +// .password(encryptedPassword) +// .build(); +// +// MemberProfile memberProfile = MemberProfile.builder() +// .name(generateNickname()) +// .build(); +// +// Member member = Member.builder() +// .loginType(LoginType.LOCAL) +// .build(); +// +// member.setAuthLocal(authLocal); +// member.setMemberProfile(memberProfile); +// +// Member savedMember = memberRepository.save(member); +// log.info("Member Created! {}", savedMember.getId()); +// } catch (Exception e) { +// log.error(e.getMessage()); +// throw new BadRequestException("Try Again Plz."); +// } +// } +// +// @Override +// public MemberProfileRes getProfileByName(String name, Long memberId) { +// +// Activity activity = Activity.builder() +// .reviews(0) +// .threads(0) +// .likes(0) +// .build(); +// +// MemberProfileRes memberProfile = MemberProfileRes.builder() +// .isMine(true) +// .name(name) +// .description("자기소개") +// .thumbnail("썸네일") +// .backgroundImage("배경 이미지") +// .activity(activity) +// .point(0L) +// .build(); +// +// return memberProfile; +// } +//} From 6f809759bf0f9e17340bbe0ddc0c5a0c87c7b4bd Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 14:12:06 +0900 Subject: [PATCH 187/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20api=20bookmarks=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/e2e/member/MemberControllerTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index a8ade1cb..1d873813 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -137,7 +137,7 @@ void getProfileByNameIfMine() throws Exception { .andExpect(jsonPath("$.point").exists()) .andExpect(jsonPath("$.activity").hasJsonPath()) .andExpect(jsonPath("$.activity.reviews").exists()) - .andExpect(jsonPath("$.activity.threads").exists()) + .andExpect(jsonPath("$.activity.bookmarks").exists()) .andExpect(jsonPath("$.activity.likes").exists()) .andDo(document("getProfileByName/successIfMine", preprocessRequest(prettyPrint()), @@ -176,9 +176,9 @@ void getProfileByNameIfMine() throws Exception { fieldWithPath("activity.reviews") .type(JsonFieldType.NUMBER) .description("작성한 리뷰 갯수"), - fieldWithPath("activity.threads") + fieldWithPath("activity.bookmarks") .type(JsonFieldType.NUMBER) - .description("작성한 쓰레드 갯수"), + .description("입덕 애니 갯수"), fieldWithPath("activity.likes") .type(JsonFieldType.NUMBER) .description("받은 좋아요 갯수")))); @@ -213,7 +213,7 @@ void getProfileByNameIfOthers() throws Exception { .andExpect(jsonPath("$.point").exists()) .andExpect(jsonPath("$.activity").hasJsonPath()) .andExpect(jsonPath("$.activity.reviews").exists()) - .andExpect(jsonPath("$.activity.threads").exists()) + .andExpect(jsonPath("$.activity.bookmarks").exists()) .andExpect(jsonPath("$.activity.likes").exists()) .andDo(document("getProfileByName/successIfOthers", preprocessRequest(prettyPrint()), @@ -252,9 +252,9 @@ void getProfileByNameIfOthers() throws Exception { fieldWithPath("activity.reviews") .type(JsonFieldType.NUMBER) .description("작성한 리뷰 갯수"), - fieldWithPath("activity.threads") + fieldWithPath("activity.bookmarks") .type(JsonFieldType.NUMBER) - .description("작성한 쓰레드 갯수"), + .description("입덕한 애니 갯수"), fieldWithPath("activity.likes") .type(JsonFieldType.NUMBER) .description("받은 좋아요 갯수")))); From f646d6820ee0736f8f9776f76fd7fbff9198172b Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 14:12:26 +0900 Subject: [PATCH 188/734] =?UTF-8?q?test:=20MemberRepository=20=EB=B6=81?= =?UTF-8?q?=EB=A7=88=ED=81=AC=20=EA=B0=AF=EC=88=98=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberRepositoryTest.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java index 39679d38..b6275192 100644 --- a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java @@ -121,6 +121,23 @@ void countLikesByMemberIdSuccess() { } } + @DisplayName("내가 입덕한 애니 수 조회") + @Nested + class countBookmarksByMemberId { + @DisplayName("회원 ID로 입덕한 애니 수 조회 성공") + @Test + void countBookmarksByMemberIdSuccess() { + // given + Long memberId = 1L; + + // when + Long likesCount = memberRepository.countBookmarksByMemberId(memberId); + + // then + assertNotNull(likesCount); + } + } + @DisplayName("내가 작성한 리뷰 수 조회") @Nested class countReviewByMemberId { @@ -131,10 +148,10 @@ void countReviewByMemberIdSuccess() { Long memberId = 1L; // when - Long likesCount = memberRepository.countLikesByMemberId(memberId); + Long bookmarksCount = memberRepository.countBookmarksByMemberId(memberId); // then - assertNotNull(likesCount); + assertNotNull(bookmarksCount); } } From 4362b44bc2054706292d00624d2f3ac9aee19105 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:16:55 +0900 Subject: [PATCH 189/734] =?UTF-8?q?feat:=20memberRepository=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=A1=B4=EC=9E=AC=20=EC=9C=A0=EB=AC=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/repository/MemberProfileRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java b/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java index 2c0bc622..968eae89 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberProfileRepository.java @@ -9,5 +9,5 @@ public interface MemberProfileRepository extends JpaRepository { Optional findByMemberId(Long memberId); - boolean existsByMemberId(Long memberId); + boolean existsByName(String name); } From 14c57f650d3eb6791978250c0e2edbcd40c89e2c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:23:22 +0900 Subject: [PATCH 190/734] =?UTF-8?q?feat:=20memberProfile=20update=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/member/entity/MemberProfile.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java index a76e1bdf..0568c617 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java +++ b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java @@ -65,4 +65,10 @@ public MemberProfile(Long id, String name, String info, Role role, String thumbn public void setMember(Member member) { this.member = member; } + public void updateName(String name) { + this.name = name; + } + public void updateInfo(String info) { + this.info = info; + } } From dc50c0c94a556370180ab7119f33e289dee7d99c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:33:02 +0900 Subject: [PATCH 191/734] =?UTF-8?q?refactor:=20set=20=EB=8C=80=EC=8B=A0=20?= =?UTF-8?q?relate=EB=A1=9C=20=EB=8C=80=EC=B2=B4=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/member/entity/Member.java | 12 ++++++------ .../api/domain/member/entity/MemberProfile.java | 2 +- .../api/global/security/auth/entity/AuthLocal.java | 2 +- .../api/global/security/auth/entity/AuthSocial.java | 2 +- .../security/auth/service/SocialLoginService.java | 4 ++-- .../java/io/oduck/api/global/stub/MemberStub.java | 12 ++++++------ 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/entity/Member.java b/src/main/java/io/oduck/api/domain/member/entity/Member.java index d9c95ee8..9c00054c 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/Member.java +++ b/src/main/java/io/oduck/api/domain/member/entity/Member.java @@ -76,24 +76,24 @@ public Member(Long id, Role role, LoginType loginType, AuthSocial authSocial, A } // TODO: set 말고 다른 이름으로 변경하기 - public void setAuthSocial(AuthSocial authSocial) { + public void relateAuthSocial(AuthSocial authSocial) { this.authSocial = authSocial; if(authSocial != null) { - authSocial.setMember(this); + authSocial.relateMember(this); } } - public void setAuthLocal(AuthLocal authLocal) { + public void relateAuthLocal(AuthLocal authLocal) { this.authLocal = authLocal; if(authLocal != null) { - authLocal.setMember(this); + authLocal.relateMember(this); } } - public void setMemberProfile(MemberProfile memberProfile) { + public void relateMemberProfile(MemberProfile memberProfile) { this.memberProfile = memberProfile; if(memberProfile != null) { - memberProfile.setMember(this); + memberProfile.relateMember(this); } } } diff --git a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java index 0568c617..7116d956 100644 --- a/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java +++ b/src/main/java/io/oduck/api/domain/member/entity/MemberProfile.java @@ -62,7 +62,7 @@ public MemberProfile(Long id, String name, String info, Role role, String thumbn this.point = point; } - public void setMember(Member member) { + public void relateMember(Member member) { this.member = member; } public void updateName(String name) { diff --git a/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java b/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java index 357a4c6e..c9472b66 100644 --- a/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java +++ b/src/main/java/io/oduck/api/global/security/auth/entity/AuthLocal.java @@ -40,7 +40,7 @@ public AuthLocal(Member member, String email, String password) { this.password = password; } - public void setMember(Member member) { + public void relateMember(Member member) { this.member = member; } } diff --git a/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java b/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java index bbe9bd61..11ddd86a 100644 --- a/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java +++ b/src/main/java/io/oduck/api/global/security/auth/entity/AuthSocial.java @@ -47,7 +47,7 @@ public AuthSocial(Long id, Member member, String email, String socialId, SocialT this.socialType = socialType; } - public void setMember(Member member) { + public void relateMember(Member member) { this.member = member; } } diff --git a/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java b/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java index 61b8d226..a6c4a8d5 100644 --- a/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java +++ b/src/main/java/io/oduck/api/global/security/auth/service/SocialLoginService.java @@ -86,8 +86,8 @@ private Member getOrSaveMember(AuthSocial authSocial) { .loginType(LoginType.SOCIAL) .build(); - member.setAuthSocial(authSocial); - member.setMemberProfile(memberProfile); + member.relateAuthSocial(authSocial); + member.relateMemberProfile(memberProfile); Member savedMember = memberRepository.save(member); log.info("Member Created! {}", savedMember.getId()); diff --git a/src/test/java/io/oduck/api/global/stub/MemberStub.java b/src/test/java/io/oduck/api/global/stub/MemberStub.java index 346131cc..3b708456 100644 --- a/src/test/java/io/oduck/api/global/stub/MemberStub.java +++ b/src/test/java/io/oduck/api/global/stub/MemberStub.java @@ -34,8 +34,8 @@ public MemberStub() { .point(0L) .build(); - member1.setAuthLocal(authLocal1); - member1.setMemberProfile(memberProfile1); + member1.relateAuthLocal(authLocal1); + member1.relateMemberProfile(memberProfile1); // member2 Member member2 = Member.builder() @@ -55,8 +55,8 @@ public MemberStub() { .point(0L) .build(); - member2.setAuthLocal(authLocal2); - member2.setMemberProfile(memberProfile2); + member2.relateAuthLocal(authLocal2); + member2.relateMemberProfile(memberProfile2); // member3 Member member3 = Member.builder() @@ -76,8 +76,8 @@ public MemberStub() { .point(0L) .build(); - member3.setAuthLocal(authLocal3); - member3.setMemberProfile(memberProfile3); + member3.relateAuthLocal(authLocal3); + member3.relateMemberProfile(memberProfile3); members.addAll(List.of(member1, member2, member3)); } From 1b129cc9bd05f5b4c16d49a5dea4563f383d98f1 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:39:32 +0900 Subject: [PATCH 192/734] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/service/MemberService.java | 3 +- .../member/service/MemberServiceImpl.java | 51 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberService.java b/src/main/java/io/oduck/api/domain/member/service/MemberService.java index 5ae00eff..d4d136d8 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberService.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberService.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.member.service; import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; +import io.oduck.api.domain.member.dto.MemberReqDto.PatchReq; import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; public interface MemberService { @@ -11,5 +12,5 @@ public interface MemberService { MemberProfileRes getProfileByName(String name, Long memberId); // 회원 정보 수정 로직 - // void updateProfile(PatchReq body); + void updateProfile(PatchReq body, Long memberId); } diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java index e859ed26..18c873d9 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java @@ -4,11 +4,13 @@ import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; +import io.oduck.api.domain.member.dto.MemberReqDto.PatchReq; import io.oduck.api.domain.member.dto.MemberResDto.Activity; import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; import io.oduck.api.domain.member.entity.LoginType; import io.oduck.api.domain.member.entity.Member; import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.repository.MemberProfileRepository; import io.oduck.api.domain.member.repository.MemberRepository; import io.oduck.api.global.exception.BadRequestException; import io.oduck.api.global.exception.ConflictException; @@ -29,6 +31,7 @@ public class MemberServiceImpl implements MemberService{ private final PasswordEncoder passwordEncoder; private final AuthLocalRepository authLocalRepository; private final MemberRepository memberRepository; + private final MemberProfileRepository memberProfileRepository; @Transactional @Override @@ -56,8 +59,8 @@ public void signUpByLocal(CreateReq createReq) { .loginType(LoginType.LOCAL) .build(); - member.setAuthLocal(authLocal); - member.setMemberProfile(memberProfile); + member.relateAuthLocal(authLocal); + member.relateMemberProfile(memberProfile); Member savedMember = memberRepository.save(member); log.info("Member Created! {}", savedMember.getId()); @@ -97,10 +100,54 @@ public MemberProfileRes getProfileByName(String name, Long memberId) { return memberProfileRes; } + @Override + public void updateProfile(PatchReq body, Long memberId) { + MemberProfile memberProfile = getProfileByMemberId(memberId); + + // Null 체크 + Optional + .ofNullable(body.getName()) + .ifPresent( + name -> { + // 이전 이름과 같은지 체크 + if (memberProfile.getName().equals(name)) { + throw new BadRequestException("Same name as before."); + } + + // 이름 중복 체크 + checkDuplicatedName(name); + + memberProfile.updateName(name); + } + ); + + // Null 체크 + Optional + .ofNullable(body.getDescription()) + .ifPresent( + memberProfile::updateInfo + ); + + memberProfileRepository.save(memberProfile); + } + + private MemberProfile getProfileByMemberId(Long memberId) { + return memberProfileRepository.findByMemberId(memberId) + .orElseThrow( + () -> new NotFoundException("Member") + ); + } + private ProfileWithoutActivity getProfileWithoutActivity(String name) { return memberRepository.selectProfileByName(name) .orElseThrow( () -> new NotFoundException("Member") ); } + + private void checkDuplicatedName(String name) { + if (memberProfileRepository.existsByName(name)) { + throw new ConflictException("Name"); + } + } } From ef6a503be404bb0ebb257181ca36b2e9cd4ec034 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:39:54 +0900 Subject: [PATCH 193/734] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=20=EB=AC=B8=EC=9E=90=20=5F=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java index 01da44f7..55abcca2 100644 --- a/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberReqDto.java @@ -31,7 +31,7 @@ public static class CreateReq { public static class PatchReq { @NotBlank - @Pattern(regexp = "^[0-9A-Za-z가-힣]{2,10}$", + @Pattern(regexp = "^[0-9A-Za-z가-힣_]{2,10}$", message = "이름은 2~10자리의 한글, 영문, 숫자여야 합니다.") private String name; From 9d93c523049260cc2685fb46214b4b0d1ad30e74 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:40:49 +0900 Subject: [PATCH 194/734] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=88=98=EC=A0=95=20api=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/controller/MemberController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index fb54571e..dcb2a776 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -51,11 +51,11 @@ public ResponseEntity getProfileByName( // 회원 프로필 수정 @PatchMapping public ResponseEntity patchProfile( - @RequestBody @Valid PatchReq body - // TODO: 인증 정보 추가 + @RequestBody @Valid PatchReq body, + @LoginUser AuthUser user ) { // TODO: 회원 정보 수정 로직 구현 - // memberService.updateProfile(body); + memberService.updateProfile(body, user.getId()); return ResponseEntity.noContent().build(); } From 66e8a97effbfa4e7566ab9f96b01468a3e4864aa Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:41:48 +0900 Subject: [PATCH 195/734] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=9D=B8=EA=B0=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/security/config/SecurityConfig.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index 0c4f1c4b..e5c1c8e9 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -14,6 +14,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -75,9 +76,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorizeRequests) -> authorizeRequests - .requestMatchers("/auth/status").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) // .requestMatchers("docs/index.html").hasAuthority(Role.ADMIN.name()) - .requestMatchers("oduckdmin/*").hasAuthority(Role.ADMIN.name()) + .requestMatchers("/auth/status").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) + .requestMatchers(HttpMethod.PUT, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) + .requestMatchers(HttpMethod.PATCH, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) + .requestMatchers(HttpMethod.DELETE, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) + .requestMatchers("/oduckdmin/**").hasAuthority(Role.ADMIN.name()) .anyRequest().permitAll() ); From 7fcd23d0615b53257ff9e1647799c6b1ac5897cf Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:42:58 +0900 Subject: [PATCH 196/734] =?UTF-8?q?test:=20Member=20Repository=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=88=98=EC=A0=95=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberRepositoryTest.java | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java index b6275192..26207e05 100644 --- a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java @@ -4,14 +4,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import io.oduck.api.domain.anime.repository.AnimeRepository; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; import io.oduck.api.domain.member.entity.LoginType; import io.oduck.api.domain.member.entity.Member; import io.oduck.api.domain.member.entity.MemberProfile; import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.domain.member.repository.MemberProfileRepository; import io.oduck.api.domain.member.repository.MemberRepository; -import io.oduck.api.domain.review.repository.ShortReviewRepository; import io.oduck.api.global.security.auth.entity.AuthLocal; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -30,9 +29,7 @@ public class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @Autowired - AnimeRepository animeRepository; - @Autowired - ShortReviewRepository shortReviewRepository; + MemberProfileRepository memberProfileRepository; @DisplayName("로컬 회원 저장") @Nested @@ -58,8 +55,8 @@ void saveMemberByLocalSuccess() { .point(0L) .build(); - member.setAuthLocal(authLocal); - member.setMemberProfile(memberProfile); + member.relateAuthLocal(authLocal); + member.relateMemberProfile(memberProfile); // when Member savedMember = memberRepository.save(member); @@ -155,7 +152,29 @@ void countReviewByMemberIdSuccess() { } } - // TODO: 회원 프로필 수정 + @DisplayName("프로필 수정") + @Nested + class updateMemberProfile { + @DisplayName("회원 ID로 프로필 수정 성공") + @Test + void updateMemberProfileSuccess() { + // given + Long memberId = 3L; + String name = "new name"; + String info = "new info"; + MemberProfile memberProfile = memberProfileRepository.findByMemberId(memberId).orElse(null); + memberProfile.updateName(name); + memberProfile.updateInfo(info); + + // when + MemberProfile updatedMemberProfile = memberProfileRepository.save(memberProfile); + + // then + assertNotNull(updatedMemberProfile); + assertEquals(name, updatedMemberProfile.getName()); + assertEquals(info, updatedMemberProfile.getInfo()); + } + } // TODO: 회원이 작성한 리뷰 목록 From b54e53a76a177845c07a824ffc950000a2873488 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:44:04 +0900 Subject: [PATCH 197/734] =?UTF-8?q?test:=20Member=20Service=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=88=98=EC=A0=95=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberServiceTest.java | 87 ++++++++++++++++++- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java index 94bf38b7..10ce615e 100644 --- a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java +++ b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java @@ -6,20 +6,24 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; +import io.oduck.api.domain.member.dto.MemberReqDto.PatchReq; import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; import io.oduck.api.domain.member.entity.Member; import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.repository.MemberProfileRepository; import io.oduck.api.domain.member.repository.MemberRepository; import io.oduck.api.domain.member.service.MemberServiceImpl; +import io.oduck.api.global.exception.BadRequestException; +import io.oduck.api.global.exception.ConflictException; import io.oduck.api.global.exception.NotFoundException; import io.oduck.api.global.stub.MemberStub; import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -35,6 +39,8 @@ public class MemberServiceTest { @Mock MemberRepository memberRepository; + @Mock + MemberProfileRepository memberProfileRepository; // TODO: 회원 가입 // TODO: 회원 가입 실패(이메일 중복, 비밀번호 유효성 등) @@ -106,8 +112,83 @@ void getMemberByNameIfNotFound() { } } - // TODO: 회원 정보 수정 - // TODO: 회원 정보 수정 실패(닉네임 중복 등) + @Nested + @DisplayName("회원 프로필 수정") + class updateProfile { + @DisplayName("회원 프로필 수정 성공") + @Test + void updateProfileSuccess() { + // given + Member member = new MemberStub().getMember(); + MemberProfile memberProfile = member.getMemberProfile(); + PatchReq patchReq = PatchReq.builder() + .name("newName") + .description("newDescription") + .build(); + MemberProfile updatedMemberProfile = MemberProfile.builder() + .member(member) + .name(patchReq.getName()) + .info(patchReq.getDescription()) + .build(); + + given(memberProfileRepository.findByMemberId(anyLong())).willReturn(Optional.ofNullable(memberProfile)); + given(memberProfileRepository.existsByName(anyString())).willReturn(false); + given(memberProfileRepository.save(any(MemberProfile.class))).willReturn(updatedMemberProfile); + + // when + // then + assertDoesNotThrow(() -> memberService.updateProfile(patchReq, 1L)); + } + @DisplayName("회원 프로필 수정시 실패. 이전 이름과 동일한 이름일때") + @Test + void updateProfileFailureWhenSameNameAsBefore() { + // given + Member member = new MemberStub().getMember(); + MemberProfile memberProfile = member.getMemberProfile(); + PatchReq patchReq = PatchReq.builder() + .name(memberProfile.getName()) + .description("newDescription") + .build(); + MemberProfile updatedMemberProfile = MemberProfile.builder() + .member(member) + .name(patchReq.getName()) + .info(patchReq.getDescription()) + .build(); + + given(memberProfileRepository.findByMemberId(anyLong())).willReturn(Optional.ofNullable(memberProfile)); + + // when + // then + assertThrows(BadRequestException.class, + () -> memberService.updateProfile(patchReq, 1L) + ); + } + @DisplayName("회원 프로필 수정시 실패. 중복 이름 존재시") + @Test + void updateProfileFailureWhenSameNameAsAlreadyExist() { + // given + Member member = new MemberStub().getMember(); + MemberProfile memberProfile = member.getMemberProfile(); + PatchReq patchReq = PatchReq.builder() + .name("newName") + .description("newDescription") + .build(); + MemberProfile updatedMemberProfile = MemberProfile.builder() + .member(member) + .name(patchReq.getName()) + .info(patchReq.getDescription()) + .build(); + + given(memberProfileRepository.findByMemberId(anyLong())).willReturn(Optional.ofNullable(memberProfile)); + given(memberProfileRepository.existsByName(anyString())).willReturn(true); + + // when + // then + assertThrows(ConflictException.class, + () -> memberService.updateProfile(patchReq, 1L) + ); + } + } // TODO: 회원이 작성한 리뷰 목록 From 83739a1d2ad175fe6e4d0d8d0bf2bc2ae124ae7c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:45:29 +0900 Subject: [PATCH 198/734] =?UTF-8?q?test:=20Member=20Controller=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=88=98=EC=A0=95=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EB=B0=8F=20=EC=82=AC=EB=A1=80=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/member/MemberControllerTest.java | 154 +++++++++++++++++- .../api/global/config/RestDocsConfig.java | 2 +- 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 1d873813..64b10f78 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -190,7 +190,7 @@ void getProfileByNameIfMine() throws Exception { void getProfileByNameIfOthers() throws Exception { // given // 회원 프로필 조회에 필요한 데이터 - String name = "david"; + String name = "admin"; // when // 요청 실행 @@ -318,14 +318,15 @@ void getProfileByName() throws Exception { @DisplayName("회원 정보 수정") class PatchProfile { - @DisplayName("회원 가입 성공시 201 Created 반환") + @DisplayName("회원 프로필 수정 성공시 204 NoContent 응답") @Test - void patchProfile() throws Exception { + @WithCustomMockMember(id = 3L, email = "david", password = "Qwer!234", role = Role.MEMBER) + void patchProfileSuccess() throws Exception { // given // 회원 정보 수정에 필요한 데이터 PatchReq body = PatchReq.builder() - .name("bob") - .description("hello, world!") + .name("데이비드") + .description("new david description") .build(); String content = gson.toJson(body); @@ -335,14 +336,21 @@ void patchProfile() throws Exception { patch("/members") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") .content(content)); // then actions .andExpect(status().isNoContent()) - .andDo(document("patchProfile/success", + .andDo( + document("patchProfile/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .description("Header Cookie, 세션 쿠키") + ), requestFields( attributes(key("title") .value("Fields for member creation")), @@ -354,10 +362,140 @@ void patchProfile() throws Exception { fieldWithPath("description") .type(JsonFieldType.STRING) .attributes(field("constraints", "문자열 0-100자")) - .description("자기 소개 변경시 필요한 내용")))); + .description("자기 소개 변경시 필요한 내용") + ) + ) + ); + } + + @DisplayName("회원 프로필 수정 기존 이름과 같을 시 400 BadRequest 응답") + @Test + @WithCustomMockMember(id = 1L, email = "admin", password = "Qwer!234", role = Role.MEMBER) + void updateProfileFailureWhenSameNameAsBefore() throws Exception { + PatchReq body = PatchReq.builder() + .name("admin") + .description("new admin description") + .build(); + + String content = gson.toJson(body); + + // when + ResultActions actions = mockMvc.perform( + patch("/members") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") + .content(content)); + + // then + actions + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").exists()) + .andExpect(jsonPath("$.fieldErrors").value(equalTo(null))) + .andExpect(jsonPath("$.violationErrors").value(equalTo(null))) + .andDo( + document("patchProfile/failureWhenSameNameAsBefore", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .description("Header Cookie, 세션 쿠키") + ), + requestFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes( + field("constraints", "한,영소문자, 숫자 포함 2-10자. ^[0-9A-Za-z가-힣]{2,10}$")) + .description("이름 변경시 필요한 이름"), + fieldWithPath("description") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "문자열 0-100자")) + .description("자기 소개 변경시 필요한 내용") + ), + responseFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("message") + .type(JsonFieldType.STRING) + .description("예외 메시지"), + fieldWithPath("fieldErrors") + .type(JsonFieldType.NULL) + .description("api 요청 필드 오류"), + fieldWithPath("violationErrors") + .type(JsonFieldType.NULL) + .description("api 요청 규칙 위반 오류") + ) + ) + ); } - // TODO: 회원 프로필 수정 실패시 + @DisplayName("회원 프로필 수정 이름 중복시 409 Confilct 응답") + @Test + @WithCustomMockMember(id = 3L, email = "david", password = "Qwer!234", role = Role.MEMBER) + void updateProfileFailureWhenSameNameAsAlreadyExist() throws Exception { + PatchReq body = PatchReq.builder() + .name("admin") + .description("new david description") + .build(); + + String content = gson.toJson(body); + + // when + ResultActions actions = mockMvc.perform( + patch("/members") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") + .content(content)); + + // then + actions + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.message").exists()) + .andExpect(jsonPath("$.fieldErrors").value(equalTo(null))) + .andExpect(jsonPath("$.violationErrors").value(equalTo(null))) + .andDo( + document("patchProfile/failureWhenSameNameAsAlreadyExist", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .description("Header Cookie, 세션 쿠키") + ), + requestFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes( + field("constraints", + "한,영소문자, 숫자 포함 2-10자. ^[0-9A-Za-z가-힣]{2,10}$")) + .description("이름 변경시 필요한 이름"), + fieldWithPath("description") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "문자열 0-100자")) + .description("자기 소개 변경시 필요한 내용") + ), + responseFields( + attributes(key("title") + .value("Fields for member creation")), + fieldWithPath("message") + .type(JsonFieldType.STRING) + .description("예외 메시지"), + fieldWithPath("fieldErrors") + .type(JsonFieldType.NULL) + .description("api 요청 필드 오류"), + fieldWithPath("violationErrors") + .type(JsonFieldType.NULL) + .description("api 요청 규칙 위반 오류") + ) + ) + ); + } } // TODO: 회원이 작성한 리뷰 목록 diff --git a/src/test/java/io/oduck/api/global/config/RestDocsConfig.java b/src/test/java/io/oduck/api/global/config/RestDocsConfig.java index a935d770..1cc77100 100644 --- a/src/test/java/io/oduck/api/global/config/RestDocsConfig.java +++ b/src/test/java/io/oduck/api/global/config/RestDocsConfig.java @@ -8,6 +8,6 @@ public class RestDocsConfig { public static final Attribute field( final String key, final String value){ - return new Attribute(key,value); + return new Attribute(key, value); } } From 53c09e753bf92a6254339a034f05fd9d3aea3f13 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:45:57 +0900 Subject: [PATCH 199/734] =?UTF-8?q?docs:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=88=98=EC=A0=95=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EB=B0=8F=20=EC=82=AC=EB=A1=80=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 2d7b8b05..41bb9c9f 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -121,6 +121,9 @@ include::{snippets}/patchProfile/success/curl-request.adoc[] .http-request include::{snippets}/patchProfile/success/http-request.adoc[] +.request-header +include::{snippets}/patchProfile/success/request-headers.adoc[] + .request-body include::{snippets}/patchProfile/success/request-body.adoc[] @@ -132,7 +135,13 @@ include::{snippets}/patchProfile/success/request-fields.adoc[] .http-response include::{snippets}/patchProfile/success/http-response.adoc[] -==== 실패시 +==== 실패시(기존 이름과 같을 경우) +.http-response +include::{snippets}/patchProfile/failureWhenSameNameAsBefore/http-response.adoc[] + +==== 실패시(이름 중복시) +.http-response +include::{snippets}/patchProfile/failureWhenSameNameAsAlreadyExist/http-response.adoc[] == animes === GET api/v1/animes/:id From 5e61e11b46fde631b8134c20f0799b7d271874df Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 17 Oct 2023 20:52:57 +0900 Subject: [PATCH 200/734] =?UTF-8?q?refactor:=20Admin=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C=EC=A0=84?= =?UTF-8?q?=EA=B9=8C=EC=A7=80=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/security/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index e5c1c8e9..503473b7 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -81,7 +81,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(HttpMethod.PUT, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) .requestMatchers(HttpMethod.PATCH, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) .requestMatchers(HttpMethod.DELETE, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) - .requestMatchers("/oduckdmin/**").hasAuthority(Role.ADMIN.name()) +// .requestMatchers("/oduckdmin/**").hasAuthority(Role.ADMIN.name()) .anyRequest().permitAll() ); From 2ad1e038a804cf6eef9855f99374f8e629866f7c Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Tue, 17 Oct 2023 22:24:11 +0900 Subject: [PATCH 201/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EC=97=94=ED=8B=B0=ED=8B=B0=EC=97=90=20Bas?= =?UTF-8?q?eEntity=20=EC=83=81=EC=86=8D=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java | 3 ++- .../io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java | 3 ++- .../java/io/oduck/api/domain/anime/entity/AnimeStudio.java | 3 ++- .../java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java index 39307ca9..52bf895a 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeGenre.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.anime.entity; import io.oduck.api.domain.genre.entity.Genre; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -15,7 +16,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AnimeGenre { +public class AnimeGenre extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java index 7fa5b7e0..c4fb6d4e 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeOriginalAuthor.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.anime.entity; import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -15,7 +16,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AnimeOriginalAuthor { +public class AnimeOriginalAuthor extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java index 3c9a1685..9f4ead5c 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeStudio.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.anime.entity; import io.oduck.api.domain.studio.entity.Studio; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -15,7 +16,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AnimeStudio { +public class AnimeStudio extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java b/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java index fdab5a07..e104d490 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/AnimeVoiceActor.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.anime.entity; import io.oduck.api.domain.voiceActor.entity.VoiceActor; +import io.oduck.api.global.audit.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -16,7 +17,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AnimeVoiceActor { +public class AnimeVoiceActor extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) From c214ee9382b4b21015ea17ebe329415790f76416 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Wed, 18 Oct 2023 00:04:07 +0900 Subject: [PATCH 202/734] =?UTF-8?q?feat:=20animeId=EB=A1=9C=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=20=EC=97=B0=EA=B2=B0=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?List=20=EC=B0=BE=EC=95=84=EC=98=A4=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/anime/repository/AnimeGenreRepository.java | 8 ++++++++ .../anime/repository/AnimeOriginalAuthorRepository.java | 8 ++++++++ .../domain/anime/repository/AnimeStudioRepository.java | 8 ++++++++ .../anime/repository/AnimeVoiceActorRepository.java | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java index cd150448..09c3428a 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java @@ -1,8 +1,16 @@ package io.oduck.api.domain.anime.repository; import io.oduck.api.domain.anime.entity.AnimeGenre; +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 AnimeGenreRepository extends JpaRepository { + @Query("select ag from AnimeGenre ag " + + "join ag.anime " + + "join ag.genre " + + "where ag.anime.id = :animeId") + List findAllByAnimeId(@Param("animeId") Long animeId); } diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java index 6f389854..91d4285c 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java @@ -1,8 +1,16 @@ package io.oduck.api.domain.anime.repository; import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +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 AnimeOriginalAuthorRepository extends JpaRepository { + @Query("select aoa from AnimeOriginalAuthor aoa " + + "join fetch aoa.anime " + + "join fetch aoa.originalAuthor " + + "where aoa.anime.id = :animeId") + List findAllByAnimeId(@Param("animeId") Long animeId); } diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java index a8840c7b..ec289492 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java @@ -1,8 +1,16 @@ package io.oduck.api.domain.anime.repository; import io.oduck.api.domain.anime.entity.AnimeStudio; +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 AnimeStudioRepository extends JpaRepository { + @Query("select ast from AnimeStudio ast " + + "join ast.anime " + + "join ast.studio " + + "where ast.anime.id = :animeId") + List findAllByAnimeId(@Param("animeId") Long animeId); } diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java index 4bea8ee8..f7663d4d 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java @@ -1,8 +1,16 @@ package io.oduck.api.domain.anime.repository; import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +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 AnimeVoiceActorRepository extends JpaRepository { + @Query("select ava from AnimeVoiceActor ava " + + "join ava.anime " + + "join ava.voiceActor " + + "where ava.anime.id = :animeId") + List findAllByAnimeId(@Param("animeId") Long animeId); } From 1751837d1c761b7c160a8cc0fa0c8c380f075f89 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Wed, 18 Oct 2023 00:07:56 +0900 Subject: [PATCH 203/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=97=B0=EA=B4=80=20=EA=B4=80=EA=B3=84=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/entity/Anime.java | 146 ++++++++++++++++-- 1 file changed, 130 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 1b347b0f..b4676add 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -14,18 +14,15 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; +import java.util.ArrayList; import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; @Entity @Getter -@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -@AllArgsConstructor public class Anime extends BaseEntity { @Id @@ -71,19 +68,136 @@ public class Anime extends BaseEntity { @ColumnDefault("0") private long bookmarkCount; + @ColumnDefault("0") + private long starRatingScoreTotal; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "series_id") private Series series; - @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private List animeOriginalAuthors; - - @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private List animeStudios; - - @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private List animeVoiceActors; - - @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST) - private List animeGenres; -} + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List animeOriginalAuthors = new ArrayList<>(); + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List animeStudios = new ArrayList<>(); + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List animeVoiceActors = new ArrayList<>(); + + @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List animeGenres = new ArrayList<>(); + + /** + * 연관 관계 관련한 메소드 + */ + // 애니와 원작 작가의 연결 엔티티의 애니 연관 관계 추가 로직 + private void addAnimeOriginalAuthor(AnimeOriginalAuthor animeOriginalAuthor){ + this.animeOriginalAuthors.add(animeOriginalAuthor); + //TODO: set 대신 다른 이름 + animeOriginalAuthor.setAnime(this); + } + + // 애니와 스튜디오의 연결 엔티티의 애니 연관 관계 추가 로직 + private void addAnimeStudio(AnimeStudio animeStudio){ + this.animeStudios.add(animeStudio); + //TODO: set 대신 다른 이름 + animeStudio.setAnime(this); + } + + // 애니와 성우의 연결 엔티티의 애니 연관 관계 추가 로직 + private void addAnimeVoiceActor(AnimeVoiceActor animeVoiceActor){ + this.animeVoiceActors.add(animeVoiceActor); + //TODO: set 대신 다른 이름 + animeVoiceActor.setAnime(this); + } + + // 애니와 장르의 연결 엔티티의 애니 연관 관계 추가 로직 + private void addAnimeGenre(AnimeGenre animeGenre){ + this.animeGenres.add(animeGenre); + //TODO: set 대신 다른 이름 + animeGenre.setAnime(this); + } + + private boolean isEntityListElementsExist(List elements) { + return !elements.isEmpty(); + } + + public void assignAnimeOriginalAuthors(List animeOriginalAuthors){ + // 만약 현재 animeOriginalAuthors에 값이 존재한다면 List를 비운다. + // (orphanRemoval = true 설정으로 AnimeOriginalAuthor 테이블의 값들이 delete됨) + if(isEntityListElementsExist(this.animeOriginalAuthors)){ + this.animeOriginalAuthors.clear(); + } + + for (AnimeOriginalAuthor animeOriginalAuthor : animeOriginalAuthors) { + addAnimeOriginalAuthor(animeOriginalAuthor); + } + } + + public void assignAnimeStudios(List animeStudios) { + // 만약 현재 animeStudios에 값이 존재한다면 List를 비운다. + // (orphanRemoval = true 설정으로 AnimeStudio 테이블의 값들이 delete됨) + if(isEntityListElementsExist(this.animeStudios)){ + this.animeStudios.clear(); + } + + for (AnimeStudio animeStudio : animeStudios) { + addAnimeStudio(animeStudio); + } + } + + public void assignAnimeVoiceActors(List animeVoiceActors) { + // 만약 현재 animeVoiceActors의 값이 존재한다면 List를 비운다. + // (orphanRemoval = true 설정으로 animeVoiceActor 테이블의 값들이 delete됨) + if(isEntityListElementsExist(this.animeVoiceActors)){ + this.animeVoiceActors.clear(); + } + + for (AnimeVoiceActor animeVoiceActor : animeVoiceActors) { + addAnimeVoiceActor(animeVoiceActor); + } + } + + public void assignAnimeGenre(List animeGenres) { + // 만약 현재 animeGenre의 값이 존재한다면 List를 비운다. + // (orphanRemoval = true 설정으로 animeGenre의 테이블의 값들이 delete됨) + if(isEntityListElementsExist(this.animeGenres)){ + this.animeGenres.clear(); + } + + for (AnimeGenre animeGenre : animeGenres) { + addAnimeGenre(animeGenre); + } + } + + + public static Anime createAnime(String title, String summary, BroadcastType broadcastType, int episodeCount, + String thumbnail, int year, Quarter quarter, Rating rating, Status status, + List animeOriginalAuthors, List animeStudios, List animeVoiceActors, List animeGenres, Series series) { + Anime anime = new Anime(); + // dto의 값으로 anime 초기화 + anime.title = title; + anime.summary = summary; + anime.broadcastType = broadcastType; + anime.episodeCount = episodeCount; + anime.thumbnail = thumbnail; + anime.year = year; + anime.quarter = quarter; + anime.rating = rating; + anime.status = status; + anime.isReleased = false; + anime.viewCount = 0; + anime.reviewCount = 0; + anime.bookmarkCount = 0; + + // 연결 엔티티 설정 + anime.assignAnimeOriginalAuthors(animeOriginalAuthors); + anime.assignAnimeStudios(animeStudios); + anime.assignAnimeVoiceActors(animeVoiceActors); + anime.assignAnimeGenre(animeGenres); + + // 시리즈 설정 + anime.series = series; + return anime; + } +} \ No newline at end of file From e85fac387680a66ddd8a14be31517bf76d0fd0f9 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Wed, 18 Oct 2023 00:08:33 +0900 Subject: [PATCH 204/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/entity/Anime.java | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index b4676add..ac8d9481 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -87,6 +87,65 @@ public class Anime extends BaseEntity { @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) private List animeGenres = new ArrayList<>(); + /** + * 비즈니스 메소드 + */ + // 조회수 증가 + public void increaseViewCount(){ + viewCount++; + } + + // 조회수 감소 + public void decreaseViewCount(){ + viewCount--; + } + + // 리뷰수 증가(짧은 리뷰, 장문 리뷰) + public void increaseReviewCount(){ + reviewCount++; + } + + // 리뷰수 감소(짧은 리뷰, 장문 리뷰) + public void decreaseReviewCount(){ + reviewCount--; + } + + // 북마크수 증가 + public void increaseBookmarkCount(){ + bookmarkCount++; + } + + // 북마크수 감소 + public void decreaseBookmarkCount(){ + bookmarkCount--; + } + + // 애니 공개 전환 + public void releaseAnime(){ + isReleased = true; + } + + // 비공개 전환 + public void setPrivateAnime(){ + isReleased = false; + } + + // 평가할 때 점수 합산(별점) + public void increaseStarRatingScore(int score){ + if(score <= 0){ + throw new IllegalArgumentException("음수는 올 수 없습니다."); + } + starRatingScoreTotal += score; + } + + // 평가를 지웠을 때 점수 합산(별점) + public void decreaseStarRatingScore(int score){ + if(score <= 0){ + throw new IllegalArgumentException("음수는 올 수 없습니다."); + } + starRatingScoreTotal -= score; + } + /** * 연관 관계 관련한 메소드 */ @@ -170,7 +229,6 @@ public void assignAnimeGenre(List animeGenres) { } } - public static Anime createAnime(String title, String summary, BroadcastType broadcastType, int episodeCount, String thumbnail, int year, Quarter quarter, Rating rating, Status status, List animeOriginalAuthors, List animeStudios, List animeVoiceActors, List animeGenres, Series series) { From 29fde77b1e46a4c8c4376da883cac9acad381a31 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Wed, 18 Oct 2023 00:10:03 +0900 Subject: [PATCH 205/734] =?UTF-8?q?test:=20=EC=95=A0=EB=8B=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EC=88=98=EC=A0=95=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anime/repository/AnimeRepositoryTest.java | 499 ++++++++++++++++++ 1 file changed, 499 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java diff --git a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java new file mode 100644 index 00000000..ccbea69c --- /dev/null +++ b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java @@ -0,0 +1,499 @@ +package io.oduck.api.unit.anime.repository; + +import static io.oduck.api.global.utils.AnimeTestUtils.getBroadcastType; +import static io.oduck.api.global.utils.AnimeTestUtils.getEpisodeCount; +import static io.oduck.api.global.utils.AnimeTestUtils.getQuarter; +import static io.oduck.api.global.utils.AnimeTestUtils.getRating; +import static io.oduck.api.global.utils.AnimeTestUtils.getStatus; +import static io.oduck.api.global.utils.AnimeTestUtils.getSummary; +import static io.oduck.api.global.utils.AnimeTestUtils.getThumbnail; +import static io.oduck.api.global.utils.AnimeTestUtils.getTitle; +import static io.oduck.api.global.utils.AnimeTestUtils.getYear; +import static org.assertj.core.api.Assertions.assertThat; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.domain.anime.repository.AnimeGenreRepository; +import io.oduck.api.domain.anime.repository.AnimeOriginalAuthorRepository; +import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.anime.repository.AnimeStudioRepository; +import io.oduck.api.domain.anime.repository.AnimeVoiceActorRepository; +import io.oduck.api.domain.genre.entity.Genre; +import io.oduck.api.domain.genre.repository.GenreRepository; +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +import io.oduck.api.domain.originalAuthor.repository.OriginalAuthorRepository; +import io.oduck.api.domain.series.entity.Series; +import io.oduck.api.domain.series.repository.SeriesRepository; +import io.oduck.api.domain.studio.entity.Studio; +import io.oduck.api.domain.studio.repository.StudioRepository; +import io.oduck.api.domain.voiceActor.entity.VoiceActor; +import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; +import java.util.ArrayList; +import java.util.List; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +public class AnimeRepositoryTest { + @Autowired + private AnimeRepository animeRepository; + + @Autowired + private AnimeOriginalAuthorRepository animeOriginalAuthorRepository; + + @Autowired + private OriginalAuthorRepository originalAuthorRepository; + + @Autowired + private StudioRepository studioRepository; + + @Autowired + private AnimeStudioRepository animeStudioRepository; + + @Autowired + private VoiceActorRepository voiceActorRepository; + + @Autowired + private AnimeVoiceActorRepository animeVoiceActorRepository; + + @Autowired + private GenreRepository genreRepository; + + @Autowired + private AnimeGenreRepository animeGenreRepository; + + @Autowired + private SeriesRepository seriesRepository; + + @Nested + @DisplayName("애니 등록") + class PostAnime{ + + @Test + @DisplayName("연관 관계 설정 성공") + void saveAnime(){ + /** + * 원작 작가 연관 관계 설정 + */ + // 1. OriginalAuthor 생성 size=1 + String originalAuthorName = "엔도 타츠야"; + OriginalAuthor originalAuthor = OriginalAuthor.builder() + .name(originalAuthorName) + .build(); + originalAuthorRepository.save(originalAuthor); + + // 2. AnimeOriginalAuthor 생성 + AnimeOriginalAuthor animeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor(originalAuthor); + + List animeOriginalAuthors = new ArrayList<>(); + animeOriginalAuthors.add(animeOriginalAuthor); + + /** + * 스튜디오 생성 + */ + // 1. Studio 생성 size=1 + String studioName = "ufortable"; + Studio studio = Studio.builder() + .name(studioName) + .build(); + studioRepository.save(studio); + + assertThat(studio.getName()).isEqualTo(studioName); + + // 2. AnimeStudio 생성 + AnimeStudio animeStudio = AnimeStudio.createAnimeStudio(studio); + + List animeStudios = new ArrayList<>(); + animeStudios.add(animeStudio); + + /** + * 성우 생성 + */ + // 1. VoiceActor 생성 size=5 + int voiceActorSize = 5; + + List voiceActors = new ArrayList<>(); + for(int i = 0; i < voiceActorSize; i++){ + VoiceActor voiceActor = VoiceActor.builder() + .name("성우"+i) + .build(); + voiceActors.add(voiceActor); + } + + voiceActorRepository.saveAll(voiceActors); + + // 2. AnimeVoiceActor 생성 + List animeVoiceActors = new ArrayList<>(); + for (VoiceActor voiceActor : voiceActors) { + AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("파트", voiceActor); + animeVoiceActors.add(animeVoiceActor); + } + + /** + * 장르 생성 + */ + // 1. Genre 생성 size = 2 + int genreSize = 2; + + List genres = new ArrayList<>(); + for(int i = 0; i < genreSize; i++){ + Genre genre = Genre.builder() + .name("장르"+i) + .build(); + genres.add(genre); + } + + genreRepository.saveAll(genres); + + // 2. AnimeGenre 생성 + List animeGenres = new ArrayList<>(); + for (Genre genre : genres) { + AnimeGenre animeGenre = AnimeGenre.createAnimeGenre(genre); + animeGenres.add(animeGenre); + } + + /** + * 시리즈 생성 + */ + // 1. Series + String seriesTitle = "귀멸의 칼날"; + Series series = Series.builder() + .title(seriesTitle) + .build(); + + // 2. Series 생성 + seriesRepository.save(series); + + // 애니 생성 + Anime anime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, series + ); + + Anime savedAnime = animeRepository.save(anime); + + assertThat(savedAnime.getTitle()).isEqualTo(getTitle()); + assertThat(savedAnime.getSummary()).isEqualTo(getSummary()); + assertThat(savedAnime.getBroadcastType()).isEqualTo(getBroadcastType()); + assertThat(savedAnime.getEpisodeCount()).isEqualTo(getEpisodeCount()); + assertThat(savedAnime.getThumbnail()).isEqualTo(getThumbnail()); + assertThat(savedAnime.getYear()).isEqualTo(getYear()); + assertThat(savedAnime.getQuarter()).isEqualTo(getQuarter()); + assertThat(savedAnime.getRating()).isEqualTo(getRating()); + assertThat(savedAnime.getStatus()).isEqualTo(getStatus()); + assertThat(savedAnime.isReleased()).isFalse(); + assertThat(savedAnime.getViewCount()).isEqualTo(0); + assertThat(savedAnime.getReviewCount()).isEqualTo(0); + assertThat(savedAnime.getBookmarkCount()).isEqualTo(0); + assertThat(savedAnime.getStarRatingScoreTotal()).isEqualTo(0); + + assertThat(savedAnime.getAnimeOriginalAuthors().size()).isEqualTo(1); + assertThat(savedAnime.getAnimeStudios().size()).isEqualTo(1); + assertThat(savedAnime.getAnimeVoiceActors().size()).isEqualTo(voiceActorSize); + assertThat(savedAnime.getAnimeGenres().size()).isEqualTo(genreSize); + assertThat(savedAnime.getSeries().getTitle()).isEqualTo(seriesTitle); + } + + @Test + @DisplayName("연관 관계 설정 시 연결 테이블의 값이 없을 때") + void saveAnimeNoAnimeOriginalAuthor(){ + List animeOriginalAuthors = new ArrayList<>(); + List animeStudios = new ArrayList<>(); + List animeVoiceActors = new ArrayList<>(); + List animeGenres = new ArrayList<>(); + + Anime anime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, null + ); + + Anime savedAnime = animeRepository.save(anime); + assertThat(savedAnime.getAnimeOriginalAuthors().isEmpty()).isTrue(); + assertThat(savedAnime.getAnimeStudios().isEmpty()).isTrue(); + assertThat(savedAnime.getAnimeVoiceActors().isEmpty()).isTrue(); + assertThat(savedAnime.getAnimeGenres().isEmpty()).isTrue(); + assertThat(savedAnime.getSeries()).isNull(); + } + } + + @Nested + @DisplayName("애니 수정") + class patchAnime{ + @Test + @DisplayName("애니 원작 작가 수정 성공") + void changeAnimeOriginalAuthors(){ + // given + // 초기 원작 작가 설정 + String originalAuthorName = "작가"; + int firstInsertSize = 2; + + List originalAuthors = new ArrayList<>(); + for(int i = 0; i < firstInsertSize; i++){ + OriginalAuthor originalAuthor = OriginalAuthor.builder() + .name(originalAuthorName) + .build(); + originalAuthors.add(originalAuthor); + } + + originalAuthorRepository.saveAll(originalAuthors); + + // 원작 작가와 애니 연관 관계 테이블 연관 관계 맺기 + List animeOriginalAuthors = new ArrayList<>(); + for (OriginalAuthor originalAuthor : originalAuthors) { + AnimeOriginalAuthor animeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor(originalAuthor); + animeOriginalAuthors.add(animeOriginalAuthor); + } + + List animeStudios = new ArrayList<>(); + List animeVoiceActors = new ArrayList<>(); + List animeGenres = new ArrayList<>(); + + // 애니 생성 + Anime anime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, null + ); + + Long savedAnimeId = animeRepository.saveAndFlush(anime).getId(); + + //when + //업데이트할 작가 생성 + String updatingOriginalAuthorName = "유재석"; + OriginalAuthor updatingOriginalAuthor = OriginalAuthor.builder() + .name(updatingOriginalAuthorName) + .build(); + originalAuthorRepository.save(updatingOriginalAuthor); + + List updatingAnimeOriginalAuthors = new ArrayList<>(); + AnimeOriginalAuthor updatingAnimeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor(updatingOriginalAuthor); + updatingAnimeOriginalAuthors.add(updatingAnimeOriginalAuthor); + + // 애니 찾기 + Anime findAnime = animeRepository.findById(savedAnimeId).get(); + + // 애니 수정 + findAnime.assignAnimeOriginalAuthors(updatingAnimeOriginalAuthors); + + Long findAnimeId = findAnime.getId(); + List findAnimeOriginalAuthors = animeOriginalAuthorRepository + .findAllByAnimeId(findAnimeId); + + // then + String findOriginalAuthorName = findAnime.getAnimeOriginalAuthors().get(0).getOriginalAuthor().getName(); + assertThat(findOriginalAuthorName).isEqualTo(updatingOriginalAuthorName); + assertThat(findAnimeOriginalAuthors.size()).isNotEqualTo(firstInsertSize); + assertThat(findAnimeOriginalAuthors.size()).isEqualTo(1); + } + + @Test + @DisplayName("애니 스튜디오 수정 성공") + void changeAnimeStudios(){ + // given + // 초기 스튜디오 설정 + String studioName = "스튜디오"; + int firstInsertSize = 2; + + List studios = new ArrayList<>(); + for(int i = 0; i animeStudios = new ArrayList<>(); + for (Studio studio : studios) { + AnimeStudio animeStudio = AnimeStudio.createAnimeStudio(studio); + animeStudios.add(animeStudio); + } + + List animeOriginalAuthors = new ArrayList<>(); + List animeVoiceActors = new ArrayList<>(); + List animeGenres = new ArrayList<>(); + + // 애니 생성 + Anime anime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, null + ); + + Long savedAnimeId = animeRepository.saveAndFlush(anime).getId(); + + //when + //업데이트할 스튜디오 생성 + String updatingStudioName = "업데이트 스튜디오"; + Studio updatingStudio = Studio.builder() + .name(updatingStudioName) + .build(); + studioRepository.save(updatingStudio); + + List updatingAnimeStudios = new ArrayList<>(); + AnimeStudio animeStudio = AnimeStudio.createAnimeStudio(updatingStudio); + updatingAnimeStudios.add(animeStudio); + + // 애니 찾기 + Anime findAnime = animeRepository.findById(savedAnimeId).get(); + + // 애니 수정 + findAnime.assignAnimeStudios(updatingAnimeStudios); + + Long findAnimeId = findAnime.getId(); + List findAnimeOriginalAuthors = animeStudioRepository.findAllByAnimeId(findAnimeId); + + // then + String findStudioName = findAnime.getAnimeStudios().get(0).getStudio().getName(); + assertThat(findStudioName).isEqualTo(updatingStudioName); + assertThat(findAnimeOriginalAuthors.size()).isNotEqualTo(firstInsertSize); + assertThat(findAnimeOriginalAuthors.size()).isEqualTo(1); + } + + @Test + @DisplayName("애니 성우 수정 성공") + void changeAnimeVoiceActors(){ + // given + // 초기 성우 설정 + String voiceActorName = "성우"; + int firstInsertSize = 2; + + List voiceActors = new ArrayList<>(); + for(int i = 0; i < firstInsertSize; i++){ + VoiceActor voiceActor = VoiceActor.builder() + .name(voiceActorName) + .build(); + voiceActors.add(voiceActor); + } + + voiceActorRepository.saveAll(voiceActors); + + // 성우와 애니 연관 관계 테이블 연관 관계 맺기 + List animeVoiceActors = new ArrayList<>(); + for (VoiceActor voiceActor : voiceActors) { + AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("part", voiceActor); + animeVoiceActors.add(animeVoiceActor); + } + + List animeOriginalAuthors = new ArrayList<>(); + List animeStudios = new ArrayList<>(); + List animeGenres = new ArrayList<>(); + + // 애니 생성 + Anime anime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, null + ); + + Long savedAnimeId = animeRepository.saveAndFlush(anime).getId(); + + //when + //업데이트할 성우 생성 + String updatingVoiceActorName = "성우"; + VoiceActor updatingVoiceActor = VoiceActor.builder() + .name(updatingVoiceActorName) + .build(); + voiceActorRepository.save(updatingVoiceActor); + + List updatingAnimeVoiceActors = new ArrayList<>(); + AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("part", updatingVoiceActor); + updatingAnimeVoiceActors.add(animeVoiceActor); + + // 애니 찾기 + Anime findAnime = animeRepository.findById(savedAnimeId).get(); + + // 애니 수정 + findAnime.assignAnimeVoiceActors(updatingAnimeVoiceActors); + + Long findAnimeId = findAnime.getId(); + List findAnimeVoiceActors = animeVoiceActorRepository.findAllByAnimeId(findAnimeId); + + // then + String firstVoiceActorName = findAnime.getAnimeVoiceActors().get(0).getVoiceActor().getName(); + assertThat(firstVoiceActorName).isEqualTo(updatingVoiceActorName); + assertThat(findAnimeVoiceActors.size()).isNotEqualTo(firstInsertSize); + assertThat(findAnimeVoiceActors.size()).isEqualTo(1); + } + + @Test + @DisplayName("애니 장르 수정 성공") + void changeAnimeGenres(){ + // given + // 초기 장르 설정 + String genreName = "장르"; + int firstInsertSize = 2; + + List genres = new ArrayList<>(); + for(int i = 0; i < firstInsertSize; i++){ + Genre genre = Genre.builder() + .name(genreName) + .build(); + genres.add(genre); + } + + genreRepository.saveAll(genres); + + // 장르와 애니 연관 관계 테이블 연관 관계 맺기 + List animeGenres = new ArrayList<>(); + for (Genre genre : genres) { + AnimeGenre animeGenre = AnimeGenre.createAnimeGenre(genre); + animeGenres.add(animeGenre); + } + + List animeOriginalAuthors = new ArrayList<>(); + List animeStudios = new ArrayList<>(); + List animeVoiceActors = new ArrayList<>(); + + // 애니 생성 + Anime anime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, null + ); + + Long savedAnimeId = animeRepository.saveAndFlush(anime).getId(); + + //when + //업데이트할 장르 생성 + String updatingGenreName = "판타지"; + Genre updatingGenre = Genre.builder() + .name(updatingGenreName) + .build(); + genreRepository.save(updatingGenre); + + List updatingAnimeGenres = new ArrayList<>(); + AnimeGenre animeGenre = AnimeGenre.createAnimeGenre(updatingGenre); + updatingAnimeGenres.add(animeGenre); + + // 애니 찾기 + Anime findAnime = animeRepository.findById(savedAnimeId).get(); + + // 애니 수정 + findAnime.assignAnimeGenre(updatingAnimeGenres); + + Long findAnimeId = findAnime.getId(); + List findAnimeGenres = animeGenreRepository.findAllByAnimeId(findAnimeId); + + // then + String firstGenreName = findAnime.getAnimeGenres().get(0).getGenre().getName(); + assertThat(firstGenreName).isEqualTo(updatingGenreName); + assertThat(findAnimeGenres.size()).isNotEqualTo(firstInsertSize); + assertThat(findAnimeGenres.size()).isEqualTo(1); + } + } +} From 89cd88617ee0861ca9ad0a7a1b1ff38d99597abb Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Wed, 18 Oct 2023 00:19:51 +0900 Subject: [PATCH 206/734] =?UTF-8?q?refactor:=20=EC=A1=B0=ED=9A=8C=EC=88=98?= =?UTF-8?q?=20=EA=B0=90=EC=86=8C=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/anime/entity/Anime.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index ac8d9481..d5853b0e 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -95,11 +95,6 @@ public void increaseViewCount(){ viewCount++; } - // 조회수 감소 - public void decreaseViewCount(){ - viewCount--; - } - // 리뷰수 증가(짧은 리뷰, 장문 리뷰) public void increaseReviewCount(){ reviewCount++; From 9f16e7b6e283614198e235c414a4e166b9eb2a0a Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:30:50 +0900 Subject: [PATCH 207/734] test: MemberInitializer -> MemberContruct #22 --- .../{MemberInitializer.java => MemberConstruct.java} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename src/test/java/io/oduck/api/global/initializer/{MemberInitializer.java => MemberConstruct.java} (81%) diff --git a/src/test/java/io/oduck/api/global/initializer/MemberInitializer.java b/src/test/java/io/oduck/api/global/initializer/MemberConstruct.java similarity index 81% rename from src/test/java/io/oduck/api/global/initializer/MemberInitializer.java rename to src/test/java/io/oduck/api/global/initializer/MemberConstruct.java index a9e34b5d..2a3c95e8 100644 --- a/src/test/java/io/oduck/api/global/initializer/MemberInitializer.java +++ b/src/test/java/io/oduck/api/global/initializer/MemberConstruct.java @@ -2,6 +2,7 @@ import io.oduck.api.domain.member.repository.MemberRepository; import io.oduck.api.global.stub.MemberStub; +import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; @@ -10,13 +11,13 @@ @Component @ActiveProfiles("test") -public class MemberInitializer implements ApplicationRunner { +public class MemberConstruct { @Autowired MemberRepository memberRepository; - @Override - public void run(ApplicationArguments args) throws Exception { + @PostConstruct + public void run() { MemberStub memberStub = new MemberStub(); memberRepository.saveAll(memberStub.getMembers()); } From 54797602303fa6a4f66ef19d3e5918566bdf3855 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:33:20 +0900 Subject: [PATCH 208/734] =?UTF-8?q?refactor:=20Bookmark=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=20=EA=B4=80=EA=B3=84=20=EC=84=A4=EC=A0=95=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/bookmark/entity/Bookmark.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java index bba90efa..f828b45c 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java +++ b/src/main/java/io/oduck/api/domain/bookmark/entity/Bookmark.java @@ -38,4 +38,12 @@ public class Bookmark { @CreationTimestamp @Column(nullable = false, updatable = false) protected LocalDateTime createdAt; + + public void relateAnime(Anime anime) { + this.anime = anime; + } + + public void relateMemer(Member member) { + this.member = member; + } } From e87c65d0ee453f046a5d2a13a037fb4058291543 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:33:41 +0900 Subject: [PATCH 209/734] =?UTF-8?q?feat:=20Bookmark=20Dto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bookmark/dto/BookmarkDslDto.java | 21 ++++++++++++ .../domain/bookmark/dto/BookmarkResDto.java | 32 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkDslDto.java create mode 100644 src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkDslDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkDslDto.java new file mode 100644 index 00000000..469006f0 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkDslDto.java @@ -0,0 +1,21 @@ +package io.oduck.api.domain.bookmark.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class BookmarkDslDto { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class BookmarkDsl { + private Long animeId; + private String title; + private String thumbnail; + private Integer myScore; + private LocalDateTime createdAt; + } +} diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java new file mode 100644 index 00000000..4ee33a11 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java @@ -0,0 +1,32 @@ +package io.oduck.api.domain.bookmark.dto; + +import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class BookmarkResDto { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class BookmarkRes { + private Long animeId; + private String title; + private String thumbnail; + private Integer myScore; + private LocalDateTime createdAt; + + public static BookmarkRes of(BookmarkDsl bookmarkDsl) { + return BookmarkRes.builder() + .animeId(bookmarkDsl.getAnimeId()) + .title(bookmarkDsl.getTitle()) + .thumbnail(bookmarkDsl.getThumbnail()) + .myScore(bookmarkDsl.getMyScore()) + .createdAt(bookmarkDsl.getCreatedAt()) + .build(); + } + } +} From c8b48f51b90caf382da8426686d13c503c6033dc Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:40:47 +0900 Subject: [PATCH 210/734] feat: BookmarkRepository #22 --- .../repository/BookmarkRepository.java | 2 +- .../repository/BookmarkRepositoryCustom.java | 14 ++++++ .../repository/BookmarkRepositoryImpl.java | 45 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryCustom.java create mode 100644 src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java index 4769d2f1..27176c49 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java @@ -5,6 +5,6 @@ import org.springframework.stereotype.Repository; @Repository -public interface BookmarkRepository extends JpaRepository { +public interface BookmarkRepository extends JpaRepository, BookmarkRepositoryCustom { } diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryCustom.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryCustom.java new file mode 100644 index 00000000..a24816eb --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryCustom.java @@ -0,0 +1,14 @@ +package io.oduck.api.domain.bookmark.repository; + +import static io.oduck.api.domain.anime.entity.QAnime.anime; +import static io.oduck.api.domain.bookmark.entity.QBookmark.bookmark; +import static io.oduck.api.domain.member.entity.QMember.member; +import static io.oduck.api.domain.starRating.entity.QStarRating.starRating; + +import com.querydsl.core.types.Projections; +import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import java.util.List; + +public interface BookmarkRepositoryCustom { + List selectBookmarks(Long memberId) ; +} diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java new file mode 100644 index 00000000..e0729bbb --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java @@ -0,0 +1,45 @@ +package io.oduck.api.domain.bookmark.repository; + +import static io.oduck.api.domain.anime.entity.QAnime.anime; +import static io.oduck.api.domain.bookmark.entity.QBookmark.bookmark; +import static io.oduck.api.domain.member.entity.QMember.member; +import static io.oduck.api.domain.starRating.entity.QStarRating.starRating; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class BookmarkRepositoryImpl implements BookmarkRepositoryCustom{ + private final JPAQueryFactory query; +// private final QueryDslUtils queryDslUtils; + + @Override + public List selectBookmarks(Long memberId) { + List bookmarks = query + .select( + Projections.constructor( + BookmarkDsl.class, + anime.id, + anime.title, + anime.thumbnail, + starRating.score, + bookmark.createdAt + ) + ) + .from(bookmark) + .join(member).on(member.id.eq(bookmark.member.id)) + .join(anime).on(bookmark.anime.id.eq(anime.id)) + .leftJoin(starRating).on(member.id.eq(starRating.member.id).and(anime.id.eq(starRating.anime.id))) + .where(member.id.eq(memberId)) + .fetch(); + + return bookmarks; + } +} From e045205f7c16225a287aeb8f13262844790fe47a Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:43:38 +0900 Subject: [PATCH 211/734] feat: BookmarkService #22 --- .../bookmark/service/BookmarkService.java | 10 ++++++++ .../bookmark/service/BookmarkServiceImpl.java | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java create mode 100644 src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java new file mode 100644 index 00000000..4a5476a9 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.bookmark.service; + +import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; +import java.util.List; + +public interface BookmarkService { + + List getBookmarksByMemberId(Long memberId); + +} diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java new file mode 100644 index 00000000..8bfc98d6 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java @@ -0,0 +1,24 @@ +package io.oduck.api.domain.bookmark.service; + +import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; +import io.oduck.api.domain.bookmark.repository.BookmarkRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class BookmarkServiceImpl implements BookmarkService { + private final BookmarkRepository bookmarkRepository; + @Override + public List getBookmarksByMemberId(Long memberId) { + List bookmarks = bookmarkRepository.selectBookmarks(memberId); + List bookmarkRes = bookmarks.stream() + .map(BookmarkRes::of) + .toList(); + return bookmarkRes; + } +} From e26c7a42f6d4fac4effcd094d5011ee4679906ed Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:44:27 +0900 Subject: [PATCH 212/734] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=ED=8C=85=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/member/repository/MemberRepositoryCustom.java | 2 -- .../api/domain/member/repository/MemberRepositoryImpl.java | 1 - 2 files changed, 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java index c11c1192..e0223793 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryCustom.java @@ -5,9 +5,7 @@ public interface MemberRepositoryCustom { Optional selectProfileByName(String name); - Long countReviewsByMemberId(Long id); Long countBookmarksByMemberId(Long id); - Long countLikesByMemberId(Long id); } diff --git a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java index 92fd22a0..8263ca58 100644 --- a/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java +++ b/src/main/java/io/oduck/api/domain/member/repository/MemberRepositoryImpl.java @@ -7,7 +7,6 @@ import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; -import io.oduck.api.domain.bookmark.entity.QBookmark; import io.oduck.api.domain.member.dto.MemberDslDto.ProfileWithoutActivity; import java.util.Optional; import lombok.RequiredArgsConstructor; From f869faeb3f115d78ee0450d201011668e5fda468 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:45:20 +0900 Subject: [PATCH 213/734] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EB=B6=81?= =?UTF-8?q?=EB=A7=88=ED=81=AC=20=EC=A1=B0=ED=9A=8C=20api=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index dcb2a776..62403390 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -1,13 +1,15 @@ package io.oduck.api.domain.member.controller; +import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; +import io.oduck.api.domain.bookmark.service.BookmarkService; import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; import io.oduck.api.domain.member.dto.MemberReqDto.PatchReq; import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; import io.oduck.api.domain.member.service.MemberService; -import io.oduck.api.global.common.SingleResponse; import io.oduck.api.global.security.auth.dto.AuthUser; import io.oduck.api.global.security.auth.dto.LoginUser; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -25,6 +27,7 @@ @RestController public class MemberController { private final MemberService memberService; + private final BookmarkService bookmarkService; // 로컬 회원 가입 @PostMapping @@ -60,7 +63,19 @@ public ResponseEntity patchProfile( return ResponseEntity.noContent().build(); } - // 회원이 작성한 리뷰 목록 - - // 회원 북마크 애니 목록 + @GetMapping("/{id}/bookmarks") + public ResponseEntity getBookmaks( + @PathVariable("id") Long id + ) { + // TODO: slice 및 정렬 구현 + List res = bookmarkService.getBookmarksByMemberId(id); + return ResponseEntity.ok(res); + } +// +// @GetMapping("/{name}/short-reviews") +// public ResponseEntity getShortReviews( +// @LoginUser AuthUser user +// ) { +// return ResponseEntity.ok(SliceResponse.of()); +// } } From 109b0211ff49fc4bbaf06a16c23cf8adf346b857 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:47:07 +0900 Subject: [PATCH 214/734] =?UTF-8?q?refactor:=20StarRating=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=20=EA=B4=80=EA=B3=84=20=EC=84=A4=EC=A0=95=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/starRating/entity/StarRating.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java b/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java index c156af61..72569dcb 100644 --- a/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java +++ b/src/main/java/io/oduck/api/domain/starRating/entity/StarRating.java @@ -11,12 +11,16 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor public class StarRating extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -32,4 +36,11 @@ public class StarRating extends BaseEntity { @Column(nullable = false) private int score; + + public void relateMember(Member member) { + this.member = member; + } + public void relateAnime(Anime anime) { + this.anime = anime; + } } From 83c07db9b4e2f8cc9639d8525e64d42638443fe3 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:47:31 +0900 Subject: [PATCH 215/734] =?UTF-8?q?feat:=20StarRatingRepository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starRating/repository/StarRatingRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/starRating/repository/StarRatingRepository.java diff --git a/src/main/java/io/oduck/api/domain/starRating/repository/StarRatingRepository.java b/src/main/java/io/oduck/api/domain/starRating/repository/StarRatingRepository.java new file mode 100644 index 00000000..ee6e694e --- /dev/null +++ b/src/main/java/io/oduck/api/domain/starRating/repository/StarRatingRepository.java @@ -0,0 +1,8 @@ +package io.oduck.api.domain.starRating.repository; + +import io.oduck.api.domain.starRating.entity.StarRating; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StarRatingRepository extends JpaRepository { + +} From db24d5882c3f0b7ae701ca946cd5fa96f009908f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:48:50 +0900 Subject: [PATCH 216/734] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20Stub=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/stub/AnimeStub.java | 41 +++++++++++++++++++ .../oduck/api/global/stub/BookmarkStub.java | 26 ++++++++++++ .../io/oduck/api/global/stub/MemberStub.java | 3 ++ .../oduck/api/global/stub/StarRatingStub.java | 33 +++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/test/java/io/oduck/api/global/stub/AnimeStub.java create mode 100644 src/test/java/io/oduck/api/global/stub/BookmarkStub.java create mode 100644 src/test/java/io/oduck/api/global/stub/StarRatingStub.java diff --git a/src/test/java/io/oduck/api/global/stub/AnimeStub.java b/src/test/java/io/oduck/api/global/stub/AnimeStub.java new file mode 100644 index 00000000..b9c2ffba --- /dev/null +++ b/src/test/java/io/oduck/api/global/stub/AnimeStub.java @@ -0,0 +1,41 @@ +package io.oduck.api.global.stub; + +import io.oduck.api.domain.anime.entity.Anime; +import java.util.ArrayList; +import java.util.List; + +public class AnimeStub { + List animes = new ArrayList<>(); + + public AnimeStub() { + Anime anime1 = Anime.builder() + .title("anime1") + .thumbnail("http://thumbnail.com") + .summary("summary") + .episodeCount(12) + .isReleased(true) + .build(); + + Anime anime2 = Anime.builder() + .title("anime2") + .thumbnail("http://thumbnail.com") + .summary("summary") + .episodeCount(12) + .isReleased(true) + .build(); + + Anime anime3 = Anime.builder() + .title("anime3") + .thumbnail("http://thumbnail.com") + .summary("summary") + .episodeCount(12) + .isReleased(true) + .build(); + + animes.addAll(List.of(anime1, anime2, anime3)); + } + + public List getAnimes() { + return animes; + } +} diff --git a/src/test/java/io/oduck/api/global/stub/BookmarkStub.java b/src/test/java/io/oduck/api/global/stub/BookmarkStub.java new file mode 100644 index 00000000..52c717d9 --- /dev/null +++ b/src/test/java/io/oduck/api/global/stub/BookmarkStub.java @@ -0,0 +1,26 @@ +package io.oduck.api.global.stub; + +import io.oduck.api.domain.bookmark.entity.Bookmark; +import java.util.ArrayList; +import java.util.List; + +public class BookmarkStub { + List bookmarks = new ArrayList<>(); + + public BookmarkStub() { + Bookmark bookmark1 = Bookmark.builder() + .build(); + + Bookmark bookmark2 = Bookmark.builder() + .build(); + + Bookmark bookmark3 = Bookmark.builder() + .build(); + + bookmarks.addAll(List.of(bookmark1, bookmark2, bookmark3)); + } + + public List getBookmarks() { + return bookmarks; + } +} diff --git a/src/test/java/io/oduck/api/global/stub/MemberStub.java b/src/test/java/io/oduck/api/global/stub/MemberStub.java index 3b708456..db497a77 100644 --- a/src/test/java/io/oduck/api/global/stub/MemberStub.java +++ b/src/test/java/io/oduck/api/global/stub/MemberStub.java @@ -85,4 +85,7 @@ public MemberStub() { public Member getMember() { return members.get(0); } + public List getMemberList() { + return members; + } } diff --git a/src/test/java/io/oduck/api/global/stub/StarRatingStub.java b/src/test/java/io/oduck/api/global/stub/StarRatingStub.java new file mode 100644 index 00000000..fb2189a0 --- /dev/null +++ b/src/test/java/io/oduck/api/global/stub/StarRatingStub.java @@ -0,0 +1,33 @@ +package io.oduck.api.global.stub; + +import io.oduck.api.domain.starRating.entity.StarRating; +import java.util.ArrayList; +import java.util.List; + +public class StarRatingStub { + List starRatings = new ArrayList<>(); + + public StarRatingStub() { + StarRating starRating1 = StarRating.builder() + .score(1) + .build(); + + StarRating starRating2 = StarRating.builder() + .score(2) + .build(); + + StarRating starRating3 = StarRating.builder() + .score(2) + .build(); + + StarRating starRating4 = StarRating.builder() + .score(3) + .build(); + + starRatings.addAll(List.of(starRating1, starRating2, starRating3, starRating4)); + } + + public List getStarRatings() { + return starRatings; + } +} From 6857900542b1d62eb36d44a1e17a2187d3ac72e2 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:49:50 +0900 Subject: [PATCH 217/734] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B4=88=EA=B8=B0=ED=99=94=20Con?= =?UTF-8?q?struct=20=EB=B0=8F=20Initializer=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/initializer/AnimeConstruct.java | 22 ++++++ .../global/initializer/StubInitializer.java | 77 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/test/java/io/oduck/api/global/initializer/AnimeConstruct.java create mode 100644 src/test/java/io/oduck/api/global/initializer/StubInitializer.java diff --git a/src/test/java/io/oduck/api/global/initializer/AnimeConstruct.java b/src/test/java/io/oduck/api/global/initializer/AnimeConstruct.java new file mode 100644 index 00000000..94cf2033 --- /dev/null +++ b/src/test/java/io/oduck/api/global/initializer/AnimeConstruct.java @@ -0,0 +1,22 @@ +package io.oduck.api.global.initializer; + +import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.global.stub.AnimeStub; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.test.context.ActiveProfiles; + +@Component +@ActiveProfiles("test") +public class AnimeConstruct { + + @Autowired + AnimeRepository animeRepository; + + @PostConstruct + public void run() { + AnimeStub animeStub = new AnimeStub(); + animeRepository.saveAll(animeStub.getAnimes()); + } +} diff --git a/src/test/java/io/oduck/api/global/initializer/StubInitializer.java b/src/test/java/io/oduck/api/global/initializer/StubInitializer.java new file mode 100644 index 00000000..747c36dd --- /dev/null +++ b/src/test/java/io/oduck/api/global/initializer/StubInitializer.java @@ -0,0 +1,77 @@ +package io.oduck.api.global.initializer; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.bookmark.entity.Bookmark; +import io.oduck.api.domain.bookmark.repository.BookmarkRepository; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.domain.starRating.entity.StarRating; +import io.oduck.api.domain.starRating.repository.StarRatingRepository; +import io.oduck.api.global.stub.BookmarkStub; +import io.oduck.api.global.stub.StarRatingStub; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import org.springframework.test.context.ActiveProfiles; + +@Component +@ActiveProfiles("test") +public class StubInitializer implements ApplicationRunner { + @Autowired + MemberRepository memberRepository; + + @Autowired + AnimeRepository animeRepository; + + @Autowired + BookmarkRepository bookmarkRepository; + + @Autowired + StarRatingRepository starRatingRepository; + + int bookmarkIterator = 0; + + @Override + public void run(ApplicationArguments args) throws Exception { + Member member = memberRepository.findById(1L).get(); + List members = memberRepository.findAll(); + List animes = animeRepository.findAll(); + + BookmarkStub bookmarkStub = new BookmarkStub(); + + bookmarkStub.getBookmarks().forEach(bookmark -> { + bookmark.relateAnime(animes.get(bookmarkIterator)); + bookmark.relateMemer(member); + bookmarkIterator++; + }); + + bookmarkRepository.saveAll(bookmarkStub.getBookmarks()); + + StarRatingStub starRatingStub = new StarRatingStub(); + + List starRatings = starRatingStub.getStarRatings(); + + StarRating starRating1 = starRatings.get(0); + starRating1.relateAnime(animes.get(0)); + starRating1.relateMember(members.get(0)); + + StarRating starRating2 = starRatings.get(1); + starRating2.relateAnime(animes.get(1)); + starRating2.relateMember(members.get(0)); + + StarRating starRating3 = starRatings.get(2); + starRating3.relateAnime(animes.get(0)); + starRating3.relateMember(members.get(1)); + + StarRating starRating4 = starRatings.get(3); + starRating4.relateAnime(animes.get(0)); + starRating4.relateMember(members.get(2)); + + starRatingRepository.saveAll(starRatings); + List bookmarkList = bookmarkRepository.findAll(); + List starRatingList = starRatingRepository.findAll(); + } +} From 0552bb0392802f67b2fb6945246e72b099bf28bf Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 17:50:41 +0900 Subject: [PATCH 218/734] =?UTF-8?q?test:=20Bookmark=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/BookmarkRepositoryTest.java | 45 +++++++++++++++++++ .../repository/MemberRepositoryTest.java | 1 - 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java diff --git a/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java b/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java new file mode 100644 index 00000000..316406c6 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java @@ -0,0 +1,45 @@ +package io.oduck.api.unit.bookmark.repository; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import io.oduck.api.domain.bookmark.repository.BookmarkRepository; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@TestInstance(Lifecycle.PER_CLASS) +@ActiveProfiles("test") +public class BookmarkRepositoryTest { + @Autowired + BookmarkRepository memberRepository; + + @DisplayName("회원 북마크 애니 목록 조회") + @Nested + class selectBookmarks { + + @DisplayName("회원 ID로 회원이 북마크한 애니 목록 조회 성공") + @Test + void selectBookmarksSuccess() { + // given + Long memberId = 1L; + + // when + List bookmarks = memberRepository.selectBookmarks(memberId); + + assertNotNull(bookmarks); + assertNotNull(bookmarks.get(0).getAnimeId()); + assertNotNull(bookmarks.get(0).getTitle()); + assertNotNull(bookmarks.get(0).getThumbnail()); + assertNotNull(bookmarks.get(0).getMyScore()); + } + } + +} diff --git a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java index 26207e05..4e863f77 100644 --- a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java @@ -178,5 +178,4 @@ void updateMemberProfileSuccess() { // TODO: 회원이 작성한 리뷰 목록 - // TODO: 회원 북마크 애니 목록 } From e5d816e692043e992519f309a274b94173e82582 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Wed, 18 Oct 2023 19:34:35 +0900 Subject: [PATCH 219/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20starRa?= =?UTF-8?q?tingCount=20=EC=B6=94=EA=B0=80=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/anime/entity/Anime.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index d5853b0e..2f119eb5 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -71,6 +71,9 @@ public class Anime extends BaseEntity { @ColumnDefault("0") private long starRatingScoreTotal; + @ColumnDefault("0") + private long starRatingCount; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "series_id") private Series series; @@ -242,6 +245,8 @@ public static Anime createAnime(String title, String summary, BroadcastType broa anime.viewCount = 0; anime.reviewCount = 0; anime.bookmarkCount = 0; + anime.starRatingScoreTotal = 0; + anime.starRatingCount = 0; // 연결 엔티티 설정 anime.assignAnimeOriginalAuthors(animeOriginalAuthors); From 116029f7970caf94646d048451ff56f4bbcdcea0 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Wed, 18 Oct 2023 19:35:03 +0900 Subject: [PATCH 220/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=20starRating?= =?UTF-8?q?Count=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/anime/entity/Anime.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 2f119eb5..d303f5d8 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -134,6 +134,7 @@ public void increaseStarRatingScore(int score){ throw new IllegalArgumentException("음수는 올 수 없습니다."); } starRatingScoreTotal += score; + increaseStarRatingCount(); } // 평가를 지웠을 때 점수 합산(별점) @@ -142,6 +143,15 @@ public void decreaseStarRatingScore(int score){ throw new IllegalArgumentException("음수는 올 수 없습니다."); } starRatingScoreTotal -= score; + decreaseStarRatingCount(); + } + + private void increaseStarRatingCount() { + starRatingCount++; + } + + private void decreaseStarRatingCount() { + starRatingCount--; } /** From 0c52887aa186c0cf15cdb82fde8b828f07fd6dd7 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Wed, 18 Oct 2023 20:55:32 +0900 Subject: [PATCH 221/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EC=97=B0=EA=B4=80=20=EA=B4=80=EA=B3=84=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD=20#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/anime/entity/Anime.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index d303f5d8..5f1d0ffd 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -189,7 +189,7 @@ private boolean isEntityListElementsExist(List elements) { return !elements.isEmpty(); } - public void assignAnimeOriginalAuthors(List animeOriginalAuthors){ + public void updateAnimeOriginalAuthors(List animeOriginalAuthors){ // 만약 현재 animeOriginalAuthors에 값이 존재한다면 List를 비운다. // (orphanRemoval = true 설정으로 AnimeOriginalAuthor 테이블의 값들이 delete됨) if(isEntityListElementsExist(this.animeOriginalAuthors)){ @@ -201,7 +201,7 @@ public void assignAnimeOriginalAuthors(List animeOriginalAu } } - public void assignAnimeStudios(List animeStudios) { + public void updateAnimeStudios(List animeStudios) { // 만약 현재 animeStudios에 값이 존재한다면 List를 비운다. // (orphanRemoval = true 설정으로 AnimeStudio 테이블의 값들이 delete됨) if(isEntityListElementsExist(this.animeStudios)){ @@ -213,7 +213,7 @@ public void assignAnimeStudios(List animeStudios) { } } - public void assignAnimeVoiceActors(List animeVoiceActors) { + public void updateAnimeVoiceActors(List animeVoiceActors) { // 만약 현재 animeVoiceActors의 값이 존재한다면 List를 비운다. // (orphanRemoval = true 설정으로 animeVoiceActor 테이블의 값들이 delete됨) if(isEntityListElementsExist(this.animeVoiceActors)){ @@ -225,7 +225,7 @@ public void assignAnimeVoiceActors(List animeVoiceActors) { } } - public void assignAnimeGenre(List animeGenres) { + public void updateAnimeGenre(List animeGenres) { // 만약 현재 animeGenre의 값이 존재한다면 List를 비운다. // (orphanRemoval = true 설정으로 animeGenre의 테이블의 값들이 delete됨) if(isEntityListElementsExist(this.animeGenres)){ @@ -259,10 +259,10 @@ public static Anime createAnime(String title, String summary, BroadcastType broa anime.starRatingCount = 0; // 연결 엔티티 설정 - anime.assignAnimeOriginalAuthors(animeOriginalAuthors); - anime.assignAnimeStudios(animeStudios); - anime.assignAnimeVoiceActors(animeVoiceActors); - anime.assignAnimeGenre(animeGenres); + anime.updateAnimeOriginalAuthors(animeOriginalAuthors); + anime.updateAnimeStudios(animeStudios); + anime.updateAnimeVoiceActors(animeVoiceActors); + anime.updateAnimeGenre(animeGenres); // 시리즈 설정 anime.series = series; From b5565af40c7deeeb956f5d88439171731e90b359 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 21:55:25 +0900 Subject: [PATCH 222/734] =?UTF-8?q?refactor:=20ProfileRes.point=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/member/dto/MemberResDto.java | 8 ++++---- .../api/domain/member/service/MemberServiceImpl.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java index cc6638d7..5a94fb04 100644 --- a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java @@ -14,18 +14,16 @@ public static class MemberProfileRes { private String thumbnail; private String backgroundImage; private Activity activity; - private Long point; @Builder public MemberProfileRes(boolean isMine, String name, String description, String thumbnail, - String backgroundImage, Activity activity, Long point) { + String backgroundImage, Activity activity) { this.isMine = isMine; this.name = name; this.description = description; this.thumbnail = thumbnail; this.backgroundImage = backgroundImage; this.activity = activity; - this.point = point; } public boolean getIsMine() { @@ -39,12 +37,14 @@ public static class Activity { private long reviews; private long bookmarks; private long likes; + private long point; @Builder - public Activity(long reviews, long bookmarks, long likes) { + public Activity(long reviews, long bookmarks, long likes, long point) { this.reviews = reviews; this.bookmarks = bookmarks; this.likes = likes; + this.point = point; } } } diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java index 18c873d9..e280bf8d 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java @@ -85,6 +85,7 @@ public MemberProfileRes getProfileByName(String name, Long memberId) { .reviews(reviewsCount) .bookmarks(bookmarksCount) .likes(likesCount) + .point(memberProfile.getPoint()) .build(); MemberProfileRes memberProfileRes = MemberProfileRes. builder() @@ -93,7 +94,6 @@ public MemberProfileRes getProfileByName(String name, Long memberId) { .description(memberProfile.getDescription()) .thumbnail(memberProfile.getThumbnail()) .backgroundImage (memberProfile.getBackgroundImage()) - .point(memberProfile.getPoint()) .activity(activity) .build(); From 5e397ed636fedd5b724256be16538df450ac8c45 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 18 Oct 2023 21:55:36 +0900 Subject: [PATCH 223/734] =?UTF-8?q?test:=20point=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=A0=81=EC=9A=A9=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/member/MemberControllerTest.java | 78 ++++++++++--------- .../member/service/MemberServiceTest.java | 1 + 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 64b10f78..b2bd6b73 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -134,11 +134,11 @@ void getProfileByNameIfMine() throws Exception { .andExpect(jsonPath("$.description").exists()) .andExpect(jsonPath("$.thumbnail").exists()) .andExpect(jsonPath("$.backgroundImage").exists()) - .andExpect(jsonPath("$.point").exists()) .andExpect(jsonPath("$.activity").hasJsonPath()) .andExpect(jsonPath("$.activity.reviews").exists()) .andExpect(jsonPath("$.activity.bookmarks").exists()) .andExpect(jsonPath("$.activity.likes").exists()) + .andExpect(jsonPath("$.activity.point").exists()) .andDo(document("getProfileByName/successIfMine", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -152,36 +152,39 @@ void getProfileByNameIfMine() throws Exception { .description("Header Cookie, 세션 쿠키") ), responseFields( - fieldWithPath("name") - .type(JsonFieldType.STRING) - .description("회원 이름"), - fieldWithPath("isMine") - .type(JsonFieldType.BOOLEAN) - .description("본인 여부(본인 프로필 조회시 true)"), - fieldWithPath("description") - .type(JsonFieldType.STRING) - .description("자기 소개"), - fieldWithPath("thumbnail") - .type(JsonFieldType.STRING) - .description("프로필 이미지"), - fieldWithPath("backgroundImage") - .type(JsonFieldType.STRING) - .description("프로필 배경 이미지"), - fieldWithPath("point") - .type(JsonFieldType.NUMBER) - .description("회원 포인트"), - fieldWithPath("activity") - .type(JsonFieldType.OBJECT) - .description("회원 활동"), - fieldWithPath("activity.reviews") - .type(JsonFieldType.NUMBER) - .description("작성한 리뷰 갯수"), - fieldWithPath("activity.bookmarks") - .type(JsonFieldType.NUMBER) - .description("입덕 애니 갯수"), - fieldWithPath("activity.likes") - .type(JsonFieldType.NUMBER) - .description("받은 좋아요 갯수")))); + fieldWithPath("name") + .type(JsonFieldType.STRING) + .description("회원 이름"), + fieldWithPath("isMine") + .type(JsonFieldType.BOOLEAN) + .description("본인 여부(본인 프로필 조회시 true)"), + fieldWithPath("description") + .type(JsonFieldType.STRING) + .description("자기 소개"), + fieldWithPath("thumbnail") + .type(JsonFieldType.STRING) + .description("프로필 이미지"), + fieldWithPath("backgroundImage") + .type(JsonFieldType.STRING) + .description("프로필 배경 이미지"), + fieldWithPath("activity") + .type(JsonFieldType.OBJECT) + .description("회원 활동"), + fieldWithPath("activity.reviews") + .type(JsonFieldType.NUMBER) + .description("작성한 리뷰 갯수"), + fieldWithPath("activity.bookmarks") + .type(JsonFieldType.NUMBER) + .description("입덕 애니 갯수"), + fieldWithPath("activity.likes") + .type(JsonFieldType.NUMBER) + .description("받은 좋아요 갯수"), + fieldWithPath("activity.point") + .type(JsonFieldType.NUMBER) + .description("회원 포인트") + ) + ) + ); } @DisplayName("타인 프로필 조회 성공시 200 OK 반환, isMine = false") @@ -210,11 +213,11 @@ void getProfileByNameIfOthers() throws Exception { .andExpect(jsonPath("$.description").exists()) .andExpect(jsonPath("$.thumbnail").exists()) .andExpect(jsonPath("$.backgroundImage").exists()) - .andExpect(jsonPath("$.point").exists()) .andExpect(jsonPath("$.activity").hasJsonPath()) .andExpect(jsonPath("$.activity.reviews").exists()) .andExpect(jsonPath("$.activity.bookmarks").exists()) .andExpect(jsonPath("$.activity.likes").exists()) + .andExpect(jsonPath("$.activity.point").exists()) .andDo(document("getProfileByName/successIfOthers", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -243,9 +246,6 @@ void getProfileByNameIfOthers() throws Exception { fieldWithPath("backgroundImage") .type(JsonFieldType.STRING) .description("프로필 배경 이미지"), - fieldWithPath("point") - .type(JsonFieldType.NUMBER) - .description("회원 포인트"), fieldWithPath("activity") .type(JsonFieldType.OBJECT) .description("회원 활동"), @@ -257,7 +257,13 @@ void getProfileByNameIfOthers() throws Exception { .description("입덕한 애니 갯수"), fieldWithPath("activity.likes") .type(JsonFieldType.NUMBER) - .description("받은 좋아요 갯수")))); + .description("받은 좋아요 갯수"), + fieldWithPath("activity.point") + .type(JsonFieldType.NUMBER) + .description("회원 포인트") + ) + ) + ); } // TODO: 회원 프로필 조회 실패시 diff --git a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java index 10ce615e..801fef09 100644 --- a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java +++ b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java @@ -59,6 +59,7 @@ class GetProfileByName { .description(memberProfile.getInfo()) .thumbnail(memberProfile.getThumbnail()) .backgroundImage(memberProfile.getBackgroundImage()) + .point(memberProfile.getPoint()) .build(); @DisplayName("본인 프로필 조회 성공시 오류 없이 회원 프로필 반환") From 8c1d1c1df266da494898f8da15baf96a183572a5 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:42:49 +0900 Subject: [PATCH 224/734] feat: pageable util class #22 --- .../io/oduck/api/global/utils/PagingUtils.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/utils/PagingUtils.java diff --git a/src/main/java/io/oduck/api/global/utils/PagingUtils.java b/src/main/java/io/oduck/api/global/utils/PagingUtils.java new file mode 100644 index 00000000..cf852697 --- /dev/null +++ b/src/main/java/io/oduck/api/global/utils/PagingUtils.java @@ -0,0 +1,16 @@ +package io.oduck.api.global.utils; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Direction; + +public class PagingUtils { + + public static Pageable applyPageableForNonOffset(String property, String direction, int size) { + return PageRequest.of(0, size, direction.equals("desc") ? Direction.DESC : Direction.ASC, property); + } + + public static Pageable applyPageableForOffset(int page,String property, String direction, int size) { + return PageRequest.of(page, size, direction.equals("DESC") ? Direction.DESC : Direction.ASC, property); + } +} From 9b9c018f7a4f37234ecfbcd241dff9a01a4e3bcf Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:44:34 +0900 Subject: [PATCH 225/734] refactor querydsl util class #22 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 단일 OrderSpecifier로 변경 - JPAQuery pagenation fetch 추가 --- .../oduck/api/global/utils/QueryDslUtils.java | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java index 05dca60d..612b049c 100644 --- a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java +++ b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java @@ -4,34 +4,35 @@ import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Path; import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.NumberPath; -import java.util.ArrayList; +import com.querydsl.jpa.impl.JPAQuery; import java.util.List; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; -// QueryDsl 정렬 기준 생성용 클래스 @Component public class QueryDslUtils { - // pageable에서 sort 객체로 OrderSpecifier를 생성하여 반환. - public OrderSpecifier[] getAllOrderSpecifiers(Pageable pageable, Path path) { - List orders = convertToDslOrder(pageable, path); - return orders.toArray(OrderSpecifier[]::new); + // JPAQuery를 이용하여 Slice를 생성하여 반환. + public static Slice fetchSliceByCursor(Path path, JPAQuery query, Pageable pageable) { + Sort.Order order = pageable.getSort().iterator().next(); + + int pageSize = pageable.getPageSize(); + + List content = query + .orderBy(getOrderSpecifier(order, path)) + .limit(pageSize + 1) + .fetch(); + + return new SliceImpl<>(content, pageable, isHasNext(pageSize, content)); } - // Sort 객체에 Order가 존재할 때 QueryDSL Order로 변환 - private static List convertToDslOrder(Pageable pageable, Path path) { - List orders = new ArrayList<>(); - if (!pageable.getSort().isEmpty()) { - for (Sort.Order order : pageable.getSort()) { - Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; + // sort.order 객체로 OrderSpecifier를 생성하여 반환. + public static OrderSpecifier getOrderSpecifier(Sort.Order order, Path path) { - OrderSpecifier orderBy = createOrderSpecifier(direction, path, order.getProperty()); - orders.add(orderBy); - } - } - return orders; + Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; + return createOrderSpecifier(direction, path, order.getProperty()); } private static OrderSpecifier createOrderSpecifier(Order order, Path parent, String fieldName) { @@ -40,4 +41,14 @@ private static OrderSpecifier createOrderSpecifier(Order order, Path paren return new OrderSpecifier(order, fieldPath); } + + private static boolean isHasNext(int pageSize, List content) { +// boolean hasNext = pageSize <= content.size(); + boolean hasNext = false; + if (pageSize <= content.size()) { + hasNext = true; + content.remove(pageSize); + } + return hasNext; + } } \ No newline at end of file From 91f3f493b73f4a8e587df3e4874d2671f132e76f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:45:18 +0900 Subject: [PATCH 226/734] =?UTF-8?q?feat:=20bookmark=20sort=20enum=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/bookmark/dto/BookmarkReqDto.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java new file mode 100644 index 00000000..92626099 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java @@ -0,0 +1,17 @@ +package io.oduck.api.domain.bookmark.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +public class BookmarkReqDto { + @Getter + @AllArgsConstructor + public enum Sort { + LATEST("createdAt"), + TITLE("title"), + SCORE("score"); + + private final String sort; + } + +} From 0b33407391abcb0cac61e14338610a4afa8c2bbd Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:46:10 +0900 Subject: [PATCH 227/734] =?UTF-8?q?refactor:=20bookmark=20res=20dto?= =?UTF-8?q?=EC=97=90=20EntityBased=20=EA=B5=AC=ED=98=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/bookmark/dto/BookmarkResDto.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java index 4ee33a11..849a3aa9 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java @@ -1,6 +1,7 @@ package io.oduck.api.domain.bookmark.dto; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import io.oduck.api.global.common.EntityBased; import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; @@ -12,7 +13,7 @@ public class BookmarkResDto { @Builder @NoArgsConstructor @AllArgsConstructor - public static class BookmarkRes { + public static class BookmarkRes implements EntityBased { private Long animeId; private String title; private String thumbnail; @@ -28,5 +29,10 @@ public static BookmarkRes of(BookmarkDsl bookmarkDsl) { .createdAt(bookmarkDsl.getCreatedAt()) .build(); } + + @Override + public Long getId() { + return this.animeId; + } } } From 9b95b5cd982077e38de2dd2cc4fa74c978b4319e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:49:45 +0900 Subject: [PATCH 228/734] =?UTF-8?q?refactor:=20bookmarkRepository=20slice?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/BookmarkRepositoryCustom.java | 11 ++--- .../repository/BookmarkRepositoryImpl.java | 46 +++++++++++++++++-- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryCustom.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryCustom.java index a24816eb..6d4135d0 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryCustom.java +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryCustom.java @@ -1,14 +1,9 @@ package io.oduck.api.domain.bookmark.repository; -import static io.oduck.api.domain.anime.entity.QAnime.anime; -import static io.oduck.api.domain.bookmark.entity.QBookmark.bookmark; -import static io.oduck.api.domain.member.entity.QMember.member; -import static io.oduck.api.domain.starRating.entity.QStarRating.starRating; - -import com.querydsl.core.types.Projections; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; -import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; public interface BookmarkRepositoryCustom { - List selectBookmarks(Long memberId) ; + Slice selectBookmarks(Long memberId, String cursor, Pageable pageable); } diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java index e0729bbb..415a36a2 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java @@ -4,13 +4,21 @@ import static io.oduck.api.domain.bookmark.entity.QBookmark.bookmark; import static io.oduck.api.domain.member.entity.QMember.member; import static io.oduck.api.domain.starRating.entity.QStarRating.starRating; +import static io.oduck.api.global.utils.QueryDslUtils.fetchSliceByCursor; +import com.querydsl.core.types.Path; import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; 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.Sort.Direction; +import org.springframework.data.domain.Sort.Order; import org.springframework.stereotype.Repository; @Slf4j @@ -18,11 +26,11 @@ @RequiredArgsConstructor public class BookmarkRepositoryImpl implements BookmarkRepositoryCustom{ private final JPAQueryFactory query; -// private final QueryDslUtils queryDslUtils; @Override - public List selectBookmarks(Long memberId) { - List bookmarks = query + public Slice selectBookmarks(Long memberId, String cursor, Pageable pageable) { + String property = pageable.getSort().get().toList().get(0).getProperty(); + JPAQuery bookmarks = query .select( Projections.constructor( BookmarkDsl.class, @@ -38,8 +46,36 @@ public List selectBookmarks(Long memberId) { .join(anime).on(bookmark.anime.id.eq(anime.id)) .leftJoin(starRating).on(member.id.eq(starRating.member.id).and(anime.id.eq(starRating.anime.id))) .where(member.id.eq(memberId)) - .fetch(); + .where(cursorCondition(cursor, pageable)) + .limit(pageable.getPageSize()); - return bookmarks; + return fetchSliceByCursor(sortPath(property), bookmarks, pageable); + } + + private Path sortPath(String property) { + switch (property) { + case "score": + return starRating; + case "title": + return anime; + default: + return bookmark; + } + } + + private BooleanExpression cursorCondition(String cursor, Pageable pageable) { + if (cursor == null) { + return null; + } + + List orderList = pageable.getSort().get().toList(); + Direction direction = orderList.get(0).getDirection(); +// String property = orderList.get(0).getProperty(); + + if (direction == Direction.ASC) { + return bookmark.id.gt(Long.parseLong(cursor)); + } else { + return bookmark.id.lt(Long.parseLong(cursor)); + } } } From eea9275ed1cd8286df7826939c3d5a7b296028c8 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:50:04 +0900 Subject: [PATCH 229/734] =?UTF-8?q?refactor:=20querydsl=20util=20=EC=98=A4?= =?UTF-8?q?=ED=83=88=EC=9E=90=20=EC=88=98=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/global/utils/QueryDslUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java index 612b049c..7aa69207 100644 --- a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java +++ b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java @@ -45,7 +45,7 @@ private static OrderSpecifier createOrderSpecifier(Order order, Path paren private static boolean isHasNext(int pageSize, List content) { // boolean hasNext = pageSize <= content.size(); boolean hasNext = false; - if (pageSize <= content.size()) { + if (pageSize < content.size()) { hasNext = true; content.remove(pageSize); } From 2f283d7e02c5867728e89248d535d6ff14ad460e Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:55:21 +0900 Subject: [PATCH 230/734] =?UTF-8?q?refactor:=20bookmarkService=20slice=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark/service/BookmarkService.java | 6 +++-- .../bookmark/service/BookmarkServiceImpl.java | 24 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java index 4a5476a9..f26cf8b9 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java @@ -1,10 +1,12 @@ package io.oduck.api.domain.bookmark.service; import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; -import java.util.List; +import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; +import io.oduck.api.global.common.OrderDirection; +import io.oduck.api.global.common.SliceResponse; public interface BookmarkService { - List getBookmarksByMemberId(Long memberId); + SliceResponse getBookmarksByMemberId(Long memberId, String cursor, Sort sort, OrderDirection order, int size); } diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java index 8bfc98d6..8893d1d1 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java @@ -1,11 +1,17 @@ package io.oduck.api.domain.bookmark.service; +import static io.oduck.api.global.utils.PagingUtils.applyPageableForNonOffset; + import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; import io.oduck.api.domain.bookmark.repository.BookmarkRepository; +import io.oduck.api.global.common.OrderDirection; +import io.oduck.api.global.common.SliceResponse; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @Slf4j @@ -14,11 +20,21 @@ public class BookmarkServiceImpl implements BookmarkService { private final BookmarkRepository bookmarkRepository; @Override - public List getBookmarksByMemberId(Long memberId) { - List bookmarks = bookmarkRepository.selectBookmarks(memberId); - List bookmarkRes = bookmarks.stream() + public SliceResponse getBookmarksByMemberId(Long memberId, String cursor, Sort sort, OrderDirection order, int size) { + Slice bookmarks = bookmarkRepository.selectBookmarks( + memberId, + cursor, + applyPageableForNonOffset( + sort.getSort(), + order.getOrder(), + size + ) + ); + + List bookmarkRes = bookmarks.getContent().stream() .map(BookmarkRes::of) .toList(); - return bookmarkRes; + + return SliceResponse.of(bookmarks, bookmarkRes); } } From 35f41bfc1ab1a9fa90c6c634d284eadac7a0d6db Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:56:01 +0900 Subject: [PATCH 231/734] =?UTF-8?q?feat:=20=EA=B3=B5=EC=9A=A9=20OrderDirec?= =?UTF-8?q?tion=20enum=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/common/OrderDirection.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/common/OrderDirection.java diff --git a/src/main/java/io/oduck/api/global/common/OrderDirection.java b/src/main/java/io/oduck/api/global/common/OrderDirection.java new file mode 100644 index 00000000..3bc2738c --- /dev/null +++ b/src/main/java/io/oduck/api/global/common/OrderDirection.java @@ -0,0 +1,13 @@ +package io.oduck.api.global.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum OrderDirection { + ASC("asc"), + DESC("desc"); + + private final String order; +} From 0e78c2cfba97db0922fc6a5962855f864e2fd434 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 17:57:06 +0900 Subject: [PATCH 232/734] =?UTF-8?q?feat:=20=EC=9A=94=EC=B2=AD=20param=20St?= =?UTF-8?q?ring=EC=9D=84=20enum=20=EC=9C=BC=EB=A1=9C=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=98=EB=8A=94=20Converter=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/global/config/WebConfig.java | 10 ++++++++++ .../converter/StringToBookmarkSortConverter.java | 12 ++++++++++++ .../converter/StringToOrderDirectionConverter.java | 12 ++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/main/java/io/oduck/api/global/converter/StringToBookmarkSortConverter.java create mode 100644 src/main/java/io/oduck/api/global/converter/StringToOrderDirectionConverter.java diff --git a/src/main/java/io/oduck/api/global/config/WebConfig.java b/src/main/java/io/oduck/api/global/config/WebConfig.java index fb73a5e0..06b9d8e2 100644 --- a/src/main/java/io/oduck/api/global/config/WebConfig.java +++ b/src/main/java/io/oduck/api/global/config/WebConfig.java @@ -1,9 +1,12 @@ package io.oduck.api.global.config; +import io.oduck.api.global.converter.StringToBookmarkSortConverter; +import io.oduck.api.global.converter.StringToOrderDirectionConverter; import io.oduck.api.global.security.resolver.LoginUserArgumentResolver; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -12,6 +15,13 @@ public class WebConfig implements WebMvcConfigurer { private final LoginUserArgumentResolver loginUserArgumentResolver; + @Override + public void addFormatters(FormatterRegistry registry) { + // config에 converter 등록 + registry.addConverter(new StringToOrderDirectionConverter()); + registry.addConverter(new StringToBookmarkSortConverter()); + } + @Override public void addArgumentResolvers(List argumentResolvers) { argumentResolvers.add(loginUserArgumentResolver); diff --git a/src/main/java/io/oduck/api/global/converter/StringToBookmarkSortConverter.java b/src/main/java/io/oduck/api/global/converter/StringToBookmarkSortConverter.java new file mode 100644 index 00000000..e1308900 --- /dev/null +++ b/src/main/java/io/oduck/api/global/converter/StringToBookmarkSortConverter.java @@ -0,0 +1,12 @@ +package io.oduck.api.global.converter; + +import org.springframework.core.convert.converter.Converter; +import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; + +public class StringToBookmarkSortConverter implements Converter { + + @Override + public Sort convert(String sort) { + return Sort.valueOf(sort.toUpperCase()); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/converter/StringToOrderDirectionConverter.java b/src/main/java/io/oduck/api/global/converter/StringToOrderDirectionConverter.java new file mode 100644 index 00000000..b4702a59 --- /dev/null +++ b/src/main/java/io/oduck/api/global/converter/StringToOrderDirectionConverter.java @@ -0,0 +1,12 @@ +package io.oduck.api.global.converter; + +import io.oduck.api.global.common.OrderDirection; +import org.springframework.core.convert.converter.Converter; + +public class StringToOrderDirectionConverter implements Converter { + + @Override + public OrderDirection convert(String order) { + return OrderDirection.valueOf(order.toUpperCase()); + } +} \ No newline at end of file From 8827191f2fa68ea4ea1ccea6517dd84df0aff88c Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 18:14:54 +0900 Subject: [PATCH 233/734] =?UTF-8?q?refactor:=20SliceResponse=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/common/SliceResponse.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/oduck/api/global/common/SliceResponse.java b/src/main/java/io/oduck/api/global/common/SliceResponse.java index 66876ef4..5a1b2678 100644 --- a/src/main/java/io/oduck/api/global/common/SliceResponse.java +++ b/src/main/java/io/oduck/api/global/common/SliceResponse.java @@ -8,20 +8,37 @@ public class SliceResponse { private final List items; private final int size; // 한 페이지에 보여줄 아이템의 개수 - private final boolean isLastPage; // 마지막 페이지일 경우, true 반환. + private final boolean hasNext; // 마지막 페이지일 경우, true 반환. private Long lastId; // 마지막 아이템의 id + public SliceResponse(Slice sliceContent) { this.items = sliceContent.getContent(); this.size = sliceContent.getSize(); - this.isLastPage = sliceContent.isLast(); + this.hasNext = sliceContent.isLast(); - if (!items.isEmpty() && !isLastPage) { + if (!items.isEmpty() && !hasNext) { T lastItem = items.get(items.size() - 1); lastId = lastItem.getId(); } } - public static SliceResponse of(Slice sliceContent) { - return new SliceResponse<>(sliceContent); + public SliceResponse(Slice slice, List items) { + this.items = items; + this.size = slice.getSize(); + this.hasNext = slice.hasNext(); + + if (!items.isEmpty() && hasNext) { + T lastItem = items.get(items.size() - 1); + long lastId = lastItem.getId(); + this.lastId = lastId; + } + } + + public static SliceResponse of(Slice slice) { + return new SliceResponse<>(slice); + } + + public static SliceResponse of(Slice slice, List items) { + return new SliceResponse<>(slice, items); } } \ No newline at end of file From bf80a74a923d8f90058eadbd8441f92e77b108bb Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 18:15:29 +0900 Subject: [PATCH 234/734] =?UTF-8?q?refactor:=20MemberController=20?= =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20slice=20=EC=A0=81=EC=9A=A9=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index 62403390..08d84ce8 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -4,13 +4,19 @@ import io.oduck.api.domain.bookmark.service.BookmarkService; import io.oduck.api.domain.member.dto.MemberReqDto.CreateReq; import io.oduck.api.domain.member.dto.MemberReqDto.PatchReq; +import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; import io.oduck.api.domain.member.dto.MemberResDto.MemberProfileRes; import io.oduck.api.domain.member.service.MemberService; +import io.oduck.api.global.common.OrderDirection; +import io.oduck.api.global.common.SliceResponse; import io.oduck.api.global.security.auth.dto.AuthUser; import io.oduck.api.global.security.auth.dto.LoginUser; import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -19,6 +25,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.RequestParam; import org.springframework.web.bind.annotation.RestController; @Validated @@ -65,10 +72,14 @@ public ResponseEntity patchProfile( @GetMapping("/{id}/bookmarks") public ResponseEntity getBookmaks( - @PathVariable("id") Long id - ) { + @PathVariable("id") Long id, + @RequestParam(required = false) String cursor, + @RequestParam(required = false, defaultValue = "latest") Sort sort, + @RequestParam(required = false, defaultValue = "DESC") OrderDirection order, + @RequestParam(required = false, defaultValue = "10") @Min(1) @Max(100) int size + ) { // TODO: slice 및 정렬 구현 - List res = bookmarkService.getBookmarksByMemberId(id); + SliceResponse res = bookmarkService.getBookmarksByMemberId(id, cursor, sort, order, size); return ResponseEntity.ok(res); } // From 471ba39a7e1db715eead0c8a721b29d4a51b3121 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Thu, 19 Oct 2023 19:39:47 +0900 Subject: [PATCH 235/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#28?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/anime/entity/Anime.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 5f1d0ffd..06a17dca 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -237,6 +237,23 @@ public void updateAnimeGenre(List animeGenres) { } } + public void update(String title, String summary, BroadcastType broadcastType, int episodeCount, + String thumbnail, int year, Quarter quarter, Rating rating, Status status){ + this.title = title; + this.summary = summary; + this.broadcastType = broadcastType; + this.episodeCount = episodeCount; + this.thumbnail = thumbnail; + this.year = year; + this.quarter = quarter; + this.rating = rating; + this.status = status; + } + + public void update(Series series){ + this.series = series; + } + public static Anime createAnime(String title, String summary, BroadcastType broadcastType, int episodeCount, String thumbnail, int year, Quarter quarter, Rating rating, Status status, List animeOriginalAuthors, List animeStudios, List animeVoiceActors, List animeGenres, Series series) { From f7896ab167a4e66a1e1aff719a47d94728d694da Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 20:16:53 +0900 Subject: [PATCH 236/734] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20Initializer=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/initializer/AnimeConstruct.java | 44 ++--- .../global/initializer/MemberConstruct.java | 48 +++--- .../global/initializer/StubInitializer.java | 154 +++++++++--------- 3 files changed, 123 insertions(+), 123 deletions(-) diff --git a/src/test/java/io/oduck/api/global/initializer/AnimeConstruct.java b/src/test/java/io/oduck/api/global/initializer/AnimeConstruct.java index 94cf2033..cf70d96f 100644 --- a/src/test/java/io/oduck/api/global/initializer/AnimeConstruct.java +++ b/src/test/java/io/oduck/api/global/initializer/AnimeConstruct.java @@ -1,22 +1,22 @@ -package io.oduck.api.global.initializer; - -import io.oduck.api.domain.anime.repository.AnimeRepository; -import io.oduck.api.global.stub.AnimeStub; -import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.test.context.ActiveProfiles; - -@Component -@ActiveProfiles("test") -public class AnimeConstruct { - - @Autowired - AnimeRepository animeRepository; - - @PostConstruct - public void run() { - AnimeStub animeStub = new AnimeStub(); - animeRepository.saveAll(animeStub.getAnimes()); - } -} +//package io.oduck.api.global.initializer; +// +//import io.oduck.api.domain.anime.repository.AnimeRepository; +//import io.oduck.api.global.stub.AnimeStub; +//import jakarta.annotation.PostConstruct; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.stereotype.Component; +//import org.springframework.test.context.ActiveProfiles; +// +//@Component +//@ActiveProfiles("test") +//public class AnimeConstruct { +// +// @Autowired +// AnimeRepository animeRepository; +// +// @PostConstruct +// public void run() { +// AnimeStub animeStub = new AnimeStub(); +// animeRepository.saveAll(animeStub.getAnimes()); +// } +//} diff --git a/src/test/java/io/oduck/api/global/initializer/MemberConstruct.java b/src/test/java/io/oduck/api/global/initializer/MemberConstruct.java index 2a3c95e8..3b8c3af6 100644 --- a/src/test/java/io/oduck/api/global/initializer/MemberConstruct.java +++ b/src/test/java/io/oduck/api/global/initializer/MemberConstruct.java @@ -1,24 +1,24 @@ -package io.oduck.api.global.initializer; - -import io.oduck.api.domain.member.repository.MemberRepository; -import io.oduck.api.global.stub.MemberStub; -import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; -import org.springframework.test.context.ActiveProfiles; - -@Component -@ActiveProfiles("test") -public class MemberConstruct { - - @Autowired - MemberRepository memberRepository; - - @PostConstruct - public void run() { - MemberStub memberStub = new MemberStub(); - memberRepository.saveAll(memberStub.getMembers()); - } -} +//package io.oduck.api.global.initializer; +// +//import io.oduck.api.domain.member.repository.MemberRepository; +//import io.oduck.api.global.stub.MemberStub; +//import jakarta.annotation.PostConstruct; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.ApplicationArguments; +//import org.springframework.boot.ApplicationRunner; +//import org.springframework.stereotype.Component; +//import org.springframework.test.context.ActiveProfiles; +// +//@Component +//@ActiveProfiles("test") +//public class MemberConstruct { +// +// @Autowired +// MemberRepository memberRepository; +// +// @PostConstruct +// public void run() { +// MemberStub memberStub = new MemberStub(); +// memberRepository.saveAll(memberStub.getMembers()); +// } +//} diff --git a/src/test/java/io/oduck/api/global/initializer/StubInitializer.java b/src/test/java/io/oduck/api/global/initializer/StubInitializer.java index 747c36dd..ac687ca0 100644 --- a/src/test/java/io/oduck/api/global/initializer/StubInitializer.java +++ b/src/test/java/io/oduck/api/global/initializer/StubInitializer.java @@ -1,77 +1,77 @@ -package io.oduck.api.global.initializer; - -import io.oduck.api.domain.anime.entity.Anime; -import io.oduck.api.domain.anime.repository.AnimeRepository; -import io.oduck.api.domain.bookmark.entity.Bookmark; -import io.oduck.api.domain.bookmark.repository.BookmarkRepository; -import io.oduck.api.domain.member.entity.Member; -import io.oduck.api.domain.member.repository.MemberRepository; -import io.oduck.api.domain.starRating.entity.StarRating; -import io.oduck.api.domain.starRating.repository.StarRatingRepository; -import io.oduck.api.global.stub.BookmarkStub; -import io.oduck.api.global.stub.StarRatingStub; -import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; -import org.springframework.test.context.ActiveProfiles; - -@Component -@ActiveProfiles("test") -public class StubInitializer implements ApplicationRunner { - @Autowired - MemberRepository memberRepository; - - @Autowired - AnimeRepository animeRepository; - - @Autowired - BookmarkRepository bookmarkRepository; - - @Autowired - StarRatingRepository starRatingRepository; - - int bookmarkIterator = 0; - - @Override - public void run(ApplicationArguments args) throws Exception { - Member member = memberRepository.findById(1L).get(); - List members = memberRepository.findAll(); - List animes = animeRepository.findAll(); - - BookmarkStub bookmarkStub = new BookmarkStub(); - - bookmarkStub.getBookmarks().forEach(bookmark -> { - bookmark.relateAnime(animes.get(bookmarkIterator)); - bookmark.relateMemer(member); - bookmarkIterator++; - }); - - bookmarkRepository.saveAll(bookmarkStub.getBookmarks()); - - StarRatingStub starRatingStub = new StarRatingStub(); - - List starRatings = starRatingStub.getStarRatings(); - - StarRating starRating1 = starRatings.get(0); - starRating1.relateAnime(animes.get(0)); - starRating1.relateMember(members.get(0)); - - StarRating starRating2 = starRatings.get(1); - starRating2.relateAnime(animes.get(1)); - starRating2.relateMember(members.get(0)); - - StarRating starRating3 = starRatings.get(2); - starRating3.relateAnime(animes.get(0)); - starRating3.relateMember(members.get(1)); - - StarRating starRating4 = starRatings.get(3); - starRating4.relateAnime(animes.get(0)); - starRating4.relateMember(members.get(2)); - - starRatingRepository.saveAll(starRatings); - List bookmarkList = bookmarkRepository.findAll(); - List starRatingList = starRatingRepository.findAll(); - } -} +//package io.oduck.api.global.initializer; +// +//import io.oduck.api.domain.anime.entity.Anime; +//import io.oduck.api.domain.anime.repository.AnimeRepository; +//import io.oduck.api.domain.bookmark.entity.Bookmark; +//import io.oduck.api.domain.bookmark.repository.BookmarkRepository; +//import io.oduck.api.domain.member.entity.Member; +//import io.oduck.api.domain.member.repository.MemberRepository; +//import io.oduck.api.domain.starRating.entity.StarRating; +//import io.oduck.api.domain.starRating.repository.StarRatingRepository; +//import io.oduck.api.global.stub.BookmarkStub; +//import io.oduck.api.global.stub.StarRatingStub; +//import java.util.List; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.ApplicationArguments; +//import org.springframework.boot.ApplicationRunner; +//import org.springframework.stereotype.Component; +//import org.springframework.test.context.ActiveProfiles; +// +//@Component +//@ActiveProfiles("test") +//public class StubInitializer implements ApplicationRunner { +// @Autowired +// MemberRepository memberRepository; +// +// @Autowired +// AnimeRepository animeRepository; +// +// @Autowired +// BookmarkRepository bookmarkRepository; +// +// @Autowired +// StarRatingRepository starRatingRepository; +// +// int bookmarkIterator = 0; +// +// @Override +// public void run(ApplicationArguments args) throws Exception { +// Member member = memberRepository.findById(1L).get(); +// List members = memberRepository.findAll(); +// List animes = animeRepository.findAll(); +// +// BookmarkStub bookmarkStub = new BookmarkStub(); +// +// bookmarkStub.getBookmarks().forEach(bookmark -> { +// bookmark.relateAnime(animes.get(bookmarkIterator)); +// bookmark.relateMemer(member); +// bookmarkIterator++; +// }); +// +// bookmarkRepository.saveAll(bookmarkStub.getBookmarks()); +// +// StarRatingStub starRatingStub = new StarRatingStub(); +// +// List starRatings = starRatingStub.getStarRatings(); +// +// StarRating starRating1 = starRatings.get(0); +// starRating1.relateAnime(animes.get(0)); +// starRating1.relateMember(members.get(0)); +// +// StarRating starRating2 = starRatings.get(1); +// starRating2.relateAnime(animes.get(1)); +// starRating2.relateMember(members.get(0)); +// +// StarRating starRating3 = starRatings.get(2); +// starRating3.relateAnime(animes.get(0)); +// starRating3.relateMember(members.get(1)); +// +// StarRating starRating4 = starRatings.get(3); +// starRating4.relateAnime(animes.get(0)); +// starRating4.relateMember(members.get(2)); +// +// starRatingRepository.saveAll(starRatings); +// List bookmarkList = bookmarkRepository.findAll(); +// List starRatingList = starRatingRepository.findAll(); +// } +//} From fe2c59c755440a4c375dbbe60c3808e21eb9b988 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 20:17:30 +0900 Subject: [PATCH 237/734] =?UTF-8?q?test:=20test=20data=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/db/data.sql | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/test/resources/db/data.sql diff --git a/src/test/resources/db/data.sql b/src/test/resources/db/data.sql new file mode 100644 index 00000000..81349c49 --- /dev/null +++ b/src/test/resources/db/data.sql @@ -0,0 +1,33 @@ +INSERT INTO member(created_at, updated_at, login_type, `role`) VALUES('2023-10-10 21:05:31.859', '2023-10-10 21:05:31.859', 'LOCAL', 'MEMBER'); +INSERT INTO auth_local(created_at, member_id, updated_at, password, email) VALUES('2023-10-10 21:05:31.859', 1, '2023-10-10 21:05:31.859', '{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i', 'oduckdmin@gmail.com'); +INSERT INTO member_profile (created_at, member_id, updated_at, name, info, thumbnail) VALUES('2023-10-10 21:05:31.859', 1, '2023-10-10 21:05:31.859', 'admin', 'admin info', 'http://thumbnail.com'); + +INSERT INTO member(created_at, updated_at, login_type, `role`) VALUES('2023-10-11 21:05:31.859', '2023-10-11 21:05:31.859', 'LOCAL', 'MEMBER'); +INSERT INTO auth_local(created_at, member_id, updated_at, password, email) VALUES('2023-10-11 21:05:31.859', 2, '2023-10-11 21:05:31.859', '{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i', 'john@gmail.com'); +INSERT INTO member_profile (created_at, member_id, updated_at, name, info, thumbnail) VALUES('2023-10-11 21:05:31.859', 2, '2023-10-11 21:05:31.859', 'john', 'john info', 'http://thumbnail.com'); + +INSERT INTO member(created_at, updated_at, login_type, `role`) VALUES('2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859', 'LOCAL', 'MEMBER'); +INSERT INTO auth_local(created_at, member_id, updated_at, password, email) VALUES('2023-10-12 21:05:31.859', 3, '2023-10-12 21:05:31.859', '{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i', 'david@gmail.com'); +INSERT INTO member_profile (created_at, member_id, updated_at, name, info, thumbnail) VALUES('2023-10-12 21:05:31.859', 3, '2023-10-12 21:05:31.859', 'david', 'david info', 'http://thumbnail.com'); + +INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year) +VALUES(0, 1, '2023-10-10 21:05:31.859', '2023-10-10 21:05:31.859', '강연금', 'http://thumbnail.com', 'TVA', 'Q1', 'ALL', 'ONGOING', '1', 2009); +INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year) +VALUES(0, 1, '2023-10-11 21:05:31.859', '2023-10-11 21:05:31.859', '스파패', 'http://thumbnail.com', 'TVA', 'Q2', 'ALL', 'ONGOING', '2', 2023); +INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year) +VALUES(0, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859', '귀멸칼날', 'http://thumbnail.com', 'TVA', 'Q3', 'ALL', 'ONGOING', '3', 2022); + +INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(1, '2023-10-10 21:05:31.859', 1); +INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(2, '2023-10-11 21:05:31.859', 1); +INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(3, '2023-10-12 21:05:31.859', 1); + +INSERT INTO star_rating(score, anime_id, created_at, member_id, updated_at) VALUES(1, 1, '2023-10-10 21:05:31.859', 1, '2023-10-10 21:05:31.859'); +INSERT INTO star_rating(score, anime_id, created_at, member_id, updated_at) VALUES(2, 2, '2023-10-11 21:05:31.859', 1, '2023-10-11 21:05:31.859'); +INSERT INTO star_rating(score, anime_id, created_at, member_id, updated_at) VALUES(2, 1, '2023-10-12 21:05:31.859', 2, '2023-10-12 21:05:31.859'); +INSERT INTO star_rating(score, anime_id, created_at, member_id, updated_at) VALUES(3, 1, '2023-10-13 21:05:31.859', 3, '2023-10-13 21:05:31.859'); + +INSERT INTO short_review(has_spoiler, anime_id, created_at, member_id, updated_at, content) VALUES(0, 1, '2023-10-10 21:05:31.859', 1, '2023-10-10 21:05:31.859', '최고'); +INSERT INTO short_review(has_spoiler, anime_id, created_at, member_id, updated_at, content) VALUES(0, 2, '2023-10-11 21:05:31.859', 1, '2023-10-11 21:05:31.859', '힐링'); + +INSERT INTO short_review_like(created_at, member_id, short_review_id, updated_at) VALUES('2023-10-10 21:05:31.859', 1, 1, '2023-10-10 21:05:31.859'); +INSERT INTO short_review_like(created_at, member_id, short_review_id, updated_at) VALUES('2023-10-11 21:05:31.859', 1, 1, '2023-10-11 21:05:31.859'); \ No newline at end of file From 23520469fca52c4368ea6c82235ef812590bc2ca Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 21:42:56 +0900 Subject: [PATCH 238/734] =?UTF-8?q?test:=20test=20=EC=88=98=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/e2e/member/MemberControllerTest.java | 8 ++++---- .../e2e/shortReview/ShortReviewControllerTest.java | 9 +++++---- .../bookmark/repository/BookmarkRepositoryTest.java | 13 ++++++++----- .../member/repository/MemberRepositoryTest.java | 8 ++++---- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index b2bd6b73..b9228d8e 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -133,7 +133,7 @@ void getProfileByNameIfMine() throws Exception { .andExpect(jsonPath("$.isMine").value(true)) .andExpect(jsonPath("$.description").exists()) .andExpect(jsonPath("$.thumbnail").exists()) - .andExpect(jsonPath("$.backgroundImage").exists()) + .andExpect(jsonPath("$.backgroundImage").value(equalTo(null))) .andExpect(jsonPath("$.activity").hasJsonPath()) .andExpect(jsonPath("$.activity.reviews").exists()) .andExpect(jsonPath("$.activity.bookmarks").exists()) @@ -165,7 +165,7 @@ void getProfileByNameIfMine() throws Exception { .type(JsonFieldType.STRING) .description("프로필 이미지"), fieldWithPath("backgroundImage") - .type(JsonFieldType.STRING) + .type(JsonFieldType.NULL) .description("프로필 배경 이미지"), fieldWithPath("activity") .type(JsonFieldType.OBJECT) @@ -212,7 +212,7 @@ void getProfileByNameIfOthers() throws Exception { .andExpect(jsonPath("$.isMine").value(false)) .andExpect(jsonPath("$.description").exists()) .andExpect(jsonPath("$.thumbnail").exists()) - .andExpect(jsonPath("$.backgroundImage").exists()) + .andExpect(jsonPath("$.backgroundImage").value(equalTo(null))) .andExpect(jsonPath("$.activity").hasJsonPath()) .andExpect(jsonPath("$.activity.reviews").exists()) .andExpect(jsonPath("$.activity.bookmarks").exists()) @@ -244,7 +244,7 @@ void getProfileByNameIfOthers() throws Exception { .type(JsonFieldType.STRING) .description("프로필 이미지"), fieldWithPath("backgroundImage") - .type(JsonFieldType.STRING) + .type(JsonFieldType.NULL) .description("프로필 배경 이미지"), fieldWithPath("activity") .type(JsonFieldType.OBJECT) diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index f8623276..5a391f8d 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -78,6 +78,7 @@ void getShortReviews() throws Exception{ .andExpect(jsonPath("$.items[0].shortReviewLikeCount").exists()) .andExpect(jsonPath("$.items[0].member.name").exists()) .andExpect(jsonPath("$.items[0].member.thumbnail").exists()) + .andExpect(jsonPath("$.hasNext").exists()) .andDo(document("getShortReviews/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -108,8 +109,8 @@ void getShortReviews() throws Exception{ .type(JsonFieldType.NUMBER) .description("리뷰 좋아요 수") ,fieldWithPath("items[].member") - .type(JsonFieldType.OBJECT) - .description("회원 관련 데이터"), + .type(JsonFieldType.OBJECT) + .description("회원 관련 데이터"), fieldWithPath("items[].member.name") .type(JsonFieldType.STRING) .description("회원 이름"), @@ -125,9 +126,9 @@ void getShortReviews() throws Exception{ fieldWithPath("lastId") .type(JsonFieldType.NUMBER) .description("마지막 아이템의 id"), - fieldWithPath("lastPage") + fieldWithPath("hasNext") .type(JsonFieldType.BOOLEAN) - .description("마지막 페이지일 경우, true 반환.") + .description("마지막 페이지일 경우, false 반환.") ) )); diff --git a/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java b/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java index 316406c6..a54f1d21 100644 --- a/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java @@ -1,5 +1,6 @@ package io.oduck.api.unit.bookmark.repository; +import static io.oduck.api.global.utils.PagingUtils.applyPageableForNonOffset; import static org.junit.jupiter.api.Assertions.assertNotNull; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; @@ -12,6 +13,8 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.test.context.ActiveProfiles; @SpringBootTest @@ -30,15 +33,15 @@ class selectBookmarks { void selectBookmarksSuccess() { // given Long memberId = 1L; + Pageable pageable = applyPageableForNonOffset("createdAt", "desc", 10); // when - List bookmarks = memberRepository.selectBookmarks(memberId); + Slice bookmarks = memberRepository.selectBookmarks(memberId, null, pageable); assertNotNull(bookmarks); - assertNotNull(bookmarks.get(0).getAnimeId()); - assertNotNull(bookmarks.get(0).getTitle()); - assertNotNull(bookmarks.get(0).getThumbnail()); - assertNotNull(bookmarks.get(0).getMyScore()); + assertNotNull(bookmarks.getContent().get(0).getAnimeId()); + assertNotNull(bookmarks.getContent().get(0).getTitle()); + assertNotNull(bookmarks.getContent().get(0).getThumbnail()); } } diff --git a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java index 4e863f77..5dbac8ce 100644 --- a/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/member/repository/MemberRepositoryTest.java @@ -44,14 +44,14 @@ void saveMemberByLocalSuccess() { .build(); AuthLocal authLocal = AuthLocal.builder() - .email("bob@gmail.com") + .email("michael@gmail.com") .password("{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i") .build(); MemberProfile memberProfile = MemberProfile.builder() - .name("bob") - .info("bob info") - .thumbnail("bob thumbnail") + .name("michael") + .info("michael info") + .thumbnail("michael thumbnail") .point(0L) .build(); From b72c9af4b9058fd4563f3d408e84de922eaafcff Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 21:43:35 +0900 Subject: [PATCH 239/734] =?UTF-8?q?docs:=20slice=20=EC=A0=81=EC=9A=A9=20#2?= =?UTF-8?q?2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 02966098..85a13537 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -181,7 +181,7 @@ include::{snippets}/getShortReviews/success/path-parameters.adoc[] ==== 성공시 .http-response -include::{snippets}/getShortReviews/success/response-fields.adoc[] +include::{snippets}/getShortReviews/success/http-response.adoc[] .response-body include::{snippets}/getShortReviews/success/response-body.adoc[] From ab97037dfb151cdaf55f69465803b0358da46b0a Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Thu, 19 Oct 2023 22:42:03 +0900 Subject: [PATCH 240/734] =?UTF-8?q?test:=20sql=20series=20insert=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/db/data.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/resources/db/data.sql b/src/test/resources/db/data.sql index 81349c49..0228c9aa 100644 --- a/src/test/resources/db/data.sql +++ b/src/test/resources/db/data.sql @@ -17,6 +17,10 @@ VALUES(0, 1, '2023-10-11 21:05:31.859', '2023-10-11 21:05:31.859', '스파패', INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year) VALUES(0, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859', '귀멸칼날', 'http://thumbnail.com', 'TVA', 'Q3', 'ALL', 'ONGOING', '3', 2022); +INSERT INTO series(created_at, title, updated_at) VALUES('2023-10-10 21:05:31.859', '귀멸의 칼날', '2023-10-10 21:05:31.859'); +INSERT INTO series(created_at, title, updated_at) VALUES('2023-10-10 21:05:31.859', '원피스', '2023-10-10 21:05:31.859'); +INSERT INTO series(created_at, title, updated_at) VALUES('2023-10-10 21:05:31.859', '나루토', '2023-10-10 21:05:31.859'); + INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(1, '2023-10-10 21:05:31.859', 1); INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(2, '2023-10-11 21:05:31.859', 1); INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(3, '2023-10-12 21:05:31.859', 1); From 2e41492ca979de86f30beb4b3e274da7593a54da Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 18:34:24 +0900 Subject: [PATCH 241/734] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=95=88=20=ED=95=98=EB=8A=94=20import=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/member/controller/MemberController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index 08d84ce8..42433424 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -14,9 +14,7 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; -import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; From e113608db34a8b5daccb073829c12044c871e725 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 18:35:08 +0900 Subject: [PATCH 242/734] =?UTF-8?q?refactor:=20entityBased=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/bookmark/dto/BookmarkResDto.java | 5 +++-- .../io/oduck/api/domain/review/dto/ShortReviewResDto.java | 2 +- src/main/java/io/oduck/api/global/common/EntityBased.java | 2 +- .../java/io/oduck/api/global/common/SliceResponse.java | 7 ++++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java index 849a3aa9..eef58276 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java @@ -21,17 +21,18 @@ public static class BookmarkRes implements EntityBased { private LocalDateTime createdAt; public static BookmarkRes of(BookmarkDsl bookmarkDsl) { + return BookmarkRes.builder() .animeId(bookmarkDsl.getAnimeId()) .title(bookmarkDsl.getTitle()) .thumbnail(bookmarkDsl.getThumbnail()) - .myScore(bookmarkDsl.getMyScore()) + .myScore(bookmarkDsl.getMyScore() == null ? -1 : bookmarkDsl.getMyScore()) .createdAt(bookmarkDsl.getCreatedAt()) .build(); } @Override - public Long getId() { + public Long bringId() { return this.animeId; } } diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java index 3e3e8caf..ad693766 100644 --- a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java @@ -22,7 +22,7 @@ public static class ShortReview implements EntityBased { private MemberProfile member; @Override - public Long getId() { + public Long bringId() { return this.getReviewId(); } } diff --git a/src/main/java/io/oduck/api/global/common/EntityBased.java b/src/main/java/io/oduck/api/global/common/EntityBased.java index e89f44e2..f2e1e037 100644 --- a/src/main/java/io/oduck/api/global/common/EntityBased.java +++ b/src/main/java/io/oduck/api/global/common/EntityBased.java @@ -1,5 +1,5 @@ package io.oduck.api.global.common; public interface EntityBased { - Long getId(); + Long bringId(); } diff --git a/src/main/java/io/oduck/api/global/common/SliceResponse.java b/src/main/java/io/oduck/api/global/common/SliceResponse.java index 5a1b2678..f68aeb46 100644 --- a/src/main/java/io/oduck/api/global/common/SliceResponse.java +++ b/src/main/java/io/oduck/api/global/common/SliceResponse.java @@ -18,7 +18,7 @@ public SliceResponse(Slice sliceContent) { if (!items.isEmpty() && !hasNext) { T lastItem = items.get(items.size() - 1); - lastId = lastItem.getId(); + lastId = lastItem.bringId(); } } @@ -29,8 +29,9 @@ public SliceResponse(Slice slice, List items) { if (!items.isEmpty() && hasNext) { T lastItem = items.get(items.size() - 1); - long lastId = lastItem.getId(); - this.lastId = lastId; + this.lastId = lastItem.bringId(); + } else { + this.lastId = -1L; } } From ebc3d3aca9b0338e27f836c20b47e96ef12e9841 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 18:37:23 +0900 Subject: [PATCH 243/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=EC=9D=98=20?= =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/member/MemberControllerTest.java | 181 +++++++++++++++++- .../bookmark/service/BookmarkServiceTest.java | 65 +++++++ 2 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index b9228d8e..287a087f 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -14,6 +14,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -504,7 +505,183 @@ void updateProfileFailureWhenSameNameAsAlreadyExist() throws Exception { } } - // TODO: 회원이 작성한 리뷰 목록 + @DisplayName("회원의 북마크 애니 목록 조회") + @Nested + class GetBookmarks { + @DisplayName("회원의 북마크 애니 목록 조회 성공시 200 OK 응답") + @Test + void getBookmarksSuccess() throws Exception { + // given + // 회원의 북마크 애니 목록 조회에 필요한 데이터 + int size = 2; + String sort = "latest"; + String order = "desc"; + + // when + ResultActions actions = mockMvc.perform( + get("/members/{memberId}/bookmarks", 1) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .param("size", String.valueOf(size)) + .param("sort", sort) + .param("direction", order) + ); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.items").isArray()) + .andExpect(jsonPath("$.items[0].animeId").exists()) + .andExpect(jsonPath("$.items[0].title").exists()) + .andExpect(jsonPath("$.items[0].thumbnail").exists()) + .andExpect(jsonPath("$.size").exists()) + .andExpect(jsonPath("$.lastId").exists()) + .andExpect(jsonPath("$.hasNext").exists()) + .andDo( + document("getBookmarks/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("memberId") + .description("회원 id")), + queryParameters( + parameterWithName("size") + .attributes(field("constraints", "1-100, 기본 10")) + .optional() + .description("한 페이지에 보여줄 아이템의 개수"), + parameterWithName("sort") + .attributes(field("constraints", "latest")) + .optional() + .description("정렬 기준"), + parameterWithName("direction") + .attributes(field("constraints", "asc, desc")) + .optional() + .description("정렬 방향"), + parameterWithName("cursor") + .optional() + .description("마지막 아이템 id") + ), + responseFields( + fieldWithPath("items") + .type(JsonFieldType.ARRAY) + .description("애니 리스트"), + fieldWithPath("items[].animeId") + .type(JsonFieldType.NUMBER) + .description("애니 id"), + fieldWithPath("items[].title") + .type(JsonFieldType.STRING) + .description("애니 제목"), + fieldWithPath("items[].thumbnail") + .type(JsonFieldType.STRING) + .description("애니 썸네일"), + fieldWithPath("items[].myScore") + .type(JsonFieldType.NUMBER) + .description("해당 회원이 애니에 매긴 별점. 없을 경우 -1"), + fieldWithPath("items[].createdAt") + .type(JsonFieldType.STRING) + .description("애니 북마크 생성 날짜"), + fieldWithPath("size") + .type(JsonFieldType.NUMBER) + .description("한 페이지에 보여줄 아이템의 개수"), + fieldWithPath("hasNext") + .type(JsonFieldType.BOOLEAN) + .description("마지막 페이지 여부"), + fieldWithPath("lastId") + .type(JsonFieldType.NUMBER) + .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 -1") + ) + ) + ); + } + + @DisplayName("회원의 북마크 애니 목록 조회 성공시 200 OK 응답") + @Test + void getBookmarksSuccessWithCursor() throws Exception { + // given + // 회원의 북마크 애니 목록 조회에 필요한 데이터 + int size = 2; + String sort = "latest"; + String order = "desc"; + String cursor = "2"; + + // when + ResultActions actions = mockMvc.perform( + get("/members/{memberId}/bookmarks", 1) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .param("size", String.valueOf(size)) + .param("sort", sort) + .param("direction", order) + .param("cursor", cursor) + ); + + // then + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.items").isArray()) + .andExpect(jsonPath("$.items[0].animeId").exists()) + .andExpect(jsonPath("$.items[0].title").exists()) + .andExpect(jsonPath("$.items[0].thumbnail").exists()) + .andExpect(jsonPath("$.size").exists()) + .andExpect(jsonPath("$.lastId").exists()) + .andExpect(jsonPath("$.hasNext").exists()) + .andDo( + document("getBookmarks/successWithCursor", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("memberId") + .description("회원 id")), + queryParameters( + parameterWithName("size") + .attributes(field("constraints", "1-100, 기본 10")) + .optional() + .description("한 페이지에 보여줄 아이템의 개수"), + parameterWithName("sort") + .attributes(field("constraints", "latest")) + .optional() + .description("정렬 기준"), + parameterWithName("direction") + .attributes(field("constraints", "asc, desc")) + .optional() + .description("정렬 방향"), + parameterWithName("cursor") + .optional() + .description("마지막 아이템 id") + ), + responseFields( + fieldWithPath("items") + .type(JsonFieldType.ARRAY) + .description("애니 리스트"), + fieldWithPath("items[].animeId") + .type(JsonFieldType.NUMBER) + .description("애니 id"), + fieldWithPath("items[].title") + .type(JsonFieldType.STRING) + .description("애니 제목"), + fieldWithPath("items[].thumbnail") + .type(JsonFieldType.STRING) + .description("애니 썸네일"), + fieldWithPath("items[].myScore") + .type(JsonFieldType.NUMBER) + .description("해당 회원이 애니에 매긴 별점. 없을 경우 -1"), + fieldWithPath("items[].createdAt") + .type(JsonFieldType.STRING) + .description("애니 북마크 생성 날짜"), + fieldWithPath("size") + .type(JsonFieldType.NUMBER) + .description("한 페이지에 보여줄 아이템의 개수"), + fieldWithPath("hasNext") + .type(JsonFieldType.BOOLEAN) + .description("마지막 페이지 여부"), + fieldWithPath("lastId") + .type(JsonFieldType.NUMBER) + .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 -1") + ) + ) + ); + } + } - // TODO: 회원 북마크 애니 목록 + // TODO: 회원 리뷰 목록 조회 } diff --git a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java new file mode 100644 index 00000000..8a3022b9 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java @@ -0,0 +1,65 @@ +package io.oduck.api.unit.bookmark.service; + +import static io.oduck.api.global.utils.PagingUtils.applyPageableForNonOffset; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; +import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; +import io.oduck.api.domain.bookmark.repository.BookmarkRepository; +import io.oduck.api.domain.bookmark.service.BookmarkServiceImpl; +import io.oduck.api.global.common.OrderDirection; +import io.oduck.api.global.common.SliceResponse; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; + +@ExtendWith(MockitoExtension.class) +public class BookmarkServiceTest { + @InjectMocks + private BookmarkServiceImpl bookmarkService; + + @Mock + BookmarkRepository bookmarkRepository; + + @DisplayName("회원의 북마크 목록 조회") + @Nested + class GetBookmarksByMemberId { + @DisplayName("회원의 북마크 목록 조회 성공") + @Test + void getBookmarksByMemberIdSuccess() { + Long memberId = 1L; + String cursor = null; + Sort sort = Sort.LATEST; + OrderDirection order = OrderDirection.ASC; + int size = 10; + + List sampleBookmarks = new ArrayList<>(); + + Slice sampleSlice = new SliceImpl<>(sampleBookmarks); + + when(bookmarkRepository.selectBookmarks( + memberId, + cursor, + applyPageableForNonOffset(sort.getSort(), order.getOrder(), size) + )).thenReturn(sampleSlice); + + SliceResponse result = bookmarkService.getBookmarksByMemberId(memberId, cursor, sort, order, size); + + assertEquals(sampleSlice.getSize(), result.getItems().size()); + assertNotNull(result.getLastId()); + assertFalse(result.isHasNext()); + } + } +} From 4622e3af7434e31c19e21523e89fbc4de9b5879f Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 18:37:46 +0900 Subject: [PATCH 244/734] =?UTF-8?q?docs:=20=ED=9A=8C=EC=9B=90=EC=9D=98=20?= =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 85a13537..cb1148c5 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -143,6 +143,51 @@ include::{snippets}/patchProfile/failureWhenSameNameAsBefore/http-response.adoc[ .http-response include::{snippets}/patchProfile/failureWhenSameNameAsAlreadyExist/http-response.adoc[] +=== GET api/v1/members/:name/bookmarks +==== 성공시, cursor가 없을 경우 +.curl-request +include::{snippets}/getBookmarks/success/curl-request.adoc[] + +.http-request +include::{snippets}/getBookmarks/success/http-request.adoc[] + +.request-param +include::{snippets}/getBookmarks/success/path-parameters.adoc[] + +.query-parameters +include::{snippets}/getBookmarks/success/query-parameters.adoc[] + +.http-response +include::{snippets}/getBookmarks/success/http-response.adoc[] + +.response-body +include::{snippets}/getBookmarks/success/response-body.adoc[] + +.response-fields +include::{snippets}/getBookmarks/success/response-fields.adoc[] + +==== 성공시, cursor가 있을 경우 +.curl-request +include::{snippets}/getBookmarks/successWithCursor/curl-request.adoc[] + +.http-request +include::{snippets}/getBookmarks/successWithCursor/http-request.adoc[] + +.request-param +include::{snippets}/getBookmarks/successWithCursor/path-parameters.adoc[] + +.query-parameters +include::{snippets}/getBookmarks/successWithCursor/query-parameters.adoc[] + +.http-response +include::{snippets}/getBookmarks/successWithCursor/http-response.adoc[] + +.response-body +include::{snippets}/getBookmarks/successWithCursor/response-body.adoc[] + +.response-fields +include::{snippets}/getBookmarks/successWithCursor/response-fields.adoc[] + == animes === GET api/v1/animes/:id From 10dbc3f04bdacd5cbfec2b4f0f84c8b2c11d0143 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 21:33:56 +0900 Subject: [PATCH 245/734] =?UTF-8?q?refactor:=20PageableForOffset=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=88=98=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/global/utils/PagingUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/global/utils/PagingUtils.java b/src/main/java/io/oduck/api/global/utils/PagingUtils.java index cf852697..73006011 100644 --- a/src/main/java/io/oduck/api/global/utils/PagingUtils.java +++ b/src/main/java/io/oduck/api/global/utils/PagingUtils.java @@ -10,7 +10,7 @@ public static Pageable applyPageableForNonOffset(String property, String directi return PageRequest.of(0, size, direction.equals("desc") ? Direction.DESC : Direction.ASC, property); } - public static Pageable applyPageableForOffset(int page,String property, String direction, int size) { - return PageRequest.of(page, size, direction.equals("DESC") ? Direction.DESC : Direction.ASC, property); + public static Pageable applyPageableForOffset(int page, String property, String direction, int size) { + return PageRequest.of(page, size, direction.equals("desc") ? Direction.DESC : Direction.ASC, property); } } From ec728236563f9275c7c7d290e580d293e62cbcb0 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 21:35:41 +0900 Subject: [PATCH 246/734] =?UTF-8?q?refactor:=20enum=20=EA=B0=92=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java index 92626099..5f310a44 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java @@ -7,7 +7,7 @@ public class BookmarkReqDto { @Getter @AllArgsConstructor public enum Sort { - LATEST("createdAt"), + CREATED_AT("createdAt"), TITLE("title"), SCORE("score"); From 4a81c78f4a1d943844c65a3dc163c4ecbeb6aff3 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 21:36:06 +0900 Subject: [PATCH 247/734] =?UTF-8?q?refactor:=20builder=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java index eef58276..0a087f93 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java @@ -21,12 +21,13 @@ public static class BookmarkRes implements EntityBased { private LocalDateTime createdAt; public static BookmarkRes of(BookmarkDsl bookmarkDsl) { + Integer myScore = bookmarkDsl.getMyScore(); return BookmarkRes.builder() .animeId(bookmarkDsl.getAnimeId()) .title(bookmarkDsl.getTitle()) .thumbnail(bookmarkDsl.getThumbnail()) - .myScore(bookmarkDsl.getMyScore() == null ? -1 : bookmarkDsl.getMyScore()) + .myScore(myScore == null ? -1 : myScore) .createdAt(bookmarkDsl.getCreatedAt()) .build(); } From 8b4b04c4007535b4ef23e575098e5b8888301bfd Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 21:36:29 +0900 Subject: [PATCH 248/734] =?UTF-8?q?refactor:=20BookmarkRepository=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/BookmarkRepositoryImpl.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java index 415a36a2..cca64e76 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java @@ -12,6 +12,7 @@ import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; +import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -77,5 +78,26 @@ private BooleanExpression cursorCondition(String cursor, Pageable pageable) { } else { return bookmark.id.lt(Long.parseLong(cursor)); } + +// switch (property) { +// case "score": +// if (direction == Direction.ASC) { +// return starRating.score.gt(Double.parseDouble(cursor)); +// } else { +// return starRating.score.lt(Double.parseDouble(cursor)); +// } +// case "title": +// if (direction == Direction.ASC) { +// return anime.title.gt(cursor); +// } else { +// return anime.title.lt(cursor); +// } +// default: +// if (direction == Direction.ASC) { +// return bookmark.createdAt.gt(LocalDateTime.parse(cursor)); +// } else { +// return bookmark.createdAt.lt(LocalDateTime.parse(cursor)); +// } +// } } } From 7228bfaae641679dec5ed5bb0eed771e7543dd03 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 21:37:26 +0900 Subject: [PATCH 249/734] =?UTF-8?q?refactor:=20MemberController=20?= =?UTF-8?q?=EB=B6=81=EB=A7=88=ED=81=ACs=20=EC=A1=B0=ED=9A=8C=20sort=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=EA=B0=92=20=EC=88=98=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/member/controller/MemberController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java index 42433424..8af7c2f9 100644 --- a/src/main/java/io/oduck/api/domain/member/controller/MemberController.java +++ b/src/main/java/io/oduck/api/domain/member/controller/MemberController.java @@ -72,7 +72,7 @@ public ResponseEntity patchProfile( public ResponseEntity getBookmaks( @PathVariable("id") Long id, @RequestParam(required = false) String cursor, - @RequestParam(required = false, defaultValue = "latest") Sort sort, + @RequestParam(required = false, defaultValue = "created_at") Sort sort, @RequestParam(required = false, defaultValue = "DESC") OrderDirection order, @RequestParam(required = false, defaultValue = "10") @Min(1) @Max(100) int size ) { From ed39abd8bca0ee4a850a529cbb851631cec4cf6a Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 21:38:11 +0900 Subject: [PATCH 250/734] =?UTF-8?q?test:=20EntityBased,=20Bookmar.Sort=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/e2e/member/MemberControllerTest.java | 4 ++-- .../oduck/api/e2e/shortReview/ShortReviewControllerTest.java | 3 --- .../oduck/api/unit/bookmark/service/BookmarkServiceTest.java | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 287a087f..aaf19de0 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -514,7 +514,7 @@ void getBookmarksSuccess() throws Exception { // given // 회원의 북마크 애니 목록 조회에 필요한 데이터 int size = 2; - String sort = "latest"; + String sort = "created_at"; String order = "desc"; // when @@ -600,7 +600,7 @@ void getBookmarksSuccessWithCursor() throws Exception { // given // 회원의 북마크 애니 목록 조회에 필요한 데이터 int size = 2; - String sort = "latest"; + String sort = "created_at"; String order = "desc"; String cursor = "2"; diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index 5a391f8d..fa506283 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -117,9 +117,6 @@ void getShortReviews() throws Exception{ fieldWithPath("items[].member.thumbnail") .type(JsonFieldType.STRING) .description("회원 이미지 사진"), - fieldWithPath("items[].id") - .type(JsonFieldType.NUMBER) - .description("애니 아이디"), fieldWithPath("size") .type(JsonFieldType.NUMBER) .description("한 페이지에 보여줄 아이템의 개수"), diff --git a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java index 8a3022b9..4b3d63c7 100644 --- a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java +++ b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java @@ -41,7 +41,7 @@ class GetBookmarksByMemberId { void getBookmarksByMemberIdSuccess() { Long memberId = 1L; String cursor = null; - Sort sort = Sort.LATEST; + Sort sort = Sort.CREATED_AT; OrderDirection order = OrderDirection.ASC; int size = 10; From 04ff6af0d82f026c998d47477e13a31a86f35669 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Fri, 20 Oct 2023 21:46:02 +0900 Subject: [PATCH 251/734] =?UTF-8?q?fix:=20Anime=20Test=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EC=8B=A4=ED=8C=A8=20=ED=95=B4=EA=B2=B0=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/global/stub/AnimeStub.java | 82 +++++++++---------- .../anime/repository/AnimeRepositoryTest.java | 74 ++++++++++------- 2 files changed, 84 insertions(+), 72 deletions(-) diff --git a/src/test/java/io/oduck/api/global/stub/AnimeStub.java b/src/test/java/io/oduck/api/global/stub/AnimeStub.java index b9c2ffba..cea5a8c0 100644 --- a/src/test/java/io/oduck/api/global/stub/AnimeStub.java +++ b/src/test/java/io/oduck/api/global/stub/AnimeStub.java @@ -1,41 +1,41 @@ -package io.oduck.api.global.stub; - -import io.oduck.api.domain.anime.entity.Anime; -import java.util.ArrayList; -import java.util.List; - -public class AnimeStub { - List animes = new ArrayList<>(); - - public AnimeStub() { - Anime anime1 = Anime.builder() - .title("anime1") - .thumbnail("http://thumbnail.com") - .summary("summary") - .episodeCount(12) - .isReleased(true) - .build(); - - Anime anime2 = Anime.builder() - .title("anime2") - .thumbnail("http://thumbnail.com") - .summary("summary") - .episodeCount(12) - .isReleased(true) - .build(); - - Anime anime3 = Anime.builder() - .title("anime3") - .thumbnail("http://thumbnail.com") - .summary("summary") - .episodeCount(12) - .isReleased(true) - .build(); - - animes.addAll(List.of(anime1, anime2, anime3)); - } - - public List getAnimes() { - return animes; - } -} +//package io.oduck.api.global.stub; +// +//import io.oduck.api.domain.anime.entity.Anime; +//import java.util.ArrayList; +//import java.util.List; +// +//public class AnimeStub { +// List animes = new ArrayList<>(); +// +// public AnimeStub() { +// Anime anime1 = Anime.builder() +// .title("anime1") +// .thumbnail("http://thumbnail.com") +// .summary("summary") +// .episodeCount(12) +// .isReleased(true) +// .build(); +// +// Anime anime2 = Anime.builder() +// .title("anime2") +// .thumbnail("http://thumbnail.com") +// .summary("summary") +// .episodeCount(12) +// .isReleased(true) +// .build(); +// +// Anime anime3 = Anime.builder() +// .title("anime3") +// .thumbnail("http://thumbnail.com") +// .summary("summary") +// .episodeCount(12) +// .isReleased(true) +// .build(); +// +// animes.addAll(List.of(anime1, anime2, anime3)); +// } +// +// public List getAnimes() { +// return animes; +// } +//} diff --git a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java index ccbea69c..cc889891 100644 --- a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java @@ -45,6 +45,7 @@ @Transactional @ActiveProfiles("test") public class AnimeRepositoryTest { + @Autowired private AnimeRepository animeRepository; @@ -77,11 +78,11 @@ public class AnimeRepositoryTest { @Nested @DisplayName("애니 등록") - class PostAnime{ + class PostAnime { @Test @DisplayName("연관 관계 설정 성공") - void saveAnime(){ + void saveAnime() { /** * 원작 작가 연관 관계 설정 */ @@ -93,7 +94,8 @@ void saveAnime(){ originalAuthorRepository.save(originalAuthor); // 2. AnimeOriginalAuthor 생성 - AnimeOriginalAuthor animeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor(originalAuthor); + AnimeOriginalAuthor animeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor( + originalAuthor); List animeOriginalAuthors = new ArrayList<>(); animeOriginalAuthors.add(animeOriginalAuthor); @@ -123,9 +125,9 @@ void saveAnime(){ int voiceActorSize = 5; List voiceActors = new ArrayList<>(); - for(int i = 0; i < voiceActorSize; i++){ + for (int i = 0; i < voiceActorSize; i++) { VoiceActor voiceActor = VoiceActor.builder() - .name("성우"+i) + .name("성우" + i) .build(); voiceActors.add(voiceActor); } @@ -135,7 +137,8 @@ void saveAnime(){ // 2. AnimeVoiceActor 생성 List animeVoiceActors = new ArrayList<>(); for (VoiceActor voiceActor : voiceActors) { - AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("파트", voiceActor); + AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("파트", + voiceActor); animeVoiceActors.add(animeVoiceActor); } @@ -146,9 +149,9 @@ void saveAnime(){ int genreSize = 2; List genres = new ArrayList<>(); - for(int i = 0; i < genreSize; i++){ + for (int i = 0; i < genreSize; i++) { Genre genre = Genre.builder() - .name("장르"+i) + .name("장르" + i) .build(); genres.add(genre); } @@ -207,7 +210,7 @@ void saveAnime(){ @Test @DisplayName("연관 관계 설정 시 연결 테이블의 값이 없을 때") - void saveAnimeNoAnimeOriginalAuthor(){ + void saveAnimeNoAnimeOriginalAuthor() { List animeOriginalAuthors = new ArrayList<>(); List animeStudios = new ArrayList<>(); List animeVoiceActors = new ArrayList<>(); @@ -230,17 +233,18 @@ void saveAnimeNoAnimeOriginalAuthor(){ @Nested @DisplayName("애니 수정") - class patchAnime{ + class patchAnime { + @Test @DisplayName("애니 원작 작가 수정 성공") - void changeAnimeOriginalAuthors(){ + void changeAnimeOriginalAuthors() { // given // 초기 원작 작가 설정 String originalAuthorName = "작가"; int firstInsertSize = 2; List originalAuthors = new ArrayList<>(); - for(int i = 0; i < firstInsertSize; i++){ + for (int i = 0; i < firstInsertSize; i++) { OriginalAuthor originalAuthor = OriginalAuthor.builder() .name(originalAuthorName) .build(); @@ -252,7 +256,8 @@ void changeAnimeOriginalAuthors(){ // 원작 작가와 애니 연관 관계 테이블 연관 관계 맺기 List animeOriginalAuthors = new ArrayList<>(); for (OriginalAuthor originalAuthor : originalAuthors) { - AnimeOriginalAuthor animeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor(originalAuthor); + AnimeOriginalAuthor animeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor( + originalAuthor); animeOriginalAuthors.add(animeOriginalAuthor); } @@ -278,21 +283,23 @@ void changeAnimeOriginalAuthors(){ originalAuthorRepository.save(updatingOriginalAuthor); List updatingAnimeOriginalAuthors = new ArrayList<>(); - AnimeOriginalAuthor updatingAnimeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor(updatingOriginalAuthor); + AnimeOriginalAuthor updatingAnimeOriginalAuthor = AnimeOriginalAuthor.createAnimeOriginalAuthor( + updatingOriginalAuthor); updatingAnimeOriginalAuthors.add(updatingAnimeOriginalAuthor); // 애니 찾기 Anime findAnime = animeRepository.findById(savedAnimeId).get(); // 애니 수정 - findAnime.assignAnimeOriginalAuthors(updatingAnimeOriginalAuthors); + findAnime.updateAnimeOriginalAuthors(updatingAnimeOriginalAuthors); Long findAnimeId = findAnime.getId(); List findAnimeOriginalAuthors = animeOriginalAuthorRepository .findAllByAnimeId(findAnimeId); // then - String findOriginalAuthorName = findAnime.getAnimeOriginalAuthors().get(0).getOriginalAuthor().getName(); + String findOriginalAuthorName = findAnime.getAnimeOriginalAuthors().get(0) + .getOriginalAuthor().getName(); assertThat(findOriginalAuthorName).isEqualTo(updatingOriginalAuthorName); assertThat(findAnimeOriginalAuthors.size()).isNotEqualTo(firstInsertSize); assertThat(findAnimeOriginalAuthors.size()).isEqualTo(1); @@ -300,14 +307,14 @@ void changeAnimeOriginalAuthors(){ @Test @DisplayName("애니 스튜디오 수정 성공") - void changeAnimeStudios(){ + void changeAnimeStudios() { // given // 초기 스튜디오 설정 String studioName = "스튜디오"; int firstInsertSize = 2; List studios = new ArrayList<>(); - for(int i = 0; i findAnimeOriginalAuthors = animeStudioRepository.findAllByAnimeId(findAnimeId); + List findAnimeOriginalAuthors = animeStudioRepository.findAllByAnimeId( + findAnimeId); // then String findStudioName = findAnime.getAnimeStudios().get(0).getStudio().getName(); @@ -366,14 +374,14 @@ void changeAnimeStudios(){ @Test @DisplayName("애니 성우 수정 성공") - void changeAnimeVoiceActors(){ + void changeAnimeVoiceActors() { // given // 초기 성우 설정 String voiceActorName = "성우"; int firstInsertSize = 2; List voiceActors = new ArrayList<>(); - for(int i = 0; i < firstInsertSize; i++){ + for (int i = 0; i < firstInsertSize; i++) { VoiceActor voiceActor = VoiceActor.builder() .name(voiceActorName) .build(); @@ -385,7 +393,8 @@ void changeAnimeVoiceActors(){ // 성우와 애니 연관 관계 테이블 연관 관계 맺기 List animeVoiceActors = new ArrayList<>(); for (VoiceActor voiceActor : voiceActors) { - AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("part", voiceActor); + AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("part", + voiceActor); animeVoiceActors.add(animeVoiceActor); } @@ -411,20 +420,23 @@ void changeAnimeVoiceActors(){ voiceActorRepository.save(updatingVoiceActor); List updatingAnimeVoiceActors = new ArrayList<>(); - AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("part", updatingVoiceActor); + AnimeVoiceActor animeVoiceActor = AnimeVoiceActor.createAnimeVoiceActor("part", + updatingVoiceActor); updatingAnimeVoiceActors.add(animeVoiceActor); // 애니 찾기 Anime findAnime = animeRepository.findById(savedAnimeId).get(); // 애니 수정 - findAnime.assignAnimeVoiceActors(updatingAnimeVoiceActors); + findAnime.updateAnimeVoiceActors(updatingAnimeVoiceActors); Long findAnimeId = findAnime.getId(); - List findAnimeVoiceActors = animeVoiceActorRepository.findAllByAnimeId(findAnimeId); + List findAnimeVoiceActors = animeVoiceActorRepository.findAllByAnimeId( + findAnimeId); // then - String firstVoiceActorName = findAnime.getAnimeVoiceActors().get(0).getVoiceActor().getName(); + String firstVoiceActorName = findAnime.getAnimeVoiceActors().get(0).getVoiceActor() + .getName(); assertThat(firstVoiceActorName).isEqualTo(updatingVoiceActorName); assertThat(findAnimeVoiceActors.size()).isNotEqualTo(firstInsertSize); assertThat(findAnimeVoiceActors.size()).isEqualTo(1); @@ -432,14 +444,14 @@ void changeAnimeVoiceActors(){ @Test @DisplayName("애니 장르 수정 성공") - void changeAnimeGenres(){ + void changeAnimeGenres() { // given // 초기 장르 설정 String genreName = "장르"; int firstInsertSize = 2; List genres = new ArrayList<>(); - for(int i = 0; i < firstInsertSize; i++){ + for (int i = 0; i < firstInsertSize; i++) { Genre genre = Genre.builder() .name(genreName) .build(); @@ -484,7 +496,7 @@ void changeAnimeGenres(){ Anime findAnime = animeRepository.findById(savedAnimeId).get(); // 애니 수정 - findAnime.assignAnimeGenre(updatingAnimeGenres); + findAnime.updateAnimeGenre(updatingAnimeGenres); Long findAnimeId = findAnime.getId(); List findAnimeGenres = animeGenreRepository.findAllByAnimeId(findAnimeId); @@ -496,4 +508,4 @@ void changeAnimeGenres(){ assertThat(findAnimeGenres.size()).isEqualTo(1); } } -} +} \ No newline at end of file From 0c20ec8dec14238b842d47d27df2d1572687bd67 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 21 Oct 2023 15:01:16 +0900 Subject: [PATCH 252/734] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20memberI?= =?UTF-8?q?=E3=85=87=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/member/dto/MemberResDto.java | 4 +++- .../io/oduck/api/domain/member/service/MemberServiceImpl.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java index 5a94fb04..5d88e11d 100644 --- a/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java +++ b/src/main/java/io/oduck/api/domain/member/dto/MemberResDto.java @@ -9,6 +9,7 @@ public class MemberResDto { @NoArgsConstructor public static class MemberProfileRes { private boolean isMine; + private Long memberId; private String name; private String description; private String thumbnail; @@ -16,9 +17,10 @@ public static class MemberProfileRes { private Activity activity; @Builder - public MemberProfileRes(boolean isMine, String name, String description, String thumbnail, + public MemberProfileRes(boolean isMine, Long memberId, String name, String description, String thumbnail, String backgroundImage, Activity activity) { this.isMine = isMine; + this.memberId = memberId; this.name = name; this.description = description; this.thumbnail = thumbnail; diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java index e280bf8d..69c1da91 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java @@ -90,6 +90,7 @@ public MemberProfileRes getProfileByName(String name, Long memberId) { MemberProfileRes memberProfileRes = MemberProfileRes. builder() .isMine(memberProfile.getMemberId().equals(memberId)) + .memberId(memberProfile.getMemberId()) .name (memberProfile.getName()) .description(memberProfile.getDescription()) .thumbnail(memberProfile.getThumbnail()) From c25395b4ab3411f5997cafea3744742383449100 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sat, 21 Oct 2023 15:01:52 +0900 Subject: [PATCH 253/734] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20memberId=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20#22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/member/MemberControllerTest.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index aaf19de0..471e6964 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -130,8 +130,9 @@ void getProfileByNameIfMine() throws Exception { // 응답 결과 검증 후 문서화 actions .andExpect(status().isOk()) - .andExpect(jsonPath("$.name").exists()) .andExpect(jsonPath("$.isMine").value(true)) + .andExpect(jsonPath("$.memberId").exists()) + .andExpect(jsonPath("$.name").exists()) .andExpect(jsonPath("$.description").exists()) .andExpect(jsonPath("$.thumbnail").exists()) .andExpect(jsonPath("$.backgroundImage").value(equalTo(null))) @@ -153,12 +154,15 @@ void getProfileByNameIfMine() throws Exception { .description("Header Cookie, 세션 쿠키") ), responseFields( - fieldWithPath("name") - .type(JsonFieldType.STRING) - .description("회원 이름"), fieldWithPath("isMine") .type(JsonFieldType.BOOLEAN) .description("본인 여부(본인 프로필 조회시 true)"), + fieldWithPath("memberId") + .type(JsonFieldType.NUMBER) + .description("회원 id"), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .description("회원 이름"), fieldWithPath("description") .type(JsonFieldType.STRING) .description("자기 소개"), @@ -209,8 +213,9 @@ void getProfileByNameIfOthers() throws Exception { // 응답 결과 검증 후 문서화 actions .andExpect(status().isOk()) - .andExpect(jsonPath("$.name").exists()) .andExpect(jsonPath("$.isMine").value(false)) + .andExpect(jsonPath("$.memberId").exists()) + .andExpect(jsonPath("$.name").exists()) .andExpect(jsonPath("$.description").exists()) .andExpect(jsonPath("$.thumbnail").exists()) .andExpect(jsonPath("$.backgroundImage").value(equalTo(null))) @@ -232,12 +237,15 @@ void getProfileByNameIfOthers() throws Exception { .description("Header Cookie, 세션 쿠키") ), responseFields( - fieldWithPath("name") - .type(JsonFieldType.STRING) - .description("회원 이름"), fieldWithPath("isMine") .type(JsonFieldType.BOOLEAN) .description("본인 여부(본인 프로필 조회시 true)"), + fieldWithPath("memberId") + .type(JsonFieldType.NUMBER) + .description("회원 id"), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .description("회원 이름"), fieldWithPath("description") .type(JsonFieldType.STRING) .description("자기 소개"), From e89cd2b2f99d6ee8ed17bca0dfc4aabda44e3e7b Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 15:28:35 +0900 Subject: [PATCH 254/734] =?UTF-8?q?refactor:=20IllegalArgumentException?= =?UTF-8?q?=EC=9D=84=20BadRequestException=EC=9C=BC=EB=A1=9C=20=EB=8D=98?= =?UTF-8?q?=EC=A7=80=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/anime/entity/Anime.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 06a17dca..53ff2113 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -2,6 +2,7 @@ import io.oduck.api.domain.series.entity.Series; import io.oduck.api.global.audit.BaseEntity; +import io.oduck.api.global.exception.BadRequestException; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -131,7 +132,7 @@ public void setPrivateAnime(){ // 평가할 때 점수 합산(별점) public void increaseStarRatingScore(int score){ if(score <= 0){ - throw new IllegalArgumentException("음수는 올 수 없습니다."); + throw new BadRequestException("0 이하 올 수 없습니다."); } starRatingScoreTotal += score; increaseStarRatingCount(); @@ -140,7 +141,7 @@ public void increaseStarRatingScore(int score){ // 평가를 지웠을 때 점수 합산(별점) public void decreaseStarRatingScore(int score){ if(score <= 0){ - throw new IllegalArgumentException("음수는 올 수 없습니다."); + throw new BadRequestException("0 이하 올 수 없습니다."); } starRatingScoreTotal -= score; decreaseStarRatingCount(); From 9032f0a5a019387d321ae866ddd1ef30f1a7f6de Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 15:29:19 +0900 Subject: [PATCH 255/734] =?UTF-8?q?refactor:=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EC=9D=98=20=EB=A9=94=EC=86=8C=EB=93=9C=EB=AA=85,=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EB=B3=80=EA=B2=BD=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/anime/service/AnimeService.java | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java index e2c35c91..5982dc25 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java @@ -1,13 +1,8 @@ package io.oduck.api.domain.anime.service; -import io.oduck.api.domain.anime.dto.AnimeReq.*; +import static io.oduck.api.domain.anime.dto.AnimeReq.*; + import io.oduck.api.domain.anime.dto.AnimeRes; -import io.oduck.api.domain.anime.entity.AnimeGenre; -import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; -import io.oduck.api.domain.anime.entity.AnimeStudio; -import io.oduck.api.domain.anime.entity.AnimeVoiceActor; -import io.oduck.api.domain.series.entity.Series; -import java.util.List; public interface AnimeService { @@ -20,11 +15,9 @@ public interface AnimeService { /** * 애니 저장 로직 - * @param req * @return AnimeId */ Long save(PostReq req); - /** * 애니 수정 로직 * @param animeId @@ -37,33 +30,33 @@ public interface AnimeService { * @param animeId * @param originalAuthors */ - void updateAnimeOriginalAuthors(Long animeId, List originalAuthors); + void updateAnimeOriginalAuthors(Long animeId, PatchOriginalAuthorIdsReq originalAuthors); /** * 애니 스튜디오 수정 로직 * @param animeId - * @param animeStudios + * @param patchReq */ - void updateAnimeStudios(Long animeId, List animeStudios); + void updateAnimeStudios(Long animeId, PatchStudioIdsReq patchReq); /** * 애니 성우 수정 로직 * @param animeId - * @param animeVoiceActors + * @param patchReq */ - void updateAnimeVoiceActors(Long animeId, List animeVoiceActors); + void updateAnimeVoiceActors(Long animeId, PatchVoiceActorIdsReq patchReq); /** * 애니 장르 수정 로직 * @param animeId - * @param animeGenres + * @param patchReq */ - void updateAnimeGenres(Long animeId, List animeGenres); + void updateAnimeGenres(Long animeId, PatchGenreIdsReq patchReq); /** * 애니 시리즈 수정 로직 * @param animeId - * @param series + * @param patchReq */ - void update(Long animeId, Series series); + void updateSeries(Long animeId, PatchSeriesIdReq patchReq); } From 74527d40ad55e28acdb19c884eb62a5fb2b08d17 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 15:30:02 +0900 Subject: [PATCH 256/734] =?UTF-8?q?refactor:=20AnimeServiceStub=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C,=20AnimeServiceImpl=20=EA=B5=AC=ED=98=84=20#?= =?UTF-8?q?32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anime/service/AnimeServiceImpl.java | 274 ++++++++++++++++++ .../anime/service/AnimeServiceStub.java | 132 --------- 2 files changed, 274 insertions(+), 132 deletions(-) create mode 100644 src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java delete mode 100644 src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java new file mode 100644 index 00000000..f644ebe4 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -0,0 +1,274 @@ +package io.oduck.api.domain.anime.service; + +import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import io.oduck.api.domain.anime.dto.AnimeRes; +import io.oduck.api.domain.anime.dto.VoiceActorReq; +import io.oduck.api.domain.anime.dto.VoiceActorRes; +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.domain.anime.entity.BroadcastType; +import io.oduck.api.domain.anime.entity.Quarter; +import io.oduck.api.domain.anime.entity.Rating; +import io.oduck.api.domain.anime.entity.Status; +import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.genre.entity.Genre; +import io.oduck.api.domain.genre.repository.GenreRepository; +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +import io.oduck.api.domain.originalAuthor.repository.OriginalAuthorRepository; +import io.oduck.api.domain.series.entity.Series; +import io.oduck.api.domain.series.repository.SeriesRepository; +import io.oduck.api.domain.studio.entity.Studio; +import io.oduck.api.domain.studio.repository.StudioRepository; +import io.oduck.api.domain.voiceActor.entity.VoiceActor; +import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; +import io.oduck.api.global.exception.NotFoundException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class AnimeServiceImpl implements AnimeService{ + + private final AnimeRepository animeRepository; + private final OriginalAuthorRepository originalAuthorRepository; + private final VoiceActorRepository voiceActorRepository; + private final StudioRepository studioRepository; + private final GenreRepository genreRepository; + private final SeriesRepository seriesRepository; + + @Override + public AnimeRes getAnimeById(Long animeId) { + //TODO: 애니 상세 조회 구현 + AnimeRes anime = createAnimeDto(animeId); + + return anime; + } + + @Override + public Long save(PostReq postReq) { + // 원작 작가 + List originalAuthorIds = postReq.getOriginalAuthorIds(); + List originalAuthors = originalAuthorRepository.findAllById(originalAuthorIds); + + List animeOriginalAuthors = originalAuthors.stream() + .map(AnimeOriginalAuthor::createAnimeOriginalAuthor) + .collect(Collectors.toList()); + + // 애니에 참여한 성우 리스트 + List voiceActorDtoList = postReq.getVoiceActors(); + + // 성우의 아이디 리스트 구하기 + List voiceActorIds = voiceActorDtoList.stream() + .map(VoiceActorReq::getId) + .collect(Collectors.toList()); + + List voiceActors = voiceActorRepository.findAllById(voiceActorIds); + + // Id와 Part로 구성된 map 생성 + Map voiceActorDtoMap = voiceActorDtoList.stream() + .collect(Collectors.toMap(VoiceActorReq::getId, VoiceActorReq::getPart)); + + List animeVoiceActors = voiceActors.stream() + .filter(voiceActor -> voiceActorDtoMap.containsKey(voiceActor.getId())) + .map(voiceActor -> AnimeVoiceActor.createAnimeVoiceActor(voiceActorDtoMap.get(voiceActor.getId()), voiceActor)) + .collect(Collectors.toList()); + + // 스튜디오 + List studioIds = postReq.getStudioIds(); + List studios = studioRepository.findAllById(studioIds); + + List animeStudios = studios.stream() + .map(AnimeStudio::createAnimeStudio) + .collect(Collectors.toList()); + + // 장르 + List genreIds = postReq.getGenreIds(); + + List genres = genreRepository.findAllById(genreIds); + + List animeGenres = genres.stream() + .map(AnimeGenre::createAnimeGenre) + .collect(Collectors.toList()); + + // 시리즈 + Long seriesId = postReq.getSeriesId(); + Series series = createSeries(seriesId); + + + Anime anime = Anime.createAnime(postReq.getTitle(), postReq.getSummary(), postReq.getBroadcastType(), postReq.getEpisodeCount(), postReq.getThumbnail(), + postReq.getYear(), postReq.getQuarter(), postReq.getRating(), postReq.getStatus(), animeOriginalAuthors, animeStudios, animeVoiceActors, animeGenres, series); + return animeRepository.save(anime).getId(); + } + + + private Series createSeries(Long seriesId) { + if(seriesId == null){ + return null; + }else{ + return seriesRepository.findById(seriesId) + .orElseThrow(() -> new NotFoundException("Series")); + } + } + + @Override + public void update(Long animeId, PatchAnimeReq req) { + Anime anime = findAnime(animeId); + + anime.update(req.getTitle(), req.getSummary(), req.getBroadcastType(), req.getEpisodeCount(), req.getThumbnail(), req.getYear(), + req.getQuarter(), req.getRating(), req.getStatus()); + } + + @Override + public void updateAnimeOriginalAuthors(Long animeId, + PatchOriginalAuthorIdsReq patchReq) { + + Anime anime = findAnime(animeId); + + List originalAuthorIds = patchReq.getOriginalAuthorIds(); + List originalAuthors = originalAuthorRepository.findAllById(originalAuthorIds); + + List animeOriginalAuthors = originalAuthors.stream() + .map(AnimeOriginalAuthor::createAnimeOriginalAuthor) + .collect(Collectors.toList()); + + anime.updateAnimeOriginalAuthors(animeOriginalAuthors); + } + + @Override + public void updateAnimeStudios(Long animeId, PatchStudioIdsReq patchReq) { + Anime anime = findAnime(animeId); + List studioIds = patchReq.getStudioIds(); + + List studios = studioRepository.findAllById(studioIds); + List animeStudios = studios.stream() + .map(AnimeStudio::createAnimeStudio) + .collect(Collectors.toList()); + + anime.updateAnimeStudios(animeStudios); + } + + @Override + public void updateAnimeVoiceActors(Long animeId, PatchVoiceActorIdsReq patchReq) { + Anime anime = findAnime(animeId); + + // 애니에 참여한 성우 리스트 + List voiceActorDtoList = patchReq.getVoiceActors(); + + // 성우의 아이디 리스트 구하기 + List voiceActorIds = voiceActorDtoList.stream() + .map(VoiceActorReq::getId) + .collect(Collectors.toList()); + + List voiceActors = voiceActorRepository.findAllById(voiceActorIds); + + // Id와 Part로 구성된 map 생성 + Map voiceActorDtoMap = voiceActorDtoList.stream() + .collect(Collectors.toMap(VoiceActorReq::getId, VoiceActorReq::getPart)); + + List animeVoiceActors = voiceActors.stream() + .filter(va -> voiceActorDtoMap.containsKey(va.getId())) + .map(voiceActor -> AnimeVoiceActor.createAnimeVoiceActor(voiceActorDtoMap.get(voiceActor.getId()), voiceActor)) + .collect(Collectors.toList()); + + anime.updateAnimeVoiceActors(animeVoiceActors); + } + + @Override + public void updateAnimeGenres(Long animeId, PatchGenreIdsReq patchReq) { + Anime anime = findAnime(animeId); + + List genreIds = patchReq.getGenreIds(); + List genres = genreRepository.findAllById(genreIds); + + List animeGenres = genres.stream() + .map(AnimeGenre::createAnimeGenre) + .collect(Collectors.toList()); + + anime.updateAnimeGenre(animeGenres); + } + + @Override + public void updateSeries(Long animeId, PatchSeriesIdReq patchReq) { + Anime anime = findAnime(animeId); + + Long seriesId = patchReq.getSeriesId(); + Series series = seriesRepository.findById(seriesId) + .orElseThrow(() -> new NotFoundException("Series")); + + anime.update(series); + } + + @Transactional(readOnly = true) + public Anime findAnime(Long animeId) { + return animeRepository.findById(animeId).orElseThrow(() -> new NotFoundException("Anime")); + } + + private AnimeRes createAnimeDto(Long animeId) { + return AnimeRes.builder() + .id(animeId) + .title("귀멸의 칼날: 도공 마을편") + .thumbnail("https://image파일경로/uuid.jpg") + .broadcastType(BroadcastType.TVA) + .year(2023) + .quarter(Quarter.Q2) + .summary( + "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다. 탄지로가 검이 수리되기를 기다리는 동안, 상현 혈귀 한텐구와 쿗코가 숨겨진 마을인 ‘도공 마을'을 습격한다. 공격할 때마다 분열해서 위력이 커지는 한텐구로 인해 탄지로와 겐야는 고전을 면치 못한다. 한편, 타인에 대한 관심이 희박한 하주 토키토 무이치로는 혈귀들에게 공격당하고 있는 코테츠를 목격하는데….") + .episodeCount(11) + .rating(Rating.ADULT) + .status(Status.FINISHED) + .genres(getGenres()) + .originalAuthors(getOriginalAuthors()) + .voiceActors(getVoiceActors()) + .studios(getStudios()) + .reviewCount(172) + .bookmarkCount(72) + .build(); + } + + private List getStudios() { + List studios = new ArrayList<>(); + studios.add("ufotable"); + return studios; + } + + private List getVoiceActors() { + List voiceActors = new ArrayList<>(); + + for(int i = 0; i<5; i++){ + VoiceActorRes voiceActor = new VoiceActorRes("성우"+i, "카마도 탄지로"+i); + voiceActors.add(voiceActor); + } + + return voiceActors; + } + + private List getGenres(){ + List genres = new ArrayList<>(); + genres.add("판타지"); + genres.add("액션"); + return genres; + } + + private List getOriginalAuthors() { + List originalAuthors = new ArrayList<>(); + originalAuthors.add("고토게 코요하루"); + return originalAuthors; + } +} diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java deleted file mode 100644 index 9118a856..00000000 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceStub.java +++ /dev/null @@ -1,132 +0,0 @@ -package io.oduck.api.domain.anime.service; - -import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; -import io.oduck.api.domain.anime.dto.AnimeRes; -import io.oduck.api.domain.anime.dto.AnimeRes.Anime; -import io.oduck.api.domain.anime.entity.AnimeGenre; -import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; -import io.oduck.api.domain.anime.entity.AnimeStudio; -import io.oduck.api.domain.anime.entity.AnimeVoiceActor; -import io.oduck.api.domain.anime.entity.BroadcastType; -import io.oduck.api.domain.anime.entity.Quarter; -import io.oduck.api.domain.anime.entity.Rating; -import io.oduck.api.domain.anime.entity.Status; -import io.oduck.api.domain.series.entity.Series; -import java.util.ArrayList; -import java.util.List; -import org.springframework.stereotype.Service; - -@Service -public class AnimeServiceStub implements AnimeService{ - - @Override - public Long save(PostReq req) { - return 1L; - } - - @Override - public AnimeRes getAnimeById(Long animeId) { - Anime anime = createAnime(animeId); - - return AnimeRes.builder() - .anime(anime) - .build(); - } - - @Override - public void update(Long animeId, PatchAnimeReq req) { - - } - - @Override - public void updateAnimeOriginalAuthors(Long animeId, List originalAuthors) { - - } - - @Override - public void updateAnimeStudios(Long animeId, List animeStudios) { - - } - - @Override - public void updateAnimeVoiceActors(Long animeId, List animeVoiceActors) { - - } - - @Override - public void updateAnimeGenres(Long animeId, List animeGenres) { - - } - - @Override - public void update(Long animeId, Series series) { - - } - - private Anime createAnime(Long animeId) { - return Anime.builder() - .id(animeId) - .title("귀멸의 칼날: 도공 마을편") - .thumbnail("https://image파일경로/uuid.jpg") - .broadcastType(BroadcastType.TVA) - .year(2023) - .quarter(Quarter.Q2) - .summary( - "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다. 탄지로가 검이 수리되기를 기다리는 동안, 상현 혈귀 한텐구와 쿗코가 숨겨진 마을인 ‘도공 마을'을 습격한다. 공격할 때마다 분열해서 위력이 커지는 한텐구로 인해 탄지로와 겐야는 고전을 면치 못한다. 한편, 타인에 대한 관심이 희박한 하주 토키토 무이치로는 혈귀들에게 공격당하고 있는 코테츠를 목격하는데….") - .episodeCount(11) - .rating(Rating.ADULT) - .status(Status.FINISHED) - .genres(getGenres()) - .originalAuthors(getOriginalAuthors()) - .voiceActors(getVoiceActors()) - .studios(getStudios()) - .reviewCount(172) - .bookmarkCount(72) - .build(); - } - - private List getStudios() { - List studios = new ArrayList<>(); - studios.add("ufotable"); - return studios; - } - - private List getVoiceActors() { - List voiceActors = new ArrayList<>(); - voiceActors.add("하나에 나츠키"); - voiceActors.add("키토 아카리"); - voiceActors.add("카와니시 켄고"); - voiceActors.add("하나자와 카나"); - voiceActors.add("오카모토 노부히코"); - voiceActors.add("나미카와 다이스케"); - voiceActors.add("후루카와 토시오"); - voiceActors.add("토리우미 코스케"); - voiceActors.add("타케우치 슌스케"); - voiceActors.add("우메하라 유이치로"); - voiceActors.add("사이토 소마"); - voiceActors.add("이시카와 카이토"); - voiceActors.add("야마데라 코이치"); - voiceActors.add("무라세 아유무"); - voiceActors.add("타케모토 에이지"); - voiceActors.add("야라 유사쿠"); - voiceActors.add("시모노 히로"); - voiceActors.add("마츠오카 요시츠구"); - voiceActors.add("우에다 레이나"); - voiceActors.add("에하라 유리"); - return voiceActors; - } - - private List getGenres(){ - List genres = new ArrayList<>(); - genres.add("판타지"); - genres.add("액션"); - return genres; - } - - private List getOriginalAuthors() { - List originalAuthors = new ArrayList<>(); - originalAuthors.add("고토게 코요하루"); - return originalAuthors; - } -} From b008a8e0355cf07d48149373021acd294c0d978c Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 16:41:51 +0900 Subject: [PATCH 257/734] =?UTF-8?q?feat:=20=EC=9B=90=EC=9E=91=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80,=20=EC=8A=A4=ED=8A=9C=EB=94=94=EC=98=A4,=20=EC=84=B1?= =?UTF-8?q?=EC=9A=B0,=20=EC=9E=A5=EB=A5=B4,=20=EC=8B=9C=EB=A6=AC=EC=A6=88?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=B6=94=EA=B0=80=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/genre/service/GenreService.java | 5 +++++ .../domain/genre/service/GenreServiceImpl.java | 16 ++++++++++++++++ .../service/OriginalAuthorService.java | 5 +++++ .../service/OriginalAuthorServiceImpl.java | 16 ++++++++++++++++ .../domain/series/service/SeriesService.java | 5 +++++ .../series/service/SeriesServiceImpl.java | 16 ++++++++++++++++ .../domain/studio/service/StudioService.java | 4 ++++ .../studio/service/StudioServiceImpl.java | 17 +++++++++++++++++ .../voiceActor/service/VoiceActorService.java | 4 ++++ .../service/VoiceActorServiceImpl.java | 16 ++++++++++++++++ 10 files changed, 104 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/genre/service/GenreService.java create mode 100644 src/main/java/io/oduck/api/domain/genre/service/GenreServiceImpl.java create mode 100644 src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorService.java create mode 100644 src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorServiceImpl.java create mode 100644 src/main/java/io/oduck/api/domain/series/service/SeriesService.java create mode 100644 src/main/java/io/oduck/api/domain/series/service/SeriesServiceImpl.java create mode 100644 src/main/java/io/oduck/api/domain/studio/service/StudioService.java create mode 100644 src/main/java/io/oduck/api/domain/studio/service/StudioServiceImpl.java create mode 100644 src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorService.java create mode 100644 src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorServiceImpl.java diff --git a/src/main/java/io/oduck/api/domain/genre/service/GenreService.java b/src/main/java/io/oduck/api/domain/genre/service/GenreService.java new file mode 100644 index 00000000..bb87e0d3 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/genre/service/GenreService.java @@ -0,0 +1,5 @@ +package io.oduck.api.domain.genre.service; + +public interface GenreService { + +} diff --git a/src/main/java/io/oduck/api/domain/genre/service/GenreServiceImpl.java b/src/main/java/io/oduck/api/domain/genre/service/GenreServiceImpl.java new file mode 100644 index 00000000..41f5b60d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/genre/service/GenreServiceImpl.java @@ -0,0 +1,16 @@ +package io.oduck.api.domain.genre.service; + +import io.oduck.api.domain.genre.repository.GenreRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class GenreServiceImpl implements GenreService{ + + private final GenreRepository genreRepository; +} diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorService.java b/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorService.java new file mode 100644 index 00000000..0b6b38f7 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorService.java @@ -0,0 +1,5 @@ +package io.oduck.api.domain.originalAuthor.service; + +public interface OriginalAuthorService { + +} diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorServiceImpl.java b/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorServiceImpl.java new file mode 100644 index 00000000..4442af73 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorServiceImpl.java @@ -0,0 +1,16 @@ +package io.oduck.api.domain.originalAuthor.service; + +import io.oduck.api.domain.originalAuthor.repository.OriginalAuthorRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class OriginalAuthorServiceImpl implements OriginalAuthorService{ + + private final OriginalAuthorRepository originalAuthorRepository; +} diff --git a/src/main/java/io/oduck/api/domain/series/service/SeriesService.java b/src/main/java/io/oduck/api/domain/series/service/SeriesService.java new file mode 100644 index 00000000..11a31d17 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/series/service/SeriesService.java @@ -0,0 +1,5 @@ +package io.oduck.api.domain.series.service; + +public interface SeriesService { + +} diff --git a/src/main/java/io/oduck/api/domain/series/service/SeriesServiceImpl.java b/src/main/java/io/oduck/api/domain/series/service/SeriesServiceImpl.java new file mode 100644 index 00000000..66127a64 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/series/service/SeriesServiceImpl.java @@ -0,0 +1,16 @@ +package io.oduck.api.domain.series.service; + +import io.oduck.api.domain.series.repository.SeriesRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class SeriesServiceImpl implements SeriesService{ + + private final SeriesRepository seriesRepository; +} diff --git a/src/main/java/io/oduck/api/domain/studio/service/StudioService.java b/src/main/java/io/oduck/api/domain/studio/service/StudioService.java new file mode 100644 index 00000000..452bb321 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/studio/service/StudioService.java @@ -0,0 +1,4 @@ +package io.oduck.api.domain.studio.service; + +public interface StudioService { +} diff --git a/src/main/java/io/oduck/api/domain/studio/service/StudioServiceImpl.java b/src/main/java/io/oduck/api/domain/studio/service/StudioServiceImpl.java new file mode 100644 index 00000000..5b6c0d3d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/studio/service/StudioServiceImpl.java @@ -0,0 +1,17 @@ +package io.oduck.api.domain.studio.service; + +import io.oduck.api.domain.studio.repository.StudioRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class StudioServiceImpl implements StudioService{ + + private final StudioRepository studioRepository; + +} diff --git a/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorService.java b/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorService.java new file mode 100644 index 00000000..1ce4346e --- /dev/null +++ b/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorService.java @@ -0,0 +1,4 @@ +package io.oduck.api.domain.voiceActor.service; + +public interface VoiceActorService { +} diff --git a/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorServiceImpl.java b/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorServiceImpl.java new file mode 100644 index 00000000..e7e64523 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorServiceImpl.java @@ -0,0 +1,16 @@ +package io.oduck.api.domain.voiceActor.service; + +import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j +public class VoiceActorServiceImpl implements VoiceActorService{ + + private final VoiceActorRepository voiceActorRepository; +} From 78de1fd22a863e52752e845fcb9909d4c8a3123f Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 16:42:24 +0900 Subject: [PATCH 258/734] =?UTF-8?q?feat:=20=EC=84=B1=EC=9A=B0=20dto=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/dto/VoiceActorReq.java | 13 +++++++++++++ .../oduck/api/domain/anime/dto/VoiceActorRes.java | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/anime/dto/VoiceActorReq.java create mode 100644 src/main/java/io/oduck/api/domain/anime/dto/VoiceActorRes.java diff --git a/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorReq.java b/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorReq.java new file mode 100644 index 00000000..859aae15 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorReq.java @@ -0,0 +1,13 @@ +package io.oduck.api.domain.anime.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class VoiceActorReq { + private Long id; + private String part; +} diff --git a/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorRes.java b/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorRes.java new file mode 100644 index 00000000..7e608278 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorRes.java @@ -0,0 +1,13 @@ +package io.oduck.api.domain.anime.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class VoiceActorRes { + private String name; + private String part; +} From 1ff85a3914d7d9df74c8608574ce347e026e3b9a Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 16:45:27 +0900 Subject: [PATCH 259/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20dto,=20=EC=9D=91=EB=8B=B5=20dto=EC=9D=98=20?= =?UTF-8?q?=EC=84=B1=EC=9A=B0=20List=20=EC=88=98=EC=A0=95=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/dto/AnimeReq.java | 20 +++++----- .../oduck/api/domain/anime/dto/AnimeRes.java | 38 ++++++++----------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java index a0ce1168..13288072 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java @@ -51,7 +51,7 @@ public static class PostReq { private List studioIds; - private List voiceActorIds; + private List voiceActors; private List genreIds; @@ -59,7 +59,7 @@ public static class PostReq { public PostReq (String title, String summary, BroadcastType broadcastType, int episodeCount, String thumbnail, int year, Quarter quarter, Rating rating, Status status, - List originalAuthorIds, List studioIds, List voiceActorIds, + List originalAuthorIds, List studioIds, List voiceActors, List genreIds, Long seriesId) { this.title = title; @@ -84,10 +84,10 @@ public PostReq (String title, String summary, BroadcastType broadcastType, this.studioIds = studioIds; } - if(voiceActorIds == null) { - this.voiceActorIds = new ArrayList<>(); + if(voiceActors == null) { + this.voiceActors = new ArrayList<>(); }else { - this.voiceActorIds = voiceActorIds; + this.voiceActors = voiceActors; } if(genreIds == null) { @@ -166,13 +166,13 @@ public PatchStudioIdsReq(List studioIds) { @Getter @NoArgsConstructor public static class PatchVoiceActorIdsReq { - private List voiceActorIds; + private List voiceActors; - public PatchVoiceActorIdsReq(List voiceActorIds) { - if(voiceActorIds == null){ - this.voiceActorIds = new ArrayList<>(); + public PatchVoiceActorIdsReq(List voiceActors) { + if(voiceActors == null){ + this.voiceActors = new ArrayList<>(); }else{ - this.voiceActorIds = voiceActorIds; + this.voiceActors = voiceActors; } } } diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java index a0d84a60..83624fd9 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -11,27 +11,21 @@ @Getter @Builder public class AnimeRes { - private Anime anime; + private Long id; + private String title; + private String thumbnail; + private BroadcastType broadcastType; + private int year; + private Quarter quarter; + private String summary; + private int episodeCount; + private Rating rating; + private Status status; - @Getter - @Builder - public static class Anime { - private Long id; - private String title; - private String thumbnail; - private BroadcastType broadcastType; - private int year; - private Quarter quarter; - private String summary; - private int episodeCount; - private Rating rating; - private Status status; - - private List genres; - private List originalAuthors; - private List voiceActors; - private List studios; - private long reviewCount; - private long bookmarkCount; - } + private List genres; + private List originalAuthors; + private List voiceActors; + private List studios; + private long reviewCount; + private long bookmarkCount; } From 2b4e0105b875be026eced607f6c2134d2017b57a Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 16:52:40 +0900 Subject: [PATCH 260/734] =?UTF-8?q?refactor:=20@DisplayName=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/unit/anime/service/AnimeServiceTest.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index 2ad167bf..d669ab26 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -21,9 +21,8 @@ public class AnimeServiceTest { private AnimeServiceStub animeService; @Nested - @DisplayName("애니 조회") - class GetAnime{ - + @DisplayName("조회") + class findAnime{ @Test @DisplayName("애니 상세 조회") void getAnimeById() { @@ -40,8 +39,13 @@ void getAnimeById() { } @Nested - @DisplayName("애니 등록") - class PostAnime{ + @DisplayName("등록") + class SaveAnime{ + Anime anime = createAnime(); + List originalAuthors = getOriginalAuthors(); + List voiceActors = getVoiceActors(); + List studios = getStudios(); + List genres = AnimeTestUtils.getGenres(); @Test @DisplayName("애니 등록 성공") From 587884377b7ab6712cc3cf87d02e76b79231b3e9 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 16:54:33 +0900 Subject: [PATCH 261/734] =?UTF-8?q?refactor:=20Anime=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=95=84=EC=9D=B4=EB=94=94=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=95=98=EC=A7=80=20=EC=95=8A=EA=B3=A0=20voi?= =?UTF-8?q?d=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/anime/service/AnimeService.java | 2 +- .../io/oduck/api/domain/anime/service/AnimeServiceImpl.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java index 5982dc25..ef6f673f 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java @@ -17,7 +17,7 @@ public interface AnimeService { * 애니 저장 로직 * @return AnimeId */ - Long save(PostReq req); + void save(PostReq req); /** * 애니 수정 로직 * @param animeId diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index f644ebe4..ecea8bdc 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -62,7 +62,7 @@ public AnimeRes getAnimeById(Long animeId) { } @Override - public Long save(PostReq postReq) { + public void save(PostReq postReq) { // 원작 작가 List originalAuthorIds = postReq.getOriginalAuthorIds(); List originalAuthors = originalAuthorRepository.findAllById(originalAuthorIds); @@ -114,7 +114,8 @@ public Long save(PostReq postReq) { Anime anime = Anime.createAnime(postReq.getTitle(), postReq.getSummary(), postReq.getBroadcastType(), postReq.getEpisodeCount(), postReq.getThumbnail(), postReq.getYear(), postReq.getQuarter(), postReq.getRating(), postReq.getStatus(), animeOriginalAuthors, animeStudios, animeVoiceActors, animeGenres, series); - return animeRepository.save(anime).getId(); + + animeRepository.save(anime); } From 187e4312778bfd05ebb1a9b28e2ae5bd3e356fd9 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 16:55:45 +0900 Subject: [PATCH 262/734] =?UTF-8?q?refactor:=20AnimeTestUtils=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/global/utils/AnimeTestUtils.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java index d3995b88..3de3886a 100644 --- a/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java +++ b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java @@ -14,7 +14,7 @@ public static PostReq createPostAnimeRequest(){ return new PostReq( getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), getYear(), getQuarter(), getRating(), getStatus(), - getOriginalAuthorIds(), getStudioIds(), getVoiceActorIds(), getGenreIds(), getSeriesId()); + getOriginalAuthorIds(), getStudioIds(), getVoiceActorReqs(), getGenreIds(), getSeriesId()); } public static PatchAnimeReq createPatchAnimeRequest(){ @@ -71,11 +71,13 @@ public static List getGenreIds() { return genreIds; } - public static List getVoiceActorIds() { - List voiceActorIds = getStudioIds(); - voiceActorIds.add(2L); - voiceActorIds.add(4L); - return voiceActorIds; + public static List getVoiceActorReqs() { + List voiceActors = new ArrayList<>(); + VoiceActorReq firstDto = new VoiceActorReq(5L, "카마도 탄지로"); + VoiceActorReq secondDto = new VoiceActorReq(6L, "카마도 네즈코"); + voiceActors.add(firstDto); + voiceActors.add(secondDto); + return voiceActors; } public static List getStudioIds() { @@ -86,7 +88,7 @@ public static List getStudioIds() { public static List getOriginalAuthorIds() { List originalAuthorIds = new ArrayList(); - originalAuthorIds.add(5L); + originalAuthorIds.add(1L); return originalAuthorIds; } } From 2e204d11bdd153365c3218ae6c2f48fde950ac0a Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 16:56:07 +0900 Subject: [PATCH 263/734] =?UTF-8?q?feat:=20AnimeTestUtils=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/global/utils/AnimeTestUtils.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java index 3de3886a..4f9ea555 100644 --- a/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java +++ b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java @@ -2,10 +2,17 @@ import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import io.oduck.api.domain.anime.dto.VoiceActorReq; +import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; import io.oduck.api.domain.anime.entity.Rating; import io.oduck.api.domain.anime.entity.Status; +import io.oduck.api.domain.genre.entity.Genre; +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +import io.oduck.api.domain.series.entity.Series; +import io.oduck.api.domain.studio.entity.Studio; +import io.oduck.api.domain.voiceActor.entity.VoiceActor; import java.util.ArrayList; import java.util.List; @@ -91,4 +98,74 @@ public static List getOriginalAuthorIds() { originalAuthorIds.add(1L); return originalAuthorIds; } + + public static Anime createAnime() { + Anime anime = Anime.createAnime(getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), + getThumbnail(), getYear(), getQuarter(), getRating(), getStatus(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), null); + return anime; + } + + public static List getOriginalAuthors() { + List originalAuthors = new ArrayList<>(); + + for(int i = 0; i<2; i++){ + OriginalAuthor originalAuthor = OriginalAuthor.builder() + .name("작가"+i) + .build(); + originalAuthors.add(originalAuthor); + } + return originalAuthors; + } + + public static List getVoiceActors() { + List voiceActors = new ArrayList<>(); + + VoiceActor firstVoiceActor = VoiceActor.builder() + .id(5L) + .name("성우A") + .build(); + VoiceActor secondVoiceActor = VoiceActor.builder() + .id(6L) + .name("성우B") + .build(); + + voiceActors.add(firstVoiceActor); + voiceActors.add(firstVoiceActor); + return voiceActors; + } + + public static List getVoiceActorIds() { + List voiceActorIds = new ArrayList<>(); + voiceActorIds.add(5L); + voiceActorIds.add(6L); + return voiceActorIds; + } + + public static List getStudios() { + List studios = new ArrayList<>(); + Studio studio = Studio.builder() + .name("ufortable") + .build(); + + studios.add(studio); + return studios; + } + + public static List getGenres() { + List genres = new ArrayList<>(); + + for(int i = 0; i < 2; i++){ + Genre genre = Genre.builder() + .name("장르"+i) + .build(); + genres.add(genre); + } + return genres; + } + + public static Series getSeries() { + return Series.builder() + .title("귀멸의 칼날") + .build(); + } } From acfd63e729d88d473bea87d690ab78abaebe1f41 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 16:56:52 +0900 Subject: [PATCH 264/734] =?UTF-8?q?refactor:=20@DisplayName=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/unit/anime/repository/AnimeRepositoryTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java index cc889891..a52a06c0 100644 --- a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java @@ -77,7 +77,7 @@ public class AnimeRepositoryTest { private SeriesRepository seriesRepository; @Nested - @DisplayName("애니 등록") + @DisplayName("등록") class PostAnime { @Test @@ -232,7 +232,7 @@ void saveAnimeNoAnimeOriginalAuthor() { } @Nested - @DisplayName("애니 수정") + @DisplayName("수정") class patchAnime { @Test From 364c1bc40b42576a016b41d8c9b8a7b75baf5b21 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 17:34:57 +0900 Subject: [PATCH 265/734] =?UTF-8?q?style:=20=EC=95=A0=EB=8B=88=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EC=9D=98=20=ED=96=89=20=EC=88=98=EC=A0=95=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/anime/service/AnimeService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java index ef6f673f..274ec6ad 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeService.java @@ -18,6 +18,7 @@ public interface AnimeService { * @return AnimeId */ void save(PostReq req); + /** * 애니 수정 로직 * @param animeId From 49f74ae9857e0c0e439ac905b56fe2a4c35fe508 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 17:38:25 +0900 Subject: [PATCH 266/734] =?UTF-8?q?test:=20=EC=95=A0=EB=8B=88=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=EC=9D=98=20=EB=93=B1=EB=A1=9D=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unit/anime/service/AnimeServiceTest.java | 227 +++++++++++++++++- 1 file changed, 219 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index d669ab26..fb4336d0 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -1,24 +1,71 @@ package io.oduck.api.unit.anime.service; +import static io.oduck.api.global.utils.AnimeTestUtils.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; import io.oduck.api.domain.anime.dto.AnimeRes; -import io.oduck.api.domain.anime.service.AnimeServiceStub; +import io.oduck.api.domain.anime.dto.VoiceActorReq; +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.anime.service.AnimeServiceImpl; +import io.oduck.api.domain.genre.entity.Genre; +import io.oduck.api.domain.genre.repository.GenreRepository; +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +import io.oduck.api.domain.originalAuthor.repository.OriginalAuthorRepository; +import io.oduck.api.domain.series.entity.Series; +import io.oduck.api.domain.series.repository.SeriesRepository; +import io.oduck.api.domain.studio.entity.Studio; +import io.oduck.api.domain.studio.repository.StudioRepository; +import io.oduck.api.domain.voiceActor.entity.VoiceActor; +import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; import io.oduck.api.global.utils.AnimeTestUtils; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class AnimeServiceTest { @InjectMocks - private AnimeServiceStub animeService; + private AnimeServiceImpl animeService; + + @Mock + private AnimeRepository animeRepository; + + @Mock + private OriginalAuthorRepository originalAuthorRepository; + + @Mock + private VoiceActorRepository voiceActorRepository; + + @Mock + private StudioRepository studioRepository; + + @Mock + private GenreRepository genreRepository; + + @Mock + private SeriesRepository seriesRepository; @Nested @DisplayName("조회") @@ -33,7 +80,7 @@ void getAnimeById() { AnimeRes response = animeService.getAnimeById(animeId); //then - assertThat(response.getAnime().getId()).isEqualTo(animeId); + assertThat(response.getId()).isEqualTo(animeId); assertThatNoException(); } } @@ -41,7 +88,6 @@ void getAnimeById() { @Nested @DisplayName("등록") class SaveAnime{ - Anime anime = createAnime(); List originalAuthors = getOriginalAuthors(); List voiceActors = getVoiceActors(); List studios = getStudios(); @@ -49,12 +95,177 @@ class SaveAnime{ @Test @DisplayName("애니 등록 성공") - void postAnime(){ - PostReq req = AnimeTestUtils.createPostAnimeRequest(); + void registerAnimeSuccess(){ + //given + List originalAuthorIds = getOriginalAuthorIds(); + given(originalAuthorRepository.findAllById(originalAuthorIds)).willReturn(originalAuthors); + + List voiceActorIds = getVoiceActorIds(); + given(voiceActorRepository.findAllById(voiceActorIds)).willReturn(voiceActors); + + List studioIds = getStudioIds(); + given(studioRepository.findAllById(studioIds)).willReturn(studios); + + List genreIds = getGenreIds(); + given(genreRepository.findAllById(genreIds)).willReturn(genres); + + Long seriesId = getSeriesId(); + given(seriesRepository.findById(seriesId)).willReturn(Optional.ofNullable(getSeries())); + + // when + PostReq req = createPostAnimeRequest(); + animeService.save(req); + + // then + assertThatNoException(); + } + } + + @Nested + @DisplayName("수정") + class UpdateAnime{ + Anime anime = createAnime(); + List originalAuthors = getOriginalAuthors(); + List voiceActors = getVoiceActors(); + List studios = getStudios(); + List genres = getGenres(); + Series series = getSeries(); + + @Test + @DisplayName("애니 수정 성공") + void updateAnime(){ + //given + Long animeId = 1L; + PatchAnimeReq patchAnimeRequest = createPatchAnimeRequest(); + + given(animeRepository.findById(animeId)).willReturn(Optional.ofNullable(anime)); + + //when + animeService.update(animeId, patchAnimeRequest); + + //then + assertThatNoException(); + + //verify + verify(animeRepository, times(1)).findById(anyLong()); + } + + @Test + @DisplayName("애니의 원작 작가 수정") + void updateAnimeAuthor(){ + //given + Long animeId = 1L; + + List originalAuthorIds = getOriginalAuthorIds(); + PatchOriginalAuthorIdsReq patchReq = new PatchOriginalAuthorIdsReq(originalAuthorIds); + + given(animeRepository.findById(animeId)).willReturn(Optional.ofNullable(anime)); + given(originalAuthorRepository.findAllById(originalAuthorIds)).willReturn(originalAuthors); + + //when + animeService.updateAnimeOriginalAuthors(animeId, patchReq); + + //then + assertThatNoException(); + + //verify + verify(animeRepository, times(1)).findById(anyLong()); + verify(originalAuthorRepository, times(1)).findAllById(anyList()); + } + + @Test + @DisplayName("애니의 스튜디오 수정") + void updateAnimeStudios(){ + //given + Long animeId = 1L; + + List studioIds = getStudioIds(); + PatchStudioIdsReq patchReq = new PatchStudioIdsReq(studioIds); + + given(animeRepository.findById(animeId)).willReturn(Optional.ofNullable(anime)); + given(studioRepository.findAllById(studioIds)).willReturn(studios); + + //when + animeService.updateAnimeStudios(animeId, patchReq); - Long animeId = animeService.save(req); + //then + assertThatNoException(); + + //verify + verify(animeRepository, times(1)).findById(anyLong()); + verify(studioRepository, times(1)).findAllById(anyList()); + } + + @Test + @DisplayName("애니의 성우 수정") + void updateAnimeVoiceActors(){ + //given + Long animeId = 1L; + + List patchReqs = getVoiceActorReqs(); + List voiceActorIds = patchReqs.stream().map(VoiceActorReq::getId) + .collect(Collectors.toList()); + + PatchVoiceActorIdsReq patchReq = new PatchVoiceActorIdsReq(patchReqs); + + given(animeRepository.findById(animeId)).willReturn(Optional.ofNullable(anime)); + given(voiceActorRepository.findAllById(voiceActorIds)).willReturn(voiceActors); + + //when + animeService.updateAnimeVoiceActors(animeId, patchReq); + + //then + assertThatNoException(); + + //verify + verify(animeRepository, times(1)).findById(anyLong()); + verify(voiceActorRepository, times(1)).findAllById(anyList()); + } + + @Test + @DisplayName("애니의 장르 수정") + void updateAnimeGenres(){ + //given + Long animeId = 1L; + + List genreIds = getGenreIds(); + PatchGenreIdsReq patchReq = new PatchGenreIdsReq(genreIds); + + given(animeRepository.findById(animeId)).willReturn(Optional.ofNullable(anime)); + given(genreRepository.findAllById(genreIds)).willReturn(genres); + + //when + animeService.updateAnimeGenres(animeId, patchReq); + + //then + assertThatNoException(); + + //verify + verify(animeRepository, times(1)).findById(anyLong()); + verify(genreRepository, times(1)).findAllById(anyList()); + } + + @Test + @DisplayName("애니의 시리즈 수정") + void updateSeries(){ + //given + Long animeId = 1L; + + Long seriesId = getSeriesId(); + PatchSeriesIdReq patchReq = new PatchSeriesIdReq(seriesId); + + given(animeRepository.findById(animeId)).willReturn(Optional.ofNullable(anime)); + given(seriesRepository.findById(seriesId)).willReturn(Optional.ofNullable(series)); + + //when + animeService.updateSeries(animeId, patchReq); + + //then + assertThatNoException(); - assertThat(animeId).isEqualTo(1L); + //verify + verify(animeRepository, times(1)).findById(anyLong()); + verify(seriesRepository, times(1)).findById(anyLong()); } } } From 248dc8df633d1b587737bf79187581c06fca7a95 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 17:39:11 +0900 Subject: [PATCH 267/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20dto=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/anime/AnimeControllerTest.java | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index 479a8bac..7c8c6329 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -58,22 +58,22 @@ void getAnimes() throws Exception { //then actions .andExpect(status().isOk()) - .andExpect(jsonPath("$.anime.id").exists()) - .andExpect(jsonPath("$.anime.title").exists()) - .andExpect(jsonPath("$.anime.thumbnail").exists()) - .andExpect(jsonPath("$.anime.broadcastType").exists()) - .andExpect(jsonPath("$.anime.year").exists()) - .andExpect(jsonPath("$.anime.quarter").exists()) - .andExpect(jsonPath("$.anime.summary").exists()) - .andExpect(jsonPath("$.anime.episodeCount").exists()) - .andExpect(jsonPath("$.anime.rating").exists()) - .andExpect(jsonPath("$.anime.status").exists()) - .andExpect(jsonPath("$.anime.genres").exists()) - .andExpect(jsonPath("$.anime.originalAuthors").exists()) - .andExpect(jsonPath("$.anime.voiceActors").exists()) - .andExpect(jsonPath("$.anime.studios").exists()) - .andExpect(jsonPath("$.anime.reviewCount").exists()) - .andExpect(jsonPath("$.anime.bookmarkCount").exists()) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.title").exists()) + .andExpect(jsonPath("$.thumbnail").exists()) + .andExpect(jsonPath("$.broadcastType").exists()) + .andExpect(jsonPath("$.year").exists()) + .andExpect(jsonPath("$.quarter").exists()) + .andExpect(jsonPath("$.summary").exists()) + .andExpect(jsonPath("$.episodeCount").exists()) + .andExpect(jsonPath("$.rating").exists()) + .andExpect(jsonPath("$.status").exists()) + .andExpect(jsonPath("$.genres").exists()) + .andExpect(jsonPath("$.originalAuthors").exists()) + .andExpect(jsonPath("$.voiceActors").exists()) + .andExpect(jsonPath("$.studios").exists()) + .andExpect(jsonPath("$.reviewCount").exists()) + .andExpect(jsonPath("$.bookmarkCount").exists()) .andDo(document("getAnimeById/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), @@ -82,55 +82,58 @@ void getAnimes() throws Exception { .description("애니의 고유 식별자") ), responseFields( - fieldWithPath("anime") - .type(JsonFieldType.OBJECT) - .description("애니 정보"), - fieldWithPath("anime.id") + fieldWithPath("id") .type(JsonFieldType.NUMBER) .description("애니의 고유 식별자"), - fieldWithPath("anime.title") + fieldWithPath("title") .type(JsonFieldType.STRING) .description("애니 제목"), - fieldWithPath("anime.thumbnail") + fieldWithPath("thumbnail") .type(JsonFieldType.STRING) .description("애니 이미지, 경로로 저장됨"), - fieldWithPath("anime.broadcastType") + fieldWithPath("broadcastType") .type(JsonFieldType.STRING) .description("작품의 출시 방식. TVA, OVA, ONA, MOV가 있음"), - fieldWithPath("anime.year") + fieldWithPath("year") .type(JsonFieldType.NUMBER) .description("작품의 출시 년도"), - fieldWithPath("anime.quarter") + fieldWithPath("quarter") .type(JsonFieldType.STRING) .description("작품의 출시 분기"), - fieldWithPath("anime.summary") + fieldWithPath("summary") .type(JsonFieldType.STRING) .description("애니를 소개할 때 줄거리"), - fieldWithPath("anime.episodeCount") + fieldWithPath("episodeCount") .type(JsonFieldType.NUMBER) .description("에피소드의 숫자. 16화까지 나왔으면 16으로 표기됨."), - fieldWithPath("anime.rating") + fieldWithPath("rating") .type(JsonFieldType.STRING) .description("작품의 심의 등급. ADULT, FIFTEEN, TWELVE, ALL가 있다."), - fieldWithPath("anime.status") + fieldWithPath("status") .type(JsonFieldType.STRING) .description("방영 상태. FINISHED, ONGOING, UPCOMING, UNKNOWN이 있다."), - fieldWithPath("anime.genres") + fieldWithPath("genres") .type(JsonFieldType.ARRAY) .description("애니의 장르."), - fieldWithPath("anime.originalAuthors") + fieldWithPath("originalAuthors") .type(JsonFieldType.ARRAY) .description("애니의 원작 작가."), - fieldWithPath("anime.voiceActors") + fieldWithPath("voiceActors") .type(JsonFieldType.ARRAY) - .description("애니의 출연 성우."), - fieldWithPath("anime.studios") + .description("애니 성우 리스트"), + fieldWithPath("voiceActors[].name") + .type(JsonFieldType.STRING) + .description("성우의 이름"), + fieldWithPath("voiceActors[].part") + .type(JsonFieldType.STRING) + .description("성우의 역할"), + fieldWithPath("studios") .type(JsonFieldType.ARRAY) .description("애니의 제작사."), - fieldWithPath("anime.reviewCount") + fieldWithPath("reviewCount") .type(JsonFieldType.NUMBER) .description("애니의 리뷰 개수."), - fieldWithPath("anime.bookmarkCount") + fieldWithPath("bookmarkCount") .type(JsonFieldType.NUMBER) .description("애니의 덕후 수.") ) From 0d6dee222b2018f7c50e8e3818ec8964d2c8a4f3 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 17:40:41 +0900 Subject: [PATCH 268/734] =?UTF-8?q?feat:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 43 +++---------------- 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java index 6f9503d7..22af1884 100644 --- a/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java +++ b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java @@ -8,15 +8,8 @@ import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; -import io.oduck.api.domain.anime.entity.AnimeGenre; -import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; -import io.oduck.api.domain.anime.entity.AnimeStudio; -import io.oduck.api.domain.anime.entity.AnimeVoiceActor; import io.oduck.api.domain.anime.service.AnimeService; -import io.oduck.api.domain.series.entity.Series; import jakarta.validation.Valid; -import java.util.ArrayList; -import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -40,7 +33,7 @@ public class AdminController { //애니 등록 @PostMapping("/animes") public ResponseEntity postAnime(@RequestBody @Valid PostReq req){ - // TODO: 애니 추가 로직 구현 + animeService.save(req); return ResponseEntity.ok().build(); @@ -51,7 +44,7 @@ public ResponseEntity postAnime(@RequestBody @Valid PostReq req){ public ResponseEntity patchAnime( @PathVariable Long animeId, @RequestBody @Valid PatchAnimeReq req ){ - //TODO: 애니 수정 로직 구현 + animeService.update(animeId, req); return ResponseEntity.noContent().build(); @@ -62,12 +55,7 @@ public ResponseEntity patchAnime( public ResponseEntity patchAnimeOriginalAuthors( @PathVariable Long animeId, @RequestBody PatchOriginalAuthorIdsReq req ){ - List originalAuthorIds = req.getOriginalAuthorIds(); - //TODO: 컨트롤러에서 애니 아이디와 원작 작가 아이디로 AnimeOriginalAuthor들을 찾아야 함. - List animeOriginalAuthors = new ArrayList<>(); - - //TODO: 애니 수정 로직 구현 - animeService.updateAnimeOriginalAuthors(animeId, animeOriginalAuthors); + animeService.updateAnimeOriginalAuthors(animeId, req); return ResponseEntity.noContent().build(); } @@ -77,12 +65,8 @@ public ResponseEntity patchAnimeOriginalAuthors( public ResponseEntity patchAnimeStudios( @PathVariable Long animeId, @RequestBody PatchStudioIdsReq req ){ - List originalAuthorIds = req.getStudioIds(); - //TODO: 컨트롤러에서 애니 아이디와 원작 작가 아이디로 AnimeOriginalAuthor들을 찾아야 함. - List animeStudios = new ArrayList<>(); - //TODO: 애니 수정 로직 구현 - animeService.updateAnimeStudios(animeId, animeStudios); + animeService.updateAnimeStudios(animeId, req); return ResponseEntity.noContent().build(); } @@ -91,12 +75,7 @@ public ResponseEntity patchAnimeStudios( public ResponseEntity patchAnimeVoiceActors( @PathVariable Long animeId, @RequestBody PatchVoiceActorIdsReq req ){ - List voiceActorIds = req.getVoiceActorIds(); - //TODO: 컨트롤러에서 애니 아이디와 성우 아이디로 AnimeOriginalAuthor들을 찾아야 함. - List animeVoiceActors = new ArrayList<>(); - - //TODO: 애니 수정 로직 구현 - animeService.updateAnimeVoiceActors(animeId, animeVoiceActors); + animeService.updateAnimeVoiceActors(animeId, req); return ResponseEntity.noContent().build(); } @@ -106,12 +85,8 @@ public ResponseEntity patchAnimeVoiceActors( public ResponseEntity patchAnimeGenres( @PathVariable Long animeId, @RequestBody PatchGenreIdsReq req ){ - List genreIds = req.getGenreIds(); - //TODO: 컨트롤러에서 애니 아이디와 장르 아이디로 AnimeGenre들을 찾아야 함. - List animeGenres = new ArrayList<>(); - //TODO: 애니 수정 로직 구현 - animeService.updateAnimeGenres(animeId, animeGenres); + animeService.updateAnimeGenres(animeId, req); return ResponseEntity.noContent().build(); } @@ -121,12 +96,8 @@ public ResponseEntity patchAnimeGenres( public ResponseEntity patchAnimeSeries( @PathVariable Long animeId, @RequestBody PatchSeriesIdReq req ){ - Long seriesId = req.getSeriesId(); - //TODO: 컨트롤러에서 시리즈 아이디로 시리즈를 찾아야 함. - Series series = null; - //TODO: 애니 수정 로직 구현 - animeService.update(animeId, series); + animeService.updateSeries(animeId, req); return ResponseEntity.noContent().build(); } From fd06a878becf024289041cfab5c0ec6fc5a0c48a Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sat, 21 Oct 2023 17:41:22 +0900 Subject: [PATCH 269/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20dto=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#32?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/admin/AdminControllerTest.java | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java index ec5f5023..d5e54fb4 100644 --- a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java @@ -24,6 +24,7 @@ import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import io.oduck.api.domain.anime.dto.VoiceActorReq; import io.oduck.api.global.utils.AnimeTestUtils; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -57,11 +58,11 @@ public class AdminControllerTest { private final static String ADMIN_URL = "/oduckdmin"; @Nested - @DisplayName("애니 등록") + @DisplayName("등록") class PostAnime{ @Test - @DisplayName("등록 성공 시 Http Status 200 반환") + @DisplayName("성공 시 Http Status 200 반환") void postAnime() throws Exception { //given PostReq request = AnimeTestUtils.createPostAnimeRequest(); @@ -128,11 +129,19 @@ void postAnime() throws Exception { .description("애니 스튜디오 아이디 리스트") .attributes(field("constraints", "List만 허용합니다.")) .optional(), - fieldWithPath("voiceActorIds") + fieldWithPath("voiceActors") .type(JsonFieldType.ARRAY) - .description("애니 성우 아이디 리스트") + .description("애니 성우 리스트") .attributes(field("constraints", "List만 허용합니다.")) .optional(), + fieldWithPath("voiceActors[].id") + .type(JsonFieldType.NUMBER) + .description("성우의 아이디") + .attributes(field("constraints", "숫자만 허용합니다.")), + fieldWithPath("voiceActors[].part") + .type(JsonFieldType.STRING) + .description("성우의 역할") + .attributes(field("constraints", "100자 이하만 허용합니다.")), fieldWithPath("genreIds") .type(JsonFieldType.ARRAY) .description("애니 장르 아이디 리스트") @@ -150,7 +159,7 @@ void postAnime() throws Exception { } @Nested - @DisplayName("애니 수정") + @DisplayName("수정") class PatchAnime{ @Test @@ -302,9 +311,9 @@ void patchAnimeStudios() throws Exception { void patchAnimeVoiceActors() throws Exception { //given Long animeId = 1L; - List voiceActorIds = AnimeTestUtils.getVoiceActorIds(); + List voiceActors = AnimeTestUtils.getVoiceActorReqs(); - PatchVoiceActorIdsReq req = new PatchVoiceActorIdsReq(voiceActorIds); + PatchVoiceActorIdsReq req = new PatchVoiceActorIdsReq(voiceActors); String content = gson.toJson(req); //when @@ -324,11 +333,19 @@ void patchAnimeVoiceActors() throws Exception { parameterWithName("animeId").description("애니의 식별자") ), requestFields(attributes(key("title").value("Fields for anime creation")), - fieldWithPath("voiceActorIds") + fieldWithPath("voiceActors") .type(JsonFieldType.ARRAY) - .description("애니 성우 아이디 리스트") + .description("애니 성우 리스트") .attributes(field("constraints", "List만 허용합니다.")) - .optional() + .optional(), + fieldWithPath("voiceActors[].id") + .type(JsonFieldType.NUMBER) + .description("성우의 아이디") + .attributes(field("constraints", "숫자만 허용합니다.")), + fieldWithPath("voiceActors[].part") + .type(JsonFieldType.STRING) + .description("성우의 역할") + .attributes(field("constraints", "100자 이하만 허용합니다.")) ) )); } From d7b51af46a0e64d7161aba90077196fbdff77db9 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 14:54:27 +0900 Subject: [PATCH 270/734] =?UTF-8?q?refactor:=20pagenation=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Entitybased 메소드 변경 - SliceResponse lastId 타입 Long -> String - Sort로 생성하는 생성자 추가 - orderSpecifier List로 변경 --- .../oduck/api/global/common/EntityBased.java | 3 +- .../api/global/common/SliceResponse.java | 22 +++++++------- .../oduck/api/global/utils/PagingUtils.java | 7 ++++- .../oduck/api/global/utils/QueryDslUtils.java | 29 +++++++++++++++---- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/oduck/api/global/common/EntityBased.java b/src/main/java/io/oduck/api/global/common/EntityBased.java index f2e1e037..c624a9d3 100644 --- a/src/main/java/io/oduck/api/global/common/EntityBased.java +++ b/src/main/java/io/oduck/api/global/common/EntityBased.java @@ -1,5 +1,6 @@ package io.oduck.api.global.common; public interface EntityBased { - Long bringId(); + + String bringId(String property); } diff --git a/src/main/java/io/oduck/api/global/common/SliceResponse.java b/src/main/java/io/oduck/api/global/common/SliceResponse.java index f68aeb46..42991b24 100644 --- a/src/main/java/io/oduck/api/global/common/SliceResponse.java +++ b/src/main/java/io/oduck/api/global/common/SliceResponse.java @@ -7,39 +7,39 @@ @Getter public class SliceResponse { private final List items; - private final int size; // 한 페이지에 보여줄 아이템의 개수 + private final int size; // 한 페이지에 보여줄 아이템의 개수 private final boolean hasNext; // 마지막 페이지일 경우, true 반환. - private Long lastId; // 마지막 아이템의 id + private String lastId; // 마지막 아이템의 id - public SliceResponse(Slice sliceContent) { + public SliceResponse(Slice sliceContent, String property) { this.items = sliceContent.getContent(); this.size = sliceContent.getSize(); this.hasNext = sliceContent.isLast(); if (!items.isEmpty() && !hasNext) { T lastItem = items.get(items.size() - 1); - lastId = lastItem.bringId(); + lastId = lastItem.bringId(property); } } - public SliceResponse(Slice slice, List items) { + public SliceResponse(Slice slice, List items, String property) { this.items = items; this.size = slice.getSize(); this.hasNext = slice.hasNext(); if (!items.isEmpty() && hasNext) { T lastItem = items.get(items.size() - 1); - this.lastId = lastItem.bringId(); + this.lastId = lastItem.bringId(property); } else { - this.lastId = -1L; + this.lastId = ""; } } - public static SliceResponse of(Slice slice) { - return new SliceResponse<>(slice); + public static SliceResponse of(Slice slice, String property) { + return new SliceResponse<>(slice, property); } - public static SliceResponse of(Slice slice, List items) { - return new SliceResponse<>(slice, items); + public static SliceResponse of(Slice slice, List items, String property) { + return new SliceResponse<>(slice, items, property); } } \ No newline at end of file diff --git a/src/main/java/io/oduck/api/global/utils/PagingUtils.java b/src/main/java/io/oduck/api/global/utils/PagingUtils.java index 73006011..ced08b1c 100644 --- a/src/main/java/io/oduck/api/global/utils/PagingUtils.java +++ b/src/main/java/io/oduck/api/global/utils/PagingUtils.java @@ -2,14 +2,19 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; public class PagingUtils { - public static Pageable applyPageableForNonOffset(String property, String direction, int size) { + public static Pageable applyPageableForNonOffset(int size, String property, String direction) { return PageRequest.of(0, size, direction.equals("desc") ? Direction.DESC : Direction.ASC, property); } + public static Pageable applyPageableForNonOffset(int size, Sort sort) { + return PageRequest.of(0, size, sort); + } + public static Pageable applyPageableForOffset(int page, String property, String direction, int size) { return PageRequest.of(page, size, direction.equals("desc") ? Direction.DESC : Direction.ASC, property); } diff --git a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java index 7aa69207..3787f8da 100644 --- a/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java +++ b/src/main/java/io/oduck/api/global/utils/QueryDslUtils.java @@ -5,6 +5,8 @@ import com.querydsl.core.types.Path; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQuery; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -15,13 +17,13 @@ @Component public class QueryDslUtils { // JPAQuery를 이용하여 Slice를 생성하여 반환. - public static Slice fetchSliceByCursor(Path path, JPAQuery query, Pageable pageable) { - Sort.Order order = pageable.getSort().iterator().next(); + public static Slice fetchSliceByCursor(List paths, JPAQuery query, Pageable pageable) { + Sort sort = pageable.getSort(); int pageSize = pageable.getPageSize(); List content = query - .orderBy(getOrderSpecifier(order, path)) + .orderBy(getAllOrderSpecifiers(sort, paths)) .limit(pageSize + 1) .fetch(); @@ -29,10 +31,25 @@ public static Slice fetchSliceByCursor(Path path, JPAQuery query, Page } // sort.order 객체로 OrderSpecifier를 생성하여 반환. - public static OrderSpecifier getOrderSpecifier(Sort.Order order, Path path) { + public static OrderSpecifier[] getAllOrderSpecifiers(Sort sort, List paths) { + List orders = convertToDslOrder(sort, paths); + return orders.toArray(OrderSpecifier[]::new); + } + + private static List convertToDslOrder(Sort sort, List paths) { + List orders = new ArrayList<>(); + Iterator iterator = paths.iterator(); + if (!sort.isEmpty()) { + for (Sort.Order order : sort) { + Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; - Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; - return createOrderSpecifier(direction, path, order.getProperty()); + Path path = iterator.next(); + + OrderSpecifier orderBy = createOrderSpecifier(direction, path, order.getProperty()); + orders.add(orderBy); + } + } + return orders; } private static OrderSpecifier createOrderSpecifier(Order order, Path parent, String fieldName) { From 6d85503a91e8c5d57afb612deece8f88cfe2cecc Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 14:56:52 +0900 Subject: [PATCH 271/734] =?UTF-8?q?refactor:=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=20score=20=EC=A0=95=EC=83=81=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bookmark/dto/BookmarkResDto.java | 22 +++--- .../repository/BookmarkRepositoryImpl.java | 73 +++++++++++-------- .../bookmark/service/BookmarkServiceImpl.java | 23 ++++-- 3 files changed, 73 insertions(+), 45 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java index 0a087f93..16680e75 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java @@ -23,18 +23,22 @@ public static class BookmarkRes implements EntityBased { public static BookmarkRes of(BookmarkDsl bookmarkDsl) { Integer myScore = bookmarkDsl.getMyScore(); - return BookmarkRes.builder() - .animeId(bookmarkDsl.getAnimeId()) - .title(bookmarkDsl.getTitle()) - .thumbnail(bookmarkDsl.getThumbnail()) - .myScore(myScore == null ? -1 : myScore) - .createdAt(bookmarkDsl.getCreatedAt()) - .build(); + return BookmarkRes.builder().animeId(bookmarkDsl.getAnimeId()) + .title(bookmarkDsl.getTitle()).thumbnail(bookmarkDsl.getThumbnail()) + .myScore(myScore == null ? -1 : myScore).createdAt(bookmarkDsl.getCreatedAt()) + .build(); } @Override - public Long bringId() { - return this.animeId; + public String bringId(String property) { + switch (property) { + case "score": + return this.myScore.toString() + ", " + this.createdAt.toString(); + case "title": + return this.title; + default: + return this.createdAt.toString(); + } } } } diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java index cca64e76..9f3a47ba 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepositoryImpl.java @@ -13,6 +13,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -53,15 +54,19 @@ public Slice selectBookmarks(Long memberId, String cursor, Pageable return fetchSliceByCursor(sortPath(property), bookmarks, pageable); } - private Path sortPath(String property) { + private List sortPath(String property) { + List path = new ArrayList<>(); switch (property) { case "score": - return starRating; + path.add(starRating); + path.add(bookmark); case "title": - return anime; + path.add(anime); default: - return bookmark; + path.add(bookmark); } + + return path; } private BooleanExpression cursorCondition(String cursor, Pageable pageable) { @@ -71,33 +76,41 @@ private BooleanExpression cursorCondition(String cursor, Pageable pageable) { List orderList = pageable.getSort().get().toList(); Direction direction = orderList.get(0).getDirection(); -// String property = orderList.get(0).getProperty(); - - if (direction == Direction.ASC) { - return bookmark.id.gt(Long.parseLong(cursor)); - } else { - return bookmark.id.lt(Long.parseLong(cursor)); - } + String property = orderList.get(0).getProperty(); -// switch (property) { -// case "score": -// if (direction == Direction.ASC) { -// return starRating.score.gt(Double.parseDouble(cursor)); -// } else { -// return starRating.score.lt(Double.parseDouble(cursor)); -// } -// case "title": -// if (direction == Direction.ASC) { -// return anime.title.gt(cursor); -// } else { -// return anime.title.lt(cursor); -// } -// default: -// if (direction == Direction.ASC) { -// return bookmark.createdAt.gt(LocalDateTime.parse(cursor)); -// } else { -// return bookmark.createdAt.lt(LocalDateTime.parse(cursor)); -// } +// if (direction == Direction.ASC) { +// return bookmark.id.gt(Long.parseLong(cursor)); +// } else { +// return bookmark.id.lt(Long.parseLong(cursor)); // } + + switch (property) { + case "score": + String[] scoreAndcreatedAt = cursor.split(", "); + int score = Integer.parseInt(scoreAndcreatedAt[0]); + LocalDateTime createdAt = LocalDateTime.parse(scoreAndcreatedAt[1]); + + if (direction == Direction.ASC) { + return starRating.score.gt(score) + .or(starRating.score.goe(score).and(bookmark.createdAt.lt(createdAt))) + .or(starRating.score.isNull().and(bookmark.createdAt.lt(createdAt))); + } else { + return starRating.score.lt(Integer.parseInt(scoreAndcreatedAt[0])) + .or(starRating.score.loe(score).and(bookmark.createdAt.lt(createdAt))) + .or(starRating.score.isNull().and(bookmark.createdAt.lt(createdAt))); + } + case "title": + if (direction == Direction.ASC) { + return anime.title.gt(cursor); + } else { + return anime.title.lt(cursor); + } + default: + if (direction == Direction.ASC) { + return bookmark.createdAt.gt(LocalDateTime.parse(cursor)); + } else { + return bookmark.createdAt.lt(LocalDateTime.parse(cursor)); + } + } } } diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java index 8893d1d1..7b959143 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java @@ -3,7 +3,7 @@ import static io.oduck.api.global.utils.PagingUtils.applyPageableForNonOffset; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; -import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; +import io.oduck.api.domain.bookmark.dto.BookmarkReqDto; import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; import io.oduck.api.domain.bookmark.repository.BookmarkRepository; import io.oduck.api.global.common.OrderDirection; @@ -12,6 +12,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import org.springframework.stereotype.Service; @Slf4j @@ -19,15 +21,24 @@ @RequiredArgsConstructor public class BookmarkServiceImpl implements BookmarkService { private final BookmarkRepository bookmarkRepository; + @Override - public SliceResponse getBookmarksByMemberId(Long memberId, String cursor, Sort sort, OrderDirection order, int size) { + public SliceResponse getBookmarksByMemberId(Long memberId, String cursor, BookmarkReqDto.Sort sort, OrderDirection order, int size) { + Sort sortList = Sort.by( + Direction.fromString(order.getOrder()), + sort.getSort() + ); + + if (sort == BookmarkReqDto.Sort.SCORE) { + sortList = sortList.and(Sort.by(Direction.DESC, "createdAt")); + } + Slice bookmarks = bookmarkRepository.selectBookmarks( memberId, cursor, applyPageableForNonOffset( - sort.getSort(), - order.getOrder(), - size + size, + sortList ) ); @@ -35,6 +46,6 @@ public SliceResponse getBookmarksByMemberId(Long memberId, String c .map(BookmarkRes::of) .toList(); - return SliceResponse.of(bookmarks, bookmarkRes); + return SliceResponse.of(bookmarks, bookmarkRes, sort.getSort()); } } From 17b40f5a818e4233ddeb044a659e25d12b982be4 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 15:01:23 +0900 Subject: [PATCH 272/734] =?UTF-8?q?refactor:=20shortReview=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EB=A6=AC=ED=84=B4=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EntityBased 변경에 따른 타입 변경 --- .../io/oduck/api/domain/review/dto/ShortReviewResDto.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java index ad693766..12cddcce 100644 --- a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java @@ -22,8 +22,8 @@ public static class ShortReview implements EntityBased { private MemberProfile member; @Override - public Long bringId() { - return this.getReviewId(); + public String bringId(String property) { + return this.reviewId.toString(); } } From 0d256cdf0a5842a1d0f54717cfc10b33c469dab1 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 15:01:59 +0900 Subject: [PATCH 273/734] =?UTF-8?q?refactor:=20sliceResponse=20=EC=A0=95?= =?UTF-8?q?=EC=A0=81=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/global/common/SliceResponse.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/io/oduck/api/global/common/SliceResponse.java b/src/main/java/io/oduck/api/global/common/SliceResponse.java index 42991b24..4fb764a8 100644 --- a/src/main/java/io/oduck/api/global/common/SliceResponse.java +++ b/src/main/java/io/oduck/api/global/common/SliceResponse.java @@ -35,6 +35,10 @@ public SliceResponse(Slice slice, List items, String property) { } } + public static SliceResponse of(Slice slice) { + return new SliceResponse<>(slice, "id"); + } + public static SliceResponse of(Slice slice, String property) { return new SliceResponse<>(slice, property); } From 7cde93f94314078069e74cb43ef6d7324079aa57 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 15:27:58 +0900 Subject: [PATCH 274/734] =?UTF-8?q?feat:=20bookmark=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=EC=82=AD=EC=A0=9C=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BookmarkController.java | 34 +++++++++++++++++ .../domain/bookmark/dto/BookmarkReqDto.java | 6 +++ .../repository/BookmarkRepository.java | 2 + .../bookmark/service/BookmarkService.java | 2 +- .../bookmark/service/BookmarkServiceImpl.java | 37 +++++++++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/oduck/api/domain/bookmark/controller/BookmarkController.java diff --git a/src/main/java/io/oduck/api/domain/bookmark/controller/BookmarkController.java b/src/main/java/io/oduck/api/domain/bookmark/controller/BookmarkController.java new file mode 100644 index 00000000..fcf57fa9 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/bookmark/controller/BookmarkController.java @@ -0,0 +1,34 @@ +package io.oduck.api.domain.bookmark.controller; + +import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.CreateReq; +import io.oduck.api.domain.bookmark.service.BookmarkService; +import io.oduck.api.global.security.auth.dto.AuthUser; +import io.oduck.api.global.security.auth.dto.LoginUser; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +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; + +@Validated +@RestController +@RequestMapping("/bookmarks") +@RequiredArgsConstructor +public class BookmarkController { + + private final BookmarkService bookmarkService; + + @PostMapping + public ResponseEntity postBookmark( + @LoginUser AuthUser user, + @RequestBody @Valid CreateReq body) { + boolean isCreated = bookmarkService.toggleBookmark(user.getId(), body.getAnimeId()); + return ResponseEntity + .status(isCreated ? HttpStatus.CREATED : HttpStatus.NO_CONTENT) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java index 5f310a44..890b5db8 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java @@ -1,9 +1,15 @@ package io.oduck.api.domain.bookmark.dto; +import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; import lombok.Getter; public class BookmarkReqDto { + @Getter + public static class CreateReq { + @Positive + private Long animeId; + } @Getter @AllArgsConstructor public enum Sort { diff --git a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java index 27176c49..10affd8d 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java +++ b/src/main/java/io/oduck/api/domain/bookmark/repository/BookmarkRepository.java @@ -1,10 +1,12 @@ package io.oduck.api.domain.bookmark.repository; import io.oduck.api.domain.bookmark.entity.Bookmark; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface BookmarkRepository extends JpaRepository, BookmarkRepositoryCustom { + Optional findByMemberIdAndAnimeId(Long memberId, Long animeId); } diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java index f26cf8b9..1f5cf823 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java @@ -6,7 +6,7 @@ import io.oduck.api.global.common.SliceResponse; public interface BookmarkService { - + boolean toggleBookmark(Long memberId, Long animeId); SliceResponse getBookmarksByMemberId(Long memberId, String cursor, Sort sort, OrderDirection order, int size); } diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java index 7b959143..b54fc087 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java @@ -2,13 +2,20 @@ import static io.oduck.api.global.utils.PagingUtils.applyPageableForNonOffset; +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.repository.AnimeRepository; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; import io.oduck.api.domain.bookmark.dto.BookmarkReqDto; import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; +import io.oduck.api.domain.bookmark.entity.Bookmark; import io.oduck.api.domain.bookmark.repository.BookmarkRepository; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.repository.MemberRepository; import io.oduck.api.global.common.OrderDirection; import io.oduck.api.global.common.SliceResponse; +import io.oduck.api.global.exception.NotFoundException; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Slice; @@ -21,6 +28,32 @@ @RequiredArgsConstructor public class BookmarkServiceImpl implements BookmarkService { private final BookmarkRepository bookmarkRepository; + private final MemberRepository memberRepository; + private final AnimeRepository animeRepository; + + @Override + public boolean toggleBookmark(Long memberId, Long animeId) { + Optional optionalBookmark = getBookmark(memberId, animeId); + + if (optionalBookmark.isPresent()) { + bookmarkRepository.delete(optionalBookmark.get()); + return false; + } + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new NotFoundException("존재하지 않는 회원입니다.")); + + Anime anime = animeRepository.findById(animeId) + .orElseThrow(() -> new NotFoundException("존재하지 않는 애니메이션입니다.")); + + bookmarkRepository.save( + Bookmark.builder() + .member(member) + .anime(anime) + .build() + ); + return true; + } @Override public SliceResponse getBookmarksByMemberId(Long memberId, String cursor, BookmarkReqDto.Sort sort, OrderDirection order, int size) { @@ -48,4 +81,8 @@ public SliceResponse getBookmarksByMemberId(Long memberId, String c return SliceResponse.of(bookmarks, bookmarkRes, sort.getSort()); } + + private Optional getBookmark(Long memberId, Long animeId) { + return bookmarkRepository.findByMemberIdAndAnimeId(memberId, animeId); + } } From a9351ecf48311d6e3bad9fbe0b2060e72332e5e3 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 15:43:50 +0900 Subject: [PATCH 275/734] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lastId 타입 변경 - applyPageableForNonOffset --- .../oduck/api/e2e/member/MemberControllerTest.java | 14 +++++++------- .../e2e/shortReview/ShortReviewControllerTest.java | 4 ++-- .../repository/BookmarkRepositoryTest.java | 2 +- .../unit/bookmark/service/BookmarkServiceTest.java | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 471e6964..e715ed85 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -516,7 +516,7 @@ void updateProfileFailureWhenSameNameAsAlreadyExist() throws Exception { @DisplayName("회원의 북마크 애니 목록 조회") @Nested class GetBookmarks { - @DisplayName("회원의 북마크 애니 목록 조회 성공시 200 OK 응답") + @DisplayName("회원의 북마크 애니 목록 조회 성공시 200 OK 응답(cursor 없이 최초 요청)") @Test void getBookmarksSuccess() throws Exception { // given @@ -595,14 +595,14 @@ void getBookmarksSuccess() throws Exception { .type(JsonFieldType.BOOLEAN) .description("마지막 페이지 여부"), fieldWithPath("lastId") - .type(JsonFieldType.NUMBER) - .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 -1") + .type(JsonFieldType.STRING) + .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 \"\"") ) ) ); } - @DisplayName("회원의 북마크 애니 목록 조회 성공시 200 OK 응답") + @DisplayName("회원의 북마크 애니 목록 조회 성공시 200 OK 응답(커서 요청)") @Test void getBookmarksSuccessWithCursor() throws Exception { // given @@ -610,7 +610,7 @@ void getBookmarksSuccessWithCursor() throws Exception { int size = 2; String sort = "created_at"; String order = "desc"; - String cursor = "2"; + String cursor = "2023-10-11T21:05:31.859"; // when ResultActions actions = mockMvc.perform( @@ -683,8 +683,8 @@ void getBookmarksSuccessWithCursor() throws Exception { .type(JsonFieldType.BOOLEAN) .description("마지막 페이지 여부"), fieldWithPath("lastId") - .type(JsonFieldType.NUMBER) - .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 -1") + .type(JsonFieldType.STRING) + .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 \"\"") ) ) ); diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index fa506283..179afc7e 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -121,8 +121,8 @@ void getShortReviews() throws Exception{ .type(JsonFieldType.NUMBER) .description("한 페이지에 보여줄 아이템의 개수"), fieldWithPath("lastId") - .type(JsonFieldType.NUMBER) - .description("마지막 아이템의 id"), + .type(JsonFieldType.STRING) + .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 \"\""), fieldWithPath("hasNext") .type(JsonFieldType.BOOLEAN) .description("마지막 페이지일 경우, false 반환.") diff --git a/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java b/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java index a54f1d21..38123dfc 100644 --- a/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/bookmark/repository/BookmarkRepositoryTest.java @@ -33,7 +33,7 @@ class selectBookmarks { void selectBookmarksSuccess() { // given Long memberId = 1L; - Pageable pageable = applyPageableForNonOffset("createdAt", "desc", 10); + Pageable pageable = applyPageableForNonOffset(10, "createdAt", "desc"); // when Slice bookmarks = memberRepository.selectBookmarks(memberId, null, pageable); diff --git a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java index 4b3d63c7..f8edee07 100644 --- a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java +++ b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java @@ -52,7 +52,7 @@ void getBookmarksByMemberIdSuccess() { when(bookmarkRepository.selectBookmarks( memberId, cursor, - applyPageableForNonOffset(sort.getSort(), order.getOrder(), size) + applyPageableForNonOffset(size, sort.getSort(), order.getOrder()) )).thenReturn(sampleSlice); SliceResponse result = bookmarkService.getBookmarksByMemberId(memberId, cursor, sort, order, size); From 77afe8e23e6070f30f2fae33192d35aa58f932b8 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 16:27:24 +0900 Subject: [PATCH 276/734] =?UTF-8?q?refactor:=20test=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=B9=8C=EB=8D=94=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/oduck/api/domain/anime/entity/Anime.java | 8 ++++++++ .../io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java index 06a17dca..7589f8c6 100644 --- a/src/main/java/io/oduck/api/domain/anime/entity/Anime.java +++ b/src/main/java/io/oduck/api/domain/anime/entity/Anime.java @@ -16,13 +16,17 @@ import jakarta.persistence.OneToMany; import java.util.ArrayList; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; @Entity @Getter +@Builder @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor public class Anime extends BaseEntity { @Id @@ -79,15 +83,19 @@ public class Anime extends BaseEntity { private Series series; @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) + @Builder.Default private List animeOriginalAuthors = new ArrayList<>(); @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) + @Builder.Default private List animeStudios = new ArrayList<>(); @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) + @Builder.Default private List animeVoiceActors = new ArrayList<>(); @OneToMany(mappedBy = "anime", cascade = CascadeType.PERSIST, orphanRemoval = true) + @Builder.Default private List animeGenres = new ArrayList<>(); /** diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java index 890b5db8..e0bc226c 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkReqDto.java @@ -2,10 +2,15 @@ import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; public class BookmarkReqDto { @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder public static class CreateReq { @Positive private Long animeId; From 4e48cfc63e8ac7933523d5becd1aa51a0fbf7ff0 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 16:28:20 +0900 Subject: [PATCH 277/734] =?UTF-8?q?test:=20bookmark=20service,=20controlle?= =?UTF-8?q?r=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../e2e/bookmark/BookmarkControllerTest.java | 128 ++++++++++++++++++ .../api/e2e/member/MemberControllerTest.java | 18 +-- .../bookmark/service/BookmarkServiceTest.java | 64 +++++++++ .../member/service/MemberServiceTest.java | 2 - 4 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java diff --git a/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java b/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java new file mode 100644 index 00000000..3c8bfb8e --- /dev/null +++ b/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java @@ -0,0 +1,128 @@ +package io.oduck.api.e2e.bookmark; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.google.gson.Gson; +import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.CreateReq; +import io.oduck.api.domain.member.entity.Role; +import io.oduck.api.global.mockMember.WithCustomMockMember; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@AutoConfigureRestDocs +@AutoConfigureMockMvc +@ExtendWith({ RestDocumentationExtension.class, SpringExtension.class }) +@SpringBootTest +@ActiveProfiles("test") +public class BookmarkControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private Gson gson; + + private final String BASE_URL = "/bookmarks"; + + @DisplayName("북마크 토글") + @Nested + class toggleBookmark { + @DisplayName("북마크 토글 저장 성공") + @Test + @WithCustomMockMember(id = 2L, email = "john", password = "Qwer!234", role = Role.MEMBER) + void toggleBookmarkSaveSuccess() throws Exception { + // given + Long animeId = 1L; + CreateReq createReq = CreateReq.builder() + .animeId(animeId) + .build(); + + String content = gson.toJson(createReq); + + // when + ResultActions actions = mockMvc.perform( + post(BASE_URL) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + // then + actions + .andExpect(status().isCreated()) + .andDo( + document("bookmark/toggleBookmark/save/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields( + attributes(key("title") + .value("Fields for bookmark creation")), + fieldWithPath("animeId") + .attributes(key("constraints") + .value("애니메이션 ID, NotNull, Min(1)")) + .description("애니메이션 ID") + ) + ) + ); + } + + @DisplayName("북마크 토글 삭제 성공") + @Test + @WithCustomMockMember(id = 1L, email = "admin", password = "Qwer!234", role = Role.MEMBER) + void toggleBookmarkDeleteSuccess() throws Exception { + // given + Long animeId = 1L; + CreateReq createReq = CreateReq.builder() + .animeId(animeId) + .build(); + + String content = gson.toJson(createReq); + + // when + ResultActions actions = mockMvc.perform( + post(BASE_URL) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + // then + actions + .andExpect(status().isNoContent()) + .andDo( + document("bookmark/toggleBookmark/delete/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields( + attributes(key("title") + .value("Fields for bookmark creation")), + fieldWithPath("animeId") + .attributes(key("constraints") + .value("애니메이션 ID, NotNull, Min(1)")) + .description("애니메이션 ID") + ) + ) + ); + } + } +} diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index e715ed85..a337bc73 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -75,7 +75,7 @@ void postMember() throws Exception { // when // 요청 실행 ResultActions actions = mockMvc.perform( - post("/members") + post(BASE_URL) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .content(content) @@ -120,7 +120,7 @@ void getProfileByNameIfMine() throws Exception { // when // 요청 실행 ResultActions actions = mockMvc.perform( - get("/members" + "/{name}", name) + get(BASE_URL + "/{name}", name) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") @@ -203,7 +203,7 @@ void getProfileByNameIfOthers() throws Exception { // when // 요청 실행 ResultActions actions = mockMvc.perform( - get("/members" + "/{name}", name) + get(BASE_URL + "/{name}", name) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") @@ -288,7 +288,7 @@ void getProfileByName() throws Exception { // when // 요청 실행 ResultActions actions = mockMvc.perform( - get("/members" + "/{name}", name) + get(BASE_URL + "/{name}", name) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") @@ -348,7 +348,7 @@ void patchProfileSuccess() throws Exception { // when ResultActions actions = mockMvc.perform( - patch("/members") + patch(BASE_URL) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") @@ -396,7 +396,7 @@ void updateProfileFailureWhenSameNameAsBefore() throws Exception { // when ResultActions actions = mockMvc.perform( - patch("/members") + patch(BASE_URL) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") @@ -460,7 +460,7 @@ void updateProfileFailureWhenSameNameAsAlreadyExist() throws Exception { // when ResultActions actions = mockMvc.perform( - patch("/members") + patch(BASE_URL) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") @@ -527,7 +527,7 @@ void getBookmarksSuccess() throws Exception { // when ResultActions actions = mockMvc.perform( - get("/members/{memberId}/bookmarks", 1) + get( BASE_URL + "/{memberId}/bookmarks", 1) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .param("size", String.valueOf(size)) @@ -614,7 +614,7 @@ void getBookmarksSuccessWithCursor() throws Exception { // when ResultActions actions = mockMvc.perform( - get("/members/{memberId}/bookmarks", 1) + get(BASE_URL + "/{memberId}/bookmarks", 1) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .param("size", String.valueOf(size)) diff --git a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java index f8edee07..a96c88ad 100644 --- a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java +++ b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java @@ -1,20 +1,33 @@ package io.oduck.api.unit.bookmark.service; import static io.oduck.api.global.utils.PagingUtils.applyPageableForNonOffset; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.repository.AnimeRepository; import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; +import io.oduck.api.domain.bookmark.entity.Bookmark; import io.oduck.api.domain.bookmark.repository.BookmarkRepository; import io.oduck.api.domain.bookmark.service.BookmarkServiceImpl; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.repository.MemberRepository; import io.oduck.api.global.common.OrderDirection; import io.oduck.api.global.common.SliceResponse; +import io.oduck.api.global.config.QueryDslConfig; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -22,17 +35,68 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.annotation.Import; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; @ExtendWith(MockitoExtension.class) +@Import(QueryDslConfig.class) public class BookmarkServiceTest { @InjectMocks private BookmarkServiceImpl bookmarkService; + @Mock + MemberRepository memberRepository; + + @Mock + AnimeRepository animeRepository; + @Mock BookmarkRepository bookmarkRepository; + @DisplayName("북마크 토글") + @Nested + class ToggleBookmark { + Member member = Member.builder() + .id(1L) + .build(); + Anime anime = Anime.builder() + .id(1L) + .build(); + + Bookmark bookmark = Bookmark.builder() + .member(member) + .anime(anime) + .build(); + @DisplayName("북마크 저장 성공") + @Test + void toggleBookmarkSaveSuccess() { + given(memberRepository.findById(anyLong())).willReturn(Optional.of(member)); + given(animeRepository.findById(anyLong())).willReturn(Optional.of(anime)); + given(bookmarkRepository.findByMemberIdAndAnimeId(anyLong(), anyLong())) + .willReturn(Optional.empty()); + given(bookmarkRepository.save(any(Bookmark.class))).willReturn(bookmark); + + boolean result = bookmarkService.toggleBookmark(member.getId(), anime.getId()); + + assertDoesNotThrow(() -> bookmarkService.toggleBookmark(member.getId(), anime.getId())); + assertTrue(result); + } + + @DisplayName("북마크 삭제 성공") + @Test + void toggleBookmarkDeleteSuccess() { + given(bookmarkRepository.findByMemberIdAndAnimeId(anyLong(), anyLong())) + .willReturn(Optional.of(bookmark)); + doNothing().when(bookmarkRepository).delete(any(Bookmark.class)); + + boolean result = bookmarkService.toggleBookmark(member.getId(), anime.getId()); + + assertDoesNotThrow(() -> bookmarkService.toggleBookmark(member.getId(), anime.getId())); + assertFalse(result); + } + } + @DisplayName("회원의 북마크 목록 조회") @Nested class GetBookmarksByMemberId { diff --git a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java index 801fef09..137d3af5 100644 --- a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java +++ b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java @@ -191,7 +191,5 @@ void updateProfileFailureWhenSameNameAsAlreadyExist() { } } - // TODO: 회원이 작성한 리뷰 목록 - // TODO: 회원 북마크 애니 목록 } From f863b4e5bfa337fb66f5867d32e9e564477d7d38 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 16:45:57 +0900 Subject: [PATCH 278/734] =?UTF-8?q?refactor:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=EC=97=90=20avgScore?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java index 16680e75..f1af6b6a 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java @@ -17,6 +17,7 @@ public static class BookmarkRes implements EntityBased { private Long animeId; private String title; private String thumbnail; + private double avgScore = 0.0; private Integer myScore; private LocalDateTime createdAt; From d94b8cd80417e181b2c542ca66c4153ca3566581 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 16:46:20 +0900 Subject: [PATCH 279/734] =?UTF-8?q?test:=20bookmarkController=20order=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/bookmark/BookmarkControllerTest.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java b/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java index 3c8bfb8e..5bc6d488 100644 --- a/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java @@ -16,8 +16,11 @@ import io.oduck.api.domain.member.entity.Role; import io.oduck.api.global.mockMember.WithCustomMockMember; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; @@ -46,9 +49,11 @@ public class BookmarkControllerTest { @DisplayName("북마크 토글") @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class toggleBookmark { @DisplayName("북마크 토글 저장 성공") @Test + @Order(1) @WithCustomMockMember(id = 2L, email = "john", password = "Qwer!234", role = Role.MEMBER) void toggleBookmarkSaveSuccess() throws Exception { // given @@ -79,8 +84,8 @@ void toggleBookmarkSaveSuccess() throws Exception { .value("Fields for bookmark creation")), fieldWithPath("animeId") .attributes(key("constraints") - .value("애니메이션 ID, NotNull, Min(1)")) - .description("애니메이션 ID") + .value("애니메 ID, NotNull, Min(1)")) + .description("애니메 ID") ) ) ); @@ -88,7 +93,8 @@ void toggleBookmarkSaveSuccess() throws Exception { @DisplayName("북마크 토글 삭제 성공") @Test - @WithCustomMockMember(id = 1L, email = "admin", password = "Qwer!234", role = Role.MEMBER) + @Order(2) + @WithCustomMockMember(id = 2L, email = "john", password = "Qwer!234", role = Role.MEMBER) void toggleBookmarkDeleteSuccess() throws Exception { // given Long animeId = 1L; @@ -118,8 +124,8 @@ void toggleBookmarkDeleteSuccess() throws Exception { .value("Fields for bookmark creation")), fieldWithPath("animeId") .attributes(key("constraints") - .value("애니메이션 ID, NotNull, Min(1)")) - .description("애니메이션 ID") + .value("애니메 ID, NotNull, Min(1)")) + .description("애니메 ID") ) ) ); From b3f1c7e9bd659c6d243eb67a2e51eaaf373b1d00 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 16:46:41 +0900 Subject: [PATCH 280/734] =?UTF-8?q?test:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20avgScore=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/e2e/member/MemberControllerTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index a337bc73..9f800e88 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -582,6 +582,9 @@ void getBookmarksSuccess() throws Exception { fieldWithPath("items[].thumbnail") .type(JsonFieldType.STRING) .description("애니 썸네일"), + fieldWithPath("items[].avgScore") + .type(JsonFieldType.NUMBER) + .description("해당 애니메의 평균 별점"), fieldWithPath("items[].myScore") .type(JsonFieldType.NUMBER) .description("해당 회원이 애니에 매긴 별점. 없을 경우 -1"), @@ -670,6 +673,9 @@ void getBookmarksSuccessWithCursor() throws Exception { fieldWithPath("items[].thumbnail") .type(JsonFieldType.STRING) .description("애니 썸네일"), + fieldWithPath("items[].avgScore") + .type(JsonFieldType.NUMBER) + .description("해당 애니메의 평균 별점"), fieldWithPath("items[].myScore") .type(JsonFieldType.NUMBER) .description("해당 회원이 애니에 매긴 별점. 없을 경우 -1"), From 238f9cc22fead2c85150dba5cb44f3e717b0c969 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 18:10:06 +0900 Subject: [PATCH 281/734] =?UTF-8?q?feat:=20bookmark=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=91=EB=8B=B5=20dto=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/bookmark/dto/BookmarkResDto.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java index f1af6b6a..158a749d 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java @@ -3,6 +3,7 @@ import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; import io.oduck.api.global.common.EntityBased; import java.time.LocalDateTime; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -11,7 +12,7 @@ public class BookmarkResDto { @Getter @Builder - @NoArgsConstructor + @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public static class BookmarkRes implements EntityBased { private Long animeId; @@ -42,4 +43,12 @@ public String bringId(String property) { } } } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class BookmarkedDateTimeRes { + private String createdAt; + } } From 5c9510091673cdab2a7b164d6ed9b2a21cccaad5 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 18:10:21 +0900 Subject: [PATCH 282/734] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bookmark/service/BookmarkService.java | 2 ++ .../bookmark/service/BookmarkServiceImpl.java | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java index 1f5cf823..93baaae0 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkService.java @@ -2,11 +2,13 @@ import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; +import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkedDateTimeRes; import io.oduck.api.global.common.OrderDirection; import io.oduck.api.global.common.SliceResponse; public interface BookmarkService { boolean toggleBookmark(Long memberId, Long animeId); + BookmarkedDateTimeRes checkBookmarked(Long memberId, Long animeId); SliceResponse getBookmarksByMemberId(Long memberId, String cursor, Sort sort, OrderDirection order, int size); } diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java index b54fc087..b0e1995d 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java @@ -7,6 +7,7 @@ import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; import io.oduck.api.domain.bookmark.dto.BookmarkReqDto; import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; +import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkedDateTimeRes; import io.oduck.api.domain.bookmark.entity.Bookmark; import io.oduck.api.domain.bookmark.repository.BookmarkRepository; import io.oduck.api.domain.member.entity.Member; @@ -55,6 +56,18 @@ public boolean toggleBookmark(Long memberId, Long animeId) { return true; } + public BookmarkedDateTimeRes checkBookmarked(Long memberId, Long animeId) { + Optional optionalBookmark = getBookmark(memberId, animeId); + + if (optionalBookmark.isPresent()) { + return BookmarkedDateTimeRes.builder() + .createdAt(optionalBookmark.get().getCreatedAt().toString()) + .build(); + } + + throw new NotFoundException("bookmark"); + } + @Override public SliceResponse getBookmarksByMemberId(Long memberId, String cursor, BookmarkReqDto.Sort sort, OrderDirection order, int size) { Sort sortList = Sort.by( From d46f28ca5ef93d81c6f87a00f229937719a96949 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 18:10:42 +0900 Subject: [PATCH 283/734] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EC=A1=B0=ED=9A=8C=20=ED=95=BB=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/bookmark/controller/BookmarkController.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/bookmark/controller/BookmarkController.java b/src/main/java/io/oduck/api/domain/bookmark/controller/BookmarkController.java index fcf57fa9..8ba8f990 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/controller/BookmarkController.java +++ b/src/main/java/io/oduck/api/domain/bookmark/controller/BookmarkController.java @@ -9,6 +9,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; +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; @@ -31,4 +33,11 @@ public ResponseEntity postBookmark( .status(isCreated ? HttpStatus.CREATED : HttpStatus.NO_CONTENT) .build(); } + + @GetMapping("/{animeId}") + public ResponseEntity getBookmark( + @PathVariable("animeId") Long animeId, + @LoginUser AuthUser user) { + return ResponseEntity.ok(bookmarkService.checkBookmarked(user.getId(), animeId)); + } } \ No newline at end of file From 8286faa9983bf7db7fbac81824e064d467fb9bed Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 18:11:04 +0900 Subject: [PATCH 284/734] =?UTF-8?q?feat:=20bookmark=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=9D=B8=EA=B0=80=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/global/security/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java index 503473b7..e40f442f 100644 --- a/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java +++ b/src/main/java/io/oduck/api/global/security/config/SecurityConfig.java @@ -81,6 +81,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(HttpMethod.PUT, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) .requestMatchers(HttpMethod.PATCH, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) .requestMatchers(HttpMethod.DELETE, "/members/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) + .requestMatchers( "/bookmarks/**").hasAnyAuthority(Role.MEMBER.name(), Role.ADMIN.name()) // .requestMatchers("/oduckdmin/**").hasAuthority(Role.ADMIN.name()) .anyRequest().permitAll() ); From 9c5964b2597bcc8a129925a81c269276215fb3a4 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Sun, 22 Oct 2023 18:11:45 +0900 Subject: [PATCH 285/734] =?UTF-8?q?test:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../e2e/bookmark/BookmarkControllerTest.java | 82 +++++++++++++++++++ .../bookmark/service/BookmarkServiceTest.java | 49 +++++++++++ 2 files changed, 131 insertions(+) diff --git a/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java b/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java index 5bc6d488..80561cdf 100644 --- a/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java @@ -1,12 +1,16 @@ package io.oduck.api.e2e.bookmark; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -131,4 +135,82 @@ void toggleBookmarkDeleteSuccess() throws Exception { ); } } + + @DisplayName("북마크 여부 조회") + @Nested + class checkBookmarked { + @DisplayName("북마크 여부 조회 북마크 존재시") + @Test + @WithCustomMockMember(id = 1L, email = "admin", password = "Qwer!234", role = Role.MEMBER) + void checkBookmarkedExist() throws Exception { + // given + Long animeId = 1L; + + // when + ResultActions actions = mockMvc.perform( + get(BASE_URL + "/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + actions + .andExpect(status().isOk()) + .andDo( + document("bookmark/checkBookmarked/exist", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId") + .description("애니메 id") + ), + responseFields( + attributes(key("title") + .value("Fields for bookmark creation")), + fieldWithPath("createdAt") + .description("북마크 생성일") + ) + ) + ); + } + + @DisplayName("북마크 여부 조회 북마크 부재시") + @Test + @WithCustomMockMember(id = 3L, email = "david", password = "Qwer!234", role = Role.MEMBER) + void checkBookmarkedNotExist() throws Exception { + // given + Long animeId = 1L; + + // when + ResultActions actions = mockMvc.perform( + get(BASE_URL + "/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + // then + actions + .andExpect(status().isNotFound()) + .andDo( + document("bookmark/checkBookmarked/notExist", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId") + .description("애니메 id") + ), + responseFields( + attributes(key("title") + .value("Fields for bookmark creation")), + fieldWithPath("message") + .description("에러 메시지"), + fieldWithPath("fieldErrors") + .description("필드 에러 목록"), + fieldWithPath("violationErrors") + .description("위반 에러 목록") + ) + ) + ); + } + } } diff --git a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java index a96c88ad..12cafbc6 100644 --- a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java +++ b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -17,6 +18,7 @@ import io.oduck.api.domain.bookmark.dto.BookmarkDslDto.BookmarkDsl; import io.oduck.api.domain.bookmark.dto.BookmarkReqDto.Sort; import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkRes; +import io.oduck.api.domain.bookmark.dto.BookmarkResDto.BookmarkedDateTimeRes; import io.oduck.api.domain.bookmark.entity.Bookmark; import io.oduck.api.domain.bookmark.repository.BookmarkRepository; import io.oduck.api.domain.bookmark.service.BookmarkServiceImpl; @@ -25,6 +27,8 @@ import io.oduck.api.global.common.OrderDirection; import io.oduck.api.global.common.SliceResponse; import io.oduck.api.global.config.QueryDslConfig; +import io.oduck.api.global.exception.NotFoundException; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -126,4 +130,49 @@ void getBookmarksByMemberIdSuccess() { assertFalse(result.isHasNext()); } } + + @DisplayName("북마크 체크") + @Nested + class checkBookmarked { + @DisplayName("북마크 체크시 북마크가 존재할 경우") + @Test + void checkBookmarkedExist() { + // given + Long memberId = 1L; + Long animeId = 1L; + + Bookmark bookmark = Bookmark.builder() + .member(Member.builder().id(memberId).build()) + .anime(Anime.builder().id(animeId).build()) + .createdAt(LocalDateTime.now()) + .build(); + + given(bookmarkRepository.findByMemberIdAndAnimeId(memberId, animeId)) + .willReturn(Optional.of(bookmark)); + + // when + BookmarkedDateTimeRes result = bookmarkService.checkBookmarked(memberId, animeId); + + // then + assertNotNull(result); + assertNotNull(result.getCreatedAt()); + } + + @DisplayName("북마크 체크시 북마크가 존재하지 않을 경우") + @Test + void checkBookmarkedNotExist() { + // given + Long memberId = 1L; + Long animeId = 1L; + + given(bookmarkRepository.findByMemberIdAndAnimeId(memberId, animeId)) + .willReturn(Optional.empty()); + + // when + // then + assertThrows(NotFoundException.class, + () -> bookmarkService.checkBookmarked(memberId, animeId) + ); + } + } } From a0bda2c2452ddc192ae15934abccc1a4332112ed Mon Sep 17 00:00:00 2001 From: hanyMK Date: Sun, 22 Oct 2023 19:55:35 +0900 Subject: [PATCH 286/734] =?UTF-8?q?test=20:=20=EC=A7=A7=EC=9D=80=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20test=EC=B6=94=EA=B0=80=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ShortReviewRepositoryTest.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/shortReview/repository/ShortReviewRepositoryTest.java diff --git a/src/test/java/io/oduck/api/unit/shortReview/repository/ShortReviewRepositoryTest.java b/src/test/java/io/oduck/api/unit/shortReview/repository/ShortReviewRepositoryTest.java new file mode 100644 index 00000000..bdb2c416 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/shortReview/repository/ShortReviewRepositoryTest.java @@ -0,0 +1,168 @@ + +package io.oduck.api.unit.shortReview.repository; + +import static io.oduck.api.global.utils.AnimeTestUtils.getBroadcastType; +import static io.oduck.api.global.utils.AnimeTestUtils.getEpisodeCount; +import static io.oduck.api.global.utils.AnimeTestUtils.getQuarter; +import static io.oduck.api.global.utils.AnimeTestUtils.getRating; +import static io.oduck.api.global.utils.AnimeTestUtils.getStatus; +import static io.oduck.api.global.utils.AnimeTestUtils.getSummary; +import static io.oduck.api.global.utils.AnimeTestUtils.getThumbnail; +import static io.oduck.api.global.utils.AnimeTestUtils.getTitle; +import static io.oduck.api.global.utils.AnimeTestUtils.getYear; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.entity.MemberProfile; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.domain.review.entity.ShortReview; +import io.oduck.api.domain.review.repository.ShortReviewRepository; + +import java.util.ArrayList; +import java.util.List; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +public class ShortReviewRepositoryTest { + + @Autowired + private ShortReviewRepository shortReviewRepository; + @Autowired + private AnimeRepository animeRepository; + + @Autowired + private MemberRepository memberRepository; + + @Nested + @DisplayName("리뷰 등록") + class PostShortReview{ + + @Test + @DisplayName("리뷰 등록 성공") + void saveShortReview(){ + //given + //리뷰 생성 + ShortReview shortReview = ShortReview + .builder() + .content("애니리뷰내용") + .hasSpoiler(false) + .build(); + + //회원 생성 + Member member = Member.builder() + .build(); + Member saveMember = memberRepository.save(member); + Long memberId = saveMember.getId(); + + // 애니 생성 + List animeStudios = new ArrayList<>(); + List animeVoiceActors = new ArrayList<>(); + List animeGenres = new ArrayList<>(); + List animeOriginalAuthors = new ArrayList<>(); + + Anime createAnime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, null + ); + + Anime anime = animeRepository.saveAndFlush(createAnime); + Long animeId = anime.getId(); + + shortReview.relateMember(member); + shortReview.relateAnime(anime); + + //when + ShortReview saveShortReview = shortReviewRepository.save(shortReview); + + //then + assertNotNull(saveShortReview); + assertThat(saveShortReview.getAnime().getId()).isEqualTo(animeId); + assertThat(saveShortReview.getMember().getId()).isEqualTo(memberId); + assertThat(saveShortReview.getContent()).isEqualTo(shortReview.getContent()); + assertThat(saveShortReview.isHasSpoiler()).isEqualTo(shortReview.isHasSpoiler()); + } + } + + @Nested + @DisplayName("리뷰 수정") + class PatchShortReview { + + @Test + @DisplayName("리뷰 내용 수정 성공") + void changeShortReviewContent() { + //given + Long reviewId = 1L; + Long memberId = 1L; + String content = "애니리뷰내용수정"; + boolean hasSpoiler = true; + + //회원 생성 + Member member = Member + .builder() + .id(memberId) + .build(); + + memberRepository.save(member); + + //리뷰 생성 + ShortReview shortReview = ShortReview + .builder() + .content("애니리뷰내용") + .hasSpoiler(hasSpoiler) + .build(); + + //애니 생성 + List animeStudios = new ArrayList<>(); + List animeVoiceActors = new ArrayList<>(); + List animeGenres = new ArrayList<>(); + List animeOriginalAuthors = new ArrayList<>(); + + Anime createAnime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, null + ); + + Anime anime = animeRepository.saveAndFlush(createAnime); + Long animeId = anime.getId(); + + shortReview.relateMember(member); + shortReview.relateAnime(anime); + + ShortReview saveShortReview = shortReviewRepository.save(shortReview); + + //when + ShortReview findShortReview = shortReviewRepository.findById(reviewId).get(); + + //리뷰 수정 + findShortReview.updateContent(content); + findShortReview.updateSpoiler(hasSpoiler); + + ShortReview updateShortReview = shortReviewRepository.save(findShortReview); + + //then + assertNotNull(findShortReview); + assertEquals(updateShortReview.getAnime().getId(), animeId); + assertEquals(updateShortReview.getMember().getId(), memberId); + assertEquals(updateShortReview.getContent(), content); + assertEquals(updateShortReview.isHasSpoiler(), hasSpoiler); + } + } +} \ No newline at end of file From 95bc505dd73693c3fec11d90dd9b56715c9e9daa Mon Sep 17 00:00:00 2001 From: hanyMK Date: Sun, 22 Oct 2023 19:57:42 +0900 Subject: [PATCH 287/734] =?UTF-8?q?refactor:=20=EC=A7=A7=EC=9D=80=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=97=B0=EA=B4=80=20=EA=B4=80=EA=B3=84=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/review/entity/ShortReview.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java index cf73543c..2ca38347 100644 --- a/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java +++ b/src/main/java/io/oduck/api/domain/review/entity/ShortReview.java @@ -46,4 +46,27 @@ public class ShortReview extends BaseEntity { @OneToMany(mappedBy = "shortReview", cascade = CascadeType.PERSIST) private List shortReviewLikes; + + @Builder + public ShortReview(Member member, Anime anime, String content, boolean hasSpoiler) { + this.member = member; + this.anime = anime; + this.content = content; + this.hasSpoiler = hasSpoiler; + } + + public void relateMember(Member member){ + this.member = member; + } + public void relateAnime(Anime anime){ + this.anime = anime; + } + + public void updateContent(String content){ + this.content = content; + + } + public void updateSpoiler(boolean hasSpoiler){ + this.hasSpoiler = hasSpoiler; + } } From a504207fb29aaa88524e39b37f4f59559e292100 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Sun, 22 Oct 2023 20:13:44 +0900 Subject: [PATCH 288/734] =?UTF-8?q?refactor:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=EC=9E=90=20=EB=B3=80=EA=B2=BD=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java index 1925c2e5..9ec1ddf4 100644 --- a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java @@ -14,8 +14,8 @@ public class ShortReviewReqDto { @AllArgsConstructor @Builder public static class PostShortReviewReq{ - private Long memberId; private Long animeId; + private String name; private boolean hasSpoiler; @NotBlank @Length(min = 10, max = 100, From 1b752d36a80d010dd19e6e3758cd39154c084a94 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Sun, 22 Oct 2023 20:42:10 +0900 Subject: [PATCH 289/734] refactor: getMemberId -> getName #33 --- .../io/oduck/api/global/utils/ShortReviewTestUtils.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java b/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java index a46f7d2f..9d5e8265 100644 --- a/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java +++ b/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java @@ -7,7 +7,7 @@ public class ShortReviewTestUtils { public static PostShortReviewReq createPostShoreReviewReq(){ return new PostShortReviewReq( - getMemberId(),getAnimeId(), + getAnimeId(),getName(), isHasSpoiler(),getContent() ); } @@ -18,9 +18,7 @@ public static PatchShortReviewReq createPatchShortReview(){ ); } - public static Long getMemberId(){ - return 1L; - } + public static String getName(){return "회원이름";} public static Long getAnimeId(){ return 1L; From 64a9e4d05eb0bd903ad10038ed9774e1f4a0a57e Mon Sep 17 00:00:00 2001 From: hanyMK Date: Sun, 22 Oct 2023 20:44:40 +0900 Subject: [PATCH 290/734] =?UTF-8?q?feat:=20ShortReviewServiceImpl=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20stub=20=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ShortReviewServiceImpl.java | 100 +++++++++++++ .../service/ShortReviewServiceStub.java | 132 +++++++++--------- 2 files changed, 166 insertions(+), 66 deletions(-) create mode 100644 src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceImpl.java diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceImpl.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceImpl.java new file mode 100644 index 00000000..c2be6f3d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceImpl.java @@ -0,0 +1,100 @@ +package io.oduck.api.domain.review.service; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.member.entity.Member; +import io.oduck.api.domain.member.repository.MemberRepository; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PatchShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; +import io.oduck.api.domain.review.dto.ShortReviewResDto; +import io.oduck.api.domain.review.entity.ShortReview; +import io.oduck.api.domain.review.repository.ShortReviewRepository; +import io.oduck.api.global.exception.BadRequestException; +import io.oduck.api.global.exception.NotFoundException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ShortReviewServiceImpl implements ShortReviewService{ + + private final ShortReviewRepository shortReviewRepository; + private final MemberRepository memberRepository; + private final AnimeRepository animeRepository; + + @Override + public ShortReviewResDto getShortReviews(Long anime) { + return null; + } + + @Override + public void save(Long memberId, PostShortReviewReq shortReviewReq) { + ShortReview shortReview = ShortReview + .builder() + .content(shortReviewReq.getContent()) + .hasSpoiler(shortReviewReq.isHasSpoiler()) + .build(); + + //애니 입력 + shortReview.relateAnime(getAnime(shortReviewReq.getAnimeId())); + + //회원 입력 + shortReview.relateMember(getMember(memberId)); + + ShortReview saveShortReview = shortReviewRepository.save(shortReview); + log.info("ShortReview Crated! {}", saveShortReview.getId()); + } + + @Override + public void update(Long reviewId, PatchShortReviewReq req) { + ShortReview findShortReview = getShortReview(reviewId); + + Optional + .ofNullable(req.getContent()) + .ifPresent( + content ->{ + if(findShortReview.getContent().equals(req.getContent())){ + throw new BadRequestException("Same content as before."); + } + findShortReview.updateContent(req.getContent()); + } + ); + + Optional + .ofNullable(req.getContent()) + .ifPresent( + hasSpoiler ->{ + if(findShortReview.isHasSpoiler() == req.isHasSpoiler()){ + throw new BadRequestException("Same spoiler as before."); + } + findShortReview.updateSpoiler(req.isHasSpoiler()); + } + ); + shortReviewRepository.save(findShortReview); + } + + private Member getMember(Long memberId){ + + return memberRepository.findById(memberId) + .orElseThrow( + () -> new NotFoundException("Member") + ); + } + + private Anime getAnime(Long animeId){ + return animeRepository.findById(animeId) + .orElseThrow( + () -> new NotFoundException("Anime") + ); + } + + private ShortReview getShortReview(Long reviewId){ + return shortReviewRepository.findById(reviewId) + .orElseThrow( + () -> new NotFoundException("shortReview") + ); + } +} \ No newline at end of file diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java index b152120c..8143fd54 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java @@ -1,66 +1,66 @@ -package io.oduck.api.domain.review.service; - -import io.oduck.api.domain.review.dto.ShortReviewReqDto.PatchShortReviewReq; -import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; -import io.oduck.api.domain.review.dto.ShortReviewResDto; -import io.oduck.api.domain.review.dto.ShortReviewResDto.MemberProfile; -import io.oduck.api.domain.review.dto.ShortReviewResDto.ShortReview; -import java.util.ArrayList; -import java.util.List; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import org.springframework.stereotype.Service; - -@Service -public class ShortReviewServiceStub implements ShortReviewService{ - - @Override - public ShortReviewResDto getShortReviews(Long animeId) { - List shortReviewList = new ArrayList<>(); - - for(int i = 0; i < 10; i++){ - ShortReview shortReview = createReview(animeId); - shortReviewList.add(shortReview); - } - //slice객체 만들기 - PageRequest pageable = PageRequest.of(0,10); - Slice slice = new SliceImpl<>(shortReviewList,pageable,true); - - return ShortReviewResDto - .builder() - .shortReviews(slice) - .build(); - } - - @Override - public void save(PostShortReviewReq shortReviewReq) { - - } - - @Override - public void update(Long reviewId, PatchShortReviewReq req) { - - } - - private ShortReview createReview(Long animeId){ - - return ShortReview - .builder() - .reviewId(1L) - .animeId(1L) - .content("최고의 반전의 반전") - .score(5) - .hasSpoiler(true) - .shortReviewLikeCount(100) - .member(getMemberProfile()) - .build(); - } - - private MemberProfile getMemberProfile(){ - return MemberProfile.builder() - .name("오덕12") - .thumbnail("사진 url") - .build(); - } -} +//package io.oduck.api.domain.review.service; +// +//import io.oduck.api.domain.review.dto.ShortReviewReqDto.PatchShortReviewReq; +//import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; +//import io.oduck.api.domain.review.dto.ShortReviewResDto; +//import io.oduck.api.domain.review.dto.ShortReviewResDto.MemberProfile; +//import io.oduck.api.domain.review.dto.ShortReviewResDto.ShortReview; +//import java.util.ArrayList; +//import java.util.List; +//import org.springframework.data.domain.PageRequest; +//import org.springframework.data.domain.Slice; +//import org.springframework.data.domain.SliceImpl; +//import org.springframework.stereotype.Service; +// +//@Service +//public class ShortReviewServiceStub implements ShortReviewService{ +// +// @Override +// public ShortReviewResDto getShortReviews(Long animeId) { +// List shortReviewList = new ArrayList<>(); +// +// for(int i = 0; i < 10; i++){ +// ShortReview shortReview = createReview(animeId); +// shortReviewList.add(shortReview); +// } +// //slice객체 만들기 +// PageRequest pageable = PageRequest.of(0,10); +// Slice slice = new SliceImpl<>(shortReviewList,pageable,true); +// +// return ShortReviewResDto +// .builder() +// .shortReviews(slice) +// .build(); +// } +// +// @Override +// public void save(PostShortReviewReq shortReviewReq) { +// +// } +// +// @Override +// public void update(Long reviewId, PatchShortReviewReq req) { +// +// } +// +// private ShortReview createReview(Long animeId){ +// +// return ShortReview +// .builder() +// .reviewId(1L) +// .animeId(1L) +// .content("최고의 반전의 반전") +// .score(5) +// .hasSpoiler(true) +// .shortReviewLikeCount(100) +// .member(getMemberProfile()) +// .build(); +// } +// +// private MemberProfile getMemberProfile(){ +// return MemberProfile.builder() +// .name("오덕12") +// .thumbnail("사진 url") +// .build(); +// } +//} From 23fb2ac67778d9d4b5f21ff6f5da816962344fd6 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Sun, 22 Oct 2023 20:52:55 +0900 Subject: [PATCH 291/734] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=ED=9A=8C=EC=9B=90=20=EC=95=84=EC=9D=B4=EB=94=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/review/controller/ShortReviewController.java | 5 ++++- .../oduck/api/domain/review/service/ShortReviewService.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java index 799e4954..0056e6b1 100644 --- a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java +++ b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java @@ -4,6 +4,8 @@ import io.oduck.api.domain.review.dto.ShortReviewResDto; import io.oduck.api.domain.review.service.ShortReviewService; import io.oduck.api.global.common.SliceResponse; +import io.oduck.api.global.security.auth.dto.LoginUser; +import io.oduck.api.global.security.auth.entity.AuthLocal; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -35,9 +37,10 @@ public ResponseEntity getShortReviews( } @PostMapping public ResponseEntity postShortReview( + @LoginUser AuthLocal user, @RequestBody @Valid ShortReviewReqDto.PostShortReviewReq req) { //TODO : 짧은 리뷰 작성 - shortReviewService.save(req); + shortReviewService.save(user.getId(), req); return ResponseEntity.ok().build(); } diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java index 9a521276..8015d2bd 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewService.java @@ -10,7 +10,7 @@ public interface ShortReviewService { ShortReviewResDto getShortReviews(Long anime); //애니 리뷰 작성 - void save(PostShortReviewReq shortReviewReq); + void save(Long memberId, PostShortReviewReq shortReviewReq); //애니 리뷰 수정 void update(Long reviewId, PatchShortReviewReq req); From ec323c80d90a9d0b0a285c708bb66f071a582490 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 22 Oct 2023 22:02:53 +0900 Subject: [PATCH 292/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20dto=20=EC=83=9D=EC=84=B1=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/dto/AnimeRes.java | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java index 83624fd9..59c50ffc 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -1,15 +1,19 @@ package io.oduck.api.domain.anime.dto; +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; import io.oduck.api.domain.anime.entity.Rating; import io.oduck.api.domain.anime.entity.Status; import java.util.List; -import lombok.Builder; +import java.util.stream.Collectors; import lombok.Getter; @Getter -@Builder public class AnimeRes { private Long id; private String title; @@ -22,10 +26,39 @@ public class AnimeRes { private Rating rating; private Status status; - private List genres; private List originalAuthors; private List voiceActors; + private List genres; private List studios; private long reviewCount; private long bookmarkCount; + + public AnimeRes(Anime anime, List animeOriginalAuthors, + List animeVoiceActors, List animeStudios, + List animeGenres) { + id = anime.getId(); + title = anime.getTitle(); + thumbnail = anime.getThumbnail(); + broadcastType = anime.getBroadcastType(); + year = anime.getYear(); + quarter = anime.getQuarter(); + summary = anime.getSummary(); + episodeCount = anime.getEpisodeCount(); + rating = anime.getRating(); + status = anime.getStatus(); + originalAuthors = animeOriginalAuthors.stream() + .map(aoa -> aoa.getOriginalAuthor().getName()) + .collect(Collectors.toList()); + voiceActors = animeVoiceActors.stream() + .map(ava -> new VoiceActorRes(ava.getVoiceActor().getName(), ava.getPart())) + .collect(Collectors.toList()); + genres = animeGenres.stream() + .map(ag -> ag.getGenre().getName()) + .collect(Collectors.toList()); + studios = animeStudios.stream() + .map(as -> as.getStudio().getName()) + .collect(Collectors.toList()); + reviewCount = anime.getReviewCount(); + bookmarkCount = anime.getBookmarkCount(); + } } From db79e91aeb311d251a52329c1d77cf609a9eb02b Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 22 Oct 2023 22:03:42 +0900 Subject: [PATCH 293/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=EB=B3=B4=EA=B8=B0=20=EA=B5=AC=ED=98=84=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anime/service/AnimeServiceImpl.java | 82 +++++-------------- 1 file changed, 21 insertions(+), 61 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index ecea8bdc..99cd5b10 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -9,17 +9,16 @@ import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.dto.VoiceActorReq; -import io.oduck.api.domain.anime.dto.VoiceActorRes; import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.domain.anime.entity.AnimeGenre; import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; import io.oduck.api.domain.anime.entity.AnimeStudio; import io.oduck.api.domain.anime.entity.AnimeVoiceActor; -import io.oduck.api.domain.anime.entity.BroadcastType; -import io.oduck.api.domain.anime.entity.Quarter; -import io.oduck.api.domain.anime.entity.Rating; -import io.oduck.api.domain.anime.entity.Status; +import io.oduck.api.domain.anime.repository.AnimeGenreRepository; +import io.oduck.api.domain.anime.repository.AnimeOriginalAuthorRepository; import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.anime.repository.AnimeStudioRepository; +import io.oduck.api.domain.anime.repository.AnimeVoiceActorRepository; import io.oduck.api.domain.genre.entity.Genre; import io.oduck.api.domain.genre.repository.GenreRepository; import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; @@ -31,7 +30,6 @@ import io.oduck.api.domain.voiceActor.entity.VoiceActor; import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; import io.oduck.api.global.exception.NotFoundException; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -47,18 +45,32 @@ public class AnimeServiceImpl implements AnimeService{ private final AnimeRepository animeRepository; + private final OriginalAuthorRepository originalAuthorRepository; + private final AnimeOriginalAuthorRepository animeOriginalAuthorRepository; + private final VoiceActorRepository voiceActorRepository; + private final AnimeVoiceActorRepository animeVoiceActorRepository; + private final StudioRepository studioRepository; + private final AnimeStudioRepository animeStudioRepository; + private final GenreRepository genreRepository; + private final AnimeGenreRepository animeGenreRepository; + private final SeriesRepository seriesRepository; @Override + @Transactional(readOnly = true) public AnimeRes getAnimeById(Long animeId) { - //TODO: 애니 상세 조회 구현 - AnimeRes anime = createAnimeDto(animeId); - return anime; + Anime anime = findAnime(animeId); + List animeOriginalAuthors = animeOriginalAuthorRepository.findAllFetchByAnimeId(animeId); + List animeVoiceActors = animeVoiceActorRepository.findAllFetchByAnimeId(animeId); + List animeStudios = animeStudioRepository.findAllFetchByAnimeId(animeId); + List animeGenres = animeGenreRepository.findAllFetchByAnimeId(animeId); + + return new AnimeRes(anime, animeOriginalAuthors, animeVoiceActors, animeStudios, animeGenres); } @Override @@ -220,56 +232,4 @@ public void updateSeries(Long animeId, PatchSeriesIdReq patchReq) { public Anime findAnime(Long animeId) { return animeRepository.findById(animeId).orElseThrow(() -> new NotFoundException("Anime")); } - - private AnimeRes createAnimeDto(Long animeId) { - return AnimeRes.builder() - .id(animeId) - .title("귀멸의 칼날: 도공 마을편") - .thumbnail("https://image파일경로/uuid.jpg") - .broadcastType(BroadcastType.TVA) - .year(2023) - .quarter(Quarter.Q2) - .summary( - "113년 만에 상현 혈귀가 죽자 분개한 무잔은 나머지 상현 혈귀들에게 또 다른 명령을 내린다! 한편, 규타로와의 전투 도중 검이 심하게 손상된 탄지로에게 하가네즈카는 대 격노하고 탄지로는 그 검을 만든 대장장이 하가네즈카 호타루에게 검이 어떻게 심하게 손상되었는지 설명하기 위해 도공 마을을 방문한다. 탄지로가 검이 수리되기를 기다리는 동안, 상현 혈귀 한텐구와 쿗코가 숨겨진 마을인 ‘도공 마을'을 습격한다. 공격할 때마다 분열해서 위력이 커지는 한텐구로 인해 탄지로와 겐야는 고전을 면치 못한다. 한편, 타인에 대한 관심이 희박한 하주 토키토 무이치로는 혈귀들에게 공격당하고 있는 코테츠를 목격하는데….") - .episodeCount(11) - .rating(Rating.ADULT) - .status(Status.FINISHED) - .genres(getGenres()) - .originalAuthors(getOriginalAuthors()) - .voiceActors(getVoiceActors()) - .studios(getStudios()) - .reviewCount(172) - .bookmarkCount(72) - .build(); - } - - private List getStudios() { - List studios = new ArrayList<>(); - studios.add("ufotable"); - return studios; - } - - private List getVoiceActors() { - List voiceActors = new ArrayList<>(); - - for(int i = 0; i<5; i++){ - VoiceActorRes voiceActor = new VoiceActorRes("성우"+i, "카마도 탄지로"+i); - voiceActors.add(voiceActor); - } - - return voiceActors; - } - - private List getGenres(){ - List genres = new ArrayList<>(); - genres.add("판타지"); - genres.add("액션"); - return genres; - } - - private List getOriginalAuthors() { - List originalAuthors = new ArrayList<>(); - originalAuthors.add("고토게 코요하루"); - return originalAuthors; - } } From fb27424c12d30cbf4196845824828493cf7a3ef0 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 22 Oct 2023 22:05:16 +0900 Subject: [PATCH 294/734] =?UTF-8?q?refactor:=20=EB=A6=AC=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20fetch=20join=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/anime/repository/AnimeGenreRepository.java | 8 ++++---- .../anime/repository/AnimeOriginalAuthorRepository.java | 8 ++++---- .../domain/anime/repository/AnimeStudioRepository.java | 8 ++++---- .../anime/repository/AnimeVoiceActorRepository.java | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java index 09c3428a..cbb5b501 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeGenreRepository.java @@ -8,9 +8,9 @@ public interface AnimeGenreRepository extends JpaRepository { - @Query("select ag from AnimeGenre ag " - + "join ag.anime " - + "join ag.genre " + @Query("select distinct ag from AnimeGenre ag " + + "join fetch ag.anime a " + + "join fetch ag.genre g " + "where ag.anime.id = :animeId") - List findAllByAnimeId(@Param("animeId") Long animeId); + List findAllFetchByAnimeId(@Param("animeId") Long animeId); } diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java index 91d4285c..3cc988a1 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeOriginalAuthorRepository.java @@ -8,9 +8,9 @@ public interface AnimeOriginalAuthorRepository extends JpaRepository { - @Query("select aoa from AnimeOriginalAuthor aoa " - + "join fetch aoa.anime " - + "join fetch aoa.originalAuthor " + @Query("select distinct aoa from AnimeOriginalAuthor aoa " + + "join fetch aoa.anime a " + + "join fetch aoa.originalAuthor o " + "where aoa.anime.id = :animeId") - List findAllByAnimeId(@Param("animeId") Long animeId); + List findAllFetchByAnimeId(@Param("animeId") Long animeId); } diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java index ec289492..38b5909a 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeStudioRepository.java @@ -8,9 +8,9 @@ public interface AnimeStudioRepository extends JpaRepository { - @Query("select ast from AnimeStudio ast " - + "join ast.anime " - + "join ast.studio " + @Query("select distinct ast from AnimeStudio ast " + + "join fetch ast.anime a " + + "join fetch ast.studio s " + "where ast.anime.id = :animeId") - List findAllByAnimeId(@Param("animeId") Long animeId); + List findAllFetchByAnimeId(@Param("animeId") Long animeId); } diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java index f7663d4d..97812845 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeVoiceActorRepository.java @@ -8,9 +8,9 @@ public interface AnimeVoiceActorRepository extends JpaRepository { - @Query("select ava from AnimeVoiceActor ava " - + "join ava.anime " - + "join ava.voiceActor " + @Query("select distinct ava from AnimeVoiceActor ava " + + "join fetch ava.anime a " + + "join fetch ava.voiceActor va " + "where ava.anime.id = :animeId") - List findAllByAnimeId(@Param("animeId") Long animeId); + List findAllFetchByAnimeId(@Param("animeId") Long animeId); } From d7703d12c73b8b2430f424969800ce0942c86c0a Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 22 Oct 2023 22:05:42 +0900 Subject: [PATCH 295/734] =?UTF-8?q?feat:=20=EC=95=A0=EB=8B=88=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EB=B3=B4=EA=B8=B0=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EA=B5=AC=ED=98=84=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/controller/AnimeController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java index 7d3c6fb9..97f4dcb5 100644 --- a/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java +++ b/src/main/java/io/oduck/api/domain/anime/controller/AnimeController.java @@ -24,10 +24,9 @@ public class AnimeController { @GetMapping("/{animeId}") public ResponseEntity getAnimeById(@PathVariable Long animeId){ - // TODO: 애니 조회 로직 구현 AnimeRes res = animeService.getAnimeById(animeId); - return ResponseEntity - .ok(res); + + return ResponseEntity.ok(res); } // TODO: 애니 검색 결과 페이징 From 4abac7734c7cc535ff38543081853c47f3d8aff2 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 22 Oct 2023 22:06:01 +0900 Subject: [PATCH 296/734] =?UTF-8?q?feat:=20data.sql=20=EB=8D=94=EB=AF=B8?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/db/data.sql | 53 +++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/test/resources/db/data.sql b/src/test/resources/db/data.sql index 0228c9aa..670d8692 100644 --- a/src/test/resources/db/data.sql +++ b/src/test/resources/db/data.sql @@ -10,17 +10,56 @@ INSERT INTO member(created_at, updated_at, login_type, `role`) VALUES('2023-10-1 INSERT INTO auth_local(created_at, member_id, updated_at, password, email) VALUES('2023-10-12 21:05:31.859', 3, '2023-10-12 21:05:31.859', '{bcrypt}$2a$10$C0G6uQz.MzfsSH7BZFRBz.MPBmFSV2zAloqqBIwaUpxnmMgCQK..i', 'david@gmail.com'); INSERT INTO member_profile (created_at, member_id, updated_at, name, info, thumbnail) VALUES('2023-10-12 21:05:31.859', 3, '2023-10-12 21:05:31.859', 'david', 'david info', 'http://thumbnail.com'); -INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year) -VALUES(0, 1, '2023-10-10 21:05:31.859', '2023-10-10 21:05:31.859', '강연금', 'http://thumbnail.com', 'TVA', 'Q1', 'ALL', 'ONGOING', '1', 2009); -INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year) -VALUES(0, 1, '2023-10-11 21:05:31.859', '2023-10-11 21:05:31.859', '스파패', 'http://thumbnail.com', 'TVA', 'Q2', 'ALL', 'ONGOING', '2', 2023); -INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year) -VALUES(0, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859', '귀멸칼날', 'http://thumbnail.com', 'TVA', 'Q3', 'ALL', 'ONGOING', '3', 2022); - INSERT INTO series(created_at, title, updated_at) VALUES('2023-10-10 21:05:31.859', '귀멸의 칼날', '2023-10-10 21:05:31.859'); INSERT INTO series(created_at, title, updated_at) VALUES('2023-10-10 21:05:31.859', '원피스', '2023-10-10 21:05:31.859'); INSERT INTO series(created_at, title, updated_at) VALUES('2023-10-10 21:05:31.859', '나루토', '2023-10-10 21:05:31.859'); +INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year, series_id) +VALUES(0, 1, '2023-10-10 21:05:31.859', '2023-10-10 21:05:31.859', '강연금', 'http://thumbnail.com', 'TVA', 'Q1', 'ALL', 'ONGOING', '1', 2009, 1); +INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year, series_id) +VALUES(0, 1, '2023-10-11 21:05:31.859', '2023-10-11 21:05:31.859', '스파패', 'http://thumbnail.com', 'TVA', 'Q2', 'ALL', 'ONGOING', '2', 2023, 2); +INSERT INTO anime(episode_count, is_released, created_at, updated_at, title, thumbnail, broadcast_type, quarter, rating, status, summary, release_year, series_id) +VALUES(0, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859', '귀멸칼날', 'http://thumbnail.com', 'TVA', 'Q3', 'ALL', 'ONGOING', '3', 2022, 3); + +INSERT INTO original_author(name, created_at, updated_at) VALUES('고토게 코요하루', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO original_author(name, created_at, updated_at) VALUES('엔도 타츠야', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO original_author(name, created_at, updated_at) VALUES('아라카와 히로무', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); + +INSERT INTO anime_original_author(anime_id, original_author_id, created_at, updated_at) VALUES (1, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_original_author(anime_id, original_author_id, created_at, updated_at) VALUES (2, 2, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_original_author(anime_id, original_author_id, created_at, updated_at) VALUES (3, 3, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); + +INSERT INTO voice_actor(name, created_at, updated_at) VALUES ('하나에 나츠키', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO voice_actor(name, created_at, updated_at) VALUES ('시모노 히로', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO voice_actor(name, created_at, updated_at) VALUES ('마츠오카 요시츠구', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); + +INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (1, 1, '카마도 탄지로', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (1, 2, '아가츠마 젠이츠', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (1, 3, '하바시라 이노스케', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (2, 1, '카마도 탄지로', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (2, 2, '아가츠마 젠이츠', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (1, 1, '카마도 탄지로', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (3, 3, '하바시라 이노스케', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); + +INSERT INTO studio(name, created_at, updated_at) VALUES ('ufortable', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO studio(name, created_at, updated_at) VALUES ('WIT STUDIO', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO studio(name, created_at, updated_at) VALUES ('CloverWorks', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); + +INSERT INTO anime_studio(anime_id, studio_id, created_at, updated_at) VALUES (1, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_studio(anime_id, studio_id, created_at, updated_at) VALUES (2, 2, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_studio(anime_id, studio_id, created_at, updated_at) VALUES (3, 3, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); + +INSERT INTO genre(name, created_at, updated_at) VALUES ('판타지', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO genre(name, created_at, updated_at) VALUES ('액션', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO genre(name, created_at, updated_at) VALUES ('코메디', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); + +INSERT INTO anime_genre(anime_id, genre_id, created_at, updated_at) VALUES (1, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_genre(anime_id, genre_id, created_at, updated_at) VALUES (1, 2, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_genre(anime_id, genre_id, created_at, updated_at) VALUES (2, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_genre(anime_id, genre_id, created_at, updated_at) VALUES (2, 2, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_genre(anime_id, genre_id, created_at, updated_at) VALUES (3, 1, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_genre(anime_id, genre_id, created_at, updated_at) VALUES (3, 3, '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); + INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(1, '2023-10-10 21:05:31.859', 1); INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(2, '2023-10-11 21:05:31.859', 1); INSERT INTO bookmark(anime_id, created_at, member_id) VALUES(3, '2023-10-12 21:05:31.859', 1); From 07e468fc07c0c207c7be4e73816c769cacd821ca Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 22 Oct 2023 06:30:20 -0700 Subject: [PATCH 297/734] =?UTF-8?q?refactor:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20expect=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java index 7c8c6329..9d11ca44 100644 --- a/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/anime/AnimeControllerTest.java @@ -71,6 +71,8 @@ void getAnimes() throws Exception { .andExpect(jsonPath("$.genres").exists()) .andExpect(jsonPath("$.originalAuthors").exists()) .andExpect(jsonPath("$.voiceActors").exists()) + .andExpect(jsonPath("$.voiceActors[0].name").exists()) + .andExpect(jsonPath("$.voiceActors[0].part").exists()) .andExpect(jsonPath("$.studios").exists()) .andExpect(jsonPath("$.reviewCount").exists()) .andExpect(jsonPath("$.bookmarkCount").exists()) From 7acaacbc7a50a3b24d4c5c70f8c7d863b2a3ded0 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 22 Oct 2023 06:33:22 -0700 Subject: [PATCH 298/734] =?UTF-8?q?test:=20=EC=95=A0=EB=8B=88=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unit/anime/service/AnimeServiceTest.java | 76 +++++++++++++------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index fb4336d0..b43308dd 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -1,25 +1,9 @@ package io.oduck.api.unit.anime.service; -import static io.oduck.api.global.utils.AnimeTestUtils.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; -import io.oduck.api.domain.anime.dto.AnimeRes; +import io.oduck.api.domain.anime.dto.AnimeReq.*; import io.oduck.api.domain.anime.dto.VoiceActorReq; -import io.oduck.api.domain.anime.entity.Anime; -import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.anime.entity.*; +import io.oduck.api.domain.anime.repository.*; import io.oduck.api.domain.anime.service.AnimeServiceImpl; import io.oduck.api.domain.genre.entity.Genre; import io.oduck.api.domain.genre.repository.GenreRepository; @@ -32,9 +16,6 @@ import io.oduck.api.domain.voiceActor.entity.VoiceActor; import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; import io.oduck.api.global.utils.AnimeTestUtils; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -43,6 +24,19 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static io.oduck.api.global.utils.AnimeTestUtils.*; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + @ExtendWith(MockitoExtension.class) public class AnimeServiceTest { @@ -55,33 +49,67 @@ public class AnimeServiceTest { @Mock private OriginalAuthorRepository originalAuthorRepository; + @Mock + private AnimeOriginalAuthorRepository animeOriginalAuthorRepository; + @Mock private VoiceActorRepository voiceActorRepository; + @Mock + private AnimeVoiceActorRepository animeVoiceActorRepository; + @Mock private StudioRepository studioRepository; + @Mock + private AnimeStudioRepository animeStudioRepository; + @Mock private GenreRepository genreRepository; + @Mock + private AnimeGenreRepository animeGenreRepository; + @Mock private SeriesRepository seriesRepository; @Nested @DisplayName("조회") class findAnime{ + Anime anime = createAnime(); + @Test @DisplayName("애니 상세 조회") void getAnimeById() { //given Long animeId = 1L; + List animeOriginalAuthors = new ArrayList<>(); + given(animeOriginalAuthorRepository.findAllFetchByAnimeId(animeId)).willReturn(animeOriginalAuthors); + + List animeVoiceActors = new ArrayList<>(); + given(animeVoiceActorRepository.findAllFetchByAnimeId(animeId)).willReturn(animeVoiceActors); + + List animeStudios = new ArrayList<>(); + given(animeStudioRepository.findAllFetchByAnimeId(animeId)).willReturn(animeStudios); + + List animeGenres = new ArrayList<>(); + given(animeGenreRepository.findAllFetchByAnimeId(animeId)).willReturn(animeGenres); + + given(animeRepository.findById(animeId)).willReturn(Optional.ofNullable(anime)); + //when - AnimeRes response = animeService.getAnimeById(animeId); + animeService.getAnimeById(animeId); //then - assertThat(response.getId()).isEqualTo(animeId); assertThatNoException(); + + //verify + verify(animeRepository, times(1)).findById(anyLong()); + verify(animeOriginalAuthorRepository, times(1)).findAllFetchByAnimeId(anyLong()); + verify(animeVoiceActorRepository, times(1)).findAllFetchByAnimeId(anyLong()); + verify(animeStudioRepository, times(1)).findAllFetchByAnimeId(anyLong()); + verify(animeGenreRepository, times(1)).findAllFetchByAnimeId(anyLong()); } } From 51aa9a23f27138005fea0711520931bdd57693f0 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Sun, 22 Oct 2023 06:34:04 -0700 Subject: [PATCH 299/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=9D=98=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anime/repository/AnimeRepositoryTest.java | 42 ++++++------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java index a52a06c0..8773ca43 100644 --- a/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/anime/repository/AnimeRepositoryTest.java @@ -1,26 +1,7 @@ package io.oduck.api.unit.anime.repository; -import static io.oduck.api.global.utils.AnimeTestUtils.getBroadcastType; -import static io.oduck.api.global.utils.AnimeTestUtils.getEpisodeCount; -import static io.oduck.api.global.utils.AnimeTestUtils.getQuarter; -import static io.oduck.api.global.utils.AnimeTestUtils.getRating; -import static io.oduck.api.global.utils.AnimeTestUtils.getStatus; -import static io.oduck.api.global.utils.AnimeTestUtils.getSummary; -import static io.oduck.api.global.utils.AnimeTestUtils.getThumbnail; -import static io.oduck.api.global.utils.AnimeTestUtils.getTitle; -import static io.oduck.api.global.utils.AnimeTestUtils.getYear; -import static org.assertj.core.api.Assertions.assertThat; - -import io.oduck.api.domain.anime.entity.Anime; -import io.oduck.api.domain.anime.entity.AnimeGenre; -import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; -import io.oduck.api.domain.anime.entity.AnimeStudio; -import io.oduck.api.domain.anime.entity.AnimeVoiceActor; -import io.oduck.api.domain.anime.repository.AnimeGenreRepository; -import io.oduck.api.domain.anime.repository.AnimeOriginalAuthorRepository; -import io.oduck.api.domain.anime.repository.AnimeRepository; -import io.oduck.api.domain.anime.repository.AnimeStudioRepository; -import io.oduck.api.domain.anime.repository.AnimeVoiceActorRepository; +import io.oduck.api.domain.anime.entity.*; +import io.oduck.api.domain.anime.repository.*; import io.oduck.api.domain.genre.entity.Genre; import io.oduck.api.domain.genre.repository.GenreRepository; import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; @@ -31,8 +12,6 @@ import io.oduck.api.domain.studio.repository.StudioRepository; import io.oduck.api.domain.voiceActor.entity.VoiceActor; import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; -import java.util.ArrayList; -import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -41,6 +20,12 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; + +import static io.oduck.api.global.utils.AnimeTestUtils.*; +import static org.assertj.core.api.Assertions.assertThat; + @SpringBootTest @Transactional @ActiveProfiles("test") @@ -294,8 +279,7 @@ void changeAnimeOriginalAuthors() { findAnime.updateAnimeOriginalAuthors(updatingAnimeOriginalAuthors); Long findAnimeId = findAnime.getId(); - List findAnimeOriginalAuthors = animeOriginalAuthorRepository - .findAllByAnimeId(findAnimeId); + List findAnimeOriginalAuthors = animeOriginalAuthorRepository.findAllFetchByAnimeId(findAnimeId); // then String findOriginalAuthorName = findAnime.getAnimeOriginalAuthors().get(0) @@ -362,8 +346,7 @@ void changeAnimeStudios() { findAnime.updateAnimeStudios(updatingAnimeStudios); Long findAnimeId = findAnime.getId(); - List findAnimeOriginalAuthors = animeStudioRepository.findAllByAnimeId( - findAnimeId); + List findAnimeOriginalAuthors = animeStudioRepository.findAllFetchByAnimeId(findAnimeId); // then String findStudioName = findAnime.getAnimeStudios().get(0).getStudio().getName(); @@ -431,8 +414,7 @@ void changeAnimeVoiceActors() { findAnime.updateAnimeVoiceActors(updatingAnimeVoiceActors); Long findAnimeId = findAnime.getId(); - List findAnimeVoiceActors = animeVoiceActorRepository.findAllByAnimeId( - findAnimeId); + List findAnimeVoiceActors = animeVoiceActorRepository.findAllFetchByAnimeId(findAnimeId); // then String firstVoiceActorName = findAnime.getAnimeVoiceActors().get(0).getVoiceActor() @@ -499,7 +481,7 @@ void changeAnimeGenres() { findAnime.updateAnimeGenre(updatingAnimeGenres); Long findAnimeId = findAnime.getId(); - List findAnimeGenres = animeGenreRepository.findAllByAnimeId(findAnimeId); + List findAnimeGenres = animeGenreRepository.findAllFetchByAnimeId(findAnimeId); // then String firstGenreName = findAnime.getAnimeGenres().get(0).getGenre().getName(); From dd004bfe3d5e15ba779361d301d0c300a0ca2fe0 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 00:38:55 +0900 Subject: [PATCH 300/734] =?UTF-8?q?refactor:=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20insert=20=EC=BF=BC=EB=A6=AC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/db/data.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/db/data.sql b/src/test/resources/db/data.sql index 670d8692..183e4a18 100644 --- a/src/test/resources/db/data.sql +++ b/src/test/resources/db/data.sql @@ -38,7 +38,7 @@ INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, update INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (1, 3, '하바시라 이노스케', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (2, 1, '카마도 탄지로', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (2, 2, '아가츠마 젠이츠', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); -INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (1, 1, '카마도 탄지로', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); +INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (3, 1, '카마도 탄지로', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); INSERT INTO anime_voice_actor(anime_id, voice_actor_id, part, created_at, updated_at) VALUES (3, 3, '하바시라 이노스케', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); INSERT INTO studio(name, created_at, updated_at) VALUES ('ufortable', '2023-10-12 21:05:31.859', '2023-10-12 21:05:31.859'); From d8054c71f8108d682753500c7de4842c2df15e84 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 23 Oct 2023 14:03:34 +0900 Subject: [PATCH 301/734] refactor: sliceResponse lastId -> cursor #26 --- .../io/oduck/api/domain/bookmark/dto/BookmarkResDto.java | 3 ++- .../io/oduck/api/domain/review/dto/ShortReviewResDto.java | 2 +- src/main/java/io/oduck/api/global/common/EntityBased.java | 2 +- .../java/io/oduck/api/global/common/SliceResponse.java | 8 ++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java index 158a749d..08dfe1f2 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java +++ b/src/main/java/io/oduck/api/domain/bookmark/dto/BookmarkResDto.java @@ -18,6 +18,7 @@ public static class BookmarkRes implements EntityBased { private Long animeId; private String title; private String thumbnail; + @Builder.Default private double avgScore = 0.0; private Integer myScore; private LocalDateTime createdAt; @@ -32,7 +33,7 @@ public static BookmarkRes of(BookmarkDsl bookmarkDsl) { } @Override - public String bringId(String property) { + public String bringCursor(String property) { switch (property) { case "score": return this.myScore.toString() + ", " + this.createdAt.toString(); diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java index 12cddcce..336164b7 100644 --- a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewResDto.java @@ -22,7 +22,7 @@ public static class ShortReview implements EntityBased { private MemberProfile member; @Override - public String bringId(String property) { + public String bringCursor(String property) { return this.reviewId.toString(); } } diff --git a/src/main/java/io/oduck/api/global/common/EntityBased.java b/src/main/java/io/oduck/api/global/common/EntityBased.java index c624a9d3..ea645eec 100644 --- a/src/main/java/io/oduck/api/global/common/EntityBased.java +++ b/src/main/java/io/oduck/api/global/common/EntityBased.java @@ -2,5 +2,5 @@ public interface EntityBased { - String bringId(String property); + String bringCursor(String property); } diff --git a/src/main/java/io/oduck/api/global/common/SliceResponse.java b/src/main/java/io/oduck/api/global/common/SliceResponse.java index 4fb764a8..d81eaf0e 100644 --- a/src/main/java/io/oduck/api/global/common/SliceResponse.java +++ b/src/main/java/io/oduck/api/global/common/SliceResponse.java @@ -9,7 +9,7 @@ public class SliceResponse { private final List items; private final int size; // 한 페이지에 보여줄 아이템의 개수 private final boolean hasNext; // 마지막 페이지일 경우, true 반환. - private String lastId; // 마지막 아이템의 id + private String cursor; // 마지막 아이템의 id public SliceResponse(Slice sliceContent, String property) { this.items = sliceContent.getContent(); @@ -18,7 +18,7 @@ public SliceResponse(Slice sliceContent, String property) { if (!items.isEmpty() && !hasNext) { T lastItem = items.get(items.size() - 1); - lastId = lastItem.bringId(property); + cursor = lastItem.bringCursor(property); } } @@ -29,9 +29,9 @@ public SliceResponse(Slice slice, List items, String property) { if (!items.isEmpty() && hasNext) { T lastItem = items.get(items.size() - 1); - this.lastId = lastItem.bringId(property); + this.cursor = lastItem.bringCursor(property); } else { - this.lastId = ""; + this.cursor = ""; } } From 8f6351e4b4d2b55bcd231e3f190311f64588e20a Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Mon, 23 Oct 2023 14:03:38 +0900 Subject: [PATCH 302/734] test: sliceResponse lastId -> cursor #26 --- .../io/oduck/api/e2e/member/MemberControllerTest.java | 8 ++++---- .../api/e2e/shortReview/ShortReviewControllerTest.java | 2 +- .../api/unit/bookmark/service/BookmarkServiceTest.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java index 9f800e88..96a87d3b 100644 --- a/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/member/MemberControllerTest.java @@ -543,7 +543,7 @@ void getBookmarksSuccess() throws Exception { .andExpect(jsonPath("$.items[0].title").exists()) .andExpect(jsonPath("$.items[0].thumbnail").exists()) .andExpect(jsonPath("$.size").exists()) - .andExpect(jsonPath("$.lastId").exists()) + .andExpect(jsonPath("$.cursor").exists()) .andExpect(jsonPath("$.hasNext").exists()) .andDo( document("getBookmarks/success", @@ -597,7 +597,7 @@ void getBookmarksSuccess() throws Exception { fieldWithPath("hasNext") .type(JsonFieldType.BOOLEAN) .description("마지막 페이지 여부"), - fieldWithPath("lastId") + fieldWithPath("cursor") .type(JsonFieldType.STRING) .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 \"\"") ) @@ -634,7 +634,7 @@ void getBookmarksSuccessWithCursor() throws Exception { .andExpect(jsonPath("$.items[0].title").exists()) .andExpect(jsonPath("$.items[0].thumbnail").exists()) .andExpect(jsonPath("$.size").exists()) - .andExpect(jsonPath("$.lastId").exists()) + .andExpect(jsonPath("$.cursor").exists()) .andExpect(jsonPath("$.hasNext").exists()) .andDo( document("getBookmarks/successWithCursor", @@ -688,7 +688,7 @@ void getBookmarksSuccessWithCursor() throws Exception { fieldWithPath("hasNext") .type(JsonFieldType.BOOLEAN) .description("마지막 페이지 여부"), - fieldWithPath("lastId") + fieldWithPath("cursor") .type(JsonFieldType.STRING) .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 \"\"") ) diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index 179afc7e..7c131242 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -120,7 +120,7 @@ void getShortReviews() throws Exception{ fieldWithPath("size") .type(JsonFieldType.NUMBER) .description("한 페이지에 보여줄 아이템의 개수"), - fieldWithPath("lastId") + fieldWithPath("cursor") .type(JsonFieldType.STRING) .description("마지막 아이템 id, 다음 페이지 요청시 cursor로 사용. 다음 페이지가 없다면 \"\""), fieldWithPath("hasNext") diff --git a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java index 12cafbc6..daad0b7c 100644 --- a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java +++ b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java @@ -126,7 +126,7 @@ void getBookmarksByMemberIdSuccess() { SliceResponse result = bookmarkService.getBookmarksByMemberId(memberId, cursor, sort, order, size); assertEquals(sampleSlice.getSize(), result.getItems().size()); - assertNotNull(result.getLastId()); + assertNotNull(result.getCursor()); assertFalse(result.isHasNext()); } } From 5a4c52fe3ab2356d8acfb7120903d6336e2d8d48 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:32:04 +0900 Subject: [PATCH 303/734] =?UTF-8?q?feat:=20=EC=9B=90=EC=9E=91=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80=20=EB=93=B1=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20dto=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../originalAuthor/dto/OriginalAuthorReq.java | 20 +++++++++++++++++++ .../originalAuthor/dto/OriginalAuthorRes.java | 11 ++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/originalAuthor/dto/OriginalAuthorReq.java create mode 100644 src/main/java/io/oduck/api/domain/originalAuthor/dto/OriginalAuthorRes.java diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/dto/OriginalAuthorReq.java b/src/main/java/io/oduck/api/domain/originalAuthor/dto/OriginalAuthorReq.java new file mode 100644 index 00000000..2cd09bd5 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/originalAuthor/dto/OriginalAuthorReq.java @@ -0,0 +1,20 @@ +package io.oduck.api.domain.originalAuthor.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class OriginalAuthorReq { + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class PostReq { + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String name; + } +} diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/dto/OriginalAuthorRes.java b/src/main/java/io/oduck/api/domain/originalAuthor/dto/OriginalAuthorRes.java new file mode 100644 index 00000000..bafa7c3d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/originalAuthor/dto/OriginalAuthorRes.java @@ -0,0 +1,11 @@ +package io.oduck.api.domain.originalAuthor.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class OriginalAuthorRes { + private Long id; + private String name; +} From 0438f8a6b7c416107f354ba7f912f9dda3de4824 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:32:28 +0900 Subject: [PATCH 304/734] =?UTF-8?q?feat:=20=EC=9B=90=EC=9E=91=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80=20=EB=93=B1=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/OriginalAuthorService.java | 9 +++++++ .../service/OriginalAuthorServiceImpl.java | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorService.java b/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorService.java index 0b6b38f7..3524d521 100644 --- a/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorService.java +++ b/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorService.java @@ -1,5 +1,14 @@ package io.oduck.api.domain.originalAuthor.service; +import io.oduck.api.domain.originalAuthor.dto.OriginalAuthorRes; + +import java.util.List; + +import static io.oduck.api.domain.originalAuthor.dto.OriginalAuthorReq.PostReq; + public interface OriginalAuthorService { + void save(PostReq req); + + List getOriginalAuthors(); } diff --git a/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorServiceImpl.java b/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorServiceImpl.java index 4442af73..8f66a747 100644 --- a/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/originalAuthor/service/OriginalAuthorServiceImpl.java @@ -1,11 +1,18 @@ package io.oduck.api.domain.originalAuthor.service; +import io.oduck.api.domain.originalAuthor.dto.OriginalAuthorRes; +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; import io.oduck.api.domain.originalAuthor.repository.OriginalAuthorRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + +import static io.oduck.api.domain.originalAuthor.dto.OriginalAuthorReq.PostReq; + @Service @RequiredArgsConstructor @Transactional @@ -13,4 +20,22 @@ public class OriginalAuthorServiceImpl implements OriginalAuthorService{ private final OriginalAuthorRepository originalAuthorRepository; + + @Override + public void save(PostReq req) { + + OriginalAuthor originalAuthor = OriginalAuthor.builder() + .name(req.getName()) + .build(); + originalAuthorRepository.save(originalAuthor); + } + + @Override + @Transactional(readOnly = true) + public List getOriginalAuthors() { + + return originalAuthorRepository.findAll().stream() + .map(oa -> new OriginalAuthorRes(oa.getId(), oa.getName())) + .collect(Collectors.toList()); + } } From 03fac78783214d4e7088fb2ac496eb3824c68a4e Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:34:03 +0900 Subject: [PATCH 305/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=9D=98=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=BC=20=EB=95=8C=20dto=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/domain/anime/dto/AnimeReq.java | 13 +++++++------ .../io/oduck/api/domain/anime/dto/AnimeRes.java | 4 ++-- .../{VoiceActorReq.java => AnimeVoiceActorReq.java} | 2 +- .../{VoiceActorRes.java => AnimeVoiceActorRes.java} | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) rename src/main/java/io/oduck/api/domain/anime/dto/{VoiceActorReq.java => AnimeVoiceActorReq.java} (86%) rename src/main/java/io/oduck/api/domain/anime/dto/{VoiceActorRes.java => AnimeVoiceActorRes.java} (87%) diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java index 13288072..0d3d16c8 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java @@ -6,13 +6,14 @@ import io.oduck.api.domain.anime.entity.Status; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; -import java.util.ArrayList; -import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; +import java.util.ArrayList; +import java.util.List; + public class AnimeReq { @Getter @@ -51,7 +52,7 @@ public static class PostReq { private List studioIds; - private List voiceActors; + private List voiceActors; private List genreIds; @@ -59,7 +60,7 @@ public static class PostReq { public PostReq (String title, String summary, BroadcastType broadcastType, int episodeCount, String thumbnail, int year, Quarter quarter, Rating rating, Status status, - List originalAuthorIds, List studioIds, List voiceActors, + List originalAuthorIds, List studioIds, List voiceActors, List genreIds, Long seriesId) { this.title = title; @@ -166,9 +167,9 @@ public PatchStudioIdsReq(List studioIds) { @Getter @NoArgsConstructor public static class PatchVoiceActorIdsReq { - private List voiceActors; + private List voiceActors; - public PatchVoiceActorIdsReq(List voiceActors) { + public PatchVoiceActorIdsReq(List voiceActors) { if(voiceActors == null){ this.voiceActors = new ArrayList<>(); }else{ diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java index 59c50ffc..7077757e 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeRes.java @@ -27,7 +27,7 @@ public class AnimeRes { private Status status; private List originalAuthors; - private List voiceActors; + private List voiceActors; private List genres; private List studios; private long reviewCount; @@ -50,7 +50,7 @@ public AnimeRes(Anime anime, List animeOriginalAuthors, .map(aoa -> aoa.getOriginalAuthor().getName()) .collect(Collectors.toList()); voiceActors = animeVoiceActors.stream() - .map(ava -> new VoiceActorRes(ava.getVoiceActor().getName(), ava.getPart())) + .map(ava -> new AnimeVoiceActorRes(ava.getVoiceActor().getName(), ava.getPart())) .collect(Collectors.toList()); genres = animeGenres.stream() .map(ag -> ag.getGenre().getName()) diff --git a/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorReq.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeVoiceActorReq.java similarity index 86% rename from src/main/java/io/oduck/api/domain/anime/dto/VoiceActorReq.java rename to src/main/java/io/oduck/api/domain/anime/dto/AnimeVoiceActorReq.java index 859aae15..11b26cb4 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorReq.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeVoiceActorReq.java @@ -7,7 +7,7 @@ @Getter @AllArgsConstructor @NoArgsConstructor -public class VoiceActorReq { +public class AnimeVoiceActorReq { private Long id; private String part; } diff --git a/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorRes.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeVoiceActorRes.java similarity index 87% rename from src/main/java/io/oduck/api/domain/anime/dto/VoiceActorRes.java rename to src/main/java/io/oduck/api/domain/anime/dto/AnimeVoiceActorRes.java index 7e608278..5edbbb4a 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/VoiceActorRes.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeVoiceActorRes.java @@ -7,7 +7,7 @@ @Getter @AllArgsConstructor @NoArgsConstructor -public class VoiceActorRes { +public class AnimeVoiceActorRes { private String name; private String part; } From 8fccb7e365b371154dd1ee46f290f1a5b37233c6 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:39:57 +0900 Subject: [PATCH 306/734] =?UTF-8?q?feat:=20=EC=84=B1=EC=9A=B0=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20dto=20=EC=B6=94=EA=B0=80=20#3?= =?UTF-8?q?9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/voiceActor/dto/VoiceActorReq.java | 20 +++++++++++++++++++ .../domain/voiceActor/dto/VoiceActorRes.java | 11 ++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/voiceActor/dto/VoiceActorReq.java create mode 100644 src/main/java/io/oduck/api/domain/voiceActor/dto/VoiceActorRes.java diff --git a/src/main/java/io/oduck/api/domain/voiceActor/dto/VoiceActorReq.java b/src/main/java/io/oduck/api/domain/voiceActor/dto/VoiceActorReq.java new file mode 100644 index 00000000..42b4ac01 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/voiceActor/dto/VoiceActorReq.java @@ -0,0 +1,20 @@ +package io.oduck.api.domain.voiceActor.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class VoiceActorReq { + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class PostReq { + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String name; + } +} diff --git a/src/main/java/io/oduck/api/domain/voiceActor/dto/VoiceActorRes.java b/src/main/java/io/oduck/api/domain/voiceActor/dto/VoiceActorRes.java new file mode 100644 index 00000000..4f6d25ad --- /dev/null +++ b/src/main/java/io/oduck/api/domain/voiceActor/dto/VoiceActorRes.java @@ -0,0 +1,11 @@ +package io.oduck.api.domain.voiceActor.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class VoiceActorRes { + private Long id; + private String name; +} From bec5fe1c68ff80d8dc27e84a9920877ef961c7b6 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:40:06 +0900 Subject: [PATCH 307/734] =?UTF-8?q?feat:=20=EC=84=B1=EC=9A=B0=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voiceActor/service/VoiceActorService.java | 11 +++++++++ .../service/VoiceActorServiceImpl.java | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorService.java b/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorService.java index 1ce4346e..bc4e0702 100644 --- a/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorService.java +++ b/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorService.java @@ -1,4 +1,15 @@ package io.oduck.api.domain.voiceActor.service; +import io.oduck.api.domain.voiceActor.dto.VoiceActorRes; + +import java.util.List; + +import static io.oduck.api.domain.voiceActor.dto.VoiceActorReq.PostReq; + public interface VoiceActorService { + + void save(PostReq req); + + List getVoiceActors(); + } diff --git a/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorServiceImpl.java b/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorServiceImpl.java index e7e64523..586610d6 100644 --- a/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/voiceActor/service/VoiceActorServiceImpl.java @@ -1,11 +1,18 @@ package io.oduck.api.domain.voiceActor.service; +import io.oduck.api.domain.voiceActor.dto.VoiceActorRes; +import io.oduck.api.domain.voiceActor.entity.VoiceActor; import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + +import static io.oduck.api.domain.voiceActor.dto.VoiceActorReq.PostReq; + @Service @RequiredArgsConstructor @Transactional @@ -13,4 +20,20 @@ public class VoiceActorServiceImpl implements VoiceActorService{ private final VoiceActorRepository voiceActorRepository; + + @Override + public void save(PostReq req) { + VoiceActor voiceActor = VoiceActor.builder() + .name(req.getName()) + .build(); + voiceActorRepository.save(voiceActor); + } + + @Override + @Transactional(readOnly = true) + public List getVoiceActors() { + return voiceActorRepository.findAll().stream() + .map(va -> new VoiceActorRes(va.getId(), va.getName())) + .collect(Collectors.toList()); + } } From 533b0a72f2bab4de67349ecfe1633bac707062c4 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:40:44 +0900 Subject: [PATCH 308/734] =?UTF-8?q?feat:=20=EC=8A=A4=ED=8A=9C=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EB=93=B1=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20dto=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/studio/dto/StudioReq.java | 20 +++++++++++++++++++ .../api/domain/studio/dto/StudioRes.java | 11 ++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/studio/dto/StudioReq.java create mode 100644 src/main/java/io/oduck/api/domain/studio/dto/StudioRes.java diff --git a/src/main/java/io/oduck/api/domain/studio/dto/StudioReq.java b/src/main/java/io/oduck/api/domain/studio/dto/StudioReq.java new file mode 100644 index 00000000..59fa66a1 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/studio/dto/StudioReq.java @@ -0,0 +1,20 @@ +package io.oduck.api.domain.studio.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class StudioReq { + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class PostReq { + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String name; + } +} diff --git a/src/main/java/io/oduck/api/domain/studio/dto/StudioRes.java b/src/main/java/io/oduck/api/domain/studio/dto/StudioRes.java new file mode 100644 index 00000000..cc265c4e --- /dev/null +++ b/src/main/java/io/oduck/api/domain/studio/dto/StudioRes.java @@ -0,0 +1,11 @@ +package io.oduck.api.domain.studio.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class StudioRes { + private Long id; + private String name; +} From f3fb7d94406f92aa2694237c7991bce5b9b0ba7d Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:40:57 +0900 Subject: [PATCH 309/734] =?UTF-8?q?feat:=20=EC=8A=A4=ED=8A=9C=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EB=93=B1=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/studio/service/StudioService.java | 9 ++++++++ .../studio/service/StudioServiceImpl.java | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/studio/service/StudioService.java b/src/main/java/io/oduck/api/domain/studio/service/StudioService.java index 452bb321..3a2662f7 100644 --- a/src/main/java/io/oduck/api/domain/studio/service/StudioService.java +++ b/src/main/java/io/oduck/api/domain/studio/service/StudioService.java @@ -1,4 +1,13 @@ package io.oduck.api.domain.studio.service; +import io.oduck.api.domain.studio.dto.StudioRes; + +import java.util.List; + +import static io.oduck.api.domain.studio.dto.StudioReq.PostReq; + public interface StudioService { + void save(PostReq req); + + List getStudios(); } diff --git a/src/main/java/io/oduck/api/domain/studio/service/StudioServiceImpl.java b/src/main/java/io/oduck/api/domain/studio/service/StudioServiceImpl.java index 5b6c0d3d..451f16ec 100644 --- a/src/main/java/io/oduck/api/domain/studio/service/StudioServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/studio/service/StudioServiceImpl.java @@ -1,11 +1,17 @@ package io.oduck.api.domain.studio.service; +import io.oduck.api.domain.studio.dto.StudioReq; +import io.oduck.api.domain.studio.dto.StudioRes; +import io.oduck.api.domain.studio.entity.Studio; import io.oduck.api.domain.studio.repository.StudioRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor @Transactional @@ -14,4 +20,19 @@ public class StudioServiceImpl implements StudioService{ private final StudioRepository studioRepository; + @Override + public void save(StudioReq.PostReq req) { + Studio studio = Studio.builder() + .name(req.getName()) + .build(); + studioRepository.save(studio); + } + + @Override + @Transactional(readOnly = true) + public List getStudios() { + return studioRepository.findAll().stream() + .map(st -> new StudioRes(st.getId(), st.getName())) + .collect(Collectors.toList()); + } } From d98140bff96ce311ec835822ad204112bde23155 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:41:12 +0900 Subject: [PATCH 310/734] =?UTF-8?q?feat:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20dto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/series/dto/SeriesReq.java | 20 +++++++++++++++++++ .../api/domain/series/dto/SeriesRes.java | 11 ++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/series/dto/SeriesReq.java create mode 100644 src/main/java/io/oduck/api/domain/series/dto/SeriesRes.java diff --git a/src/main/java/io/oduck/api/domain/series/dto/SeriesReq.java b/src/main/java/io/oduck/api/domain/series/dto/SeriesReq.java new file mode 100644 index 00000000..215bae46 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/series/dto/SeriesReq.java @@ -0,0 +1,20 @@ +package io.oduck.api.domain.series.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class SeriesReq { + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class PostReq { + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String title; + } +} diff --git a/src/main/java/io/oduck/api/domain/series/dto/SeriesRes.java b/src/main/java/io/oduck/api/domain/series/dto/SeriesRes.java new file mode 100644 index 00000000..7cf48c0b --- /dev/null +++ b/src/main/java/io/oduck/api/domain/series/dto/SeriesRes.java @@ -0,0 +1,11 @@ +package io.oduck.api.domain.series.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SeriesRes { + private Long id; + private String title; +} From ab2a8e77315dab2d3ab2a84800012fa6250692eb Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:41:19 +0900 Subject: [PATCH 311/734] =?UTF-8?q?feat:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/series/service/SeriesService.java | 9 ++++++++ .../series/service/SeriesServiceImpl.java | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/series/service/SeriesService.java b/src/main/java/io/oduck/api/domain/series/service/SeriesService.java index 11a31d17..d4dd886d 100644 --- a/src/main/java/io/oduck/api/domain/series/service/SeriesService.java +++ b/src/main/java/io/oduck/api/domain/series/service/SeriesService.java @@ -1,5 +1,14 @@ package io.oduck.api.domain.series.service; +import io.oduck.api.domain.series.dto.SeriesRes; + +import java.util.List; + +import static io.oduck.api.domain.series.dto.SeriesReq.PostReq; + public interface SeriesService { + void save(PostReq req); + + List getSeries(); } diff --git a/src/main/java/io/oduck/api/domain/series/service/SeriesServiceImpl.java b/src/main/java/io/oduck/api/domain/series/service/SeriesServiceImpl.java index 66127a64..a860b423 100644 --- a/src/main/java/io/oduck/api/domain/series/service/SeriesServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/series/service/SeriesServiceImpl.java @@ -1,11 +1,18 @@ package io.oduck.api.domain.series.service; +import io.oduck.api.domain.series.dto.SeriesRes; +import io.oduck.api.domain.series.entity.Series; import io.oduck.api.domain.series.repository.SeriesRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + +import static io.oduck.api.domain.series.dto.SeriesReq.PostReq; + @Service @RequiredArgsConstructor @Transactional @@ -13,4 +20,20 @@ public class SeriesServiceImpl implements SeriesService{ private final SeriesRepository seriesRepository; + + @Override + public void save(PostReq req) { + Series series = Series.builder() + .title(req.getTitle()) + .build(); + seriesRepository.save(series); + } + + @Override + @Transactional(readOnly = true) + public List getSeries() { + return seriesRepository.findAll().stream() + .map(s -> new SeriesRes(s.getId(), s.getTitle())) + .collect(Collectors.toList()); + } } From e456086112a5655d93589c0f812cd1458c7559fe Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:41:36 +0900 Subject: [PATCH 312/734] =?UTF-8?q?feat:=20=EC=9E=A5=EB=A5=B4=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20dto=20=EC=B6=94=EA=B0=80=20#3?= =?UTF-8?q?9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/genre/dto/GenreReq.java | 20 +++++++++++++++++++ .../oduck/api/domain/genre/dto/GenreRes.java | 11 ++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/genre/dto/GenreReq.java create mode 100644 src/main/java/io/oduck/api/domain/genre/dto/GenreRes.java diff --git a/src/main/java/io/oduck/api/domain/genre/dto/GenreReq.java b/src/main/java/io/oduck/api/domain/genre/dto/GenreReq.java new file mode 100644 index 00000000..484917a7 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/genre/dto/GenreReq.java @@ -0,0 +1,20 @@ +package io.oduck.api.domain.genre.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +public class GenreReq { + + @Getter + @AllArgsConstructor + @NoArgsConstructor + public static class PostReq { + @NotBlank + @Length(min = 1, max = 50, + message = "글자 수는 0~50을 허용합니다.") + private String name; + } +} diff --git a/src/main/java/io/oduck/api/domain/genre/dto/GenreRes.java b/src/main/java/io/oduck/api/domain/genre/dto/GenreRes.java new file mode 100644 index 00000000..4a4499c6 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/genre/dto/GenreRes.java @@ -0,0 +1,11 @@ +package io.oduck.api.domain.genre.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GenreRes { + private Long id; + private String name; +} From 57f641ceb3b258bfe94f3229726252672a22c64b Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:41:46 +0900 Subject: [PATCH 313/734] =?UTF-8?q?feat:=20=EC=9E=A5=EB=A5=B4=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D,=20=EC=9D=91=EB=8B=B5=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/genre/service/GenreService.java | 9 ++++++++ .../genre/service/GenreServiceImpl.java | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/genre/service/GenreService.java b/src/main/java/io/oduck/api/domain/genre/service/GenreService.java index bb87e0d3..f412bab7 100644 --- a/src/main/java/io/oduck/api/domain/genre/service/GenreService.java +++ b/src/main/java/io/oduck/api/domain/genre/service/GenreService.java @@ -1,5 +1,14 @@ package io.oduck.api.domain.genre.service; +import io.oduck.api.domain.genre.dto.GenreRes; + +import java.util.List; + +import static io.oduck.api.domain.genre.dto.GenreReq.PostReq; + public interface GenreService { + void save(PostReq req); + + List getGenres(); } diff --git a/src/main/java/io/oduck/api/domain/genre/service/GenreServiceImpl.java b/src/main/java/io/oduck/api/domain/genre/service/GenreServiceImpl.java index 41f5b60d..5be5fc7d 100644 --- a/src/main/java/io/oduck/api/domain/genre/service/GenreServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/genre/service/GenreServiceImpl.java @@ -1,11 +1,18 @@ package io.oduck.api.domain.genre.service; +import io.oduck.api.domain.genre.dto.GenreRes; +import io.oduck.api.domain.genre.entity.Genre; import io.oduck.api.domain.genre.repository.GenreRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + +import static io.oduck.api.domain.genre.dto.GenreReq.PostReq; + @Service @RequiredArgsConstructor @Transactional @@ -13,4 +20,20 @@ public class GenreServiceImpl implements GenreService{ private final GenreRepository genreRepository; + + @Override + public void save(PostReq req) { + Genre genre = Genre.builder() + .name(req.getName()) + .build(); + genreRepository.save(genre); + } + + @Override + @Transactional(readOnly = true) + public List getGenres() { + return genreRepository.findAll().stream() + .map(gr -> new GenreRes(gr.getId(), gr.getName())) + .collect(Collectors.toList()); + } } From 823a3c7597d764b43151ea4eeff843ede7022ad0 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:42:42 +0900 Subject: [PATCH 314/734] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Anime=EC=9D=98=20VoiceActor=20Dto=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/global/utils/AnimeTestUtils.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java index 4f9ea555..d4e18c37 100644 --- a/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java +++ b/src/test/java/io/oduck/api/global/utils/AnimeTestUtils.java @@ -2,7 +2,7 @@ import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; -import io.oduck.api.domain.anime.dto.VoiceActorReq; +import io.oduck.api.domain.anime.dto.AnimeVoiceActorReq; import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.domain.anime.entity.BroadcastType; import io.oduck.api.domain.anime.entity.Quarter; @@ -78,10 +78,10 @@ public static List getGenreIds() { return genreIds; } - public static List getVoiceActorReqs() { - List voiceActors = new ArrayList<>(); - VoiceActorReq firstDto = new VoiceActorReq(5L, "카마도 탄지로"); - VoiceActorReq secondDto = new VoiceActorReq(6L, "카마도 네즈코"); + public static List getVoiceActorReqs() { + List voiceActors = new ArrayList<>(); + AnimeVoiceActorReq firstDto = new AnimeVoiceActorReq(5L, "카마도 탄지로"); + AnimeVoiceActorReq secondDto = new AnimeVoiceActorReq(6L, "카마도 네즈코"); voiceActors.add(firstDto); voiceActors.add(secondDto); return voiceActors; From e7d8daf295ca420605b1c52c53acd88ccda6e9f8 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:43:35 +0900 Subject: [PATCH 315/734] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=20=EA=B4=80=EB=A0=A8=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=93=B1=EB=A1=9D,=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminController.java | 132 +++++++++++++++--- 1 file changed, 111 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java index 22af1884..7a6b3ad4 100644 --- a/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java +++ b/src/main/java/io/oduck/api/domain/admin/controller/AdminController.java @@ -1,25 +1,30 @@ package io.oduck.api.domain.admin.controller; -import static io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; -import static io.oduck.api.domain.anime.dto.AnimeReq.PostReq; - -import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; +import io.oduck.api.domain.anime.dto.AnimeReq; import io.oduck.api.domain.anime.service.AnimeService; +import io.oduck.api.domain.genre.dto.GenreReq; +import io.oduck.api.domain.genre.dto.GenreRes; +import io.oduck.api.domain.genre.service.GenreService; +import io.oduck.api.domain.originalAuthor.dto.OriginalAuthorReq; +import io.oduck.api.domain.originalAuthor.dto.OriginalAuthorRes; +import io.oduck.api.domain.originalAuthor.service.OriginalAuthorService; +import io.oduck.api.domain.series.dto.SeriesReq; +import io.oduck.api.domain.series.dto.SeriesRes; +import io.oduck.api.domain.series.service.SeriesService; +import io.oduck.api.domain.studio.dto.StudioReq; +import io.oduck.api.domain.studio.dto.StudioRes; +import io.oduck.api.domain.studio.service.StudioService; +import io.oduck.api.domain.voiceActor.dto.VoiceActorReq; +import io.oduck.api.domain.voiceActor.dto.VoiceActorRes; +import io.oduck.api.domain.voiceActor.service.VoiceActorService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; -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.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @Validated @RestController @@ -29,10 +34,15 @@ public class AdminController { private final AnimeService animeService; + private final OriginalAuthorService originalAuthorService; + private final VoiceActorService voiceActorService; + private final StudioService studioService; + private final GenreService genreService; + private final SeriesService seriesService; //애니 등록 @PostMapping("/animes") - public ResponseEntity postAnime(@RequestBody @Valid PostReq req){ + public ResponseEntity postAnime(@RequestBody @Valid AnimeReq.PostReq req){ animeService.save(req); @@ -42,7 +52,7 @@ public ResponseEntity postAnime(@RequestBody @Valid PostReq req){ //애니 관련 수정 @PatchMapping("/animes/{animeId}") public ResponseEntity patchAnime( - @PathVariable Long animeId, @RequestBody @Valid PatchAnimeReq req + @PathVariable Long animeId, @RequestBody @Valid AnimeReq.PatchAnimeReq req ){ animeService.update(animeId, req); @@ -53,7 +63,7 @@ public ResponseEntity patchAnime( // 애니의 원작 작가 수정 @PatchMapping("/animes/{animeId}/original-authors") public ResponseEntity patchAnimeOriginalAuthors( - @PathVariable Long animeId, @RequestBody PatchOriginalAuthorIdsReq req + @PathVariable Long animeId, @RequestBody AnimeReq.PatchOriginalAuthorIdsReq req ){ animeService.updateAnimeOriginalAuthors(animeId, req); @@ -63,7 +73,7 @@ public ResponseEntity patchAnimeOriginalAuthors( // 애니의 스튜디오 수정 @PatchMapping("/animes/{animeId}/studios") public ResponseEntity patchAnimeStudios( - @PathVariable Long animeId, @RequestBody PatchStudioIdsReq req + @PathVariable Long animeId, @RequestBody AnimeReq.PatchStudioIdsReq req ){ animeService.updateAnimeStudios(animeId, req); @@ -73,7 +83,7 @@ public ResponseEntity patchAnimeStudios( // 애니의 성우 수정 @PatchMapping("/animes/{animeId}/voice-actors") public ResponseEntity patchAnimeVoiceActors( - @PathVariable Long animeId, @RequestBody PatchVoiceActorIdsReq req + @PathVariable Long animeId, @RequestBody AnimeReq.PatchVoiceActorIdsReq req ){ animeService.updateAnimeVoiceActors(animeId, req); @@ -83,7 +93,7 @@ public ResponseEntity patchAnimeVoiceActors( // 애니의 장르 수정 @PatchMapping("/animes/{animeId}/genres") public ResponseEntity patchAnimeGenres( - @PathVariable Long animeId, @RequestBody PatchGenreIdsReq req + @PathVariable Long animeId, @RequestBody AnimeReq.PatchGenreIdsReq req ){ animeService.updateAnimeGenres(animeId, req); @@ -94,7 +104,7 @@ public ResponseEntity patchAnimeGenres( // 애니의 시리즈 수정 @PatchMapping("/animes/{animeId}/series") public ResponseEntity patchAnimeSeries( - @PathVariable Long animeId, @RequestBody PatchSeriesIdReq req + @PathVariable Long animeId, @RequestBody AnimeReq.PatchSeriesIdReq req ){ animeService.updateSeries(animeId, req); @@ -122,4 +132,84 @@ public ResponseEntity patchAnimeSeries( // return ResponseEntity // .ok(PageResponse.of(null)); // } + + @GetMapping("/original-authors") + public ResponseEntity getOriginalAuthors(){ + + List originalAuthors = originalAuthorService.getOriginalAuthors(); + + return ResponseEntity.ok(originalAuthors); + } + + @PostMapping("/original-authors") + public ResponseEntity postOriginalAuthor(@RequestBody @Valid OriginalAuthorReq.PostReq req) { + + originalAuthorService.save(req); + + return ResponseEntity.noContent().build(); + } + + @GetMapping("/voice-actors") + public ResponseEntity getVoiceActors() { + + List voiceActors = voiceActorService.getVoiceActors(); + + return ResponseEntity.ok(voiceActors); + } + + @PostMapping("/voice-actors") + public ResponseEntity postVoiceActor(@RequestBody @Valid VoiceActorReq.PostReq req) { + + voiceActorService.save(req); + + return ResponseEntity.noContent().build(); + } + + @GetMapping("/studios") + public ResponseEntity getStudios() { + + List studios = studioService.getStudios(); + + return ResponseEntity.ok(studios); + } + + @PostMapping("/studios") + public ResponseEntity postStudio(@RequestBody @Valid StudioReq.PostReq req) { + + studioService.save(req); + + return ResponseEntity.noContent().build(); + } + + @GetMapping("/genres") + public ResponseEntity getGenres(){ + + List genres = genreService.getGenres(); + + return ResponseEntity.ok(genres); + } + + @PostMapping("/genres") + public ResponseEntity postGenre(@RequestBody @Valid GenreReq.PostReq req) { + + genreService.save(req); + + return ResponseEntity.noContent().build(); + } + + @GetMapping("/series") + public ResponseEntity getSeries() { + + List seriesList = seriesService.getSeries(); + + return ResponseEntity.ok(seriesList); + } + + @PostMapping("/series") + public ResponseEntity postSeries(@RequestBody @Valid SeriesReq.PostReq req) { + + seriesService.save(req); + + return ResponseEntity.noContent().build(); + } } From d7bab73364fc1cfe03befe5aa20a7ca8f3f6db2b Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:45:26 +0900 Subject: [PATCH 316/734] =?UTF-8?q?test:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=20=EA=B4=80=EB=A0=A8=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=93=B1=EB=A1=9D,=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/e2e/admin/AdminControllerTest.java | 661 +++++++++++++----- 1 file changed, 477 insertions(+), 184 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java index d5e54fb4..d814649e 100644 --- a/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/admin/AdminControllerTest.java @@ -1,32 +1,14 @@ package io.oduck.api.e2e.admin; -import static io.oduck.api.global.config.RestDocsConfig.field; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.restdocs.snippet.Attributes.attributes; -import static org.springframework.restdocs.snippet.Attributes.key; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import com.google.gson.Gson; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; -import io.oduck.api.domain.anime.dto.VoiceActorReq; +import io.oduck.api.domain.anime.dto.AnimeReq.*; +import io.oduck.api.domain.anime.dto.AnimeVoiceActorReq; +import io.oduck.api.domain.genre.dto.GenreReq; +import io.oduck.api.domain.originalAuthor.dto.OriginalAuthorReq; +import io.oduck.api.domain.series.dto.SeriesReq; +import io.oduck.api.domain.studio.dto.StudioReq; +import io.oduck.api.domain.voiceActor.dto.VoiceActorReq; import io.oduck.api.global.utils.AnimeTestUtils; -import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -37,16 +19,33 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static io.oduck.api.global.config.RestDocsConfig.field; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @AutoConfigureRestDocs @AutoConfigureMockMvc @ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) @SpringBootTest +@Transactional @ActiveProfiles("test") public class AdminControllerTest { @Autowired @@ -58,11 +57,11 @@ public class AdminControllerTest { private final static String ADMIN_URL = "/oduckdmin"; @Nested - @DisplayName("등록") - class PostAnime{ + @DisplayName("애니") + class AnimeTest{ @Test - @DisplayName("성공 시 Http Status 200 반환") + @DisplayName("등록 성공 시 Http Status 200 반환") void postAnime() throws Exception { //given PostReq request = AnimeTestUtils.createPostAnimeRequest(); @@ -156,11 +155,6 @@ void postAnime() throws Exception { )); } //TODO : 애니 등록 실패 시 - } - - @Nested - @DisplayName("수정") - class PatchAnime{ @Test @DisplayName("애니 수정 성공 시 Http Status 204 반환") @@ -172,60 +166,60 @@ void patchAnime() throws Exception { //when ResultActions actions = mockMvc.perform( - patch(ADMIN_URL+"/animes/{animeId}", animeId) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(content) + patch(ADMIN_URL+"/animes/{animeId}", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) ); //then actions - .andExpect(status().isNoContent()) - .andDo(document("patchAnime/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("animeId").description("애니의 식별자") - ), - requestFields(attributes(key("title").value("Fields for anime creation")), - fieldWithPath("title") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) - .description("애니의 제목"), - fieldWithPath("summary") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "공백을 허용하지 않습니다. 1~255자를 허용합니다.")) - .description("애니 요약 설명"), - fieldWithPath("broadcastType") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "TVA,ONA,OVA,MOV만 허용합니다.")) - .description("애니 방송사"), - fieldWithPath("episodeCount") - .type(JsonFieldType.NUMBER) - .attributes(field("constraints", "0 이상의 숫자만 허용합니다.")) - .description("애피소드 화수"), - fieldWithPath("thumbnail") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "공백을 허용하지 않습니다.")) - .description("애피소드 화수"), - fieldWithPath("year") - .type(JsonFieldType.NUMBER) - .attributes(field("constraints", "1900 이상의 숫자만 허용합니다.")) - .description("애니 발행 년도"), - fieldWithPath("quarter") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "Q1,Q2,Q3,Q4만 허용합니다.")) - .description("애니 발행 분기"), - fieldWithPath("rating") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "ADULT,FIFTEEN,TWELVE,ALL만 허용합니다.")) - .description("애니 등급"), - fieldWithPath("status") - .type(JsonFieldType.STRING) - .attributes(field("constraints", "ONGOING,FINISHED,UPCOMING,UNKNOWN만 허용합니다.")) - .description("애니 상태") - ) - )); + .andExpect(status().isNoContent()) + .andDo(document("patchAnime/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("title") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("애니의 제목"), + fieldWithPath("summary") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~255자를 허용합니다.")) + .description("애니 요약 설명"), + fieldWithPath("broadcastType") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "TVA,ONA,OVA,MOV만 허용합니다.")) + .description("애니 방송사"), + fieldWithPath("episodeCount") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "0 이상의 숫자만 허용합니다.")) + .description("애피소드 화수"), + fieldWithPath("thumbnail") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다.")) + .description("애피소드 화수"), + fieldWithPath("year") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "1900 이상의 숫자만 허용합니다.")) + .description("애니 발행 년도"), + fieldWithPath("quarter") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "Q1,Q2,Q3,Q4만 허용합니다.")) + .description("애니 발행 분기"), + fieldWithPath("rating") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ADULT,FIFTEEN,TWELVE,ALL만 허용합니다.")) + .description("애니 등급"), + fieldWithPath("status") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "ONGOING,FINISHED,UPCOMING,UNKNOWN만 허용합니다.")) + .description("애니 상태") + ) + )); } //TODO: 수정 실패 시 @@ -242,28 +236,28 @@ void patchAnimeOriginalAuthors() throws Exception { //when ResultActions actions = mockMvc.perform( - patch(ADMIN_URL+"/animes/{animeId}/original-authors", animeId) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(content) + patch(ADMIN_URL+"/animes/{animeId}/original-authors", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) ); //then actions - .andDo(document("patchAnimeOriginalAuthors/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("animeId").description("애니의 식별자") - ), - requestFields(attributes(key("title").value("Fields for anime creation")), - fieldWithPath("originalAuthorIds") - .type(JsonFieldType.ARRAY) - .description("원작 작가들 아이디 리스트") - .attributes(field("constraints", "List만 허용합니다.")) - .optional() - ) - )); + .andDo(document("patchAnimeOriginalAuthors/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("originalAuthorIds") + .type(JsonFieldType.ARRAY) + .description("원작 작가들 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); } //TODO: 수정 실패 시 @@ -280,28 +274,28 @@ void patchAnimeStudios() throws Exception { //when ResultActions actions = mockMvc.perform( - patch(ADMIN_URL+"/animes/{animeId}/studios", animeId) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(content) + patch(ADMIN_URL+"/animes/{animeId}/studios", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) ); //then actions - .andDo(document("patchAnimeStudios/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("animeId").description("애니의 식별자") - ), - requestFields(attributes(key("title").value("Fields for anime creation")), - fieldWithPath("studioIds") - .type(JsonFieldType.ARRAY) - .description("스튜디오 아이디 리스트") - .attributes(field("constraints", "List만 허용합니다.")) - .optional() - ) - )); + .andDo(document("patchAnimeStudios/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("studioIds") + .type(JsonFieldType.ARRAY) + .description("스튜디오 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); } //TODO: 수정 실패 시 @@ -311,43 +305,43 @@ void patchAnimeStudios() throws Exception { void patchAnimeVoiceActors() throws Exception { //given Long animeId = 1L; - List voiceActors = AnimeTestUtils.getVoiceActorReqs(); + List voiceActors = AnimeTestUtils.getVoiceActorReqs(); PatchVoiceActorIdsReq req = new PatchVoiceActorIdsReq(voiceActors); String content = gson.toJson(req); //when ResultActions actions = mockMvc.perform( - put(ADMIN_URL+"/animes/{animeId}/voice-actors", animeId) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(content) + put(ADMIN_URL+"/animes/{animeId}/voice-actors", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) ); //then actions - .andDo(document("patchAnimeVoiceActors/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("animeId").description("애니의 식별자") - ), - requestFields(attributes(key("title").value("Fields for anime creation")), - fieldWithPath("voiceActors") - .type(JsonFieldType.ARRAY) - .description("애니 성우 리스트") - .attributes(field("constraints", "List만 허용합니다.")) - .optional(), - fieldWithPath("voiceActors[].id") - .type(JsonFieldType.NUMBER) - .description("성우의 아이디") - .attributes(field("constraints", "숫자만 허용합니다.")), - fieldWithPath("voiceActors[].part") - .type(JsonFieldType.STRING) - .description("성우의 역할") - .attributes(field("constraints", "100자 이하만 허용합니다.")) - ) - )); + .andDo(document("patchAnimeVoiceActors/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("voiceActors") + .type(JsonFieldType.ARRAY) + .description("애니 성우 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional(), + fieldWithPath("voiceActors[].id") + .type(JsonFieldType.NUMBER) + .description("성우의 아이디") + .attributes(field("constraints", "숫자만 허용합니다.")), + fieldWithPath("voiceActors[].part") + .type(JsonFieldType.STRING) + .description("성우의 역할") + .attributes(field("constraints", "100자 이하만 허용합니다.")) + ) + )); } //TODO: 수정 실패 시 @@ -363,33 +357,33 @@ void patchAnimeGenres() throws Exception { //when ResultActions actions = mockMvc.perform( - put(ADMIN_URL+"/animes/{animeId}/genres", animeId) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(content) + put(ADMIN_URL+"/animes/{animeId}/genres", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) ); //then actions - .andDo(document("patchAnimeGenres/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("animeId").description("애니의 식별자") - ), - requestFields(attributes(key("title").value("Fields for anime creation")), - fieldWithPath("genreIds") - .type(JsonFieldType.ARRAY) - .description("애니 장르 아이디 리스트") - .attributes(field("constraints", "List만 허용합니다.")) - .optional() - ) - )); + .andDo(document("patchAnimeGenres/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("genreIds") + .type(JsonFieldType.ARRAY) + .description("애니 장르 아이디 리스트") + .attributes(field("constraints", "List만 허용합니다.")) + .optional() + ) + )); } //TODO: 수정 실패 시 @Test - @DisplayName("애니의 시리즈 수정 성공 시 Http Status 204 반환") + @DisplayName("시리즈 수정 성공 시 Http Status 204 반환") void patchAnimeSeries() throws Exception { //given Long animeId = 1L; @@ -398,30 +392,329 @@ void patchAnimeSeries() throws Exception { //when ResultActions actions = mockMvc.perform( - patch(ADMIN_URL+"/animes/{animeId}/series", animeId) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .content(content) + patch(ADMIN_URL+"/animes/{animeId}/series", animeId) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) ); //then actions - .andExpect(status().isNoContent()) - .andDo(document("patchAnimeSeries/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("animeId").description("애니의 식별자") - ), - requestFields(attributes(key("title").value("Fields for anime creation")), - fieldWithPath("seriesId") - .type(JsonFieldType.NUMBER) - .description("애니 시리즈 아이디") - .attributes(field("constraints", "숫자만 허용합니다.")) - .optional() - ) - )); + .andExpect(status().isNoContent()) + .andDo(document("patchAnimeSeries/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("animeId").description("애니의 식별자") + ), + requestFields(attributes(key("title").value("Fields for anime creation")), + fieldWithPath("seriesId") + .type(JsonFieldType.NUMBER) + .description("애니 시리즈 아이디") + .attributes(field("constraints", "숫자만 허용합니다.")) + .optional() + ) + )); } //TODO: 수정 실패 시 + + } + + @Nested + @DisplayName("원작 작가") + class OriginalAuthorTest{ + @Test + @DisplayName("조회 성공 시 Http status 200 반환") + void getOriginalAuthors() throws Exception { + ResultActions actions = mockMvc.perform( + RestDocumentationRequestBuilders.get(ADMIN_URL+"/original-authors") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").exists()) + .andExpect(jsonPath("$[0].name").exists()) + .andDo(document("getOriginalAuthors/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("[].id") + .type(JsonFieldType.NUMBER) + .description("원작 작가의 고유 식별자"), + fieldWithPath("[].name") + .type(JsonFieldType.STRING) + .description("원작 작가의 이름") + ) + )); + } + + @Test + @DisplayName("등록 성공 시 Http Status 204 반환") + void postOriginalAuthor() throws Exception { + String name = "고토게 코요하루"; + OriginalAuthorReq.PostReq postReq = new OriginalAuthorReq.PostReq(name); + String content = gson.toJson(postReq); + + ResultActions actions = mockMvc.perform( + post(ADMIN_URL + "/original-authors") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + actions + .andExpect(status().isNoContent()) + .andDo(document("postOriginalAuthor/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields(attributes(key("title").value("Fields for original author creation")), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("원작 작가의 이름") + ) + )); + } + } + + @Nested + @DisplayName("성우") + class VoiceActorTest{ + @Test + @DisplayName("조회 성공 시 Http status 200 반환") + void getOriginalAuthors() throws Exception { + ResultActions actions = mockMvc.perform( + RestDocumentationRequestBuilders.get(ADMIN_URL+"/voice-actors") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").exists()) + .andExpect(jsonPath("$[0].name").exists()) + .andDo(document("getVoiceActors/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("[].id") + .type(JsonFieldType.NUMBER) + .description("성우의 고유 식별자"), + fieldWithPath("[].name") + .type(JsonFieldType.STRING) + .description("성우의 이름") + ) + )); + } + + @Test + @DisplayName("등록 성공 시 Http Status 204 반환") + void postVoiceActor() throws Exception { + String name = "에구치 타쿠야"; + VoiceActorReq.PostReq postReq = new VoiceActorReq.PostReq(name); + String content = gson.toJson(postReq); + + ResultActions actions = mockMvc.perform( + post(ADMIN_URL + "/voice-actors") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + actions + .andExpect(status().isNoContent()) + .andDo(document("postVoiceActor/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields(attributes(key("title").value("Fields for original author creation")), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("성우의 이름") + ) + )); + } + } + + @Nested + @DisplayName("스튜디오") + class StudioTest{ + @Test + @DisplayName("조회 성공 시 Http status 200 반환") + void getStudios() throws Exception { + ResultActions actions = mockMvc.perform( + RestDocumentationRequestBuilders.get(ADMIN_URL+"/studios") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").exists()) + .andExpect(jsonPath("$[0].name").exists()) + .andDo(document("getStudios/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("[].id") + .type(JsonFieldType.NUMBER) + .description("스튜디오의 고유 식별자"), + fieldWithPath("[].name") + .type(JsonFieldType.STRING) + .description("스튜디오의 이름") + ) + )); + } + + @Test + @DisplayName("등록 성공 시 Http Status 204 반환") + void postVoiceActor() throws Exception { + String name = "ufortable"; + StudioReq.PostReq postReq = new StudioReq.PostReq(name); + String content = gson.toJson(postReq); + + ResultActions actions = mockMvc.perform( + post(ADMIN_URL + "/studios") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + actions + .andExpect(status().isNoContent()) + .andDo(document("postStudio/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields(attributes(key("title").value("Fields for original author creation")), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("스튜디오의 이름") + ) + )); + } + } + + + @Nested + @DisplayName("장르") + class GenreTest{ + @Test + @DisplayName("조회 성공 시 Http status 200 반환") + void getGenres() throws Exception { + ResultActions actions = mockMvc.perform( + RestDocumentationRequestBuilders.get(ADMIN_URL+"/genres") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").exists()) + .andExpect(jsonPath("$[0].name").exists()) + .andDo(document("getGenres/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("[].id") + .type(JsonFieldType.NUMBER) + .description("장르의 고유 식별자"), + fieldWithPath("[].name") + .type(JsonFieldType.STRING) + .description("장르의 이름") + ) + )); + } + + @Test + @DisplayName("등록 성공 시 Http Status 204 반환") + void postVoiceActor() throws Exception { + String name = "판타지"; + GenreReq.PostReq postReq = new GenreReq.PostReq(name); + String content = gson.toJson(postReq); + + ResultActions actions = mockMvc.perform( + post(ADMIN_URL + "/genres") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + actions + .andExpect(status().isNoContent()) + .andDo(document("postGenre/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields(attributes(key("title").value("Fields for original author creation")), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("장르의 이름") + ) + )); + } + } + + + @Nested + @DisplayName("시리즈") + class SeriesTest{ + @Test + @DisplayName("조회 성공 시 Http status 200 반환") + void getSeries() throws Exception { + ResultActions actions = mockMvc.perform( + RestDocumentationRequestBuilders.get(ADMIN_URL+"/series") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").exists()) + .andExpect(jsonPath("$[0].title").exists()) + .andDo(document("getSeries/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("[].id") + .type(JsonFieldType.NUMBER) + .description("시리즈의 고유 식별자"), + fieldWithPath("[].title") + .type(JsonFieldType.STRING) + .description("시리즈의 제목") + ) + )); + } + + @Test + @DisplayName("등록 성공 시 Http Status 204 반환") + void postSeries() throws Exception { + String title = "귀멸의 칼날"; + SeriesReq.PostReq postReq = new SeriesReq.PostReq(title); + String content = gson.toJson(postReq); + + ResultActions actions = mockMvc.perform( + post(ADMIN_URL + "/series") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .content(content) + ); + + actions + .andExpect(status().isNoContent()) + .andDo(document("postSeries/success", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields(attributes(key("title").value("Fields for original author creation")), + fieldWithPath("title") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "공백을 허용하지 않습니다. 1~50자를 허용합니다.")) + .description("시리즈의 제목") + ) + )); + } } + } From 7dd77d11e752c0533b3d88bfc19ec4f043ff4686 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:45:37 +0900 Subject: [PATCH 317/734] =?UTF-8?q?docs:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=20=EA=B4=80=EB=A0=A8=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=93=B1=EB=A1=9D,=20=EC=A1=B0=ED=9A=8C=20docs=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 197 ++++++++++++++++++++++++++++++++++- 1 file changed, 196 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index cb1148c5..3c1bf841 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -441,4 +441,199 @@ include::{snippets}/patchAnimeSeries/success/request-fields.adoc[] .http-response include::{snippets}/patchAnimeSeries/success/http-response.adoc[] -==== 실패시 \ No newline at end of file +==== 실패시 + +=== GET api/v1/oduckdmin/original-authors + +.curl-request +include::{snippets}/getOriginalAuthors/success/curl-request.adoc[] + +.http-request +include::{snippets}/getOriginalAuthors/success/http-request.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getOriginalAuthors/success/http-response.adoc[] + +.response-body +include::{snippets}/getOriginalAuthors/success/response-body.adoc[] + +.response-fields +include::{snippets}/getOriginalAuthors/success/response-fields.adoc[] + +==== 실패 시 + +=== POST api/v1/oduckdmin/original-authors +.curl-request +include::{snippets}/postOriginalAuthor/success/curl-request.adoc[] + +.http-request +include::{snippets}/postOriginalAuthor/success/http-request.adoc[] + +.request-body +include::{snippets}/postOriginalAuthor/success/request-body.adoc[] + +.request-fields +include::{snippets}/postOriginalAuthor/success/request-fields.adoc[] + +==== 성공 시 +.http-response +include::{snippets}/postOriginalAuthor/success/http-response.adoc[] + +==== 실패 시 + +=== GET api/v1/oduckdmin/voice-actors + +.curl-request +include::{snippets}/getVoiceActors/success/curl-request.adoc[] + +.http-request +include::{snippets}/getVoiceActors/success/http-request.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getVoiceActors/success/http-response.adoc[] + +.response-body +include::{snippets}/getVoiceActors/success/response-body.adoc[] + +.response-fields +include::{snippets}/getVoiceActors/success/response-fields.adoc[] + +==== 실패 시 + +=== POST api/v1/oduckdmin/voice-actors +.curl-request +include::{snippets}/postVoiceActor/success/curl-request.adoc[] + +.http-request +include::{snippets}/postVoiceActor/success/http-request.adoc[] + +.request-body +include::{snippets}/postVoiceActor/success/request-body.adoc[] + +.request-fields +include::{snippets}/postVoiceActor/success/request-fields.adoc[] + +==== 성공 시 +.http-response +include::{snippets}/postVoiceActor/success/http-response.adoc[] + +==== 실패 시 + +=== GET api/v1/oduckdmin/studios + +.curl-request +include::{snippets}/getStudios/success/curl-request.adoc[] + +.http-request +include::{snippets}/getStudios/success/http-request.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getStudios/success/http-response.adoc[] + +.response-body +include::{snippets}/getStudios/success/response-body.adoc[] + +.response-fields +include::{snippets}/getStudios/success/response-fields.adoc[] + +==== 실패 시 + +=== POST api/v1/oduckdmin/studios +.curl-request +include::{snippets}/postStudio/success/curl-request.adoc[] + +.http-request +include::{snippets}/postStudio/success/http-request.adoc[] + +.request-body +include::{snippets}/postStudio/success/request-body.adoc[] + +.request-fields +include::{snippets}/postStudio/success/request-fields.adoc[] + +==== 성공 시 +.http-response +include::{snippets}/postStudio/success/http-response.adoc[] + +==== 실패 시 + +=== GET api/v1/oduckdmin/genres + +.curl-request +include::{snippets}/getGenres//success/curl-request.adoc[] + +.http-request +include::{snippets}/getGenres/success/http-request.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getGenres/success/http-response.adoc[] + +.response-body +include::{snippets}/getGenres/success/response-body.adoc[] + +.response-fields +include::{snippets}/getGenres/success/response-fields.adoc[] + +==== 실패 시 + +=== POST api/v1/oduckdmin/genres +.curl-request +include::{snippets}/postGenre/success/curl-request.adoc[] + +.http-request +include::{snippets}/postGenre/success/http-request.adoc[] + +.request-body +include::{snippets}/postGenre/success/request-body.adoc[] + +.request-fields +include::{snippets}/postGenre/success/request-fields.adoc[] + +==== 성공 시 +.http-response +include::{snippets}/postGenre/success/http-response.adoc[] + +==== 실패 시 + +=== GET api/v1/oduckdmin/series + +.curl-request +include::{snippets}/getSeries//success/curl-request.adoc[] + +.http-request +include::{snippets}/getSeries/success/http-request.adoc[] + +==== 성공시 +.http-response +include::{snippets}/getSeries/success/http-response.adoc[] + +.response-body +include::{snippets}/getSeries/success/response-body.adoc[] + +.response-fields +include::{snippets}/getSeries/success/response-fields.adoc[] + +==== 실패 시 + +=== POST api/v1/oduckdmin/genres +.curl-request +include::{snippets}/postSeries/success/curl-request.adoc[] + +.http-request +include::{snippets}/postSeries/success/http-request.adoc[] + +.request-body +include::{snippets}/postSeries/success/request-body.adoc[] + +.request-fields +include::{snippets}/postSeries/success/request-fields.adoc[] + +==== 성공 시 +.http-response +include::{snippets}/postSeries/success/http-response.adoc[] + +==== 실패 시 \ No newline at end of file From 241f6e3710e4e837a26fc8928b899c635bfa353c Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 16:47:27 +0900 Subject: [PATCH 318/734] =?UTF-8?q?refactor:=20Anime=EC=9D=98=20VoiceActor?= =?UTF-8?q?=20Dto=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=88=98=EC=A0=95=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/anime/service/AnimeServiceImpl.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index 99cd5b10..1a747cc9 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -8,7 +8,7 @@ import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; import io.oduck.api.domain.anime.dto.AnimeRes; -import io.oduck.api.domain.anime.dto.VoiceActorReq; +import io.oduck.api.domain.anime.dto.AnimeVoiceActorReq; import io.oduck.api.domain.anime.entity.Anime; import io.oduck.api.domain.anime.entity.AnimeGenre; import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; @@ -84,18 +84,18 @@ public void save(PostReq postReq) { .collect(Collectors.toList()); // 애니에 참여한 성우 리스트 - List voiceActorDtoList = postReq.getVoiceActors(); + List voiceActorDtoList = postReq.getVoiceActors(); // 성우의 아이디 리스트 구하기 List voiceActorIds = voiceActorDtoList.stream() - .map(VoiceActorReq::getId) + .map(AnimeVoiceActorReq::getId) .collect(Collectors.toList()); List voiceActors = voiceActorRepository.findAllById(voiceActorIds); // Id와 Part로 구성된 map 생성 Map voiceActorDtoMap = voiceActorDtoList.stream() - .collect(Collectors.toMap(VoiceActorReq::getId, VoiceActorReq::getPart)); + .collect(Collectors.toMap(AnimeVoiceActorReq::getId, AnimeVoiceActorReq::getPart)); List animeVoiceActors = voiceActors.stream() .filter(voiceActor -> voiceActorDtoMap.containsKey(voiceActor.getId())) @@ -182,18 +182,18 @@ public void updateAnimeVoiceActors(Long animeId, PatchVoiceActorIdsReq patchReq) Anime anime = findAnime(animeId); // 애니에 참여한 성우 리스트 - List voiceActorDtoList = patchReq.getVoiceActors(); + List voiceActorDtoList = patchReq.getVoiceActors(); // 성우의 아이디 리스트 구하기 List voiceActorIds = voiceActorDtoList.stream() - .map(VoiceActorReq::getId) + .map(AnimeVoiceActorReq::getId) .collect(Collectors.toList()); List voiceActors = voiceActorRepository.findAllById(voiceActorIds); // Id와 Part로 구성된 map 생성 Map voiceActorDtoMap = voiceActorDtoList.stream() - .collect(Collectors.toMap(VoiceActorReq::getId, VoiceActorReq::getPart)); + .collect(Collectors.toMap(AnimeVoiceActorReq::getId, AnimeVoiceActorReq::getPart)); List animeVoiceActors = voiceActors.stream() .filter(va -> voiceActorDtoMap.containsKey(va.getId())) From fbf577b688a8cf1c08eec1b455161470e92c3991 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 17:07:47 +0900 Subject: [PATCH 319/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EC=9D=98=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=BC=20=EB=95=8C=20dto=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/unit/anime/service/AnimeServiceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index b43308dd..e581ecab 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -1,7 +1,7 @@ package io.oduck.api.unit.anime.service; import io.oduck.api.domain.anime.dto.AnimeReq.*; -import io.oduck.api.domain.anime.dto.VoiceActorReq; +import io.oduck.api.domain.anime.dto.AnimeVoiceActorReq; import io.oduck.api.domain.anime.entity.*; import io.oduck.api.domain.anime.repository.*; import io.oduck.api.domain.anime.service.AnimeServiceImpl; @@ -230,8 +230,8 @@ void updateAnimeVoiceActors(){ //given Long animeId = 1L; - List patchReqs = getVoiceActorReqs(); - List voiceActorIds = patchReqs.stream().map(VoiceActorReq::getId) + List patchReqs = getVoiceActorReqs(); + List voiceActorIds = patchReqs.stream().map(AnimeVoiceActorReq::getId) .collect(Collectors.toList()); PatchVoiceActorIdsReq patchReq = new PatchVoiceActorIdsReq(patchReqs); From 596ebc0868f1c327e650c9b4adb448bc1e982cbc Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 17:08:25 +0900 Subject: [PATCH 320/734] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=EC=9D=98=20=EC=95=A0=EB=8B=88=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EB=82=B4=EB=B6=80=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=AA=85=20=EC=88=98=EC=A0=95=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/unit/anime/service/AnimeServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index e581ecab..fc16fbc0 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -75,7 +75,7 @@ public class AnimeServiceTest { @Nested @DisplayName("조회") - class findAnime{ + class GetAnime{ Anime anime = createAnime(); @Test From f587fc83a81aea85234db09b0bf3abf0e99b8f97 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 17:08:49 +0900 Subject: [PATCH 321/734] =?UTF-8?q?test:=20=EC=95=A0=EB=8B=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=8B=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EC=97=90=EC=84=9C=20verify=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/unit/anime/service/AnimeServiceTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index fc16fbc0..25e1e74f 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -146,6 +146,13 @@ void registerAnimeSuccess(){ // then assertThatNoException(); + + //verify + verify(originalAuthorRepository, times(1)).findAllById(anyList()); + verify(voiceActorRepository, times(1)).findAllById(anyList()); + verify(studioRepository, times(1)).findAllById(anyList()); + verify(genreRepository, times(1)).findAllById(anyList()); + verify(seriesRepository, times(1)).findById(anyLong()); } } From 7e26028e32d3faad4911e684d76524e324be3930 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 17:09:11 +0900 Subject: [PATCH 322/734] =?UTF-8?q?test:=20=EC=9E=A5=EB=A5=B4=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4,=20=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genre/repository/GenreRepositoryTest.java | 66 +++++++++++++++++++ .../unit/genre/service/GenreServiceTest.java | 63 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/genre/repository/GenreRepositoryTest.java create mode 100644 src/test/java/io/oduck/api/unit/genre/service/GenreServiceTest.java diff --git a/src/test/java/io/oduck/api/unit/genre/repository/GenreRepositoryTest.java b/src/test/java/io/oduck/api/unit/genre/repository/GenreRepositoryTest.java new file mode 100644 index 00000000..6d850c5b --- /dev/null +++ b/src/test/java/io/oduck/api/unit/genre/repository/GenreRepositoryTest.java @@ -0,0 +1,66 @@ +package io.oduck.api.unit.genre.repository; + +import io.oduck.api.domain.genre.entity.Genre; +import io.oduck.api.domain.genre.repository.GenreRepository; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +public class GenreRepositoryTest { + + @Autowired + private GenreRepository genreRepository; + + @Nested + @DisplayName("등록") + class SaveGenre { + @Test + @DisplayName("장르 등록 성공") + void saveGenre() { + //given + Genre genre = Genre.builder() + .name("로맨스") + .build(); + + //when + Genre savedGenre = genreRepository.save(genre); + + //then + assertThat(genre.getId()).isEqualTo(savedGenre.getId()); + assertThat(genre.getName()).isEqualTo(savedGenre.getName()); + } + } + + @Nested + @DisplayName("조회") + class FindGenre { + @Test + @DisplayName("장르 조회 성공") + void findGenre() { + //given + Genre genre = Genre.builder() + .name("판타지") + .build(); + + Genre savedGenre = genreRepository.save(genre); + + //when + List genres = genreRepository.findAll(); + + //then + assertThat(savedGenre).isIn(genres); + + } + } +} diff --git a/src/test/java/io/oduck/api/unit/genre/service/GenreServiceTest.java b/src/test/java/io/oduck/api/unit/genre/service/GenreServiceTest.java new file mode 100644 index 00000000..a395d032 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/genre/service/GenreServiceTest.java @@ -0,0 +1,63 @@ +package io.oduck.api.unit.genre.service; + +import io.oduck.api.domain.genre.dto.GenreReq; +import io.oduck.api.domain.genre.entity.Genre; +import io.oduck.api.domain.genre.repository.GenreRepository; +import io.oduck.api.domain.genre.service.GenreServiceImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class GenreServiceTest { + + @InjectMocks + private GenreServiceImpl genreService; + + @Mock + private GenreRepository genreRepository; + + @Nested + @DisplayName("등록") + class SaveGenre { + @Test + @DisplayName("장르 등록 성공") + void saveVoiceActors() { + //given + GenreReq.PostReq postReq = new GenreReq.PostReq("로맨스"); + + //when + genreService.save(postReq); + + //then + assertThatNoException(); + + //verify + verify(genreRepository, times(1)).save(any(Genre.class)); + } + } + + @Nested + @DisplayName("조회") + class GetVoiceActors { + @Test + @DisplayName("장르 조회 성공") + void getVoiceActors() { + //when + genreService.getGenres(); + + //then + assertThatNoException(); + + //verify + verify(genreRepository, times(1)).findAll(); + } + } +} From e562723c3da867daaae2c803852bad2ab43616e0 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 17:10:31 +0900 Subject: [PATCH 323/734] =?UTF-8?q?test:=20=EC=9B=90=EC=9E=91=20=EC=9E=91?= =?UTF-8?q?=EA=B0=80=20=EC=84=9C=EB=B9=84=EC=8A=A4,=20=EB=A6=AC=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OriginalAuthorRepositoryTest.java | 66 +++++++++++++++++++ .../service/OriginalAuthorServiceTest.java | 65 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/originalAuthor/repository/OriginalAuthorRepositoryTest.java create mode 100644 src/test/java/io/oduck/api/unit/originalAuthor/service/OriginalAuthorServiceTest.java diff --git a/src/test/java/io/oduck/api/unit/originalAuthor/repository/OriginalAuthorRepositoryTest.java b/src/test/java/io/oduck/api/unit/originalAuthor/repository/OriginalAuthorRepositoryTest.java new file mode 100644 index 00000000..cc831e55 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/originalAuthor/repository/OriginalAuthorRepositoryTest.java @@ -0,0 +1,66 @@ +package io.oduck.api.unit.originalAuthor.repository; + +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +import io.oduck.api.domain.originalAuthor.repository.OriginalAuthorRepository; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +public class OriginalAuthorRepositoryTest { + + @Autowired + private OriginalAuthorRepository originalAuthorRepository; + + @Nested + @DisplayName("등록") + class SaveOriginalAuthorTest{ + @Test + @DisplayName("원작 작가 등록 성공") + void SaveOriginalAuthor() { + //given + OriginalAuthor originalAuthor = OriginalAuthor.builder() + .name("엔도 타츠야") + .build(); + + //when + OriginalAuthor savedOriginalAuthor = originalAuthorRepository.save(originalAuthor); + + //then + assertThat(originalAuthor.getId()).isEqualTo(savedOriginalAuthor.getId()); + assertThat(originalAuthor.getName()).isEqualTo(savedOriginalAuthor.getName()); + } + } + + @Nested + @DisplayName("조회") + class FindOriginalAuthor { + @Test + @DisplayName("원작 작가 조회 성공") + void findOriginalAuthor() { + //given + OriginalAuthor originalAuthor = OriginalAuthor.builder() + .name("성우A") + .build(); + + OriginalAuthor savedOriginalAuthor = originalAuthorRepository.save(originalAuthor); + + //when + List originalAuthors = originalAuthorRepository.findAll(); + + //then + assertThat(savedOriginalAuthor).isIn(originalAuthors); + + } + } +} diff --git a/src/test/java/io/oduck/api/unit/originalAuthor/service/OriginalAuthorServiceTest.java b/src/test/java/io/oduck/api/unit/originalAuthor/service/OriginalAuthorServiceTest.java new file mode 100644 index 00000000..4652cbb7 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/originalAuthor/service/OriginalAuthorServiceTest.java @@ -0,0 +1,65 @@ +package io.oduck.api.unit.originalAuthor.service; + +import io.oduck.api.domain.originalAuthor.dto.OriginalAuthorReq; +import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; +import io.oduck.api.domain.originalAuthor.repository.OriginalAuthorRepository; +import io.oduck.api.domain.originalAuthor.service.OriginalAuthorServiceImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +public class OriginalAuthorServiceTest { + + @InjectMocks + private OriginalAuthorServiceImpl originalAuthorService; + + @Mock + private OriginalAuthorRepository originalAuthorRepository; + + @Nested + @DisplayName("등록") + class PostOriginalAuthor { + @Test + @DisplayName("원작 작가 등록 성공") + void postOriginalAuthor() { + //given + OriginalAuthorReq.PostReq postReq = new OriginalAuthorReq.PostReq("하나에 나츠키"); + + //when + originalAuthorService.save(postReq); + + //then + assertThatNoException(); + + //verify + verify(originalAuthorRepository, times(1)).save(any(OriginalAuthor.class)); + } + } + + @Nested + @DisplayName("조회") + class GetOriginalAuthors { + @Test + @DisplayName("원작 작가들 조회") + void getOriginalAuthors(){ + //when + originalAuthorService.getOriginalAuthors(); + + //then + assertThatNoException(); + + //verify + verify(originalAuthorRepository, times(1)).findAll(); + } + } +} From e134c5bd2d7c2ae330cd7efcc746c60c3e36785e Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 17:10:44 +0900 Subject: [PATCH 324/734] =?UTF-8?q?test:=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4,=20=EB=A6=AC=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SeriesRepositoryTest.java | 65 +++++++++++++++++++ .../series/service/SeriesServiceTest.java | 64 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/series/repository/SeriesRepositoryTest.java create mode 100644 src/test/java/io/oduck/api/unit/series/service/SeriesServiceTest.java diff --git a/src/test/java/io/oduck/api/unit/series/repository/SeriesRepositoryTest.java b/src/test/java/io/oduck/api/unit/series/repository/SeriesRepositoryTest.java new file mode 100644 index 00000000..66c80cbd --- /dev/null +++ b/src/test/java/io/oduck/api/unit/series/repository/SeriesRepositoryTest.java @@ -0,0 +1,65 @@ +package io.oduck.api.unit.series.repository; + +import io.oduck.api.domain.series.entity.Series; +import io.oduck.api.domain.series.repository.SeriesRepository; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +public class SeriesRepositoryTest { + + @Autowired + private SeriesRepository seriesRepository; + + @Nested + @DisplayName("등록") + class SaveSeries { + @Test + @DisplayName("시리즈 등록 성공") + void saveSeries() { + //given + Series series = Series.builder() + .title("스파이 패밀리") + .build(); + + //when + Series savedSeries = seriesRepository.save(series); + + //then + assertThat(series.getId()).isEqualTo(savedSeries.getId()); + assertThat(series.getTitle()).isEqualTo(savedSeries.getTitle()); + } + } + + @Nested + @DisplayName("조회") + class FindSeries { + @Test + @DisplayName("시리즈 조회 성공") + void findSeries() { + //given + Series series = Series.builder() + .title("스파이 패밀리") + .build(); + + Series savedSeries = seriesRepository.save(series); + + //when + List originalAuthors = seriesRepository.findAll(); + + //then + assertThat(savedSeries).isIn(originalAuthors); + } + } +} diff --git a/src/test/java/io/oduck/api/unit/series/service/SeriesServiceTest.java b/src/test/java/io/oduck/api/unit/series/service/SeriesServiceTest.java new file mode 100644 index 00000000..7f6bbf25 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/series/service/SeriesServiceTest.java @@ -0,0 +1,64 @@ +package io.oduck.api.unit.series.service; + +import io.oduck.api.domain.series.dto.SeriesReq; +import io.oduck.api.domain.series.entity.Series; +import io.oduck.api.domain.series.repository.SeriesRepository; +import io.oduck.api.domain.series.service.SeriesServiceImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +public class SeriesServiceTest { + @InjectMocks + private SeriesServiceImpl seriesService; + + @Mock + private SeriesRepository seriesRepository; + + @Nested + @DisplayName("등록") + class SaveSeries { + @Test + @DisplayName("시리즈 등록 성공") + void saveSeriesSuccess() { + //given + SeriesReq.PostReq postReq = new SeriesReq.PostReq("스파이 패밀리"); + + //when + seriesService.save(postReq); + + //then + assertThatNoException(); + + //verify + verify(seriesRepository, times(1)).save(any(Series.class)); + } + } + + @Nested + @DisplayName("조회") + class GetSeries { + @Test + @DisplayName("시리즈 조회 성공") + void getSeries() { + //when + seriesService.getSeries(); + + //then + assertThatNoException(); + + //verify + verify(seriesRepository, times(1)).findAll(); + } + } +} From 2813a01ec6dec114f570f1b70cd2fa535baaa4c5 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 17:10:55 +0900 Subject: [PATCH 325/734] =?UTF-8?q?test:=20=EC=8A=A4=ED=8A=9C=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EC=84=9C=EB=B9=84=EC=8A=A4,=20=EB=A6=AC=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/StudioRepositoryTest.java | 63 ++++++++++++++++++ .../studio/service/StudioServiceTest.java | 64 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/studio/repository/StudioRepositoryTest.java create mode 100644 src/test/java/io/oduck/api/unit/studio/service/StudioServiceTest.java diff --git a/src/test/java/io/oduck/api/unit/studio/repository/StudioRepositoryTest.java b/src/test/java/io/oduck/api/unit/studio/repository/StudioRepositoryTest.java new file mode 100644 index 00000000..b7ac06e1 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/studio/repository/StudioRepositoryTest.java @@ -0,0 +1,63 @@ +package io.oduck.api.unit.studio.repository; + +import io.oduck.api.domain.studio.entity.Studio; +import io.oduck.api.domain.studio.repository.StudioRepository; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +public class StudioRepositoryTest { + + @Autowired + private StudioRepository studioRepository; + + @Nested + @DisplayName("등록") + class SaveStudio { + @Test + @DisplayName("스튜디오 등록 성공") + void saveStudio() { + //given + Studio studio = Studio.builder() + .name("ufortable") + .build(); + + Studio savedStudio = studioRepository.save(studio); + + assertThat(studio.getId()).isEqualTo(savedStudio.getId()); + assertThat(studio.getName()).isEqualTo(savedStudio.getName()); + } + } + + @Nested + @DisplayName("조회") + class FindSeries { + @Test + @DisplayName("스튜디오 조회 성공") + void findSeries() { + //given + Studio studio = Studio.builder() + .name("스튜디오A") + .build(); + + Studio savedStudio = studioRepository.save(studio); + + //when + List studios = studioRepository.findAll(); + + //then + assertThat(savedStudio).isIn(studios); + } + } +} diff --git a/src/test/java/io/oduck/api/unit/studio/service/StudioServiceTest.java b/src/test/java/io/oduck/api/unit/studio/service/StudioServiceTest.java new file mode 100644 index 00000000..123a8b8c --- /dev/null +++ b/src/test/java/io/oduck/api/unit/studio/service/StudioServiceTest.java @@ -0,0 +1,64 @@ +package io.oduck.api.unit.studio.service; + +import io.oduck.api.domain.studio.dto.StudioReq; +import io.oduck.api.domain.studio.entity.Studio; +import io.oduck.api.domain.studio.repository.StudioRepository; +import io.oduck.api.domain.studio.service.StudioServiceImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class StudioServiceTest { + + @InjectMocks + private StudioServiceImpl studioService; + + @Mock + private StudioRepository studioRepository; + + @Nested + @DisplayName("등록") + class SaveStudio { + + @Test + @DisplayName("스튜디오 등록 성공") + void saveStudio() { + //given + StudioReq.PostReq postReq = new StudioReq.PostReq("ufortable"); + + //when + studioService.save(postReq); + + //then + assertThatNoException(); + + //verify + verify(studioRepository,times(1)).save(any(Studio.class)); + } + } + + @Nested + @DisplayName("조회") + class GetStudios { + @Test + @DisplayName("스튜디오 조회 성공") + void getStudios() { + //when + studioService.getStudios(); + + //then + assertThatNoException(); + + //verify + verify(studioRepository, times(1)).findAll(); + } + } +} From 6f46261c3af045bf4e316bc920425e85c5075718 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 17:12:13 +0900 Subject: [PATCH 326/734] =?UTF-8?q?test:=20=EC=84=B1=EC=9A=B0=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4,=20=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80=20#39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/VoiceActorRepositoryTest.java | 66 +++++++++++++++++++ .../service/VoiceActorServiceTest.java | 65 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/voiceActor/repository/VoiceActorRepositoryTest.java create mode 100644 src/test/java/io/oduck/api/unit/voiceActor/service/VoiceActorServiceTest.java diff --git a/src/test/java/io/oduck/api/unit/voiceActor/repository/VoiceActorRepositoryTest.java b/src/test/java/io/oduck/api/unit/voiceActor/repository/VoiceActorRepositoryTest.java new file mode 100644 index 00000000..e351d7f6 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/voiceActor/repository/VoiceActorRepositoryTest.java @@ -0,0 +1,66 @@ +package io.oduck.api.unit.voiceActor.repository; + +import io.oduck.api.domain.voiceActor.entity.VoiceActor; +import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +public class VoiceActorRepositoryTest { + + @Autowired + private VoiceActorRepository voiceActorRepository; + + @Nested + @DisplayName("등록") + class SaveVoiceActor { + + @Test + @DisplayName("성우 등록 성공") + void saveVoiceActor() { + //given + VoiceActor voiceActor = VoiceActor.builder() + .name("성우1") + .build(); + + //when + VoiceActor savedVoiceActor = voiceActorRepository.save(voiceActor); + + //then + assertThat(voiceActor.getId()).isEqualTo(savedVoiceActor.getId()); + assertThat(voiceActor.getName()).isEqualTo(savedVoiceActor.getName()); + } + } + + @Nested + @DisplayName("조회") + class FindSeries { + @Test + @DisplayName("성우 조회 성공") + void findSeries() { + //given + VoiceActor voiceActor = VoiceActor.builder() + .name("성우A") + .build(); + + VoiceActor savedVoiceActor = voiceActorRepository.save(voiceActor); + + //when + List voiceActors = voiceActorRepository.findAll(); + + //then + assertThat(savedVoiceActor).isIn(voiceActors); + } + } +} diff --git a/src/test/java/io/oduck/api/unit/voiceActor/service/VoiceActorServiceTest.java b/src/test/java/io/oduck/api/unit/voiceActor/service/VoiceActorServiceTest.java new file mode 100644 index 00000000..5374d72e --- /dev/null +++ b/src/test/java/io/oduck/api/unit/voiceActor/service/VoiceActorServiceTest.java @@ -0,0 +1,65 @@ +package io.oduck.api.unit.voiceActor.service; + +import io.oduck.api.domain.voiceActor.dto.VoiceActorReq; +import io.oduck.api.domain.voiceActor.entity.VoiceActor; +import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; +import io.oduck.api.domain.voiceActor.service.VoiceActorServiceImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +public class VoiceActorServiceTest { + + @InjectMocks + private VoiceActorServiceImpl voiceActorService; + + @Mock + private VoiceActorRepository voiceActorRepository; + + @Nested + @DisplayName("등록") + class SaveVoiceActor { + @Test + @DisplayName("성우 등록 성공") + void saveVoiceActor() { + //given + VoiceActorReq.PostReq postReq = new VoiceActorReq.PostReq("성우1"); + + //when + voiceActorService.save(postReq); + + //then + assertThatNoException(); + + //verify + verify(voiceActorRepository, times(1)).save(any(VoiceActor.class)); + } + } + + @Nested + @DisplayName("조회") + class GetVoiceActors { + @Test + @DisplayName("성우 조회 성공") + void getVoiceActors() { + //when + voiceActorService.getVoiceActors(); + + //then + assertThatNoException(); + + //verify + verify(voiceActorRepository, times(1)).findAll(); + } + } +} From 00206b80358bad777614d70a39b4295763da25c2 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 22:02:07 +0900 Subject: [PATCH 327/734] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EB=A9=94=EC=86=8C=EB=93=9C=20private?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anime/service/AnimeServiceImpl.java | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index 99cd5b10..3615db42 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -1,24 +1,10 @@ package io.oduck.api.domain.anime.service; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchAnimeReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchGenreIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchOriginalAuthorIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchSeriesIdReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchStudioIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PatchVoiceActorIdsReq; -import io.oduck.api.domain.anime.dto.AnimeReq.PostReq; +import io.oduck.api.domain.anime.dto.AnimeReq.*; import io.oduck.api.domain.anime.dto.AnimeRes; import io.oduck.api.domain.anime.dto.VoiceActorReq; -import io.oduck.api.domain.anime.entity.Anime; -import io.oduck.api.domain.anime.entity.AnimeGenre; -import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; -import io.oduck.api.domain.anime.entity.AnimeStudio; -import io.oduck.api.domain.anime.entity.AnimeVoiceActor; -import io.oduck.api.domain.anime.repository.AnimeGenreRepository; -import io.oduck.api.domain.anime.repository.AnimeOriginalAuthorRepository; -import io.oduck.api.domain.anime.repository.AnimeRepository; -import io.oduck.api.domain.anime.repository.AnimeStudioRepository; -import io.oduck.api.domain.anime.repository.AnimeVoiceActorRepository; +import io.oduck.api.domain.anime.entity.*; +import io.oduck.api.domain.anime.repository.*; import io.oduck.api.domain.genre.entity.Genre; import io.oduck.api.domain.genre.repository.GenreRepository; import io.oduck.api.domain.originalAuthor.entity.OriginalAuthor; @@ -30,14 +16,15 @@ import io.oduck.api.domain.voiceActor.entity.VoiceActor; import io.oduck.api.domain.voiceActor.repository.VoiceActorRepository; import io.oduck.api.global.exception.NotFoundException; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor @Transactional @@ -228,8 +215,7 @@ public void updateSeries(Long animeId, PatchSeriesIdReq patchReq) { anime.update(series); } - @Transactional(readOnly = true) - public Anime findAnime(Long animeId) { + private Anime findAnime(Long animeId) { return animeRepository.findById(animeId).orElseThrow(() -> new NotFoundException("Anime")); } } From 1f631a60389cf66a86613c6e7b802163e8bd0715 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 22:15:02 +0900 Subject: [PATCH 328/734] =?UTF-8?q?feat:=20AnimeRepository=EC=9D=98=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=EC=97=90=EC=84=9C=EC=9D=98=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9,=20=EC=A1=B0=ED=9A=8C=EC=88=98=20=EC=A6=9D?= =?UTF-8?q?=EA=B0=80=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/repository/AnimeRepository.java | 5 +++++ .../io/oduck/api/domain/anime/service/AnimeServiceImpl.java | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java index ec192c9d..516459d7 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java @@ -2,9 +2,14 @@ import io.oduck.api.domain.anime.entity.Anime; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface AnimeRepository extends JpaRepository { + @Query("select a from Anime a where a.deletedAt = null and a.isReleased = true") + Optional findReleasedAnimeById(); } diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index 3615db42..ad77a806 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -51,7 +51,11 @@ public class AnimeServiceImpl implements AnimeService{ @Transactional(readOnly = true) public AnimeRes getAnimeById(Long animeId) { - Anime anime = findAnime(animeId); + Anime anime = animeRepository.findReleasedAnimeById() + .orElseThrow(() -> new NotFoundException("Anime")); + + anime.increaseViewCount(); + List animeOriginalAuthors = animeOriginalAuthorRepository.findAllFetchByAnimeId(animeId); List animeVoiceActors = animeVoiceActorRepository.findAllFetchByAnimeId(animeId); List animeStudios = animeStudioRepository.findAllFetchByAnimeId(animeId); From 99b80800a8a5e9686c16715123d84e49e3eef026 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 22:23:13 +0900 Subject: [PATCH 329/734] =?UTF-8?q?fix:=20=EB=A6=AC=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=9D=98=20animeId=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/repository/AnimeRepository.java | 7 ++++--- .../oduck/api/domain/anime/service/AnimeServiceImpl.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java index 516459d7..14421af2 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java @@ -1,5 +1,6 @@ package io.oduck.api.domain.anime.repository; +import io.lettuce.core.dynamic.annotation.Param; import io.oduck.api.domain.anime.entity.Anime; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -8,8 +9,8 @@ import java.util.Optional; @Repository -public interface AnimeRepository extends JpaRepository { +public interface AnimeRepository extends JpaRepository { - @Query("select a from Anime a where a.deletedAt = null and a.isReleased = true") - Optional findReleasedAnimeById(); + @Query("select a from Anime a where a.id = :animeId and a.deletedAt = null and a.isReleased = true") + Optional findReleasedAnimeById(@Param("animeId") Long animeId); } diff --git a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java index ad77a806..fcd90b05 100644 --- a/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/anime/service/AnimeServiceImpl.java @@ -51,7 +51,7 @@ public class AnimeServiceImpl implements AnimeService{ @Transactional(readOnly = true) public AnimeRes getAnimeById(Long animeId) { - Anime anime = animeRepository.findReleasedAnimeById() + Anime anime = animeRepository.findReleasedAnimeById(animeId) .orElseThrow(() -> new NotFoundException("Anime")); anime.increaseViewCount(); From 13c56cd744014df01418293e5887ada4c106a3fe Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Mon, 23 Oct 2023 22:23:22 +0900 Subject: [PATCH 330/734] =?UTF-8?q?fix:=20=EB=A6=AC=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=9D=98=20animeId=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oduck/api/unit/anime/service/AnimeServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java index b43308dd..60623a22 100644 --- a/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java +++ b/src/test/java/io/oduck/api/unit/anime/service/AnimeServiceTest.java @@ -96,7 +96,7 @@ void getAnimeById() { List animeGenres = new ArrayList<>(); given(animeGenreRepository.findAllFetchByAnimeId(animeId)).willReturn(animeGenres); - given(animeRepository.findById(animeId)).willReturn(Optional.ofNullable(anime)); + given(animeRepository.findReleasedAnimeById(animeId)).willReturn(Optional.ofNullable(anime)); //when animeService.getAnimeById(animeId); @@ -105,7 +105,7 @@ void getAnimeById() { assertThatNoException(); //verify - verify(animeRepository, times(1)).findById(anyLong()); + verify(animeRepository, times(1)).findReleasedAnimeById(anyLong()); verify(animeOriginalAuthorRepository, times(1)).findAllFetchByAnimeId(anyLong()); verify(animeVoiceActorRepository, times(1)).findAllFetchByAnimeId(anyLong()); verify(animeStudioRepository, times(1)).findAllFetchByAnimeId(anyLong()); From 45c1a34dbcaa7f64c5b8eb7949a639f9de2dd3a4 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 24 Oct 2023 00:02:06 +0900 Subject: [PATCH 331/734] =?UTF-8?q?refactor:=20Anime=20bookmarkCount=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/anime/repository/AnimeRepository.java | 12 +++++++++++- .../bookmark/service/BookmarkServiceImpl.java | 15 +++++++++++---- .../domain/member/service/MemberServiceImpl.java | 2 -- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java index ec192c9d..bef6e6c2 100644 --- a/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java +++ b/src/main/java/io/oduck/api/domain/anime/repository/AnimeRepository.java @@ -1,10 +1,20 @@ package io.oduck.api.domain.anime.repository; import io.oduck.api.domain.anime.entity.Anime; +import jakarta.persistence.LockModeType; +import jakarta.persistence.QueryHint; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface AnimeRepository extends JpaRepository { - + @Query("select a from Anime a where a.id = :id") + @Lock(LockModeType.PESSIMISTIC_WRITE) + @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value ="3000")}) + Optional findByIdForUpdate(@Param("id")Long id); } diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java index b0e1995d..051e4284 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java @@ -15,6 +15,7 @@ import io.oduck.api.global.common.OrderDirection; import io.oduck.api.global.common.SliceResponse; import io.oduck.api.global.exception.NotFoundException; +import jakarta.persistence.LockModeType; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -22,7 +23,9 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.jpa.repository.Lock; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @@ -33,26 +36,30 @@ public class BookmarkServiceImpl implements BookmarkService { private final AnimeRepository animeRepository; @Override + @Transactional + @Lock(LockModeType.PESSIMISTIC_READ) public boolean toggleBookmark(Long memberId, Long animeId) { - Optional optionalBookmark = getBookmark(memberId, animeId); + Optional optionalBookmark = getBookmark(27L, animeId); + + Anime anime = animeRepository.findByIdForUpdate(animeId) + .orElseThrow(() -> new NotFoundException("존재하지 않는 애니메이션입니다.")); if (optionalBookmark.isPresent()) { bookmarkRepository.delete(optionalBookmark.get()); + anime.decreaseBookmarkCount(); return false; } Member member = memberRepository.findById(memberId) .orElseThrow(() -> new NotFoundException("존재하지 않는 회원입니다.")); - Anime anime = animeRepository.findById(animeId) - .orElseThrow(() -> new NotFoundException("존재하지 않는 애니메이션입니다.")); - bookmarkRepository.save( Bookmark.builder() .member(member) .anime(anime) .build() ); + anime.increaseBookmarkCount(); return true; } diff --git a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java index 69c1da91..c4c45e48 100644 --- a/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/member/service/MemberServiceImpl.java @@ -128,8 +128,6 @@ public void updateProfile(PatchReq body, Long memberId) { .ifPresent( memberProfile::updateInfo ); - - memberProfileRepository.save(memberProfile); } private MemberProfile getProfileByMemberId(Long memberId) { From 06b4bc8151dea2e4a9a9864157f83454d0d18a95 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 24 Oct 2023 00:02:26 +0900 Subject: [PATCH 332/734] =?UTF-8?q?test:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8B=9C=20=EB=B0=98=ED=99=98=EA=B0=92?= =?UTF-8?q?=EC=97=90=20=EC=9E=85=EB=8D=95=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShortReviewControllerTest.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index f8623276..b8c17e82 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -204,15 +204,44 @@ void patchShortReview() throws Exception{ //then actions - .andExpect(status().isNoContent()) - .andDo(document("patchShortReviewReq/success", + .andExpect(status().isOk()) + .andExpect(jsonPath("$.drawing").exists()) + .andExpect(jsonPath("$.story").exists()) + .andExpect(jsonPath("$.music").exists()) + .andExpect(jsonPath("$.character").exists()) + .andExpect(jsonPath("$.voiceActor").exists()) + .andDo(document("PatchShortReviews/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( parameterWithName("reviewId") .description("리뷰 식별자") ), - requestFields(attributes(key("title").value("Fields for shortreview creation")), + responseFields( + fieldWithPath("drawing") + .type(JsonFieldType.BOOLEAN) + .description("그림 입덕포인트"), + fieldWithPath("story") + .type(JsonFieldType.BOOLEAN) + .description("그림 입덕포인트"), + fieldWithPath("music") + .type(JsonFieldType.BOOLEAN) + .description("음악 입덕포인트"), + fieldWithPath("character") + .type(JsonFieldType.BOOLEAN) + .description("캐릭터 입덕포인트"), + fieldWithPath("voiceActor") + .type(JsonFieldType.BOOLEAN) + .description("성우 입덕포인트")), + requestFields(attributes(key("title").value("Fields for shortReview creation")), + fieldWithPath("animeId") + .type(JsonFieldType.NUMBER) + .attributes(field("constraints", "숫자만 가능합니다.")) + .description("애니 식별자"), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "회원의 이름입니다.")) + .description("회원의 이름"), fieldWithPath("hasSpoiler") .type(JsonFieldType.BOOLEAN) .attributes(field("constraints", "true 또는 false.")) From 2c978ea1da12f620331242a22f1e390ca7770365 Mon Sep 17 00:00:00 2001 From: jaykayBaek Date: Tue, 24 Oct 2023 11:51:44 +0900 Subject: [PATCH 333/734] =?UTF-8?q?fix:=20Valid=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95=20#38?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/anime/dto/AnimeReq.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java index 0d3d16c8..895f32eb 100644 --- a/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java +++ b/src/main/java/io/oduck/api/domain/anime/dto/AnimeReq.java @@ -6,6 +6,7 @@ import io.oduck.api.domain.anime.entity.Status; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -19,33 +20,34 @@ public class AnimeReq { @Getter public static class PostReq { @NotBlank - @Length(min = 1, max = 50, - message = "글자 수는 0~50을 허용합니다.") + @Length(min = 1, max = 50, message = "글자 수는 0~50을 허용합니다.") private String title; @NotBlank - @Length(min = 1, max = 255, - message = "글자 수는 0~255를 허용합니다.") + @Length(min = 1, max = 255, message = "글자 수는 0~255를 허용합니다.") private String summary; + @NotNull private BroadcastType broadcastType; - @Min(value = 0, - message = "음수가 올 수 없습니다.") + @NotNull(message = "에피소드 수를 입력하세요.") + @Min(value = 0, message = "음수가 올 수 없습니다.") private int episodeCount; - @NotBlank( - message = "섬네일을 입력하세요.") + @NotBlank(message = "섬네일을 입력하세요.") private String thumbnail; - @Min(value = 1900, - message = "1900년대 이상을 입력하세요.") + @NotNull(message = "년도를 입력하세요.") + @Min(value = 1900, message = "1900년대 이상을 입력하세요.") private int year; + @NotNull(message = "분기를 입력하세요.") private Quarter quarter; + @NotNull(message = "심의를 입력하세요.") private Rating rating; + @NotNull(message = "방영 상태를 입력하세요.") private Status status; private List originalAuthorIds; @@ -105,33 +107,34 @@ public PostReq (String title, String summary, BroadcastType broadcastType, @AllArgsConstructor public static class PatchAnimeReq{ @NotBlank - @Length(min = 1, max = 50, - message = "글자 수는 0~50을 허용합니다.") + @Length(min = 1, max = 50, message = "글자 수는 0~50을 허용합니다.") private String title; @NotBlank - @Length(min = 1, max = 255, - message = "글자 수는 0~255를 허용합니다.") + @Length(min = 1, max = 255, message = "글자 수는 0~255를 허용합니다.") private String summary; + @NotNull private BroadcastType broadcastType; - @Min(value = 0, - message = "음수가 올 수 없습니다.") + @NotNull(message = "에피소드 수를 입력하세요.") + @Min(value = 0, message = "음수가 올 수 없습니다.") private int episodeCount; - @NotBlank( - message = "섬네일을 입력하세요.") + @NotBlank(message = "섬네일을 입력하세요.") private String thumbnail; - @Min(value = 1900, - message = "1900년대 이상을 입력하세요.") + @NotNull(message = "년도를 입력하세요.") + @Min(value = 1900, message = "1900년대 이상을 입력하세요.") private int year; + @NotNull(message = "분기를 입력하세요.") private Quarter quarter; + @NotNull(message = "심의를 입력하세요.") private Rating rating; + @NotNull(message = "방영 상태를 입력하세요.") private Status status; } From b2926f0e30650b99ebec16b3acacc6762c9aa827 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 24 Oct 2023 14:45:02 +0900 Subject: [PATCH 334/734] =?UTF-8?q?fix:=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=B0=8F=20=EA=B3=A0=EC=A0=95=20memberId?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20#45?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/domain/bookmark/service/BookmarkServiceImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java index 051e4284..9d256ca9 100644 --- a/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/bookmark/service/BookmarkServiceImpl.java @@ -37,9 +37,8 @@ public class BookmarkServiceImpl implements BookmarkService { @Override @Transactional - @Lock(LockModeType.PESSIMISTIC_READ) public boolean toggleBookmark(Long memberId, Long animeId) { - Optional optionalBookmark = getBookmark(27L, animeId); + Optional optionalBookmark = getBookmark(memberId, animeId); Anime anime = animeRepository.findByIdForUpdate(animeId) .orElseThrow(() -> new NotFoundException("존재하지 않는 애니메이션입니다.")); From c919b9bfa64ce12b60455cb282014feb27d635c5 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 24 Oct 2023 14:45:23 +0900 Subject: [PATCH 335/734] =?UTF-8?q?test:=20findByIdForUpdate=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20#45?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oduck/api/unit/bookmark/service/BookmarkServiceTest.java | 5 ++++- .../io/oduck/api/unit/member/service/MemberServiceTest.java | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java index daad0b7c..58194b02 100644 --- a/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java +++ b/src/test/java/io/oduck/api/unit/bookmark/service/BookmarkServiceTest.java @@ -76,7 +76,8 @@ class ToggleBookmark { @Test void toggleBookmarkSaveSuccess() { given(memberRepository.findById(anyLong())).willReturn(Optional.of(member)); - given(animeRepository.findById(anyLong())).willReturn(Optional.of(anime)); + given(animeRepository.findByIdForUpdate(anyLong())) + .willReturn(Optional.of(anime)); given(bookmarkRepository.findByMemberIdAndAnimeId(anyLong(), anyLong())) .willReturn(Optional.empty()); given(bookmarkRepository.save(any(Bookmark.class))).willReturn(bookmark); @@ -92,6 +93,8 @@ void toggleBookmarkSaveSuccess() { void toggleBookmarkDeleteSuccess() { given(bookmarkRepository.findByMemberIdAndAnimeId(anyLong(), anyLong())) .willReturn(Optional.of(bookmark)); + given(animeRepository.findByIdForUpdate(anyLong())) + .willReturn(Optional.of(anime)); doNothing().when(bookmarkRepository).delete(any(Bookmark.class)); boolean result = bookmarkService.toggleBookmark(member.getId(), anime.getId()); diff --git a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java index 137d3af5..7f45fa9f 100644 --- a/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java +++ b/src/test/java/io/oduck/api/unit/member/service/MemberServiceTest.java @@ -134,7 +134,6 @@ void updateProfileSuccess() { given(memberProfileRepository.findByMemberId(anyLong())).willReturn(Optional.ofNullable(memberProfile)); given(memberProfileRepository.existsByName(anyString())).willReturn(false); - given(memberProfileRepository.save(any(MemberProfile.class))).willReturn(updatedMemberProfile); // when // then From a7b786418a950dc46526967e1c9ba20a07063ed1 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 24 Oct 2023 14:52:04 +0900 Subject: [PATCH 336/734] =?UTF-8?q?chore:=20application-example=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#45?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- src/main/resources/application-example.yml | 97 ++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/application-example.yml diff --git a/.gitignore b/.gitignore index 8fce7672..59018f73 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,10 @@ build/ *.gz *.tmp src/main/resources/static/docs/index.html -application-*.yml +application-prod.yml +application-dev.yml +application-local.yml +application-test.yml ### STS ### .apt_generated diff --git a/src/main/resources/application-example.yml b/src/main/resources/application-example.yml new file mode 100644 index 00000000..a7934b82 --- /dev/null +++ b/src/main/resources/application-example.yml @@ -0,0 +1,97 @@ +server: + port: 8000 + servlet: + contextPath: + session: + cookie: + path: + name: + domain: + http-only: + secure: + timeout: + +spring: + config: + activate: + on-profile: + h2: + console: + enabled: true + path: /h2 + jpa: + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + show_sql: true + default_batch_fetch_size: + datasource: + driver-class-name: + url: + username: + password: + data: + redis: + host: + port: 6379 + session: + store-type: redis + security: + basic: + enabled: false + oauth2: + client: + registration: + google: + client-id: + client-secret: + scope: profile, email + redirect-uri: + + naver: + client-id: + client-secret: + redirect-uri: + authorization-grant-type: + scope: email + client-name: Naver + + kakao: + client-id: + client-secret: + redirect-uri: + client-authentication-method: client_secret_post + authorization-grant-type: authorization_code + scope: account_email + client-name: Kakao + + provider: + naver: + authorization_uri: + token_uri: + user-info-uri: https://openapi.naver.com/v1/nid/me + user_name_attribute: response + + kakao: + authorization-uri: + token-uri: + user-info-uri: + user-name-attribute: id + +logging: + level: + root: INFO + file: + path: + name: + logback: + rollingpolicy: + max-file-size: + max-history: 30 #default 7 + file-name-pattern: + +config: + base: + url: From 1fc703169ddc7291d1be3ef27d14b43d05a9889d Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 24 Oct 2023 15:33:30 +0900 Subject: [PATCH 337/734] =?UTF-8?q?test:=20req=20header=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#47?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../e2e/bookmark/BookmarkControllerTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java b/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java index 80561cdf..b5ca56f8 100644 --- a/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/bookmark/BookmarkControllerTest.java @@ -1,5 +1,8 @@ package io.oduck.api.e2e.bookmark; +import static io.oduck.api.global.config.RestDocsConfig.field; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; @@ -30,6 +33,7 @@ import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.test.context.ActiveProfiles; @@ -73,6 +77,7 @@ void toggleBookmarkSaveSuccess() throws Exception { post(BASE_URL) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") .content(content) ); @@ -83,6 +88,11 @@ void toggleBookmarkSaveSuccess() throws Exception { document("bookmark/toggleBookmark/save/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .description("Header Cookie, 세션 쿠키") + ), requestFields( attributes(key("title") .value("Fields for bookmark creation")), @@ -113,6 +123,7 @@ void toggleBookmarkDeleteSuccess() throws Exception { post(BASE_URL) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") .content(content) ); @@ -123,6 +134,11 @@ void toggleBookmarkDeleteSuccess() throws Exception { document("bookmark/toggleBookmark/delete/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .description("Header Cookie, 세션 쿠키") + ), requestFields( attributes(key("title") .value("Fields for bookmark creation")), @@ -151,6 +167,7 @@ void checkBookmarkedExist() throws Exception { get(BASE_URL + "/{animeId}", animeId) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") ); // then @@ -164,6 +181,11 @@ void checkBookmarkedExist() throws Exception { parameterWithName("animeId") .description("애니메 id") ), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .description("Header Cookie, 세션 쿠키") + ), responseFields( attributes(key("title") .value("Fields for bookmark creation")), @@ -186,6 +208,7 @@ void checkBookmarkedNotExist() throws Exception { get(BASE_URL + "/{animeId}", animeId) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.COOKIE, "oDuckio.sid={SESSION_VALUE}") ); // then @@ -199,6 +222,12 @@ void checkBookmarkedNotExist() throws Exception { parameterWithName("animeId") .description("애니메 id") ), + requestHeaders( + headerWithName(HttpHeaders.COOKIE) + .attributes(field("constraints", "oDuckio.sid={SESSION_VALUE}")) + .optional() + .description("Header Cookie, 세션 쿠키") + ), responseFields( attributes(key("title") .value("Fields for bookmark creation")), From 3c02d1d9c545d8fb8ded8af9a2c5d88585557495 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Tue, 24 Oct 2023 15:33:42 +0900 Subject: [PATCH 338/734] =?UTF-8?q?docs:=20bookmark=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#47?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index cb1148c5..be442038 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -210,6 +210,57 @@ include::{snippets}/getAnimeById/success/response-body.adoc[] include::{snippets}/getAnimeById/success/response-fields.adoc[] ==== 실패 시 +== bookmark +=== POST api/v1/bookmarks +.curl-request +include::{snippets}/bookmark/toggleBookmark/save/success/curl-request.adoc[] + +.http-request +include::{snippets}/bookmark/toggleBookmark/save/success/http-request.adoc[] + +.request-header +include::{snippets}/bookmark/toggleBookmark/save/success/request-headers.adoc[] + +.request-body +include::{snippets}/bookmark/toggleBookmark/save/success/request-body.adoc[] + +.request-fields +include::{snippets}/bookmark/toggleBookmark/save/success/request-fields.adoc[] + +==== 저장 성공시 +.http-response +include::{snippets}/bookmark/toggleBookmark/save/success/http-response.adoc[] + +==== 삭제 성공시 +.http-response +include::{snippets}/bookmark/toggleBookmark/delete/success/http-response.adoc[] + +=== GET api/v1/bookmarks/:animeId +.curl-request +include::{snippets}/bookmark/checkBookmarked/exist/curl-request.adoc[] + +.http-request +include::{snippets}/bookmark/checkBookmarked/exist/http-request.adoc[] + +.path-parameters +include::{snippets}/bookmark/checkBookmarked/exist/path-parameters.adoc[] + +.request-header +include::{snippets}/bookmark/checkBookmarked/exist/request-headers.adoc[] + +==== 북마크가 존재할 경우 +.http-response +include::{snippets}/bookmark/checkBookmarked/exist/http-response.adoc[] + +.response-body +include::{snippets}/bookmark/checkBookmarked/exist/response-body.adoc[] + +.response-fields +include::{snippets}/bookmark/checkBookmarked/exist/response-fields.adoc[] + +==== 북마크가 존재하지 않을 경우 +.http-response +include::{snippets}/bookmark/checkBookmarked/notExist/http-response.adoc[] == shortReview From 677c438fb530e3afefb45009161a3e054e2aad84 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 24 Oct 2023 23:32:59 +0900 Subject: [PATCH 339/734] =?UTF-8?q?feat:=20=EB=A6=AC=EB=B7=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EB=B0=98=ED=99=98=ED=95=A0=20=EC=9E=85?= =?UTF-8?q?=EB=8D=95=ED=8F=AC=EC=9D=B8=ED=8A=B8=20ResDto=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/AttractionPointResDto.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/attractionPoint/dto/AttractionPointResDto.java diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/dto/AttractionPointResDto.java b/src/main/java/io/oduck/api/domain/attractionPoint/dto/AttractionPointResDto.java new file mode 100644 index 00000000..4b12c34a --- /dev/null +++ b/src/main/java/io/oduck/api/domain/attractionPoint/dto/AttractionPointResDto.java @@ -0,0 +1,19 @@ +package io.oduck.api.domain.attractionPoint.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class AttractionPointResDto { + @Builder + @Getter + public static class IsAttractionPoint{ + private boolean drawing; + private boolean story; + private boolean music; + private boolean character; + private boolean voiceActor; + } + +} From 741604f04bbc64485433a177beef885230ae65c7 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 24 Oct 2023 23:37:31 +0900 Subject: [PATCH 340/734] =?UTF-8?q?feat:=20=EC=9E=85=EB=8D=95=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/AttractionPoint.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java index 6de3d3b5..16bf038c 100644 --- a/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java +++ b/src/main/java/io/oduck/api/domain/attractionPoint/entity/AttractionPoint.java @@ -12,6 +12,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -33,4 +34,23 @@ public class AttractionPoint extends BaseEntity { @Enumerated(EnumType.STRING) private AttractionElement attractionElement; + + @Builder + public AttractionPoint(Long id, Member member, Anime anime, AttractionElement attractionElement) { + this.id = id; + this.member = member; + this.anime = anime; + this.attractionElement = attractionElement; + } + + public void relateMember(Member member){ + this.member = member; + } + public void relateAnime(Anime anime){ + this.anime = anime; + } + + public void updateElement(AttractionElement attractionElement){ + this.attractionElement = attractionElement; + } } From 9055c8652c9247561102d154fc7572c93f900ee3 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 24 Oct 2023 23:38:18 +0900 Subject: [PATCH 341/734] =?UTF-8?q?feat:=20=EC=9E=85=EB=8D=95=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20repository=20=EC=B6=94=EA=B0=80=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/AttractionElementRepository.java | 17 +++++++++++++++++ .../repository/AttractionPointRepository.java | 10 ++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/attractionPoint/repository/AttractionElementRepository.java create mode 100644 src/main/java/io/oduck/api/domain/attractionPoint/repository/AttractionPointRepository.java diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/repository/AttractionElementRepository.java b/src/main/java/io/oduck/api/domain/attractionPoint/repository/AttractionElementRepository.java new file mode 100644 index 00000000..2635efc8 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/attractionPoint/repository/AttractionElementRepository.java @@ -0,0 +1,17 @@ +package io.oduck.api.domain.attractionPoint.repository; + +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.attractionPoint.entity.AttractionElement; +import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; +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 AttractionElementRepository extends JpaRepository { + + @Query("select ap from AttractionPoint ap " + + "where ap.anime.id = :animeId " + + "and ap.member.id = :memberId") + List findAllByAnimeId_memberId(@Param("memberId") Long memberId, @Param("animeId") Long animeId); +} diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/repository/AttractionPointRepository.java b/src/main/java/io/oduck/api/domain/attractionPoint/repository/AttractionPointRepository.java new file mode 100644 index 00000000..238ba0ce --- /dev/null +++ b/src/main/java/io/oduck/api/domain/attractionPoint/repository/AttractionPointRepository.java @@ -0,0 +1,10 @@ +package io.oduck.api.domain.attractionPoint.repository; + +import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AttractionPointRepository extends JpaRepository{ + +} From 4cfc9a700e63a6ba7d1899700fc0d17537126fb8 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 24 Oct 2023 23:38:58 +0900 Subject: [PATCH 342/734] =?UTF-8?q?feat:=20=EC=9E=85=EB=8D=95=20=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20service=20=EC=B6=94=EA=B0=80=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AttractionPointService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/attractionPoint/service/AttractionPointService.java diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/service/AttractionPointService.java b/src/main/java/io/oduck/api/domain/attractionPoint/service/AttractionPointService.java new file mode 100644 index 00000000..1ef6001d --- /dev/null +++ b/src/main/java/io/oduck/api/domain/attractionPoint/service/AttractionPointService.java @@ -0,0 +1,11 @@ +package io.oduck.api.domain.attractionPoint.service; + +import io.oduck.api.domain.attractionPoint.dto.AttractionPointResDto.IsAttractionPoint; +import org.springframework.stereotype.Service; + +@Service +public interface AttractionPointService { + + //입덕포인트 조회(true/false) + IsAttractionPoint isAttractionPoint(Long memberId, Long animeId); +} From ea05f407217ea4318aa87493ca2b18cb09d73023 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 24 Oct 2023 23:40:31 +0900 Subject: [PATCH 343/734] =?UTF-8?q?refactor:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8B=9C=20=EC=9E=85=EB=8D=95=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=A1=B4=EC=9E=AC=20=EC=9C=A0=EB=AC=B4=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ShortReviewController.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java index 0056e6b1..4ad921b0 100644 --- a/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java +++ b/src/main/java/io/oduck/api/domain/review/controller/ShortReviewController.java @@ -1,5 +1,7 @@ package io.oduck.api.domain.review.controller; +import io.oduck.api.domain.attractionPoint.dto.AttractionPointResDto.IsAttractionPoint; +import io.oduck.api.domain.attractionPoint.service.AttractionPointService; import io.oduck.api.domain.review.dto.ShortReviewReqDto; import io.oduck.api.domain.review.dto.ShortReviewResDto; import io.oduck.api.domain.review.service.ShortReviewService; @@ -22,10 +24,10 @@ @RequestMapping("/short-reviews") @RestController @RequiredArgsConstructor - public class ShortReviewController { private final ShortReviewService shortReviewService; + private final AttractionPointService attractionPointService; //애니의 짧은 리뷰 조회 @GetMapping("/{animeId}") @@ -46,9 +48,13 @@ public ResponseEntity postShortReview( @PatchMapping("/{reviewId}") public ResponseEntity patchShortReview( - @PathVariable Long reviewId, @RequestBody @Valid ShortReviewReqDto.PatchShortReviewReq req) { + @LoginUser AuthLocal user, + @PathVariable Long reviewId, + @RequestBody @Valid ShortReviewReqDto.PatchShortReviewReq req) { //TODO : 짧은 리뷰 수정 shortReviewService.update(reviewId, req); - return ResponseEntity.noContent().build(); + //입덕포인트 반환 + IsAttractionPoint attractionPointRes = attractionPointService.isAttractionPoint(user.getId(), req.getAnimeId()); + return ResponseEntity.ok(attractionPointRes); } } \ No newline at end of file From f080d4b73dc425f51156d9639e37ddc656eb09b1 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 24 Oct 2023 23:42:24 +0900 Subject: [PATCH 344/734] =?UTF-8?q?feat:=20=EC=9E=85=EB=8D=95=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20ServiceStub=20=EC=B6=94=EA=B0=80=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AttractionPointServiceStub.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/main/java/io/oduck/api/domain/attractionPoint/service/AttractionPointServiceStub.java diff --git a/src/main/java/io/oduck/api/domain/attractionPoint/service/AttractionPointServiceStub.java b/src/main/java/io/oduck/api/domain/attractionPoint/service/AttractionPointServiceStub.java new file mode 100644 index 00000000..64520b23 --- /dev/null +++ b/src/main/java/io/oduck/api/domain/attractionPoint/service/AttractionPointServiceStub.java @@ -0,0 +1,48 @@ +package io.oduck.api.domain.attractionPoint.service; + +import io.oduck.api.domain.attractionPoint.dto.AttractionPointResDto.IsAttractionPoint; +import io.oduck.api.domain.attractionPoint.repository.AttractionElementRepository; +import io.oduck.api.domain.attractionPoint.repository.AttractionPointRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AttractionPointServiceStub implements AttractionPointService { + + private final AttractionPointRepository attractionPointRepository; + private final AttractionElementRepository attractionElementRepository; + @Override + public IsAttractionPoint isAttractionPoint(Long memberId, Long animeId) { +// boolean drawing = false; +// boolean story = false; +// boolean music = false; +// boolean character = false; +// boolean voiceActor = false; +// +// List points = attractionElementRepository.findAllByAnimeId_memberId(memberId, animeId); +// for (AttractionPoint point : points) { +// switch (point.getAttractionElement()) { +// case DRAWING -> drawing = true; +// case MUSIC -> music = true; +// case STORY -> story = true; +// case CHARACTER -> character = true; +// case VOICE_ACTOR -> voiceActor = true; +// } +// } + return createAttractionPoint(); + } + + private IsAttractionPoint createAttractionPoint(){ + return IsAttractionPoint + .builder() + .drawing(true) + .story(true) + .music(false) + .character(false) + .voiceActor(false) + .build(); + } +} From 95dc7f03644e032e456407b8c61771a8badcab69 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Tue, 24 Oct 2023 23:43:12 +0900 Subject: [PATCH 345/734] =?UTF-8?q?test:=20=EC=9E=85=EB=8D=95=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20Service,=20Repository=20=20Test=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AttractionPointRepositoryTest.java | 119 ++++++++++++++++++ .../service/AttractionPointServiceTest.java | 37 ++++++ 2 files changed, 156 insertions(+) create mode 100644 src/test/java/io/oduck/api/unit/attractionPoint/repository/AttractionPointRepositoryTest.java create mode 100644 src/test/java/io/oduck/api/unit/attractionPoint/service/AttractionPointServiceTest.java diff --git a/src/test/java/io/oduck/api/unit/attractionPoint/repository/AttractionPointRepositoryTest.java b/src/test/java/io/oduck/api/unit/attractionPoint/repository/AttractionPointRepositoryTest.java new file mode 100644 index 00000000..e9dcccae --- /dev/null +++ b/src/test/java/io/oduck/api/unit/attractionPoint/repository/AttractionPointRepositoryTest.java @@ -0,0 +1,119 @@ +package io.oduck.api.unit.attractionPoint.repository; + +import static io.oduck.api.global.utils.AnimeTestUtils.getBroadcastType; +import static io.oduck.api.global.utils.AnimeTestUtils.getEpisodeCount; +import static io.oduck.api.global.utils.AnimeTestUtils.getQuarter; +import static io.oduck.api.global.utils.AnimeTestUtils.getRating; +import static io.oduck.api.global.utils.AnimeTestUtils.getStatus; +import static io.oduck.api.global.utils.AnimeTestUtils.getSummary; +import static io.oduck.api.global.utils.AnimeTestUtils.getThumbnail; +import static io.oduck.api.global.utils.AnimeTestUtils.getTitle; +import static io.oduck.api.global.utils.AnimeTestUtils.getYear; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.oduck.api.domain.anime.entity.Anime; +import io.oduck.api.domain.anime.entity.AnimeGenre; +import io.oduck.api.domain.anime.entity.AnimeOriginalAuthor; +import io.oduck.api.domain.anime.entity.AnimeStudio; +import io.oduck.api.domain.anime.entity.AnimeVoiceActor; +import io.oduck.api.domain.anime.repository.AnimeRepository; +import io.oduck.api.domain.attractionPoint.entity.AttractionElement; +import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; +import io.oduck.api.domain.attractionPoint.repository.AttractionElementRepository; +import io.oduck.api.domain.attractionPoint.repository.AttractionPointRepository; +import io.oduck.api.domain.member.entity.Member; +import java.util.ArrayList; +import java.util.List; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +public class AttractionPointRepositoryTest { + + @Autowired + private AttractionElementRepository elementRepository; + + @Autowired + private AttractionPointRepository attractionPointRepository; + + @Autowired + private AnimeRepository animeRepository; + + + @Nested + @DisplayName("애니 입덕 포인트 조회") + class isAttractionPoint{ + + @Test + @DisplayName("입덕포인트 조회 성공") + void isAttactionPoint(){ + //given + //회원 생성 + Long memberId = 1L; + Long pointId1 = 1L; + Long pointId2 = 2L; + Member member = Member.builder() + .id(memberId) + .build(); + + // 애니 생성 + List animeStudios = new ArrayList<>(); + List animeVoiceActors = new ArrayList<>(); + List animeGenres = new ArrayList<>(); + List animeOriginalAuthors = new ArrayList<>(); + + Anime createAnime = Anime.createAnime( + getTitle(), getSummary(), getBroadcastType(), getEpisodeCount(), getThumbnail(), + getYear(), getQuarter(), getRating(), getStatus(), animeOriginalAuthors, + animeStudios, animeVoiceActors, animeGenres, null + ); + + Anime anime = animeRepository.saveAndFlush(createAnime); + Long animeId = anime.getId(); + + //입덕포인트 등록 + AttractionPoint drawing = AttractionPoint + .builder() + .id(pointId1) + .member(member) + .anime(anime) + .attractionElement(AttractionElement.DRAWING) + .build(); + AttractionPoint story = AttractionPoint + .builder() + .id(pointId2) + .member(member) + .anime(anime) + .attractionElement(AttractionElement.STORY) + .build(); + + AttractionPoint saveDrawing = attractionPointRepository.save(drawing); + AttractionPoint saveStory = attractionPointRepository.save(story); + + //when + //입덕포인트 조회 + List res = elementRepository.findAllByAnimeId_memberId(memberId, animeId); + AttractionElement findDrawing = res.get(0).getAttractionElement(); + AttractionElement findStory = res.get(1).getAttractionElement(); + + //then + assertNotNull(res); + assertThat(res.get(0).getMember().getId()).isEqualTo(memberId); + assertThat(res.get(0).getAnime().getId()).isEqualTo(animeId); + assertThat(findDrawing).isEqualTo(saveDrawing.getAttractionElement()); + assertThat(findStory).isEqualTo(saveStory.getAttractionElement()); + } + } + + + +} + diff --git a/src/test/java/io/oduck/api/unit/attractionPoint/service/AttractionPointServiceTest.java b/src/test/java/io/oduck/api/unit/attractionPoint/service/AttractionPointServiceTest.java new file mode 100644 index 00000000..1452b9c5 --- /dev/null +++ b/src/test/java/io/oduck/api/unit/attractionPoint/service/AttractionPointServiceTest.java @@ -0,0 +1,37 @@ +package io.oduck.api.unit.attractionPoint.service; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import io.oduck.api.domain.attractionPoint.dto.AttractionPointResDto.IsAttractionPoint; +import io.oduck.api.domain.attractionPoint.service.AttractionPointServiceStub; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class AttractionPointServiceTest { + @InjectMocks + private AttractionPointServiceStub attractionPointServiceStub; + + @Nested + @DisplayName("입덕 포인트 조회") + class getAttractionPoint{ + + @Test + @DisplayName("입덕 포인트 존재 시 true, false 판별") + void isAttractionPoint(){ + //given + Long memberId = 1L; + Long animeId = 1L; + + //when + IsAttractionPoint res = attractionPointServiceStub.isAttractionPoint(memberId, animeId); + + //then + assertDoesNotThrow(() -> attractionPointServiceStub.isAttractionPoint(memberId, animeId)); + } + } +} From c5b848319d5c03613e67e7342e3dd719a712eda9 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Wed, 25 Oct 2023 00:04:33 +0900 Subject: [PATCH 346/734] =?UTF-8?q?refacor:=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=8B=9C=20=ED=9A=8C=EC=9B=90=20=EC=9D=B4=EB=A6=84,=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=20=EC=95=84=EC=9D=B4=EB=94=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java | 2 ++ .../java/io/oduck/api/global/utils/ShortReviewTestUtils.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java index 9ec1ddf4..718bebc8 100644 --- a/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java +++ b/src/main/java/io/oduck/api/domain/review/dto/ShortReviewReqDto.java @@ -28,6 +28,8 @@ public static class PostShortReviewReq{ @AllArgsConstructor @Builder public static class PatchShortReviewReq{ + private Long animeId; + private String name; private boolean hasSpoiler; @NotBlank @Length(min = 10, max = 100, diff --git a/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java b/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java index 9d5e8265..b2146f1b 100644 --- a/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java +++ b/src/test/java/io/oduck/api/global/utils/ShortReviewTestUtils.java @@ -14,7 +14,7 @@ public static PostShortReviewReq createPostShoreReviewReq(){ public static PatchShortReviewReq createPatchShortReview(){ return new PatchShortReviewReq( - isHasSpoiler(),getContent() + getAnimeId(), getName(), isHasSpoiler(),getContent() ); } From 9987d868e000f07e9a300d5f01392940adfd9d9b Mon Sep 17 00:00:00 2001 From: hanyMK Date: Wed, 25 Oct 2023 00:22:37 +0900 Subject: [PATCH 347/734] =?UTF-8?q?refacor:=20stub=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20Stub=20->=20Impl=20=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/service/ShortReviewServiceStub.java | 15 ++++++++++++++- .../service/ShortReviewServiceTest.java | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java index 8143fd54..f256dcb6 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceStub.java @@ -1,5 +1,8 @@ //package io.oduck.api.domain.review.service; // +//import io.oduck.api.domain.attractionPoint.dto.AttractionPointResDto.AttractionPointRes; +//import io.oduck.api.domain.attractionPoint.entity.AttractionPoint; +//import io.oduck.api.domain.attractionPoint.repository.AttractionPointRepository; //import io.oduck.api.domain.review.dto.ShortReviewReqDto.PatchShortReviewReq; //import io.oduck.api.domain.review.dto.ShortReviewReqDto.PostShortReviewReq; //import io.oduck.api.domain.review.dto.ShortReviewResDto; @@ -15,6 +18,8 @@ //@Service //public class ShortReviewServiceStub implements ShortReviewService{ // +// +// // @Override // public ShortReviewResDto getShortReviews(Long animeId) { // List shortReviewList = new ArrayList<>(); @@ -34,13 +39,21 @@ // } // // @Override -// public void save(PostShortReviewReq shortReviewReq) { +// public void save(Long memberId, PostShortReviewReq shortReviewReq) { // // } // +// // @Override // public void update(Long reviewId, PatchShortReviewReq req) { // +// +// +// } +// +// @Override +// public ShortReviewResDto isAttractionPoint(Long memberId, Long animeId) { +// return null; // } // // private ShortReview createReview(Long animeId){ diff --git a/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java b/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java index e75910f0..cf1f0454 100644 --- a/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java +++ b/src/test/java/io/oduck/api/unit/shortReview/service/ShortReviewServiceTest.java @@ -1,7 +1,7 @@ package io.oduck.api.unit.shortReview.service; import io.oduck.api.domain.review.dto.ShortReviewResDto; -import io.oduck.api.domain.review.service.ShortReviewServiceStub; +import io.oduck.api.domain.review.service.ShortReviewServiceImpl; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -16,7 +16,7 @@ public class ShortReviewServiceTest { @InjectMocks - private ShortReviewServiceStub shortReviewService; + private ShortReviewServiceImpl shortReviewService; @Nested @DisplayName("짧은 리뷰 조회") From 3a9e934170d40dd9fc13f2fa7e4e9bc48872b2bc Mon Sep 17 00:00:00 2001 From: hanyMK Date: Wed, 25 Oct 2023 00:36:14 +0900 Subject: [PATCH 348/734] =?UTF-8?q?refacor:=20=EC=95=A0=EB=8B=88=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC,?= =?UTF-8?q?=20=EB=A6=AC=EB=B7=B0=20=EC=9E=91=EC=84=B1=20=EC=8B=9C=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=20=EB=A6=AC=EB=B7=B0=20=EC=B9=B4=EC=9A=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=A6=9D=EA=B0=80=20=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/service/ShortReviewServiceImpl.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceImpl.java b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceImpl.java index c2be6f3d..4286122f 100644 --- a/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceImpl.java +++ b/src/main/java/io/oduck/api/domain/review/service/ShortReviewServiceImpl.java @@ -39,12 +39,25 @@ public void save(Long memberId, PostShortReviewReq shortReviewReq) { .build(); //애니 입력 - shortReview.relateAnime(getAnime(shortReviewReq.getAnimeId())); + Anime anime = getAnime(shortReviewReq.getAnimeId()); + shortReview.relateAnime(anime); //회원 입력 shortReview.relateMember(getMember(memberId)); ShortReview saveShortReview = shortReviewRepository.save(shortReview); + + Optional + .ofNullable(saveShortReview.getContent()) + .ifPresent( + content ->{ + if(saveShortReview.getContent().equals(shortReview.getContent())){ + throw new BadRequestException("Failed to saveFail."); + } + //리뷰 작성 성공시 애니 리뷰카운트 증가 + anime.increaseReviewCount(); + } + ); log.info("ShortReview Crated! {}", saveShortReview.getId()); } From 3ced1a8e99ba545a1862d4e14717f0bdf80fbd0d Mon Sep 17 00:00:00 2001 From: hanyMK Date: Wed, 25 Oct 2023 00:36:57 +0900 Subject: [PATCH 349/734] =?UTF-8?q?cleanup:=20=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20import=20=EC=A0=9C=EA=B1=B0=20=20#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unit/shortReview/repository/ShortReviewRepositoryTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/io/oduck/api/unit/shortReview/repository/ShortReviewRepositoryTest.java b/src/test/java/io/oduck/api/unit/shortReview/repository/ShortReviewRepositoryTest.java index bdb2c416..3769b1f3 100644 --- a/src/test/java/io/oduck/api/unit/shortReview/repository/ShortReviewRepositoryTest.java +++ b/src/test/java/io/oduck/api/unit/shortReview/repository/ShortReviewRepositoryTest.java @@ -21,7 +21,6 @@ import io.oduck.api.domain.anime.entity.AnimeVoiceActor; import io.oduck.api.domain.anime.repository.AnimeRepository; import io.oduck.api.domain.member.entity.Member; -import io.oduck.api.domain.member.entity.MemberProfile; import io.oduck.api.domain.member.repository.MemberRepository; import io.oduck.api.domain.review.entity.ShortReview; import io.oduck.api.domain.review.repository.ShortReviewRepository; From 11d95105c13d81029bb3c79a0d2e2ad6c90f8242 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Wed, 25 Oct 2023 15:44:20 +0900 Subject: [PATCH 350/734] =?UTF-8?q?test:=20=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?#33=20PatchShortReviews=20->=20patchShortReview=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD,=20memberId=20->=20name=EC=9C=BC=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 --- .../shortReview/ShortReviewControllerTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java index b8c17e82..ccb5746c 100644 --- a/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java +++ b/src/test/java/io/oduck/api/e2e/shortReview/ShortReviewControllerTest.java @@ -160,14 +160,15 @@ void postShortReview() throws Exception{ preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestFields(attributes(key("title").value("Fields for shortReview creation")), - fieldWithPath("memberId") - .type(JsonFieldType.NUMBER) - .attributes(field("constraints", "숫자만 가능합니다.")) - .description("리뷰를 등록할 회원의 고유 식별 번호"), + fieldWithPath("animeId") .type(JsonFieldType.NUMBER) .attributes(field("constraints", "숫자만 가능합니다.")) .description("리뷰를 등록할 애니 고유 식별 번호"), + fieldWithPath("name") + .type(JsonFieldType.STRING) + .attributes(field("constraints", "String만 가능합니다")) + .description("리뷰를 등록할 회원의 이름"), fieldWithPath("hasSpoiler") .type(JsonFieldType.BOOLEAN) .attributes(field("constraints", "true 또는 false.")) @@ -186,7 +187,7 @@ void postShortReview() throws Exception{ @DisplayName("짧은 리뷰 수정") class PatchShortReview{ - @DisplayName("짧은 리뷰 수정 성공시 Http Status 204 반환") + @DisplayName("짧은 리뷰 수정 성공시 Http Status 200 반환") @Test void patchShortReview() throws Exception{ //given @@ -210,13 +211,12 @@ void patchShortReview() throws Exception{ .andExpect(jsonPath("$.music").exists()) .andExpect(jsonPath("$.character").exists()) .andExpect(jsonPath("$.voiceActor").exists()) - .andDo(document("PatchShortReviews/success", + .andDo(document("patchShortReview/success", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( parameterWithName("reviewId") - .description("리뷰 식별자") - ), + .description("리뷰 식별자")), responseFields( fieldWithPath("drawing") .type(JsonFieldType.BOOLEAN) From ff5c857902e44be316b3b3a54d54a13762c93b09 Mon Sep 17 00:00:00 2001 From: hanyMK Date: Wed, 25 Oct 2023 15:45:28 +0900 Subject: [PATCH 351/734] =?UTF-8?q?test:=20=20PatchShortReviews=20->=20Pat?= =?UTF-8?q?chShortReviews=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=88=98=EC=A0=95=20=EC=8B=9C=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=EA=B0=92=20=EC=B6=94=EA=B0=80#33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index e4ce29e9..d5e1aa7a 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -189,23 +189,29 @@ include::{snippets}/postShortReview/success/http-response.adoc[] === PATCH api/v1/short-reviews/1 .curl-request -include::{snippets}/patchShortReviewReq/success/curl-request.adoc[] +include::{snippets}/patchShortReview/success/curl-request.adoc[] .http-request -include::{snippets}/patchShortReviewReq/success/http-request.adoc[] +include::{snippets}/patchShortReview/success/http-request.adoc[] .request-param -include::{snippets}/patchShortReviewReq/success/path-parameters.adoc[] +include::{snippets}/patchShortReview/success/path-parameters.adoc[] .request-body -include::{snippets}/patchShortReviewReq/success/request-body.adoc[] +include::{snippets}/patchShortReview/success/request-body.adoc[] .request-fields -include::{snippets}/patchShortReviewReq/success/request-fields.adoc[] +include::{snippets}/patchShortReview/success/request-fields.adoc[] ==== 성공시 .http-response -include::{snippets}/patchShortReviewReq/success/http-response.adoc[] +include::{snippets}/patchShortReview/success/http-response.adoc[] + +.response-body +include::{snippets}/patchShortReview/success/response-body.adoc[] + +.response-fields +include::{snippets}/patchShortReview/success/response-fields.adoc[] ==== 실패시 From 8a6092c82336b2bfe4292e45a5f9578acc890696 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 25 Oct 2023 19:42:14 +0900 Subject: [PATCH 352/734] =?UTF-8?q?chore:=20prometheus=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20#44?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index b5bc130f..3f913cfd 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ ext { dependencies { // 서버 상태 체크 implementation 'org.springframework.boot:spring-boot-starter-actuator' + runtimeOnly 'io.micrometer:micrometer-registry-prometheus' // security & oauth2 implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' From 2879d64a7e997b4b9a4115b009221c224f13e6b4 Mon Sep 17 00:00:00 2001 From: FaberJoo Date: Wed, 25 Oct 2023 20:37:20 +0900 Subject: [PATCH 353/734] =?UTF-8?q?chore:=20grafana=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20#44?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grafana/datasource.yml | 7 + grafana/grafana.ini | 1530 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1537 insertions(+) create mode 100644 grafana/datasource.yml create mode 100644 grafana/grafana.ini diff --git a/grafana/datasource.yml b/grafana/datasource.yml new file mode 100644 index 00000000..35d2b542 --- /dev/null +++ b/grafana/datasource.yml @@ -0,0 +1,7 @@ +apiVersion: 1 +datasources: + - name: oDuckio + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true diff --git a/grafana/grafana.ini b/grafana/grafana.ini new file mode 100644 index 00000000..655d7c3f --- /dev/null +++ b/grafana/grafana.ini @@ -0,0 +1,1530 @@ +##################### Grafana Configuration Example ##################### +# +# Everything has defaults so you only need to uncomment things you want to +# change + +# possible values : production, development +;app_mode = production + +# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty +;instance_name = ${HOSTNAME} + +# force migration will run migrations that might cause dataloss +;force_migration = false + +#################################### Paths #################################### +[paths] +# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) +;data = /var/lib/grafana + +# Temporary files in `data` directory older than given duration will be removed +;temp_data_lifetime = 24h + +# Directory where grafana can store logs +;logs = /var/log/grafana + +# Directory where grafana will automatically scan and look for plugins +;plugins = /var/lib/grafana/plugins + +# folder that contains provisioning config files that grafana will apply on startup and while running. +;provisioning = conf/provisioning + +#################################### Server #################################### +[server] +# Protocol (http, https, h2, socket) +;protocol = http + +# This is the minimum TLS version allowed. By default, this value is empty. Accepted values are: TLS1.2, TLS1.3. If nothing is set TLS1.2 would be taken +;min_tls_version = "" + +# The ip address to bind to, empty will bind to all interfaces +;http_addr = + +# The http port to use +;http_port = 3000 + +# The public facing domain name used to access grafana from a browser +;domain = localhost + +# Redirect to correct domain if host header does not match domain +# Prevents DNS rebinding attacks +;enforce_domain = false + +# The full public facing url you use in browser, used for redirects and emails +# If you use reverse proxy and sub path specify full url (with sub path) +;root_url = %(protocol)s://%(domain)s:%(http_port)s/ +root_url = %(protocol)s://%(domain)s:%(http_port)s/grafana + +# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons. +;serve_from_sub_path = false +serve_from_sub_path = true + +# Log web requests +;router_logging = false + +# the path relative working path +;static_root_path = public + +# enable gzip +;enable_gzip = false + +# https certs & key file +;cert_file = +;cert_key = + +# Unix socket gid +# Changing the gid of a file without privileges requires that the target group is in the group of the process and that the process is the file owner +# It is recommended to set the gid as http server user gid +# Not set when the value is -1 +;socket_gid = + +# Unix socket mode +;socket_mode = + +# Unix socket path +;socket = + +# CDN Url +;cdn_url = + +# Sets the maximum time using a duration format (5s/5m/5ms) before timing out read of an incoming request and closing idle connections. +# `0` means there is no timeout for reading the request. +;read_timeout = 0 + +# This setting enables you to specify additional headers that the server adds to HTTP(S) responses. +[server.custom_response_headers] +#exampleHeader1 = exampleValue1 +#exampleHeader2 = exampleValue2 + +#################################### GRPC Server ######################### +;[grpc_server] +;network = "tcp" +;address = "127.0.0.1:10000" +;use_tls = false +;cert_file = +;key_file = + +#################################### Database #################################### +[database] +# You can configure the database connection by specifying type, host, name, user and password +# as separate properties or as on string using the url properties. + +# Either "mysql", "postgres" or "sqlite3", it's your choice +;type = sqlite3 +;host = 127.0.0.1:3306 +;name = grafana +;user = root +# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" +;password = + +# Use either URL or the previous fields to configure the database +# Example: mysql://user:secret@host:port/database +;url = + +# For "postgres", use either "disable", "require" or "verify-full" +# For "mysql", use either "true", "false", or "skip-verify". +;ssl_mode = disable + +# Database drivers may support different transaction isolation levels. +# Currently, only "mysql" driver supports isolation levels. +# If the value is empty - driver's default isolation level is applied. +# For "mysql" use "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ" or "SERIALIZABLE". +;isolation_level = + +;ca_cert_path = +;client_key_path = +;client_cert_path = +;server_cert_name = + +# For "sqlite3" only, path relative to data_path setting +;path = grafana.db + +# Max idle conn setting default is 2 +;max_idle_conn = 2 + +# Max conn setting default is 0 (mean not set) +;max_open_conn = + +# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours) +;conn_max_lifetime = 14400 + +# Set to true to log the sql calls and execution times. +;log_queries = + +# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared) +;cache_mode = private + +# For "sqlite3" only. Enable/disable Write-Ahead Logging, https://sqlite.org/wal.html. Default is false. +;wal = false + +# For "mysql" only if migrationLocking feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0. +;locking_attempt_timeout_sec = 0 + +# For "sqlite" only. How many times to retry query in case of database is locked failures. Default is 0 (disabled). +;query_retries = 0 + +# For "sqlite" only. How many times to retry transaction in case of database is locked failures. Default is 5. +;transaction_retries = 5 + +# Set to true to add metrics and tracing for database queries. +;instrument_queries = false + +################################### Data sources ######################### +[datasources] +# Upper limit of data sources that Grafana will return. This limit is a temporary configuration and it will be deprecated when pagination will be introduced on the list data sources API. +;datasource_limit = 5000 + +#################################### Cache server ############################# +[remote_cache] +# Either "redis", "memcached" or "database" default is "database" +;type = database + +# cache connectionstring options +# database: will use Grafana primary database. +# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. +# memcache: 127.0.0.1:11211 +;connstr = + +# prefix prepended to all the keys in the remote cache +; prefix = + +# This enables encryption of values stored in the remote cache +;encryption = + +#################################### Data proxy ########################### +[dataproxy] + +# This enables data proxy logging, default is false +;logging = false + +# How long the data proxy waits to read the headers of the response before timing out, default is 30 seconds. +# This setting also applies to core backend HTTP data sources where query requests use an HTTP client with timeout set. +;timeout = 30 + +# How long the data proxy waits to establish a TCP connection before timing out, default is 10 seconds. +;dialTimeout = 10 + +# How many seconds the data proxy waits before sending a keepalive probe request. +;keep_alive_seconds = 30 + +# How many seconds the data proxy waits for a successful TLS Handshake before timing out. +;tls_handshake_timeout_seconds = 10 + +# How many seconds the data proxy will wait for a server's first response headers after +# fully writing the request headers if the request has an "Expect: 100-continue" +# header. A value of 0 will result in the body being sent immediately, without +# waiting for the server to approve. +;expect_continue_timeout_seconds = 1 + +# Optionally limits the total number of connections per host, including connections in the dialing, +# active, and idle states. On limit violation, dials will block. +# A value of zero (0) means no limit. +;max_conns_per_host = 0 + +# The maximum number of idle connections that Grafana will keep alive. +;max_idle_connections = 100 + +# How many seconds the data proxy keeps an idle connection open before timing out. +;idle_conn_timeout_seconds = 90 + +# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false. +;send_user_header = false + +# Limit the amount of bytes that will be read/accepted from responses of outgoing HTTP requests. +;response_limit = 0 + +# Limits the number of rows that Grafana will process from SQL data sources. +;row_limit = 1000000 + +# Sets a custom value for the `User-Agent` header for outgoing data proxy requests. If empty, the default value is `Grafana/` (for example `Grafana/9.0.0`). +;user_agent = + +#################################### Analytics #################################### +[analytics] +# Server reporting, sends usage counters to stats.grafana.org every 24 hours. +# No ip addresses are being tracked, only simple counters to track +# running instances, dashboard and error counts. It is very helpful to us. +# Change this option to false to disable reporting. +;reporting_enabled = true + +# The name of the distributor of the Grafana instance. Ex hosted-grafana, grafana-labs +;reporting_distributor = grafana-labs + +# Set to false to disable all checks to https://grafana.com +# for new versions of grafana. The check is used +# in some UI views to notify that a grafana update exists. +# This option does not cause any auto updates, nor send any information +# only a GET request to https://raw.githubusercontent.com/grafana/grafana/main/latest.json to get the latest version. +;check_for_updates = true + +# Set to false to disable all checks to https://grafana.com +# for new versions of plugins. The check is used +# in some UI views to notify that a plugin update exists. +# This option does not cause any auto updates, nor send any information +# only a GET request to https://grafana.com to get the latest versions. +;check_for_plugin_updates = true + +# Google Analytics universal tracking code, only enabled if you specify an id here +;google_analytics_ua_id = + +# Google Analytics 4 tracking code, only enabled if you specify an id here +;google_analytics_4_id = + +# When Google Analytics 4 Enhanced event measurement is enabled, we will try to avoid sending duplicate events and let Google Analytics 4 detect navigation changes, etc. +;google_analytics_4_send_manual_page_views = false + +# Google Tag Manager ID, only enabled if you specify an id here +;google_tag_manager_id = + +# Rudderstack write key, enabled only if rudderstack_data_plane_url is also set +;rudderstack_write_key = + +# Rudderstack data plane url, enabled only if rudderstack_write_key is also set +;rudderstack_data_plane_url = + +# Rudderstack SDK url, optional, only valid if rudderstack_write_key and rudderstack_data_plane_url is also set +;rudderstack_sdk_url = + +# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config +;rudderstack_config_url = + +# Intercom secret, optional, used to hash user_id before passing to Intercom via Rudderstack +;intercom_secret = + +# Controls if the UI contains any links to user feedback forms +;feedback_links_enabled = true + +#################################### Security #################################### +[security] +# disable creation of admin user on first start of grafana +;disable_initial_admin_creation = false + +# default admin user, created on startup +;admin_user = admin +admin_user = admin + +# default admin password, can be changed before first start of grafana, or in profile settings +;admin_password = admin +admin_password = Qwer!234 + +# default admin email, created on startup +;admin_email = admin@localhost + +# used for signing +;secret_key = SW2YcwTIb9zpOOhoPsMm + +# current key provider used for envelope encryption, default to static value specified by secret_key +;encryption_provider = secretKey.v1 + +# list of configured key providers, space separated (Enterprise only): e.g., awskms.v1 azurekv.v1 +;available_encryption_providers = + +# disable gravatar profile images +;disable_gravatar = false + +# data source proxy whitelist (ip_or_domain:port separated by spaces) +;data_source_proxy_whitelist = + +# disable protection against brute force login attempts +;disable_brute_force_login_protection = false + +# set to true if you host Grafana behind HTTPS. default is false. +;cookie_secure = false + +# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled" +;cookie_samesite = lax + +# set to true if you want to allow browsers to render Grafana in a ,