From 6027063b3feadd8b163d907aadaf02be098556f6 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Wed, 22 Nov 2023 23:28:26 +0900 Subject: [PATCH 01/51] init: init project --- .gitignore | 249 ++++++++++++++++++ HELP.md | 31 +++ build.gradle | 51 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++++++++++++++++++ gradlew.bat | 92 +++++++ settings.gradle | 1 + .../SpringbootBoardJpaApplication.java | 12 + src/main/resources/application.properties | 1 + 10 files changed, 693 insertions(+) create mode 100644 .gitignore create mode 100644 HELP.md 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/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java create mode 100644 src/main/resources/application.properties diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4e933faa0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,249 @@ +# Created by https://www.toptal.com/developers/gitignore/api/java,intellij,macos,windows,visualstudiocode,gradle +# Edit at https://www.toptal.com/developers/gitignore?templates=java,intellij,macos,windows,visualstudiocode,gradle + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof + +# End of https://www.toptal.com/developers/gitignore/api/java,intellij,macos,windows,visualstudiocode,gradle \ No newline at end of file diff --git a/HELP.md b/HELP.md new file mode 100644 index 000000000..9426139c2 --- /dev/null +++ b/HELP.md @@ -0,0 +1,31 @@ +# Read Me First +The following was discovered as part of building this project: + +* The original package name 'jehs.springboot-board-jpa' is invalid and this project uses 'jehs.springbootboardjpa' instead. + +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Gradle documentation](https://docs.gradle.org) +* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.1.5/gradle-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.1.5/gradle-plugin/reference/html/#build-image) +* [Spring Web](https://docs.spring.io/spring-boot/docs/3.1.5/reference/htmlsingle/index.html#web) +* [Spring REST Docs](https://docs.spring.io/spring-restdocs/docs/current/reference/html5/) +* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.1.5/reference/htmlsingle/index.html#data.sql.jpa-and-spring-data) + +### Guides +The following guides illustrate how to use some features concretely: + +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) +* [Accessing data with MySQL](https://spring.io/guides/gs/accessing-data-mysql/) +* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) + +### Additional Links +These additional references should also help you: + +* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) + diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..79b802db7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.1.5' + id 'io.spring.dependency-management' version '1.1.3' + id 'org.asciidoctor.jvm.convert' version '3.3.2' +} + +group = 'jehs' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +ext { + set('snippetsDir', file("build/generated-snippets")) +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.mysql:mysql-connector-j' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' +} + +tasks.named('bootBuildImage') { + builder = 'paketobuildpacks/builder-jammy-base:latest' +} + +tasks.named('test') { + outputs.dir snippetsDir + useJUnitPlatform() +} + +tasks.named('asciidoctor') { + inputs.dir snippetsDir + dependsOn test +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7f93135c49b765f8051ef9d0a6055ff8e46073d8 GIT binary patch literal 63721 zcmb5Wb9gP!wgnp7wrv|bwr$&XvSZt}Z6`anZSUAlc9NHKf9JdJ;NJVr`=eI(_pMp0 zy1VAAG3FfAOI`{X1O)&90s;U4K;XLp008~hCjbEC_fbYfS%6kTR+JtXK>nW$ZR+`W ze|#J8f4A@M|F5BpfUJb5h>|j$jOe}0oE!`Zf6fM>CR?!y@zU(cL8NsKk`a z6tx5mAkdjD;J=LcJ;;Aw8p!v#ouk>mUDZF@ zK>yvw%+bKu+T{Nk@LZ;zkYy0HBKw06_IWcMHo*0HKpTsEFZhn5qCHH9j z)|XpN&{`!0a>Vl+PmdQc)Yg4A(AG-z!+@Q#eHr&g<9D?7E)_aEB?s_rx>UE9TUq|? z;(ggJt>9l?C|zoO@5)tu?EV0x_7T17q4fF-q3{yZ^ipUbKcRZ4Qftd!xO(#UGhb2y>?*@{xq%`(-`2T^vc=#< zx!+@4pRdk&*1ht2OWk^Z5IAQ0YTAXLkL{(D*$gENaD)7A%^XXrCchN&z2x+*>o2FwPFjWpeaL=!tzv#JOW#( z$B)Nel<+$bkH1KZv3&-}=SiG~w2sbDbAWarg%5>YbC|}*d9hBjBkR(@tyM0T)FO$# zPtRXukGPnOd)~z=?avu+4Co@wF}1T)-uh5jI<1$HLtyDrVak{gw`mcH@Q-@wg{v^c zRzu}hMKFHV<8w}o*yg6p@Sq%=gkd~;`_VGTS?L@yVu`xuGy+dH6YOwcP6ZE`_0rK% zAx5!FjDuss`FQ3eF|mhrWkjux(Pny^k$u_)dyCSEbAsecHsq#8B3n3kDU(zW5yE|( zgc>sFQywFj5}U*qtF9Y(bi*;>B7WJykcAXF86@)z|0-Vm@jt!EPoLA6>r)?@DIobIZ5Sx zsc@OC{b|3%vaMbyeM|O^UxEYlEMHK4r)V-{r)_yz`w1*xV0|lh-LQOP`OP`Pk1aW( z8DSlGN>Ts|n*xj+%If~+E_BxK)~5T#w6Q1WEKt{!Xtbd`J;`2a>8boRo;7u2M&iOop4qcy<)z023=oghSFV zST;?S;ye+dRQe>ygiJ6HCv4;~3DHtJ({fWeE~$H@mKn@Oh6Z(_sO>01JwH5oA4nvK zr5Sr^g+LC zLt(i&ecdmqsIJGNOSUyUpglvhhrY8lGkzO=0USEKNL%8zHshS>Qziu|`eyWP^5xL4 zRP122_dCJl>hZc~?58w~>`P_s18VoU|7(|Eit0-lZRgLTZKNq5{k zE?V=`7=R&ro(X%LTS*f+#H-mGo_j3dm@F_krAYegDLk6UV{`UKE;{YSsn$ z(yz{v1@p|p!0>g04!eRSrSVb>MQYPr8_MA|MpoGzqyd*$@4j|)cD_%^Hrd>SorF>@ zBX+V<@vEB5PRLGR(uP9&U&5=(HVc?6B58NJT_igiAH*q~Wb`dDZpJSKfy5#Aag4IX zj~uv74EQ_Q_1qaXWI!7Vf@ZrdUhZFE;L&P_Xr8l@GMkhc#=plV0+g(ki>+7fO%?Jb zl+bTy7q{w^pTb{>(Xf2q1BVdq?#f=!geqssXp z4pMu*q;iiHmA*IjOj4`4S&|8@gSw*^{|PT}Aw~}ZXU`6=vZB=GGeMm}V6W46|pU&58~P+?LUs%n@J}CSrICkeng6YJ^M? zS(W?K4nOtoBe4tvBXs@@`i?4G$S2W&;$z8VBSM;Mn9 zxcaEiQ9=vS|bIJ>*tf9AH~m&U%2+Dim<)E=}KORp+cZ^!@wI`h1NVBXu{@%hB2Cq(dXx_aQ9x3mr*fwL5!ZryQqi|KFJuzvP zK1)nrKZ7U+B{1ZmJub?4)Ln^J6k!i0t~VO#=q1{?T)%OV?MN}k5M{}vjyZu#M0_*u z8jwZKJ#Df~1jcLXZL7bnCEhB6IzQZ-GcoQJ!16I*39iazoVGugcKA{lhiHg4Ta2fD zk1Utyc5%QzZ$s3;p0N+N8VX{sd!~l*Ta3|t>lhI&G`sr6L~G5Lul`>m z{!^INm?J|&7X=;{XveF!(b*=?9NAp4y&r&N3(GKcW4rS(Ejk|Lzs1PrxPI_owB-`H zg3(Rruh^&)`TKA6+_!n>RdI6pw>Vt1_j&+bKIaMTYLiqhZ#y_=J8`TK{Jd<7l9&sY z^^`hmi7^14s16B6)1O;vJWOF$=$B5ONW;;2&|pUvJlmeUS&F;DbSHCrEb0QBDR|my zIs+pE0Y^`qJTyH-_mP=)Y+u^LHcuZhsM3+P||?+W#V!_6E-8boP#R-*na4!o-Q1 zVthtYhK{mDhF(&7Okzo9dTi03X(AE{8cH$JIg%MEQca`S zy@8{Fjft~~BdzWC(di#X{ny;!yYGK9b@=b|zcKZ{vv4D8i+`ilOPl;PJl{!&5-0!w z^fOl#|}vVg%=n)@_e1BrP)`A zKPgs`O0EO}Y2KWLuo`iGaKu1k#YR6BMySxQf2V++Wo{6EHmK>A~Q5o73yM z-RbxC7Qdh0Cz!nG+7BRZE>~FLI-?&W_rJUl-8FDIaXoNBL)@1hwKa^wOr1($*5h~T zF;%f^%<$p8Y_yu(JEg=c_O!aZ#)Gjh$n(hfJAp$C2he555W5zdrBqjFmo|VY+el;o z=*D_w|GXG|p0**hQ7~9-n|y5k%B}TAF0iarDM!q-jYbR^us(>&y;n^2l0C%@2B}KM zyeRT9)oMt97Agvc4sEKUEy%MpXr2vz*lb zh*L}}iG>-pqDRw7ud{=FvTD?}xjD)w{`KzjNom-$jS^;iw0+7nXSnt1R@G|VqoRhE%12nm+PH?9`(4rM0kfrZzIK9JU=^$YNyLvAIoxl#Q)xxDz!^0@zZ zSCs$nfcxK_vRYM34O<1}QHZ|hp4`ioX3x8(UV(FU$J@o%tw3t4k1QPmlEpZa2IujG&(roX_q*%e`Hq|);0;@k z0z=fZiFckp#JzW0p+2A+D$PC~IsakhJJkG(c;CqAgFfU0Z`u$PzG~-9I1oPHrCw&)@s^Dc~^)#HPW0Ra}J^=|h7Fs*<8|b13ZzG6MP*Q1dkoZ6&A^!}|hbjM{2HpqlSXv_UUg1U4gn z3Q)2VjU^ti1myodv+tjhSZp%D978m~p& z43uZUrraHs80Mq&vcetqfQpQP?m!CFj)44t8Z}k`E798wxg&~aCm+DBoI+nKq}&j^ zlPY3W$)K;KtEajks1`G?-@me7C>{PiiBu+41#yU_c(dITaqE?IQ(DBu+c^Ux!>pCj zLC|HJGU*v+!it1(;3e`6igkH(VA)-S+k(*yqxMgUah3$@C zz`7hEM47xr>j8^g`%*f=6S5n>z%Bt_Fg{Tvmr+MIsCx=0gsu_sF`q2hlkEmisz#Fy zj_0;zUWr;Gz}$BS%Y`meb(=$d%@Crs(OoJ|}m#<7=-A~PQbyN$x%2iXP2@e*nO0b7AwfH8cCUa*Wfu@b)D_>I*%uE4O3 z(lfnB`-Xf*LfC)E}e?%X2kK7DItK6Tf<+M^mX0Ijf_!IP>7c8IZX%8_#0060P{QMuV^B9i<^E`_Qf0pv9(P%_s8D`qvDE9LK9u-jB}J2S`(mCO&XHTS04Z5Ez*vl^T%!^$~EH8M-UdwhegL>3IQ*)(MtuH2Xt1p!fS4o~*rR?WLxlA!sjc2(O znjJn~wQ!Fp9s2e^IWP1C<4%sFF}T4omr}7+4asciyo3DntTgWIzhQpQirM$9{EbQd z3jz9vS@{aOqTQHI|l#aUV@2Q^Wko4T0T04Me4!2nsdrA8QY1%fnAYb~d2GDz@lAtfcHq(P7 zaMBAGo}+NcE-K*@9y;Vt3*(aCaMKXBB*BJcD_Qnxpt75r?GeAQ}*|>pYJE=uZb73 zC>sv)18)q#EGrTG6io*}JLuB_jP3AU1Uiu$D7r|2_zlIGb9 zjhst#ni)Y`$)!fc#reM*$~iaYoz~_Cy7J3ZTiPm)E?%`fbk`3Tu-F#`{i!l5pNEn5 zO-Tw-=TojYhzT{J=?SZj=Z8#|eoF>434b-DXiUsignxXNaR3 zm_}4iWU$gt2Mw5NvZ5(VpF`?X*f2UZDs1TEa1oZCif?Jdgr{>O~7}-$|BZ7I(IKW`{f;@|IZFX*R8&iT= zoWstN8&R;}@2Ka%d3vrLtR|O??ben;k8QbS-WB0VgiCz;<$pBmIZdN!aalyCSEm)crpS9dcD^Y@XT1a3+zpi-`D}e#HV<} z$Y(G&o~PvL-xSVD5D?JqF3?B9rxGWeb=oEGJ3vRp5xfBPlngh1O$yI95EL+T8{GC@ z98i1H9KhZGFl|;`)_=QpM6H?eDPpw~^(aFQWwyXZ8_EEE4#@QeT_URray*mEOGsGc z6|sdXtq!hVZo=d#+9^@lm&L5|q&-GDCyUx#YQiccq;spOBe3V+VKdjJA=IL=Zn%P} zNk=_8u}VhzFf{UYZV0`lUwcD&)9AFx0@Fc6LD9A6Rd1=ga>Mi0)_QxM2ddCVRmZ0d z+J=uXc(?5JLX3=)e)Jm$HS2yF`44IKhwRnm2*669_J=2LlwuF5$1tAo@ROSU@-y+;Foy2IEl2^V1N;fk~YR z?&EP8#t&m0B=?aJeuz~lHjAzRBX>&x=A;gIvb>MD{XEV zV%l-+9N-)i;YH%nKP?>f`=?#`>B(`*t`aiPLoQM(a6(qs4p5KFjDBN?8JGrf3z8>= zi7sD)c)Nm~x{e<^jy4nTx${P~cwz_*a>%0_;ULou3kHCAD7EYkw@l$8TN#LO9jC( z1BeFW`k+bu5e8Ns^a8dPcjEVHM;r6UX+cN=Uy7HU)j-myRU0wHd$A1fNI~`4;I~`zC)3ul#8#^rXVSO*m}Ag>c%_;nj=Nv$rCZ z*~L@C@OZg%Q^m)lc-kcX&a*a5`y&DaRxh6O*dfhLfF+fU5wKs(1v*!TkZidw*)YBP za@r`3+^IHRFeO%!ai%rxy;R;;V^Fr=OJlpBX;(b*3+SIw}7= zIq$*Thr(Zft-RlY)D3e8V;BmD&HOfX+E$H#Y@B3?UL5L~_fA-@*IB-!gItK7PIgG9 zgWuGZK_nuZjHVT_Fv(XxtU%)58;W39vzTI2n&)&4Dmq7&JX6G>XFaAR{7_3QB6zsT z?$L8c*WdN~nZGiscY%5KljQARN;`w$gho=p006z;n(qIQ*Zu<``TMO3n0{ARL@gYh zoRwS*|Niw~cR!?hE{m*y@F`1)vx-JRfqET=dJ5_(076st(=lFfjtKHoYg`k3oNmo_ zNbQEw8&sO5jAYmkD|Zaz_yUb0rC})U!rCHOl}JhbYIDLzLvrZVw0~JO`d*6f;X&?V=#T@ND*cv^I;`sFeq4 z##H5;gpZTb^0Hz@3C*~u0AqqNZ-r%rN3KD~%Gw`0XsIq$(^MEb<~H(2*5G^<2(*aI z%7}WB+TRlMIrEK#s0 z93xn*Ohb=kWFc)BNHG4I(~RPn-R8#0lqyBBz5OM6o5|>x9LK@%HaM}}Y5goCQRt2C z{j*2TtT4ne!Z}vh89mjwiSXG=%DURar~=kGNNaO_+Nkb+tRi~Rkf!7a$*QlavziD( z83s4GmQ^Wf*0Bd04f#0HX@ua_d8 z23~z*53ePD6@xwZ(vdl0DLc=>cPIOPOdca&MyR^jhhKrdQO?_jJh`xV3GKz&2lvP8 zEOwW6L*ufvK;TN{=S&R@pzV^U=QNk^Ec}5H z+2~JvEVA{`uMAr)?Kf|aW>33`)UL@bnfIUQc~L;TsTQ6>r-<^rB8uoNOJ>HWgqMI8 zSW}pZmp_;z_2O5_RD|fGyTxaxk53Hg_3Khc<8AUzV|ZeK{fp|Ne933=1&_^Dbv5^u zB9n=*)k*tjHDRJ@$bp9mrh}qFn*s}npMl5BMDC%Hs0M0g-hW~P*3CNG06G!MOPEQ_ zi}Qs-6M8aMt;sL$vlmVBR^+Ry<64jrm1EI1%#j?c?4b*7>)a{aDw#TfTYKq+SjEFA z(aJ&z_0?0JB83D-i3Vh+o|XV4UP+YJ$9Boid2^M2en@APw&wx7vU~t$r2V`F|7Qfo z>WKgI@eNBZ-+Og<{u2ZiG%>YvH2L3fNpV9J;WLJoBZda)01Rn;o@){01{7E#ke(7U zHK>S#qZ(N=aoae*4X!0A{)nu0R_sKpi1{)u>GVjC+b5Jyl6#AoQ-1_3UDovNSo`T> z?c-@7XX*2GMy?k?{g)7?Sv;SJkmxYPJPs!&QqB12ejq`Lee^-cDveVWL^CTUldb(G zjDGe(O4P=S{4fF=#~oAu>LG>wrU^z_?3yt24FOx>}{^lCGh8?vtvY$^hbZ)9I0E3r3NOlb9I?F-Yc=r$*~l`4N^xzlV~N zl~#oc>U)Yjl0BxV>O*Kr@lKT{Z09OXt2GlvE38nfs+DD7exl|&vT;)>VFXJVZp9Np zDK}aO;R3~ag$X*|hRVY3OPax|PG`@_ESc8E!mHRByJbZQRS38V2F__7MW~sgh!a>98Q2%lUNFO=^xU52|?D=IK#QjwBky-C>zOWlsiiM&1n z;!&1((Xn1$9K}xabq~222gYvx3hnZPg}VMF_GV~5ocE=-v>V=T&RsLBo&`)DOyIj* zLV{h)JU_y*7SdRtDajP_Y+rBkNN*1_TXiKwHH2&p51d(#zv~s#HwbNy?<+(=9WBvo zw2hkk2Dj%kTFhY+$T+W-b7@qD!bkfN#Z2ng@Pd=i3-i?xYfs5Z*1hO?kd7Sp^9`;Y zM2jeGg<-nJD1er@Pc_cSY7wo5dzQX44=%6rn}P_SRbpzsA{6B+!$3B0#;}qwO37G^ zL(V_5JK`XT?OHVk|{_$vQ|oNEpab*BO4F zUTNQ7RUhnRsU`TK#~`)$icsvKh~(pl=3p6m98@k3P#~upd=k*u20SNcb{l^1rUa)>qO997)pYRWMncC8A&&MHlbW?7i^7M`+B$hH~Y|J zd>FYOGQ;j>Zc2e7R{KK7)0>>nn_jYJy&o@sK!4G>-rLKM8Hv)f;hi1D2fAc$+six2 zyVZ@wZ6x|fJ!4KrpCJY=!Mq0;)X)OoS~{Lkh6u8J`eK%u0WtKh6B>GW_)PVc zl}-k`p09qwGtZ@VbYJC!>29V?Dr>>vk?)o(x?!z*9DJ||9qG-&G~#kXxbw{KKYy}J zQKa-dPt~M~E}V?PhW0R26xdA%1T*%ra6SguGu50YHngOTIv)@N|YttEXo#OZfgtP7;H?EeZZxo<}3YlYxtBq znJ!WFR^tmGf0Py}N?kZ(#=VtpC@%xJkDmfcCoBTxq zr_|5gP?u1@vJZbxPZ|G0AW4=tpb84gM2DpJU||(b8kMOV1S3|(yuwZJ&rIiFW(U;5 zUtAW`O6F6Zy+eZ1EDuP~AAHlSY-+A_eI5Gx)%*uro5tljy}kCZU*_d7)oJ>oQSZ3* zneTn`{gnNC&uJd)0aMBzAg021?YJ~b(fmkwZAd696a=0NzBAqBN54KuNDwa*no(^O z6p05bioXUR^uXjpTol*ppHp%1v9e)vkoUAUJyBx3lw0UO39b0?^{}yb!$yca(@DUn zCquRF?t=Zb9`Ed3AI6|L{eX~ijVH`VzSMheKoP7LSSf4g>md>`yi!TkoG5P>Ofp+n z(v~rW+(5L96L{vBb^g51B=(o)?%%xhvT*A5btOpw(TKh^g^4c zw>0%X!_0`{iN%RbVk+A^f{w-4-SSf*fu@FhruNL##F~sF24O~u zyYF<3el2b$$wZ_|uW#@Ak+VAGk#e|kS8nL1g>2B-SNMjMp^8;-FfeofY2fphFHO!{ z*!o4oTb{4e;S<|JEs<1_hPsmAlVNk?_5-Fp5KKU&d#FiNW~Y+pVFk@Cua1I{T+1|+ zHx6rFMor)7L)krbilqsWwy@T+g3DiH5MyVf8Wy}XbEaoFIDr~y;@r&I>FMW{ z?Q+(IgyebZ)-i4jNoXQhq4Muy9Fv+OxU;9_Jmn+<`mEC#%2Q_2bpcgzcinygNI!&^ z=V$)o2&Yz04~+&pPWWn`rrWxJ&}8khR)6B(--!9Q zubo}h+1T)>a@c)H^i``@<^j?|r4*{;tQf78(xn0g39IoZw0(CwY1f<%F>kEaJ zp9u|IeMY5mRdAlw*+gSN^5$Q)ShM<~E=(c8QM+T-Qk)FyKz#Sw0EJ*edYcuOtO#~Cx^(M7w5 z3)rl#L)rF|(Vun2LkFr!rg8Q@=r>9p>(t3Gf_auiJ2Xx9HmxYTa|=MH_SUlYL`mz9 zTTS$`%;D-|Jt}AP1&k7PcnfFNTH0A-*FmxstjBDiZX?}%u%Yq94$fUT&z6od+(Uk> zuqsld#G(b$G8tus=M!N#oPd|PVFX)?M?tCD0tS%2IGTfh}3YA3f&UM)W$_GNV8 zQo+a(ml2Km4o6O%gKTCSDNq+#zCTIQ1*`TIJh~k6Gp;htHBFnne))rlFdGqwC6dx2+La1&Mnko*352k0y z+tQcwndQlX`nc6nb$A9?<-o|r*%aWXV#=6PQic0Ok_D;q>wbv&j7cKc!w4~KF#-{6 z(S%6Za)WpGIWf7jZ3svNG5OLs0>vCL9{V7cgO%zevIVMH{WgP*^D9ws&OqA{yr|m| zKD4*07dGXshJHd#e%x%J+qmS^lS|0Bp?{drv;{@{l9ArPO&?Q5=?OO9=}h$oVe#3b z3Yofj&Cb}WC$PxmRRS)H%&$1-)z7jELS}!u!zQ?A^Y{Tv4QVt*vd@uj-^t2fYRzQj zfxGR>-q|o$3sGn^#VzZ!QQx?h9`njeJry}@x?|k0-GTTA4y3t2E`3DZ!A~D?GiJup z)8%PK2^9OVRlP(24P^4_<|D=H^7}WlWu#LgsdHzB%cPy|f8dD3|A^mh4WXxhLTVu_ z@abE{6Saz|Y{rXYPd4$tfPYo}ef(oQWZ=4Bct-=_9`#Qgp4ma$n$`tOwq#&E18$B; z@Bp)bn3&rEi0>fWWZ@7k5WazfoX`SCO4jQWwVuo+$PmSZn^Hz?O(-tW@*DGxuf)V1 zO_xm&;NVCaHD4dqt(-MlszI3F-p?0!-e$fbiCeuaw66h^TTDLWuaV<@C-`=Xe5WL) zwooG7h>4&*)p3pKMS3O!4>-4jQUN}iAMQ)2*70?hP~)TzzR?-f@?Aqy$$1Iy8VGG$ zMM?8;j!pUX7QQD$gRc_#+=raAS577ga-w?jd`vCiN5lu)dEUkkUPl9!?{$IJNxQys z*E4e$eF&n&+AMRQR2gcaFEjAy*r)G!s(P6D&TfoApMFC_*Ftx0|D0@E-=B7tezU@d zZ{hGiN;YLIoSeRS;9o%dEua4b%4R3;$SugDjP$x;Z!M!@QibuSBb)HY!3zJ7M;^jw zlx6AD50FD&p3JyP*>o+t9YWW8(7P2t!VQQ21pHJOcG_SXQD;(5aX#M6x##5H_Re>6lPyDCjxr*R(+HE%c&QN+b^tbT zXBJk?p)zhJj#I?&Y2n&~XiytG9!1ox;bw5Rbj~)7c(MFBb4>IiRATdhg zmiEFlj@S_hwYYI(ki{}&<;_7(Z0Qkfq>am z&LtL=2qc7rWguk3BtE4zL41@#S;NN*-jWw|7Kx7H7~_%7fPt;TIX}Ubo>;Rmj94V> zNB1=;-9AR7s`Pxn}t_6^3ahlq53e&!Lh85uG zec0vJY_6e`tg7LgfrJ3k!DjR)Bi#L@DHIrZ`sK=<5O0Ip!fxGf*OgGSpP@Hbbe&$9 z;ZI}8lEoC2_7;%L2=w?tb%1oL0V+=Z`7b=P&lNGY;yVBazXRYu;+cQDKvm*7NCxu&i;zub zAJh#11%?w>E2rf2e~C4+rAb-&$^vsdACs7 z@|Ra!OfVM(ke{vyiqh7puf&Yp6cd6{DptUteYfIRWG3pI+5< zBVBI_xkBAc<(pcb$!Y%dTW(b;B;2pOI-(QCsLv@U-D1XJ z(Gk8Q3l7Ws46Aktuj>|s{$6zA&xCPuXL-kB`CgYMs}4IeyG*P51IDwW?8UNQd+$i~ zlxOPtSi5L|gJcF@DwmJA5Ju8HEJ>o{{upwIpb!f{2(vLNBw`7xMbvcw<^{Fj@E~1( z?w`iIMieunS#>nXlmUcSMU+D3rX28f?s7z;X=se6bo8;5vM|O^(D6{A9*ChnGH!RG zP##3>LDC3jZPE4PH32AxrqPk|yIIrq~`aL-=}`okhNu9aT%q z1b)7iJ)CN=V#Ly84N_r7U^SH2FGdE5FpTO2 z630TF$P>GNMu8`rOytb(lB2};`;P4YNwW1<5d3Q~AX#P0aX}R2b2)`rgkp#zTxcGj zAV^cvFbhP|JgWrq_e`~exr~sIR$6p5V?o4Wym3kQ3HA+;Pr$bQ0(PmADVO%MKL!^q z?zAM8j1l4jrq|5X+V!8S*2Wl@=7*pPgciTVK6kS1Ge zMsd_u6DFK$jTnvVtE;qa+8(1sGBu~n&F%dh(&c(Zs4Fc#A=gG^^%^AyH}1^?|8quj zl@Z47h$){PlELJgYZCIHHL= z{U8O>Tw4x3<1{?$8>k-P<}1y9DmAZP_;(3Y*{Sk^H^A=_iSJ@+s5ktgwTXz_2$~W9>VVZsfwCm@s0sQ zeB50_yu@uS+e7QoPvdCwDz{prjo(AFwR%C?z`EL{1`|coJHQTk^nX=tvs1<0arUOJ z!^`*x&&BvTYmemyZ)2p~{%eYX=JVR?DYr(rNgqRMA5E1PR1Iw=prk=L2ldy3r3Vg@27IZx43+ywyzr-X*p*d@tZV+!U#~$-q=8c zgdSuh#r?b4GhEGNai)ayHQpk>5(%j5c@C1K3(W1pb~HeHpaqijJZa-e6vq_8t-^M^ zBJxq|MqZc?pjXPIH}70a5vt!IUh;l}<>VX<-Qcv^u@5(@@M2CHSe_hD$VG-eiV^V( zj7*9T0?di?P$FaD6oo?)<)QT>Npf6Og!GO^GmPV(Km0!=+dE&bk#SNI+C9RGQ|{~O*VC+tXK3!n`5 zHfl6>lwf_aEVV3`0T!aHNZLsj$paS$=LL(?b!Czaa5bbSuZ6#$_@LK<(7yrrl+80| z{tOFd=|ta2Z`^ssozD9BINn45NxUeCQis?-BKmU*Kt=FY-NJ+)8S1ecuFtN-M?&42 zl2$G>u!iNhAk*HoJ^4v^9#ORYp5t^wDj6|lx~5w45#E5wVqI1JQ~9l?nPp1YINf++ zMAdSif~_ETv@Er(EFBI^@L4BULFW>)NI+ejHFP*T}UhWNN`I)RRS8za? z*@`1>9ZB}An%aT5K=_2iQmfE;GcBVHLF!$`I99o5GO`O%O_zLr9AG18>&^HkG(;=V z%}c!OBQ~?MX(9h~tajX{=x)+!cbM7$YzTlmsPOdp2L-?GoW`@{lY9U3f;OUo*BwRB z8A+nv(br0-SH#VxGy#ZrgnGD(=@;HME;yd46EgWJ`EL%oXc&lFpc@Y}^>G(W>h_v_ zlN!`idhX+OjL+~T?19sroAFVGfa5tX-D49w$1g2g_-T|EpHL6}K_aX4$K=LTvwtlF zL*z}j{f+Uoe7{-px3_5iKPA<_7W=>Izkk)!l9ez2w%vi(?Y;i8AxRNLSOGDzNoqoI zP!1uAl}r=_871(G?y`i&)-7{u=%nxk7CZ_Qh#!|ITec zwQn`33GTUM`;D2POWnkqngqJhJRlM>CTONzTG}>^Q0wUunQyn|TAiHzyX2_%ATx%P z%7gW)%4rA9^)M<_%k@`Y?RbC<29sWU&5;@|9thf2#zf8z12$hRcZ!CSb>kUp=4N#y zl3hE#y6>kkA8VY2`W`g5Ip?2qC_BY$>R`iGQLhz2-S>x(RuWv)SPaGdl^)gGw7tjR zH@;jwk!jIaCgSg_*9iF|a);sRUTq30(8I(obh^|}S~}P4U^BIGYqcz;MPpC~Y@k_m zaw4WG1_vz2GdCAX!$_a%GHK**@IrHSkGoN>)e}>yzUTm52on`hYot7cB=oA-h1u|R ztH$11t?54Qg2L+i33FPFKKRm1aOjKST{l1*(nps`>sv%VqeVMWjl5+Gh+9);hIP8? zA@$?}Sc z3qIRpba+y5yf{R6G(u8Z^vkg0Fu&D-7?1s=QZU`Ub{-!Y`I?AGf1VNuc^L3v>)>i# z{DV9W$)>34wnzAXUiV^ZpYKw>UElrN_5Xj6{r_3| z$X5PK`e5$7>~9Dj7gK5ash(dvs`vwfk}&RD`>04;j62zoXESkFBklYaKm5seyiX(P zqQ-;XxlV*yg?Dhlx%xt!b0N3GHp@(p$A;8|%# zZ5m2KL|{on4nr>2_s9Yh=r5ScQ0;aMF)G$-9-Ca6%wA`Pa)i?NGFA|#Yi?{X-4ZO_ z^}%7%vkzvUHa$-^Y#aA+aiR5sa%S|Ebyn`EV<3Pc?ax_f>@sBZF1S;7y$CXd5t5=WGsTKBk8$OfH4v|0?0I=Yp}7c=WBSCg!{0n)XmiU;lfx)**zZaYqmDJelxk$)nZyx5`x$6R|fz(;u zEje5Dtm|a%zK!!tk3{i9$I2b{vXNFy%Bf{50X!x{98+BsDr_u9i>G5%*sqEX|06J0 z^IY{UcEbj6LDwuMh7cH`H@9sVt1l1#8kEQ(LyT@&+K}(ReE`ux8gb0r6L_#bDUo^P z3Ka2lRo52Hdtl_%+pwVs14=q`{d^L58PsU@AMf(hENumaxM{7iAT5sYmWh@hQCO^ zK&}ijo=`VqZ#a3vE?`7QW0ZREL17ZvDfdqKGD?0D4fg{7v%|Yj&_jcKJAB)>=*RS* zto8p6@k%;&^ZF>hvXm&$PCuEp{uqw3VPG$9VMdW5$w-fy2CNNT>E;>ejBgy-m_6`& z97L1p{%srn@O_JQgFpa_#f(_)eb#YS>o>q3(*uB;uZb605(iqM$=NK{nHY=+X2*G) zO3-_Xh%aG}fHWe*==58zBwp%&`mge<8uq8;xIxOd=P%9EK!34^E9sk|(Zq1QSz-JVeP12Fp)-`F|KY$LPwUE?rku zY@OJ)Z9A!ojfzfeyJ9;zv2EM7ZQB)AR5xGa-tMn^bl)FmoIiVyJ@!~@%{}qXXD&Ns zPnfe5U+&ohKefILu_1mPfLGuapX@btta5C#gPB2cjk5m4T}Nfi+Vfka!Yd(L?-c~5 z#ZK4VeQEXNPc4r$K00Fg>g#_W!YZ)cJ?JTS<&68_$#cZT-ME`}tcwqg3#``3M3UPvn+pi}(VNNx6y zFIMVb6OwYU(2`at$gHba*qrMVUl8xk5z-z~fb@Q3Y_+aXuEKH}L+>eW__!IAd@V}L zkw#s%H0v2k5-=vh$^vPCuAi22Luu3uKTf6fPo?*nvj$9(u)4$6tvF-%IM+3pt*cgs z_?wW}J7VAA{_~!?))?s6{M=KPpVhg4fNuU*|3THp@_(q!b*hdl{fjRVFWtu^1dV(f z6iOux9hi&+UK=|%M*~|aqFK{Urfl!TA}UWY#`w(0P!KMe1Si{8|o))Gy6d7;!JQYhgMYmXl?3FfOM2nQGN@~Ap6(G z3+d_5y@=nkpKAhRqf{qQ~k7Z$v&l&@m7Ppt#FSNzKPZM z8LhihcE6i=<(#87E|Wr~HKvVWhkll4iSK$^mUHaxgy8*K$_Zj;zJ`L$naPj+^3zTi z-3NTaaKnD5FPY-~?Tq6QHnmDDRxu0mh0D|zD~Y=vv_qig5r-cIbCpxlju&8Sya)@{ zsmv6XUSi)@(?PvItkiZEeN*)AE~I_?#+Ja-r8$(XiXei2d@Hi7Rx8+rZZb?ZLa{;@*EHeRQ-YDadz~M*YCM4&F-r;E#M+@CSJMJ0oU|PQ^ z=E!HBJDMQ2TN*Y(Ag(ynAL8%^v;=~q?s4plA_hig&5Z0x_^Oab!T)@6kRN$)qEJ6E zNuQjg|G7iwU(N8pI@_6==0CL;lRh1dQF#wePhmu@hADFd3B5KIH#dx(2A zp~K&;Xw}F_N6CU~0)QpQk7s$a+LcTOj1%=WXI(U=Dv!6 z{#<#-)2+gCyyv=Jw?Ab#PVkxPDeH|sAxyG`|Ys}A$PW4TdBv%zDz z^?lwrxWR<%Vzc8Sgt|?FL6ej_*e&rhqJZ3Y>k=X(^dytycR;XDU16}Pc9Vn0>_@H+ zQ;a`GSMEG64=JRAOg%~L)x*w{2re6DVprNp+FcNra4VdNjiaF0M^*>CdPkt(m150rCue?FVdL0nFL$V%5y6N z%eLr5%YN7D06k5ji5*p4v$UMM)G??Q%RB27IvH7vYr_^3>1D-M66#MN8tWGw>WED} z5AhlsanO=STFYFs)Il_0i)l)f<8qn|$DW7ZXhf5xI;m+7M5-%P63XFQrG9>DMqHc} zsgNU9nR`b}E^mL5=@7<1_R~j@q_2U^3h|+`7YH-?C=vme1C3m`Fe0HC>pjt6f_XMh zy~-i-8R46QNYneL4t@)<0VU7({aUO?aH`z4V2+kxgH5pYD5)wCh75JqQY)jIPN=U6 z+qi8cGiOtXG2tXm;_CfpH9ESCz#i5B(42}rBJJF$jh<1sbpj^8&L;gzGHb8M{of+} zzF^8VgML2O9nxBW7AvdEt90vp+#kZxWf@A)o9f9}vKJy9NDBjBW zSt=Hcs=YWCwnfY1UYx*+msp{g!w0HC<_SM!VL1(I2PE?CS}r(eh?{I)mQixmo5^p# zV?2R!R@3GV6hwTCrfHiK#3Orj>I!GS2kYhk1S;aFBD_}u2v;0HYFq}Iz1Z(I4oca4 zxquja8$+8JW_EagDHf$a1OTk5S97umGSDaj)gH=fLs9>_=XvVj^Xj9a#gLdk=&3tl zfmK9MNnIX9v{?%xdw7568 zNrZ|roYs(vC4pHB5RJ8>)^*OuyNC>x7ad)tB_}3SgQ96+-JT^Qi<`xi=)_=$Skwv~ zdqeT9Pa`LYvCAn&rMa2aCDV(TMI#PA5g#RtV|CWpgDYRA^|55LLN^uNh*gOU>Z=a06qJ;$C9z8;n-Pq=qZnc1zUwJ@t)L;&NN+E5m zRkQ(SeM8=l-aoAKGKD>!@?mWTW&~)uF2PYUJ;tB^my`r9n|Ly~0c%diYzqs9W#FTjy?h&X3TnH zXqA{QI82sdjPO->f=^K^f>N`+B`q9&rN0bOXO79S&a9XX8zund(kW7O76f4dcWhIu zER`XSMSFbSL>b;Rp#`CuGJ&p$s~G|76){d?xSA5wVg##_O0DrmyEYppyBr%fyWbbv zp`K84JwRNP$d-pJ!Qk|(RMr?*!wi1if-9G#0p>>1QXKXWFy)eB3ai)l3601q8!9JC zvU#ZWWDNKq9g6fYs?JQ)Q4C_cgTy3FhgKb8s&m)DdmL5zhNK#8wWg!J*7G7Qhe9VU zha?^AQTDpYcuN!B+#1dE*X{<#!M%zfUQbj=zLE{dW0XeQ7-oIsGY6RbkP2re@Q{}r_$iiH0xU%iN*ST`A)-EH6eaZB$GA#v)cLi z*MpA(3bYk$oBDKAzu^kJoSUsDd|856DApz={3u8sbQV@JnRkp2nC|)m;#T=DvIL-O zI4vh;g7824l}*`_p@MT4+d`JZ2%6NQh=N9bmgJ#q!hK@_<`HQq3}Z8Ij>3%~<*= zcv=!oT#5xmeGI92lqm9sGVE%#X$ls;St|F#u!?5Y7syhx6q#MVRa&lBmmn%$C0QzU z);*ldgwwCmzM3uglr}!Z2G+?& zf%Dpo&mD%2ZcNFiN-Z0f;c_Q;A%f@>26f?{d1kxIJD}LxsQkB47SAdwinfMILZdN3 zfj^HmTzS3Ku5BxY>ANutS8WPQ-G>v4^_Qndy==P3pDm+Xc?>rUHl-4+^%Sp5atOja z2oP}ftw-rqnb}+khR3CrRg^ibi6?QYk1*i^;kQGirQ=uB9Sd1NTfT-Rbv;hqnY4neE5H1YUrjS2m+2&@uXiAo- zrKUX|Ohg7(6F(AoP~tj;NZlV#xsfo-5reuQHB$&EIAhyZk;bL;k9ouDmJNBAun;H& zn;Of1z_Qj`x&M;5X;{s~iGzBQTY^kv-k{ksbE*Dl%Qf%N@hQCfY~iUw!=F-*$cpf2 z3wix|aLBV0b;W@z^%7S{>9Z^T^fLOI68_;l@+Qzaxo`nAI8emTV@rRhEKZ z?*z_{oGdI~R*#<2{bkz$G~^Qef}$*4OYTgtL$e9q!FY7EqxJ2`zk6SQc}M(k(_MaV zSLJnTXw&@djco1~a(vhBl^&w=$fa9{Sru>7g8SHahv$&Bl(D@(Zwxo_3r=;VH|uc5 zi1Ny)J!<(KN-EcQ(xlw%PNwK8U>4$9nVOhj(y0l9X^vP1TA>r_7WtSExIOsz`nDOP zs}d>Vxb2Vo2e5x8p(n~Y5ggAyvib>d)6?)|E@{FIz?G3PVGLf7-;BxaP;c?7ddH$z zA+{~k^V=bZuXafOv!RPsE1GrR3J2TH9uB=Z67gok+u`V#}BR86hB1xl}H4v`F+mRfr zYhortD%@IGfh!JB(NUNSDh+qDz?4ztEgCz&bIG-Wg7w-ua4ChgQR_c+z8dT3<1?uX z*G(DKy_LTl*Ea!%v!RhpCXW1WJO6F`bgS-SB;Xw9#! z<*K}=#wVu9$`Yo|e!z-CPYH!nj7s9dEPr-E`DXUBu0n!xX~&|%#G=BeM?X@shQQMf zMvr2!y7p_gD5-!Lnm|a@z8Of^EKboZsTMk%5VsJEm>VsJ4W7Kv{<|#4f-qDE$D-W>gWT%z-!qXnDHhOvLk=?^a1*|0j z{pW{M0{#1VcR5;F!!fIlLVNh_Gj zbnW(_j?0c2q$EHIi@fSMR{OUKBcLr{Y&$hrM8XhPByyZaXy|dd&{hYQRJ9@Fn%h3p7*VQolBIV@Eq`=y%5BU~3RPa^$a?ixp^cCg z+}Q*X+CW9~TL29@OOng(#OAOd!)e$d%sr}^KBJ-?-X&|4HTmtemxmp?cT3uA?md4% zT8yZ0U;6Rg6JHy3fJae{6TMGS?ZUX6+gGTT{Q{)SI85$5FD{g-eR%O0KMpWPY`4@O zx!hen1*8^E(*}{m^V_?}(b5k3hYo=T+$&M32+B`}81~KKZhY;2H{7O-M@vbCzuX0n zW-&HXeyr1%I3$@ns-V1~Lb@wIpkmx|8I~ob1Of7i6BTNysEwI}=!nU%q7(V_^+d*G z7G;07m(CRTJup!`cdYi93r^+LY+`M*>aMuHJm(A8_O8C#A*$!Xvddgpjx5)?_EB*q zgE8o5O>e~9IiSC@WtZpF{4Bj2J5eZ>uUzY%TgWF7wdDE!fSQIAWCP)V{;HsU3ap?4 znRsiiDbtN7i9hapO;(|Ew>Ip2TZSvK9Z^N21%J?OiA_&eP1{(Pu_=%JjKy|HOardq ze?zK^K zA%sjF64*Wufad%H<) z^|t>e*h+Z1#l=5wHexzt9HNDNXgM=-OPWKd^5p!~%SIl>Fo&7BvNpbf8{NXmH)o{r zO=aBJ;meX1^{O%q;kqdw*5k!Y7%t_30 zy{nGRVc&5qt?dBwLs+^Sfp;f`YVMSB#C>z^a9@fpZ!xb|b-JEz1LBX7ci)V@W+kvQ89KWA0T~Lj$aCcfW#nD5bt&Y_< z-q{4ZXDqVg?|0o)j1%l0^_it0WF*LCn-+)c!2y5yS7aZIN$>0LqNnkujV*YVes(v$ zY@_-!Q;!ZyJ}Bg|G-~w@or&u0RO?vlt5*9~yeoPV_UWrO2J54b4#{D(D>jF(R88u2 zo#B^@iF_%S>{iXSol8jpmsZuJ?+;epg>k=$d`?GSegAVp3n$`GVDvK${N*#L_1`44 z{w0fL{2%)0|E+qgZtjX}itZz^KJt4Y;*8uSK}Ft38+3>j|K(PxIXXR-t4VopXo#9# zt|F{LWr-?34y`$nLBVV_*UEgA6AUI65dYIbqpNq9cl&uLJ0~L}<=ESlOm?Y-S@L*d z<7vt}`)TW#f%Rp$Q}6@3=j$7Tze@_uZO@aMn<|si{?S}~maII`VTjs&?}jQ4_cut9$)PEqMukwoXobzaKx^MV z2fQwl+;LSZ$qy%Tys0oo^K=jOw$!YwCv^ei4NBVauL)tN%=wz9M{uf{IB(BxK|lT*pFkmNK_1tV`nb%jH=a0~VNq2RCKY(rG7jz!-D^k)Ec)yS%17pE#o6&eY+ z^qN(hQT$}5F(=4lgNQhlxj?nB4N6ntUY6(?+R#B?W3hY_a*)hnr4PA|vJ<6p`K3Z5Hy z{{8(|ux~NLUW=!?9Qe&WXMTAkQnLXg(g=I@(VG3{HE13OaUT|DljyWXPs2FE@?`iU z4GQlM&Q=T<4&v@Fe<+TuXiZQT3G~vZ&^POfmI1K2h6t4eD}Gk5XFGpbj1n_g*{qmD6Xy z`6Vv|lLZtLmrnv*{Q%xxtcWVj3K4M%$bdBk_a&ar{{GWyu#ljM;dII;*jP;QH z#+^o-A4np{@|Mz+LphTD0`FTyxYq#wY)*&Ls5o{0z9yg2K+K7ZN>j1>N&;r+Z`vI| zDzG1LJZ+sE?m?>x{5LJx^)g&pGEpY=fQ-4}{x=ru;}FL$inHemOg%|R*ZXPodU}Kh zFEd5#+8rGq$Y<_?k-}r5zgQ3jRV=ooHiF|@z_#D4pKVEmn5CGV(9VKCyG|sT9nc=U zEoT67R`C->KY8Wp-fEcjjFm^;Cg(ls|*ABVHq8clBE(;~K^b+S>6uj70g? z&{XQ5U&!Z$SO7zfP+y^8XBbiu*Cv-yJG|l-oe*!s5$@Lh_KpxYL2sx`B|V=dETN>5K+C+CU~a_3cI8{vbu$TNVdGf15*>D zz@f{zIlorkY>TRh7mKuAlN9A0>N>SV`X)+bEHms=mfYTMWt_AJtz_h+JMmrgH?mZt zm=lfdF`t^J*XLg7v+iS)XZROygK=CS@CvUaJo&w2W!Wb@aa?~Drtf`JV^cCMjngVZ zv&xaIBEo8EYWuML+vxCpjjY^s1-ahXJzAV6hTw%ZIy!FjI}aJ+{rE&u#>rs)vzuxz z+$5z=7W?zH2>Eb32dvgHYZtCAf!=OLY-pb4>Ae79rd68E2LkVPj-|jFeyqtBCCwiW zkB@kO_(3wFq)7qwV}bA=zD!*@UhT`geq}ITo%@O(Z5Y80nEX~;0-8kO{oB6|(4fQh z);73T!>3@{ZobPwRv*W?7m0Ml9GmJBCJd&6E?hdj9lV= z4flNfsc(J*DyPv?RCOx!MSvk(M952PJ-G|JeVxWVjN~SNS6n-_Ge3Q;TGE;EQvZg86%wZ`MB zSMQua(i*R8a75!6$QRO^(o7sGoomb+Y{OMy;m~Oa`;P9Yqo>?bJAhqXxLr7_3g_n>f#UVtxG!^F#1+y@os6x(sg z^28bsQ@8rw%Gxk-stAEPRbv^}5sLe=VMbkc@Jjimqjvmd!3E7+QnL>|(^3!R} zD-l1l7*Amu@j+PWLGHXXaFG0Ct2Q=}5YNUxEQHCAU7gA$sSC<5OGylNnQUa>>l%sM zyu}z6i&({U@x^hln**o6r2s-(C-L50tQvz|zHTqW!ir?w&V23tuYEDJVV#5pE|OJu z7^R!A$iM$YCe?8n67l*J-okwfZ+ZTkGvZ)tVPfR;|3gyFjF)8V zyXXN=!*bpyRg9#~Bg1+UDYCt0 ztp4&?t1X0q>uz;ann$OrZs{5*r`(oNvw=$7O#rD|Wuv*wIi)4b zGtq4%BX+kkagv3F9Id6~-c+1&?zny%w5j&nk9SQfo0k4LhdSU_kWGW7axkfpgR`8* z!?UTG*Zi_baA1^0eda8S|@&F z{)Rad0kiLjB|=}XFJhD(S3ssKlveFFmkN{Vl^_nb!o5M!RC=m)V&v2%e?ZoRC@h3> zJ(?pvToFd`*Zc@HFPL#=otWKwtuuQ_dT-Hr{S%pQX<6dqVJ8;f(o)4~VM_kEQkMR+ zs1SCVi~k>M`u1u2xc}>#D!V&6nOOh-E$O&SzYrjJdZpaDv1!R-QGA141WjQe2s0J~ zQ;AXG)F+K#K8_5HVqRoRM%^EduqOnS(j2)|ctA6Q^=|s_WJYU;Z%5bHp08HPL`YF2 zR)Ad1z{zh`=sDs^&V}J z%$Z$!jd7BY5AkT?j`eqMs%!Gm@T8)4w3GYEX~IwgE~`d|@T{WYHkudy(47brgHXx& zBL1yFG6!!!VOSmDxBpefy2{L_u5yTwja&HA!mYA#wg#bc-m%~8aRR|~AvMnind@zs zy>wkShe5&*un^zvSOdlVu%kHsEo>@puMQ`b1}(|)l~E{5)f7gC=E$fP(FC2=F<^|A zxeIm?{EE!3sO!Gr7e{w)Dx(uU#3WrFZ>ibmKSQ1tY?*-Nh1TDHLe+k*;{Rp!Bmd_m zb#^kh`Y*8l|9Cz2e{;RL%_lg{#^Ar+NH|3z*Zye>!alpt{z;4dFAw^^H!6ING*EFc z_yqhr8d!;%nHX9AKhFQZBGrSzfzYCi%C!(Q5*~hX>)0N`vbhZ@N|i;_972WSx*>LH z87?en(;2_`{_JHF`Sv6Wlps;dCcj+8IJ8ca6`DsOQCMb3n# z3)_w%FuJ3>fjeOOtWyq)ag|PmgQbC-s}KRHG~enBcIwqIiGW8R8jFeBNY9|YswRY5 zjGUxdGgUD26wOpwM#8a!Nuqg68*dG@VM~SbOroL_On0N6QdT9?)NeB3@0FCC?Z|E0 z6TPZj(AsPtwCw>*{eDEE}Gby>0q{*lI+g2e&(YQrsY&uGM{O~}(oM@YWmb*F zA0^rr5~UD^qmNljq$F#ARXRZ1igP`MQx4aS6*MS;Ot(1L5jF2NJ;de!NujUYg$dr# z=TEL_zTj2@>ZZN(NYCeVX2==~=aT)R30gETO{G&GM4XN<+!&W&(WcDP%oL8PyIVUC zs5AvMgh6qr-2?^unB@mXK*Dbil^y-GTC+>&N5HkzXtozVf93m~xOUHn8`HpX=$_v2 z61H;Z1qK9o;>->tb8y%#4H)765W4E>TQ1o0PFj)uTOPEvv&}%(_mG0ISmyhnQV33Z$#&yd{ zc{>8V8XK$3u8}04CmAQ#I@XvtmB*s4t8va?-IY4@CN>;)mLb_4!&P3XSw4pA_NzDb zORn!blT-aHk1%Jpi>T~oGLuh{DB)JIGZ9KOsciWs2N7mM1JWM+lna4vkDL?Q)z_Ct z`!mi0jtr+4*L&N7jk&LodVO#6?_qRGVaucqVB8*us6i3BTa^^EI0x%EREQSXV@f!lak6Wf1cNZ8>*artIJ(ADO*=<-an`3zB4d*oO*8D1K!f z*A@P1bZCNtU=p!742MrAj%&5v%Xp_dSX@4YCw%F|%Dk=u|1BOmo)HsVz)nD5USa zR~??e61sO(;PR)iaxK{M%QM_rIua9C^4ppVS$qCT9j2%?*em?`4Z;4@>I(c%M&#cH z>4}*;ej<4cKkbCAjjDsyKS8rIm90O)Jjgyxj5^venBx&7B!xLmzxW3jhj7sR(^3Fz z84EY|p1NauwXUr;FfZjdaAfh%ivyp+^!jBjJuAaKa!yCq=?T_)R!>16?{~p)FQ3LDoMyG%hL#pR!f@P%*;#90rs_y z@9}@r1BmM-SJ#DeuqCQk=J?ixDSwL*wh|G#us;dd{H}3*-Y7Tv5m=bQJMcH+_S`zVtf;!0kt*(zwJ zs+kedTm!A}cMiM!qv(c$o5K%}Yd0|nOd0iLjus&;s0Acvoi-PFrWm?+q9f^FslxGi z6ywB`QpL$rJzWDg(4)C4+!2cLE}UPCTBLa*_=c#*$b2PWrRN46$y~yST3a2$7hEH= zNjux+wna^AzQ=KEa_5#9Ph=G1{S0#hh1L3hQ`@HrVnCx{!fw_a0N5xV(iPdKZ-HOM za)LdgK}1ww*C_>V7hbQnTzjURJL`S%`6nTHcgS+dB6b_;PY1FsrdE8(2K6FN>37!62j_cBlui{jO^$dPkGHV>pXvW0EiOA zqW`YaSUBWg_v^Y5tPJfWLcLpsA8T zG)!x>pKMpt!lv3&KV!-um= zKCir6`bEL_LCFx4Z5bAFXW$g3Cq`?Q%)3q0r852XI*Der*JNuKUZ`C{cCuu8R8nkt z%pnF>R$uY8L+D!V{s^9>IC+bmt<05h**>49R*#vpM*4i0qRB2uPbg8{{s#9yC;Z18 zD7|4m<9qneQ84uX|J&f-g8a|nFKFt34@Bt{CU`v(SYbbn95Q67*)_Esl_;v291s=9 z+#2F2apZU4Tq=x+?V}CjwD(P=U~d<=mfEFuyPB`Ey82V9G#Sk8H_Ob_RnP3s?)S_3 zr%}Pb?;lt_)Nf>@zX~D~TBr;-LS<1I##8z`;0ZCvI_QbXNh8Iv)$LS=*gHr;}dgb=w5$3k2la1keIm|=7<-JD>)U%=Avl0Vj@+&vxn zt-)`vJxJr88D&!}2^{GPXc^nmRf#}nb$4MMkBA21GzB`-Or`-3lq^O^svO7Vs~FdM zv`NvzyG+0T!P8l_&8gH|pzE{N(gv_tgDU7SWeiI-iHC#0Ai%Ixn4&nt{5y3(GQs)i z&uA;~_0shP$0Wh0VooIeyC|lak__#KVJfxa7*mYmZ22@(<^W}FdKjd*U1CqSjNKW% z*z$5$=t^+;Ui=MoDW~A7;)Mj%ibX1_p4gu>RC}Z_pl`U*{_z@+HN?AF{_W z?M_X@o%w8fgFIJ$fIzBeK=v#*`mtY$HC3tqw7q^GCT!P$I%=2N4FY7j9nG8aIm$c9 zeKTxVKN!UJ{#W)zxW|Q^K!3s;(*7Gbn;e@pQBCDS(I|Y0euK#dSQ_W^)sv5pa%<^o zyu}3d?Lx`)3-n5Sy9r#`I{+t6x%I%G(iewGbvor&I^{lhu-!#}*Q3^itvY(^UWXgvthH52zLy&T+B)Pw;5>4D6>74 zO_EBS)>l!zLTVkX@NDqyN2cXTwsUVao7$HcqV2%t$YzdAC&T)dwzExa3*kt9d(}al zA~M}=%2NVNUjZiO7c>04YH)sRelXJYpWSn^aC$|Ji|E13a^-v2MB!Nc*b+=KY7MCm zqIteKfNkONq}uM;PB?vvgQvfKLPMB8u5+Am=d#>g+o&Ysb>dX9EC8q?D$pJH!MTAqa=DS5$cb+;hEvjwVfF{4;M{5U&^_+r zvZdu_rildI!*|*A$TzJ&apQWV@p{!W`=?t(o0{?9y&vM)V)ycGSlI3`;ps(vf2PUq zX745#`cmT*ra7XECC0gKkpu2eyhFEUb?;4@X7weEnLjXj_F~?OzL1U1L0|s6M+kIhmi%`n5vvDALMagi4`wMc=JV{XiO+^ z?s9i7;GgrRW{Mx)d7rj)?(;|b-`iBNPqdwtt%32se@?w4<^KU&585_kZ=`Wy^oLu9 z?DQAh5z%q;UkP48jgMFHTf#mj?#z|=w= z(q6~17Vn}P)J3M?O)x))%a5+>TFW3No~TgP;f}K$#icBh;rSS+R|}l鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..3fa8f862f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..1aa94a426 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..93e3f59f1 --- /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 000000000..0795530c5 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'springboot-board-jpa' diff --git a/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java b/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java new file mode 100644 index 000000000..0f6fbbafa --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java @@ -0,0 +1,12 @@ +package jehs.springbootboardjpa; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringbootBoardJpaApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringbootBoardJpaApplication.class, args); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ + From b223165a233e735414aefdc44baebf8d639c3c61 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 00:24:16 +0900 Subject: [PATCH 02/51] =?UTF-8?q?config:=20yaml=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 1 - src/main/resources/application.yaml | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yaml diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b1378917..000000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 000000000..dc50caccc --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,16 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/default_db?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf8&useSSL=true + username: root + password: local59! + jpa: + open-in-view: false + database: mysql + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + defer-datasource-initialization: true \ No newline at end of file From 63b5c68f7fc275abed348fe9e9596ce30efbd582 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 00:24:41 +0900 Subject: [PATCH 03/51] =?UTF-8?q?feat:=20BaseEntity=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springbootboardjpa/entity/BaseEntity.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java diff --git a/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java b/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java new file mode 100644 index 000000000..9237b19e3 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java @@ -0,0 +1,17 @@ +package jehs.springbootboardjpa.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; + +import java.time.LocalDateTime; + +@MappedSuperclass +public class BaseEntity { + + @Column(name = "created_at", columnDefinition = "datetime") + private LocalDateTime createdAt; + @Column(name = "updated_at", columnDefinition = "datetime") + private LocalDateTime updatedAt; + @Column(name = "deleted_at", columnDefinition = "datetime") + private LocalDateTime deletedAt; +} From b923f63fb604f39bef2234165efd50a877bf8205 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 00:25:00 +0900 Subject: [PATCH 04/51] =?UTF-8?q?feat:=20User=20Entity=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jehs/springbootboardjpa/entity/User.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/entity/User.java diff --git a/src/main/java/jehs/springbootboardjpa/entity/User.java b/src/main/java/jehs/springbootboardjpa/entity/User.java new file mode 100644 index 000000000..bfa1e9ac4 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/entity/User.java @@ -0,0 +1,30 @@ +package jehs.springbootboardjpa.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Entity +@Getter +@Setter +@Table(name = "users") +public class User extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name", nullable = false, length = 59) + private String name; + + @Column(name = "age") + private Long age; + + @Column(name = "hobby", length = 59) + private String hobby; + + @OneToMany(mappedBy = "user") + private List posts; +} From 8fd8a61788dac452b0f57f2e4e37ff94de78b575 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 00:25:11 +0900 Subject: [PATCH 05/51] =?UTF-8?q?feat:=20Post=20Entity=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jehs/springbootboardjpa/entity/Post.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/entity/Post.java diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java new file mode 100644 index 000000000..71f5f68a6 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -0,0 +1,33 @@ +package jehs.springbootboardjpa.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "posts") +public class Post extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "title", length = 59) + private String title; + + @Column(name = "content", length = 1000) + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @Enumerated(EnumType.STRING) + private PostType postType; + + enum PostType { + FREE, INFO, MARKET, EMPLOYMENT + } +} From e2e231ba8e9b0b64bfa31e91933b9550f1ec4223 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:40:16 +0900 Subject: [PATCH 06/51] =?UTF-8?q?feat:=20Entity=EA=B0=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C,=20=EC=83=9D=EC=84=B1/=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EC=8B=9C=EA=B0=84=20=EC=9E=90=EB=8F=99=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../SpringbootBoardJpaApplication.java | 2 ++ .../springbootboardjpa/entity/BaseEntity.java | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java b/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java index 0f6fbbafa..19b0f0cee 100644 --- a/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java +++ b/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class SpringbootBoardJpaApplication { public static void main(String[] args) { diff --git a/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java b/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java index 9237b19e3..9a343c03d 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java +++ b/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java @@ -1,17 +1,26 @@ package jehs.springbootboardjpa.entity; import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import lombok.Builder; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; @MappedSuperclass -public class BaseEntity { +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + @Builder.Default @Column(name = "created_at", columnDefinition = "datetime") - private LocalDateTime createdAt; + private LocalDateTime createdAt = LocalDateTime.now(); + @Builder.Default + @LastModifiedDate @Column(name = "updated_at", columnDefinition = "datetime") - private LocalDateTime updatedAt; + private LocalDateTime updatedAt = LocalDateTime.now(); @Column(name = "deleted_at", columnDefinition = "datetime") private LocalDateTime deletedAt; } From bd6a35d0fc53544d65ed104596d19420c577f18a Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:42:04 +0900 Subject: [PATCH 07/51] =?UTF-8?q?feat:=20=EC=97=94=ED=84=B0=ED=8B=B0=20set?= =?UTF-8?q?ter=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20builder=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/entity/Post.java | 10 +++++++--- src/main/java/jehs/springbootboardjpa/entity/User.java | 4 +--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index 71f5f68a6..bf68493c0 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -1,13 +1,17 @@ package jehs.springbootboardjpa.entity; import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; -import lombok.Setter; +import lombok.NoArgsConstructor; -@Getter -@Setter @Entity @Table(name = "posts") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor public class Post extends BaseEntity { @Id diff --git a/src/main/java/jehs/springbootboardjpa/entity/User.java b/src/main/java/jehs/springbootboardjpa/entity/User.java index bfa1e9ac4..137b0e11a 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/User.java +++ b/src/main/java/jehs/springbootboardjpa/entity/User.java @@ -2,14 +2,12 @@ import jakarta.persistence.*; import lombok.Getter; -import lombok.Setter; import java.util.List; @Entity -@Getter -@Setter @Table(name = "users") +@Getter public class User extends BaseEntity { @Id From 46d5028130f4c6a00d9db6b3c715ca38ff7c8bd6 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:42:57 +0900 Subject: [PATCH 08/51] =?UTF-8?q?feat:=20Post,=20User=20Repository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../jehs/springbootboardjpa/repository/PostRepository.java | 7 +++++++ .../jehs/springbootboardjpa/repository/UserRepository.java | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/repository/PostRepository.java create mode 100644 src/main/java/jehs/springbootboardjpa/repository/UserRepository.java diff --git a/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java b/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java new file mode 100644 index 000000000..5fb0d109a --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java @@ -0,0 +1,7 @@ +package jehs.springbootboardjpa.repository; + +import jehs.springbootboardjpa.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostRepository extends JpaRepository { +} diff --git a/src/main/java/jehs/springbootboardjpa/repository/UserRepository.java b/src/main/java/jehs/springbootboardjpa/repository/UserRepository.java new file mode 100644 index 000000000..2b0cbade7 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/repository/UserRepository.java @@ -0,0 +1,7 @@ +package jehs.springbootboardjpa.repository; + +import jehs.springbootboardjpa.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} From 65b60c6999b41e376bb11e75f3bd39b01a20780c Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:45:19 +0900 Subject: [PATCH 09/51] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../dto/PostCreateRequest.java | 25 +++++++++++++++++ .../jehs/springbootboardjpa/entity/Post.java | 9 +++++- .../service/PostService.java | 28 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/main/java/jehs/springbootboardjpa/dto/PostCreateRequest.java create mode 100644 src/main/java/jehs/springbootboardjpa/service/PostService.java diff --git a/src/main/java/jehs/springbootboardjpa/dto/PostCreateRequest.java b/src/main/java/jehs/springbootboardjpa/dto/PostCreateRequest.java new file mode 100644 index 000000000..8a0aa59d7 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/dto/PostCreateRequest.java @@ -0,0 +1,25 @@ +package jehs.springbootboardjpa.dto; + +import jehs.springbootboardjpa.entity.Post; +import jehs.springbootboardjpa.entity.User; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class PostCreateRequest { + + private final String title; + private final String content; + private final Long userId; + private final Post.PostType postType; + + public Post toEntity(User user) { + return Post.builder() + .title(title) + .content(content) + .user(user) + .postType(postType) + .build(); + } +} diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index bf68493c0..52298544c 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -1,6 +1,7 @@ package jehs.springbootboardjpa.entity; import jakarta.persistence.*; +import jehs.springbootboardjpa.dto.PostUpdateRequest; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -31,7 +32,13 @@ public class Post extends BaseEntity { @Enumerated(EnumType.STRING) private PostType postType; - enum PostType { + private void validateUser(User updateUser) { + if (!updateUser.getId().equals(this.user.getId())) { + throw new RuntimeException("작성자의 게시글이 아닙니다."); + } + } + + public enum PostType { FREE, INFO, MARKET, EMPLOYMENT } } diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java new file mode 100644 index 000000000..77d3b1e4c --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -0,0 +1,28 @@ +package jehs.springbootboardjpa.service; + +import jehs.springbootboardjpa.dto.PostCreateRequest; +import jehs.springbootboardjpa.dto.PostUpdateRequest; +import jehs.springbootboardjpa.entity.Post; +import jehs.springbootboardjpa.entity.User; +import jehs.springbootboardjpa.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class PostService { + + private final PostRepository postRepository; + private final UserService userService; + + public void createPost(PostCreateRequest postCreateRequest) { + User user = userService.getUserById(postCreateRequest.getUserId()); + postRepository.save(postCreateRequest.toEntity(user)); + } +} From 190bae61c27518b4d5619007166424f3c4da1829 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:47:07 +0900 Subject: [PATCH 10/51] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8A=B9=EC=A0=95=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=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?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../java/jehs/springbootboardjpa/service/PostService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index 77d3b1e4c..6ea26c33f 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -25,4 +25,11 @@ public void createPost(PostCreateRequest postCreateRequest) { User user = userService.getUserById(postCreateRequest.getUserId()); postRepository.save(postCreateRequest.toEntity(user)); } + public Post getPostById(Long postId) { + return postRepository.findById(postId).orElseThrow(() -> new RuntimeException("존재하지 않는 게시글입니다.")); + } + + public Page getAllPosts(Pageable pageable) { + return postRepository.findAll(pageable); + } } From 33aa13b8c9dd64f75625edf82c846a58249e3c1a Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:49:16 +0900 Subject: [PATCH 11/51] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../dto/PostUpdateRequest.java | 13 +++++++++++++ .../jehs/springbootboardjpa/entity/Post.java | 6 ++++++ .../service/PostService.java | 8 ++++++++ .../service/UserService.java | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/dto/PostUpdateRequest.java create mode 100644 src/main/java/jehs/springbootboardjpa/service/UserService.java diff --git a/src/main/java/jehs/springbootboardjpa/dto/PostUpdateRequest.java b/src/main/java/jehs/springbootboardjpa/dto/PostUpdateRequest.java new file mode 100644 index 000000000..eff838466 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/dto/PostUpdateRequest.java @@ -0,0 +1,13 @@ +package jehs.springbootboardjpa.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class PostUpdateRequest { + + private final String title; + private final String content; + private final Long userId; +} diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index 52298544c..8b1c67a2b 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -32,6 +32,12 @@ public class Post extends BaseEntity { @Enumerated(EnumType.STRING) private PostType postType; + public void updatePost(PostUpdateRequest postUpdateRequest, User user) { + validateUser(user); + this.title = postUpdateRequest.getTitle(); + this.content = postUpdateRequest.getContent(); + } + private void validateUser(User updateUser) { if (!updateUser.getId().equals(this.user.getId())) { throw new RuntimeException("작성자의 게시글이 아닙니다."); diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index 6ea26c33f..590228021 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -25,6 +25,14 @@ public void createPost(PostCreateRequest postCreateRequest) { User user = userService.getUserById(postCreateRequest.getUserId()); postRepository.save(postCreateRequest.toEntity(user)); } + + @Transactional + public void updatePost(Long postId, PostUpdateRequest postUpdateRequest) { + Post post = getPostById(postId); + User user = userService.getUserById(postUpdateRequest.getUserId()); + post.updatePost(postUpdateRequest, user); + } + public Post getPostById(Long postId) { return postRepository.findById(postId).orElseThrow(() -> new RuntimeException("존재하지 않는 게시글입니다.")); } diff --git a/src/main/java/jehs/springbootboardjpa/service/UserService.java b/src/main/java/jehs/springbootboardjpa/service/UserService.java new file mode 100644 index 000000000..5085b8a7d --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/service/UserService.java @@ -0,0 +1,19 @@ +package jehs.springbootboardjpa.service; + +import jehs.springbootboardjpa.entity.User; +import jehs.springbootboardjpa.repository.UserRepository; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + + public User getUserById(Long userId){ + return userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("존재하지 않는 회원입니다.")); + } +} From b59bdb85fe9f8d52c5b90b10f0b50ca7368babd6 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:50:16 +0900 Subject: [PATCH 12/51] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20Rest=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 게시글 전체 조회 - 특정 게시글 조회 - 게시글 생성 - 게시글 수정 Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../controller/PostController.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/controller/PostController.java diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java new file mode 100644 index 000000000..e1611f0c4 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -0,0 +1,42 @@ +package jehs.springbootboardjpa.controller; + +import jehs.springbootboardjpa.dto.PostCreateRequest; +import jehs.springbootboardjpa.dto.PostUpdateRequest; +import jehs.springbootboardjpa.entity.Post; +import jehs.springbootboardjpa.repository.PostRepository; +import jehs.springbootboardjpa.service.PostService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/posts") +public class PostController { + + private final PostService postService; + + @GetMapping("/{id}") + public Post getPost(@PathVariable(name = "id") Long postId){ + return postService.getPostById(postId); + } + + @GetMapping + public Page getAllPosts(@PageableDefault Pageable pageable){ + return postService.getAllPosts(pageable); + } + + @PostMapping + public void createPost(@RequestBody PostCreateRequest postCreateRequest){ + postService.createPost(postCreateRequest); + } + + @PatchMapping("/{id}") + public void updatePost(@PathVariable(name = "id") Long postId, @RequestBody PostUpdateRequest postUpdateRequest){ + postService.updatePost(postId, postUpdateRequest); + } +} From f4fb87ac47f741ce7540cc6e76ff4b1ef514957c Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:50:43 +0900 Subject: [PATCH 13/51] =?UTF-8?q?chore:=20=EC=8B=A4=ED=96=89=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=9E=84=EC=8B=9C=20=EA=B0=92=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/entity/Post.java | 2 +- src/main/java/jehs/springbootboardjpa/entity/User.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index 8b1c67a2b..4816e7a94 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -25,7 +25,7 @@ public class Post extends BaseEntity { @Column(name = "content", length = 1000) private String content; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "user_id") private User user; diff --git a/src/main/java/jehs/springbootboardjpa/entity/User.java b/src/main/java/jehs/springbootboardjpa/entity/User.java index 137b0e11a..342ad95eb 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/User.java +++ b/src/main/java/jehs/springbootboardjpa/entity/User.java @@ -23,6 +23,6 @@ public class User extends BaseEntity { @Column(name = "hobby", length = 59) private String hobby; - @OneToMany(mappedBy = "user") - private List posts; +// @OneToMany(mappedBy = "user") +// private List posts; } From 51da029dae9411ccda86f6eeb28f06889af896f8 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 15:23:01 +0900 Subject: [PATCH 14/51] =?UTF-8?q?feat:=20BaseEntity=20Getter=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java b/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java index 9a343c03d..c393ade57 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java +++ b/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java @@ -4,12 +4,13 @@ import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import lombok.Builder; -import org.springframework.data.annotation.CreatedDate; +import lombok.Getter; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; +@Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class BaseEntity { From 10217c9530cfc68617b41808a75e666c1ceb6280 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 15:24:38 +0900 Subject: [PATCH 15/51] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EB=B3=B5?= =?UTF-8?q?=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/entity/Post.java | 2 +- src/main/java/jehs/springbootboardjpa/entity/User.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index 4816e7a94..8b1c67a2b 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -25,7 +25,7 @@ public class Post extends BaseEntity { @Column(name = "content", length = 1000) private String content; - @ManyToOne(fetch = FetchType.EAGER) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; diff --git a/src/main/java/jehs/springbootboardjpa/entity/User.java b/src/main/java/jehs/springbootboardjpa/entity/User.java index 342ad95eb..e871cc3e9 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/User.java +++ b/src/main/java/jehs/springbootboardjpa/entity/User.java @@ -23,6 +23,6 @@ public class User extends BaseEntity { @Column(name = "hobby", length = 59) private String hobby; -// @OneToMany(mappedBy = "user") -// private List posts; + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + private List posts; } From 3b68d6c9938f2dc08164a0002b67a10ff13b10ad Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 15:25:04 +0900 Subject: [PATCH 16/51] =?UTF-8?q?feat:=20Post=20=EC=A1=B0=ED=9A=8C=20Respo?= =?UTF-8?q?nse=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../springbootboardjpa/dto/PostResponse.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/dto/PostResponse.java diff --git a/src/main/java/jehs/springbootboardjpa/dto/PostResponse.java b/src/main/java/jehs/springbootboardjpa/dto/PostResponse.java new file mode 100644 index 000000000..78e2ad0ed --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/dto/PostResponse.java @@ -0,0 +1,28 @@ +package jehs.springbootboardjpa.dto; + +import jehs.springbootboardjpa.entity.Post; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class PostResponse { + + private final Long id; + private final String title; + private final String content; + private final Post.PostType postType; + private final LocalDateTime createdAt; + private final LocalDateTime updatedAt; + private final UserResponse userResponse; + + public PostResponse(Post post) { + this.id = post.getId(); + this.title = post.getTitle(); + this.content = post.getContent(); + this.postType = post.getPostType(); + this.createdAt = post.getCreatedAt(); + this.updatedAt = post.getUpdatedAt(); + this.userResponse = new UserResponse(post.getUser()); + } +} From 2ed642ccc77c19b80cc0ebd3c8fb31bab77d4bf1 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 15:25:44 +0900 Subject: [PATCH 17/51] =?UTF-8?q?feat:=20User=20=EC=A1=B0=ED=9A=8C=20Respo?= =?UTF-8?q?nse=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../springbootboardjpa/dto/UserResponse.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/dto/UserResponse.java diff --git a/src/main/java/jehs/springbootboardjpa/dto/UserResponse.java b/src/main/java/jehs/springbootboardjpa/dto/UserResponse.java new file mode 100644 index 000000000..9c60b1c38 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/dto/UserResponse.java @@ -0,0 +1,20 @@ +package jehs.springbootboardjpa.dto; + +import jehs.springbootboardjpa.entity.User; +import lombok.Getter; + +@Getter +public class UserResponse { + + private final Long id; + private final String name; + private final Long age; + private final String hobby; + + public UserResponse(User user) { + this.id = user.getId(); + this.name = user.getName(); + this.age = user.getAge(); + this.hobby = user.getHobby(); + } +} From b449a6e1b2594b723b4e543301169abbc4dbab8a Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Thu, 23 Nov 2023 15:31:44 +0900 Subject: [PATCH 18/51] =?UTF-8?q?mod:=20Post=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EA=B7=B8=EB=8C=80=EB=A1=9C=20=EB=85=B8=EC=B6=9C=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EA=B2=83=20=EB=B3=80=EA=B2=BD(PostResponse=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../controller/PostController.java | 14 ++++++-------- .../service/PostService.java | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index e1611f0c4..83afb8481 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -1,9 +1,8 @@ package jehs.springbootboardjpa.controller; import jehs.springbootboardjpa.dto.PostCreateRequest; +import jehs.springbootboardjpa.dto.PostResponse; import jehs.springbootboardjpa.dto.PostUpdateRequest; -import jehs.springbootboardjpa.entity.Post; -import jehs.springbootboardjpa.repository.PostRepository; import jehs.springbootboardjpa.service.PostService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -11,7 +10,6 @@ import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; -import java.util.List; @RestController @RequiredArgsConstructor @@ -21,22 +19,22 @@ public class PostController { private final PostService postService; @GetMapping("/{id}") - public Post getPost(@PathVariable(name = "id") Long postId){ - return postService.getPostById(postId); + public PostResponse getPost(@PathVariable(name = "id") Long postId) { + return postService.getPostResponseById(postId); } @GetMapping - public Page getAllPosts(@PageableDefault Pageable pageable){ + public Page getAllPosts(@PageableDefault Pageable pageable) { return postService.getAllPosts(pageable); } @PostMapping - public void createPost(@RequestBody PostCreateRequest postCreateRequest){ + public void createPost(@RequestBody PostCreateRequest postCreateRequest) { postService.createPost(postCreateRequest); } @PatchMapping("/{id}") - public void updatePost(@PathVariable(name = "id") Long postId, @RequestBody PostUpdateRequest postUpdateRequest){ + public void updatePost(@PathVariable(name = "id") Long postId, @RequestBody PostUpdateRequest postUpdateRequest) { postService.updatePost(postId, postUpdateRequest); } } diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index 590228021..946f26a92 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -1,6 +1,7 @@ package jehs.springbootboardjpa.service; import jehs.springbootboardjpa.dto.PostCreateRequest; +import jehs.springbootboardjpa.dto.PostResponse; import jehs.springbootboardjpa.dto.PostUpdateRequest; import jehs.springbootboardjpa.entity.Post; import jehs.springbootboardjpa.entity.User; @@ -11,9 +12,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Optional; - @Service @RequiredArgsConstructor public class PostService { @@ -33,11 +31,20 @@ public void updatePost(Long postId, PostUpdateRequest postUpdateRequest) { post.updatePost(postUpdateRequest, user); } + @Transactional(readOnly = true) + public PostResponse getPostResponseById(Long postId) { + Post postById = getPostById(postId); + return new PostResponse(postById); + } + public Post getPostById(Long postId) { - return postRepository.findById(postId).orElseThrow(() -> new RuntimeException("존재하지 않는 게시글입니다.")); + return postRepository.findById(postId) + .orElseThrow(() -> new RuntimeException("존재하지 않는 게시글입니다.")); } - public Page getAllPosts(Pageable pageable) { - return postRepository.findAll(pageable); + @Transactional(readOnly = true) + public Page getAllPosts(Pageable pageable) { + return postRepository.findAll(pageable) + .map(PostResponse::new); } } From 44117f557d93a7dcc1c482933a462abe41b58af3 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:40:26 +0900 Subject: [PATCH 19/51] =?UTF-8?q?feat:=20=EC=84=B1=EA=B3=B5=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../controller/SuccessMessage.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/controller/SuccessMessage.java diff --git a/src/main/java/jehs/springbootboardjpa/controller/SuccessMessage.java b/src/main/java/jehs/springbootboardjpa/controller/SuccessMessage.java new file mode 100644 index 000000000..e7dd2c090 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/controller/SuccessMessage.java @@ -0,0 +1,21 @@ +package jehs.springbootboardjpa.controller; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; + +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SuccessMessage { + + private String message; + private Object data; + + public SuccessMessage(String message, Object data) { + this.message = message; + this.data = data; + } + + public SuccessMessage(String message) { + this.message = message; + } +} From edfb18aa068a6acffb78b1dd68f3e7df5c6d9d52 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:40:49 +0900 Subject: [PATCH 20/51] =?UTF-8?q?feat:=20ResponseEntity=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../controller/PostController.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index 83afb8481..0cef65fd3 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -8,9 +8,10 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; - @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/posts") @@ -19,22 +20,26 @@ public class PostController { private final PostService postService; @GetMapping("/{id}") - public PostResponse getPost(@PathVariable(name = "id") Long postId) { - return postService.getPostResponseById(postId); + public ResponseEntity getPost(@PathVariable(name = "id") Long postId) { + PostResponse postResponse = postService.getPostResponseById(postId); + return new ResponseEntity<>(new SuccessMessage("성공적으로 게시글이 조회되었습니다.", postResponse), HttpStatus.OK); } @GetMapping - public Page getAllPosts(@PageableDefault Pageable pageable) { - return postService.getAllPosts(pageable); + public ResponseEntity getAllPosts(@PageableDefault Pageable pageable) { + Page postResponses = postService.getAllPosts(pageable); + return new ResponseEntity<>(new SuccessMessage("성공적으로 모든 게시글이 조회되었습니다.", postResponses), HttpStatus.OK); } @PostMapping - public void createPost(@RequestBody PostCreateRequest postCreateRequest) { + public ResponseEntity createPost(@RequestBody PostCreateRequest postCreateRequest) { postService.createPost(postCreateRequest); + return new ResponseEntity<>(new SuccessMessage("성공적으로 게시글이 생성되었습니다."), HttpStatus.CREATED); } @PatchMapping("/{id}") - public void updatePost(@PathVariable(name = "id") Long postId, @RequestBody PostUpdateRequest postUpdateRequest) { + public ResponseEntity updatePost(@PathVariable(name = "id") Long postId, @RequestBody PostUpdateRequest postUpdateRequest) { postService.updatePost(postId, postUpdateRequest); + return new ResponseEntity<>(new SuccessMessage("성공적으로 게시글이 수정되었습니다."), HttpStatus.OK); } } From 8a7334ed489701a0184aaf6b9188f36562699e2a Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:41:45 +0900 Subject: [PATCH 21/51] =?UTF-8?q?feat:=20User,=20Post=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=98=88=EC=99=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EB=B0=8F=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../exception/PostErrorMessage.java | 16 ++++++++++++++++ .../exception/PostException.java | 17 +++++++++++++++++ .../exception/UserErrorMessage.java | 14 ++++++++++++++ .../exception/UserException.java | 17 +++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/exception/PostErrorMessage.java create mode 100644 src/main/java/jehs/springbootboardjpa/exception/PostException.java create mode 100644 src/main/java/jehs/springbootboardjpa/exception/UserErrorMessage.java create mode 100644 src/main/java/jehs/springbootboardjpa/exception/UserException.java diff --git a/src/main/java/jehs/springbootboardjpa/exception/PostErrorMessage.java b/src/main/java/jehs/springbootboardjpa/exception/PostErrorMessage.java new file mode 100644 index 000000000..6cac99cfc --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/exception/PostErrorMessage.java @@ -0,0 +1,16 @@ +package jehs.springbootboardjpa.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum PostErrorMessage { + + NOT_FOUND("존재하지 않는 게시글입니다.", HttpStatus.NOT_FOUND), + NOT_POST_BY_USER("작성자의 게시글이 아닙니다.", HttpStatus.BAD_REQUEST); + + private final String message; + private final HttpStatus httpStatus; +} diff --git a/src/main/java/jehs/springbootboardjpa/exception/PostException.java b/src/main/java/jehs/springbootboardjpa/exception/PostException.java new file mode 100644 index 000000000..68b8fdfa5 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/exception/PostException.java @@ -0,0 +1,17 @@ +package jehs.springbootboardjpa.exception; + +import org.springframework.http.HttpStatus; + +public class PostException extends RuntimeException{ + + private final HttpStatus httpStatus; + + public PostException(PostErrorMessage postErrorMessage) { + super(postErrorMessage.getMessage()); + this.httpStatus = postErrorMessage.getHttpStatus(); + } + + public HttpStatus getHttpStatus(){ + return httpStatus; + } +} diff --git a/src/main/java/jehs/springbootboardjpa/exception/UserErrorMessage.java b/src/main/java/jehs/springbootboardjpa/exception/UserErrorMessage.java new file mode 100644 index 000000000..25dcee52a --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/exception/UserErrorMessage.java @@ -0,0 +1,14 @@ +package jehs.springbootboardjpa.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum UserErrorMessage { + NOT_FOUND("존재하지 않는 회원입니다.", HttpStatus.NOT_FOUND); + + private final String message; + private final HttpStatus httpStatus; +} diff --git a/src/main/java/jehs/springbootboardjpa/exception/UserException.java b/src/main/java/jehs/springbootboardjpa/exception/UserException.java new file mode 100644 index 000000000..483011db9 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/exception/UserException.java @@ -0,0 +1,17 @@ +package jehs.springbootboardjpa.exception; + +import org.springframework.http.HttpStatus; + +public class UserException extends RuntimeException{ + + private final HttpStatus httpStatus; + + public UserException(UserErrorMessage userErrorMessage) { + super(userErrorMessage.getMessage()); + this.httpStatus = userErrorMessage.getHttpStatus(); + } + + public HttpStatus getHttpStatus(){ + return httpStatus; + } +} From 94151db60be6c6a49649861f4148723ef92ab234 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:42:11 +0900 Subject: [PATCH 22/51] =?UTF-8?q?feat:=20User,=20Post=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=98=88=EC=99=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/entity/Post.java | 4 +++- .../java/jehs/springbootboardjpa/service/PostService.java | 4 +++- .../java/jehs/springbootboardjpa/service/UserService.java | 5 +++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index 8b1c67a2b..2087fe33a 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -2,6 +2,8 @@ import jakarta.persistence.*; import jehs.springbootboardjpa.dto.PostUpdateRequest; +import jehs.springbootboardjpa.exception.PostErrorMessage; +import jehs.springbootboardjpa.exception.PostException; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -40,7 +42,7 @@ public void updatePost(PostUpdateRequest postUpdateRequest, User user) { private void validateUser(User updateUser) { if (!updateUser.getId().equals(this.user.getId())) { - throw new RuntimeException("작성자의 게시글이 아닙니다."); + throw new PostException(PostErrorMessage.NOT_POST_BY_USER); } } diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index 946f26a92..f6ecb4d67 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -5,6 +5,8 @@ import jehs.springbootboardjpa.dto.PostUpdateRequest; import jehs.springbootboardjpa.entity.Post; import jehs.springbootboardjpa.entity.User; +import jehs.springbootboardjpa.exception.PostErrorMessage; +import jehs.springbootboardjpa.exception.PostException; import jehs.springbootboardjpa.repository.PostRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -39,7 +41,7 @@ public PostResponse getPostResponseById(Long postId) { public Post getPostById(Long postId) { return postRepository.findById(postId) - .orElseThrow(() -> new RuntimeException("존재하지 않는 게시글입니다.")); + .orElseThrow(() -> new PostException(PostErrorMessage.NOT_FOUND)); } @Transactional(readOnly = true) diff --git a/src/main/java/jehs/springbootboardjpa/service/UserService.java b/src/main/java/jehs/springbootboardjpa/service/UserService.java index 5085b8a7d..ea60b4f23 100644 --- a/src/main/java/jehs/springbootboardjpa/service/UserService.java +++ b/src/main/java/jehs/springbootboardjpa/service/UserService.java @@ -1,8 +1,9 @@ package jehs.springbootboardjpa.service; import jehs.springbootboardjpa.entity.User; +import jehs.springbootboardjpa.exception.UserErrorMessage; +import jehs.springbootboardjpa.exception.UserException; import jehs.springbootboardjpa.repository.UserRepository; -import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,6 +15,6 @@ public class UserService { public User getUserById(Long userId){ return userRepository.findById(userId) - .orElseThrow(() -> new RuntimeException("존재하지 않는 회원입니다.")); + .orElseThrow(() -> new UserException(UserErrorMessage.NOT_FOUND)); } } From 1921dc3ccae780b3d945a74a04bf6c00247649dd Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:42:42 +0900 Subject: [PATCH 23/51] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=96=B4?= =?UTF-8?q?=EB=93=9C=EB=B0=94=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../config/ExceptionMessage.java | 12 ++++++++++ .../config/GlobalExceptionHandler.java | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/main/java/jehs/springbootboardjpa/config/ExceptionMessage.java create mode 100644 src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java diff --git a/src/main/java/jehs/springbootboardjpa/config/ExceptionMessage.java b/src/main/java/jehs/springbootboardjpa/config/ExceptionMessage.java new file mode 100644 index 000000000..5116dbb5a --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/config/ExceptionMessage.java @@ -0,0 +1,12 @@ +package jehs.springbootboardjpa.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ExceptionMessage { + + private final String error; + private final String message; +} diff --git a/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java b/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java new file mode 100644 index 000000000..a71a56522 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java @@ -0,0 +1,23 @@ +package jehs.springbootboardjpa.config; + +import jehs.springbootboardjpa.exception.PostException; +import jehs.springbootboardjpa.exception.UserException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(PostException.class) + public ResponseEntity handlePostException(PostException e){ + ExceptionMessage exceptionMessage = new ExceptionMessage(e.getClass().getSimpleName(), e.getMessage()); + return new ResponseEntity<>(exceptionMessage, e.getHttpStatus()); + } + + @ExceptionHandler(UserException.class) + public ResponseEntity handleUserException(UserException e){ + ExceptionMessage exceptionMessage = new ExceptionMessage(e.getClass().getSimpleName(), e.getMessage()); + return new ResponseEntity<>(exceptionMessage, e.getHttpStatus()); + } +} From 1a9c44d0579fbe29c8afc2e86cb682f2a9543edd Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:54:33 +0900 Subject: [PATCH 24/51] =?UTF-8?q?mod:=20Post=20fetch=20join=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../controller/PostController.java | 2 +- .../repository/PostRepository.java | 11 +++++++++++ .../jehs/springbootboardjpa/service/PostService.java | 12 ++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index 0cef65fd3..dd07c4473 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -27,7 +27,7 @@ public ResponseEntity getPost(@PathVariable(name = "id") Long po @GetMapping public ResponseEntity getAllPosts(@PageableDefault Pageable pageable) { - Page postResponses = postService.getAllPosts(pageable); + Page postResponses = postService.getAllPostsWithUser(pageable); return new ResponseEntity<>(new SuccessMessage("성공적으로 모든 게시글이 조회되었습니다.", postResponses), HttpStatus.OK); } diff --git a/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java b/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java index 5fb0d109a..79f3c6fde 100644 --- a/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java +++ b/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java @@ -1,7 +1,18 @@ package jehs.springbootboardjpa.repository; import jehs.springbootboardjpa.entity.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Optional; public interface PostRepository extends JpaRepository { + + @Query("select p from Post p join fetch p.user where p.id = :id") + Optional findByIdWithUser(Long id); + + @Query("select p from Post p join fetch p.user") + Page findAllWithUser(Pageable pageable); } diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index f6ecb4d67..e51d35699 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -28,25 +28,25 @@ public void createPost(PostCreateRequest postCreateRequest) { @Transactional public void updatePost(Long postId, PostUpdateRequest postUpdateRequest) { - Post post = getPostById(postId); + Post post = getPostByIdWithUser(postId); User user = userService.getUserById(postUpdateRequest.getUserId()); post.updatePost(postUpdateRequest, user); } @Transactional(readOnly = true) public PostResponse getPostResponseById(Long postId) { - Post postById = getPostById(postId); + Post postById = getPostByIdWithUser(postId); return new PostResponse(postById); } - public Post getPostById(Long postId) { - return postRepository.findById(postId) + public Post getPostByIdWithUser(Long postId) { + return postRepository.findByIdWithUser(postId) .orElseThrow(() -> new PostException(PostErrorMessage.NOT_FOUND)); } @Transactional(readOnly = true) - public Page getAllPosts(Pageable pageable) { - return postRepository.findAll(pageable) + public Page getAllPostsWithUser(Pageable pageable) { + return postRepository.findAllWithUser(pageable) .map(PostResponse::new); } } From a98c640755db991841c9bf2a102fba2e993b3e17 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Fri, 24 Nov 2023 00:01:22 +0900 Subject: [PATCH 25/51] =?UTF-8?q?config:=20database=20default=20user=20ins?= =?UTF-8?q?ert=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- src/main/resources/application.yaml | 5 ++++- src/main/resources/data.sql | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/data.sql diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index dc50caccc..ee5a640c2 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -13,4 +13,7 @@ spring: properties: hibernate: format_sql: true - defer-datasource-initialization: true \ No newline at end of file + defer-datasource-initialization: true + sql: + init: + mode: always \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 000000000..e4ae30d57 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,3 @@ +INSERT INTO users (id, created_at, deleted_at, updated_at, age, hobby, name) +SELECT 1, '2023-11-23 10:45:12', null, '2023-11-23 10:45:13', 59, 'platypus', 'ogu' +WHERE NOT EXISTS (SELECT * FROM users WHERE id = 1); \ No newline at end of file From f793dec1054a72c5cadcafb8d64bb175659cf9b0 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Fri, 24 Nov 2023 00:36:38 +0900 Subject: [PATCH 26/51] =?UTF-8?q?feat:=20Post=20Controller=20API=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../controller/PostControllerTest.java | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java diff --git a/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java b/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java new file mode 100644 index 000000000..4cf36a65d --- /dev/null +++ b/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java @@ -0,0 +1,192 @@ +package jehs.springbootboardjpa.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jehs.springbootboardjpa.dto.PostCreateRequest; +import jehs.springbootboardjpa.dto.PostUpdateRequest; +import jehs.springbootboardjpa.entity.Post; +import jehs.springbootboardjpa.entity.User; +import jehs.springbootboardjpa.repository.PostRepository; +import jehs.springbootboardjpa.repository.UserRepository; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class PostControllerTest { + + @Autowired + private MockMvc mockMvc; + @Autowired + private PostRepository postRepository; + @Autowired + private UserRepository userRepository; + + @BeforeEach + void setUp() { + postRepository.deleteAll(); + } + + @Test + @DisplayName("ID로 Post를 조회할 수 있습니다.") + @Transactional + void testGetPostById() throws Exception { + // Given + User user = userRepository.findById(1L).orElseThrow(); + Post post = postRepository.save(Post.builder() + .title("Test Title") + .content("Test Content") + .user(user) + .postType(Post.PostType.FREE) + .build() + ); + + // When + ResultActions perform = mockMvc.perform(get("/api/v1/posts/{postId}", post.getId()) + .contentType(MediaType.APPLICATION_JSON)); + + // Then + perform + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("성공적으로 게시글이 조회되었습니다.")) + .andExpect(jsonPath("$.data.id").value(post.getId())) + .andExpect(jsonPath("$.data.title").value(post.getTitle())) + .andExpect(jsonPath("$.data.content").value(post.getContent())) + .andExpect(jsonPath("$.data.postType").value(post.getPostType().toString())) + .andExpect(jsonPath("$.data.userResponse.id").value(user.getId())) + .andExpect(jsonPath("$.data.userResponse.name").value(user.getName())) + .andExpect(jsonPath("$.data.userResponse.age").value(user.getAge())) + .andExpect(jsonPath("$.data.userResponse.hobby").value(user.getHobby())) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)); + } + + @DisplayName("페이지네이션을 통해 모든 게시글을 조회할 수 있습니다") + @CsvSource({ + "0, 10", + "0, 30", + "1, 20", + "2, 10", + "3, 10", + }) + @ParameterizedTest + @Transactional + void testGetPostPagination(int page, int size) throws Exception { + // Given + User user = userRepository.findById(1L).orElseThrow(); + List postList = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + postList.add( + postRepository.save(Post.builder() + .title("Test Title") + .content("Test Content") + .user(user) + .postType(Post.PostType.FREE) + .build() + ) + ); + } + + // When + ResultActions perform = mockMvc.perform(get("/api/v1/posts") + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size)) + .contentType(MediaType.APPLICATION_JSON)); + + // Then + final int index = page * size; + Post post = postList.get(index); + perform + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("성공적으로 모든 게시글이 조회되었습니다.")) + .andExpect(jsonPath("$.data.content[0].id").value(post.getId())) + .andExpect(jsonPath("$.data.content[0].title").value(post.getTitle())) + .andExpect(jsonPath("$.data.content[0].content").value(post.getContent())) + .andExpect(jsonPath("$.data.content[0].postType").value(post.getPostType().toString())) + .andExpect(jsonPath("$.data.content[0].userResponse.id").value(user.getId())) + .andExpect(jsonPath("$.data.content[0].userResponse.name").value(user.getName())) + .andExpect(jsonPath("$.data.content[0].userResponse.age").value(user.getAge())) + .andExpect(jsonPath("$.data.content[0].userResponse.hobby").value(user.getHobby())) + .andExpect(jsonPath("$.data.content", Matchers.hasSize(size))) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)); + } + + @Test + @DisplayName("게시글을 생성할 수 있습니다.") + @Transactional + void testCreatePost() throws Exception { + // Given + final String testTitle = "Test Title"; + PostCreateRequest postCreateRequest = new PostCreateRequest( + testTitle, + "Test Content", + 1L, + Post.PostType.FREE + ); + + // When + ResultActions perform = mockMvc.perform(post("/api/v1/posts") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(postCreateRequest))); + + // Then + perform + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.message").value("성공적으로 게시글이 생성되었습니다.")); + List postList = postRepository.findAll(); + assertThat(postList).hasSize(1); + } + + @Test + @DisplayName("게시글을 수정할 수 있습니다.") + @Transactional + void testUpdatePost() throws Exception { + // Given + User user = userRepository.findById(1L).orElseThrow(); + Post post = postRepository.save(Post.builder() + .title("Test Title") + .content("Test Content") + .user(user) + .postType(Post.PostType.FREE) + .build() + ); + final String updatedTitle = "Updated Title"; + PostUpdateRequest postUpdateRequest = new PostUpdateRequest( + updatedTitle, + "Updated Content", + 1L + ); + + // When + ResultActions perform = mockMvc.perform(patch("/api/v1/posts/{postId}", post.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(postUpdateRequest))); + + // Then + perform + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("성공적으로 게시글이 수정되었습니다.")); + Optional foundPost = postRepository.findById(post.getId()); + assertThat(foundPost).isNotEmpty(); + assertThat(foundPost.get().getTitle()).isEqualTo(updatedTitle); + } +} From ad00ca48814cfbfb2996656c985fe1f9c29ab584 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:14:46 +0900 Subject: [PATCH 27/51] =?UTF-8?q?chore:=20restdocs=20adoc=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- build.gradle | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/build.gradle b/build.gradle index 79b802db7..e7842c4e9 100644 --- a/build.gradle +++ b/build.gradle @@ -49,3 +49,21 @@ tasks.named('asciidoctor') { inputs.dir snippetsDir dependsOn test } + +asciidoctor { + dependsOn test + inputs.dir snippetsDir +} + +asciidoctor.doFirst { + delete file('src/main/resources/static/docs') +} + +bootJar { + enabled = true + dependsOn asciidoctor + copy { + from file('build/docs/asciidoc') + into file("src/main/resources/static/docs") + } +} \ No newline at end of file From 9d47d3f3331d5d3a6bca4838029d49f4d9ebd7a0 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:15:12 +0900 Subject: [PATCH 28/51] =?UTF-8?q?feat:=20PostControllerTest=20restdocs=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- src/docs/asciidoc/index.adoc | 35 ++ src/main/resources/static/docs/index.html | 574 ++++++++++++++++++ .../controller/PostControllerTest.java | 92 ++- 3 files changed, 696 insertions(+), 5 deletions(-) create mode 100644 src/docs/asciidoc/index.adoc create mode 100644 src/main/resources/static/docs/index.html diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc new file mode 100644 index 000000000..a880899fe --- /dev/null +++ b/src/docs/asciidoc/index.adoc @@ -0,0 +1,35 @@ += REST Docs +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 2 +:sectlinks: + +ifndef::snippets[] +:snippets: build/generated-snippets +endif::[] + +=== 특정 게시글 조회 +http-request +include::{snippets}/getPostById/http-request.adoc[] +http-response +include::{snippets}/getPostById/http-response.adoc[] + +=== 모든 게시글 조회 +http-request +include::{snippets}/getAllPosts/http-request.adoc[] +http-response +include::{snippets}/getAllPosts/http-response.adoc[] + +=== 게시글 생성 +http-request +include::{snippets}/createPost/http-request.adoc[] +http-response +include::{snippets}/createPost/http-response.adoc[] + +=== 게시글 수정 +http-request +include::{snippets}/updatePost/http-request.adoc[] +http-response +include::{snippets}/updatePost/http-response.adoc[] \ No newline at end of file diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html new file mode 100644 index 000000000..aa68781db --- /dev/null +++ b/src/main/resources/static/docs/index.html @@ -0,0 +1,574 @@ + + + + + + + +REST Docs + + + + + + +
+
+

특정 게시글 조회

+
+

http-request

+
+
+
+
GET /api/v1/posts/6031 HTTP/1.1
+Content-Type: application/json;charset=UTF-8
+Host: localhost:8080
+
+
+
+

http-response

+
+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 300
+
+{"message":"성공적으로 게시글이 조회되었습니다.","data":{"id":6031,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:52.837993","updatedAt":"2023-11-24T02:02:52.8745915","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}}}
+
+
+
+
+

모든 게시글 조회

+
+

http-request

+
+
+
+
GET /api/v1/posts?page=3&size=10 HTTP/1.1
+Content-Type: application/json;charset=UTF-8
+Host: localhost:8080
+
+
+
+

http-response

+
+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 2692
+
+{"message":"성공적으로 모든 게시글이 조회되었습니다.","data":{"content":[{"id":6464,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.4872727","updatedAt":"2023-11-24T02:02:58.4873521","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6465,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.4918942","updatedAt":"2023-11-24T02:02:58.4924472","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6466,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.4967948","updatedAt":"2023-11-24T02:02:58.4967948","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6467,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5019694","updatedAt":"2023-11-24T02:02:58.5019694","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6468,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5080055","updatedAt":"2023-11-24T02:02:58.5087324","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6469,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5162351","updatedAt":"2023-11-24T02:02:58.5162351","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6470,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5229304","updatedAt":"2023-11-24T02:02:58.5229304","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6471,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5291098","updatedAt":"2023-11-24T02:02:58.5291098","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6472,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5351837","updatedAt":"2023-11-24T02:02:58.5356945","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6473,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5412776","updatedAt":"2023-11-24T02:02:58.5415318","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}}],"pageable":{"pageNumber":3,"pageSize":10,"sort":{"empty":true,"sorted":false,"unsorted":true},"offset":30,"unpaged":false,"paged":true},"last":false,"totalElements":100,"totalPages":10,"size":10,"number":3,"sort":{"empty":true,"sorted":false,"unsorted":true},"numberOfElements":10,"first":false,"empty":false}}
+
+
+
+
+

게시글 생성

+
+

http-request

+
+
+
+
POST /api/v1/posts HTTP/1.1
+Content-Type: application/json;charset=UTF-8
+Content-Length: 76
+Host: localhost:8080
+
+{"title":"Test Title","content":"Test Content","userId":1,"postType":"FREE"}
+
+
+
+

http-response

+
+
+
+
HTTP/1.1 201 Created
+Content-Type: application/json
+Content-Length: 65
+
+{"message":"성공적으로 게시글이 생성되었습니다."}
+
+
+
+
+

게시글 수정

+
+

http-request

+
+
+
+
PATCH /api/v1/posts/6032 HTTP/1.1
+Content-Type: application/json;charset=UTF-8
+Content-Length: 64
+Host: localhost:8080
+
+{"title":"Updated Title","content":"Updated Content","userId":1}
+
+
+
+

http-response

+
+
+
+
HTTP/1.1 200 OK
+Content-Type: application/json
+Content-Length: 65
+
+{"message":"성공적으로 게시글이 수정되었습니다."}
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java b/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java index 4cf36a65d..4f7bc03ce 100644 --- a/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java +++ b/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; 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; @@ -27,12 +28,17 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +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.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc +@AutoConfigureRestDocs class PostControllerTest { @Autowired @@ -77,7 +83,23 @@ void testGetPostById() throws Exception { .andExpect(jsonPath("$.data.userResponse.name").value(user.getName())) .andExpect(jsonPath("$.data.userResponse.age").value(user.getAge())) .andExpect(jsonPath("$.data.userResponse.hobby").value(user.getHobby())) - .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)); + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andDo(document("getPostById", + pathParameters(parameterWithName("postId").description("게시글 아이디")), + responseFields( + fieldWithPath("message").description("성공 메시지"), + fieldWithPath("data.id").description("게시글 아이디"), + fieldWithPath("data.title").description("게시글 제목"), + fieldWithPath("data.content").description("게시글 내용"), + fieldWithPath("data.postType").description("게시글 종류"), + fieldWithPath("data.createdAt").description("게시글 생성시간"), + fieldWithPath("data.updatedAt").description("게시글 수정시간"), + fieldWithPath("data.userResponse.id").description("게시자 아이디"), + fieldWithPath("data.userResponse.name").description("게시자 이름"), + fieldWithPath("data.userResponse.age").description("게시자 나이"), + fieldWithPath("data.userResponse.hobby").description("게시자 취미") + )) + ); } @DisplayName("페이지네이션을 통해 모든 게시글을 조회할 수 있습니다") @@ -127,7 +149,44 @@ void testGetPostPagination(int page, int size) throws Exception { .andExpect(jsonPath("$.data.content[0].userResponse.age").value(user.getAge())) .andExpect(jsonPath("$.data.content[0].userResponse.hobby").value(user.getHobby())) .andExpect(jsonPath("$.data.content", Matchers.hasSize(size))) - .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)); + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andDo(document("getAllPosts", + responseFields( + fieldWithPath("message").description("성공 메시지"), + fieldWithPath("data.content[].id").description("게시글 아이디"), + fieldWithPath("data.content[].title").description("게시글 제목"), + fieldWithPath("data.content[].content").description("게시글 내용"), + fieldWithPath("data.content[].postType").description("게시글 종류"), + fieldWithPath("data.content[].createdAt").description("게시글 생성시간"), + fieldWithPath("data.content[].updatedAt").description("게시글 수정시간"), + fieldWithPath("data.content[].userResponse.id").description("게시자 아이디"), + fieldWithPath("data.content[].userResponse.name").description("게시자 이름"), + fieldWithPath("data.content[].userResponse.age").description("게시자 나이"), + fieldWithPath("data.content[].userResponse.hobby").description("게시자 취미"), + fieldWithPath("data.pageable").description("페이징 정보").optional(), + fieldWithPath("data.pageable.pageNumber").description("현재 페이지 번호"), + fieldWithPath("data.pageable.pageSize").description("페이지 크기"), + fieldWithPath("data.pageable.sort").description("정렬 정보").optional(), + fieldWithPath("data.pageable.sort.empty").description("정렬이 비어있는지 여부"), + fieldWithPath("data.pageable.sort.unsorted").description("정렬되지 않았는지 여부"), + fieldWithPath("data.pageable.sort.sorted").description("정렬되었는지 여부"), + fieldWithPath("data.pageable.offset").description("현재 페이지의 시작 오프셋"), + fieldWithPath("data.pageable.unpaged").description("페이징되지 않은지 여부"), + fieldWithPath("data.pageable.paged").description("페이징된지 여부"), + fieldWithPath("data.last").description("마지막 페이지 여부"), + fieldWithPath("data.totalPages").description("전체 페이지 수"), + fieldWithPath("data.totalElements").description("전체 엘리먼트 수"), + fieldWithPath("data.first").description("첫 번째 페이지 여부"), + fieldWithPath("data.size").description("현재 페이지의 엘리먼트 수"), + fieldWithPath("data.number").description("현재 페이지 번호"), + fieldWithPath("data.sort").description("정렬 정보").optional(), + fieldWithPath("data.sort.empty").description("정렬이 비어있는지 여부"), + fieldWithPath("data.sort.unsorted").description("정렬되지 않았는지 여부"), + fieldWithPath("data.sort.sorted").description("정렬되었는지 여부"), + fieldWithPath("data.numberOfElements").description("현재 페이지의 엘리먼트 수"), + fieldWithPath("data.empty").description("데이터가 비어있는지 여부") + )) + ); } @Test @@ -151,7 +210,19 @@ void testCreatePost() throws Exception { // Then perform .andExpect(status().isCreated()) - .andExpect(jsonPath("$.message").value("성공적으로 게시글이 생성되었습니다.")); + .andExpect(jsonPath("$.message").value("성공적으로 게시글이 생성되었습니다.")) + .andDo(document("createPost", + requestFields( + fieldWithPath("title").description("게시글 제목"), + fieldWithPath("content").description("게시글 내용"), + fieldWithPath("userId").description("게시자 아이디"), + fieldWithPath("postType").description("게시글 종류") + ), + responseFields( + fieldWithPath("message").description("성공 메시지") + ) + ) + ); List postList = postRepository.findAll(); assertThat(postList).hasSize(1); } @@ -184,7 +255,18 @@ void testUpdatePost() throws Exception { // Then perform .andExpect(status().isOk()) - .andExpect(jsonPath("$.message").value("성공적으로 게시글이 수정되었습니다.")); + .andExpect(jsonPath("$.message").value("성공적으로 게시글이 수정되었습니다.")) + .andDo(document("updatePost", + requestFields( + fieldWithPath("title").description("게시글 제목"), + fieldWithPath("content").description("게시글 내용"), + fieldWithPath("userId").description("게시자 아이디") + ), + responseFields( + fieldWithPath("message").description("성공 메시지") + ) + ) + ); Optional foundPost = postRepository.findById(post.getId()); assertThat(foundPost).isNotEmpty(); assertThat(foundPost.get().getTitle()).isEqualTo(updatedTitle); From 4becb69f997d5f943eaf34e29bcc6aaae7efb152 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Fri, 24 Nov 2023 11:03:16 +0900 Subject: [PATCH 29/51] =?UTF-8?q?feat:=20=EC=9A=94=EC=B2=AD=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=EA=B0=92=20prettyPrint=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../controller/PostControllerTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java b/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java index 4f7bc03ce..5a91fe724 100644 --- a/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java +++ b/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; 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; @@ -85,6 +86,8 @@ void testGetPostById() throws Exception { .andExpect(jsonPath("$.data.userResponse.hobby").value(user.getHobby())) .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) .andDo(document("getPostById", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), pathParameters(parameterWithName("postId").description("게시글 아이디")), responseFields( fieldWithPath("message").description("성공 메시지"), @@ -151,6 +154,8 @@ void testGetPostPagination(int page, int size) throws Exception { .andExpect(jsonPath("$.data.content", Matchers.hasSize(size))) .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) .andDo(document("getAllPosts", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), responseFields( fieldWithPath("message").description("성공 메시지"), fieldWithPath("data.content[].id").description("게시글 아이디"), @@ -212,6 +217,8 @@ void testCreatePost() throws Exception { .andExpect(status().isCreated()) .andExpect(jsonPath("$.message").value("성공적으로 게시글이 생성되었습니다.")) .andDo(document("createPost", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), requestFields( fieldWithPath("title").description("게시글 제목"), fieldWithPath("content").description("게시글 내용"), @@ -257,6 +264,8 @@ void testUpdatePost() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("성공적으로 게시글이 수정되었습니다.")) .andDo(document("updatePost", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), requestFields( fieldWithPath("title").description("게시글 제목"), fieldWithPath("content").description("게시글 내용"), From 0f3fae6478d16edffcdb6752341d7d3fbd7dbc00 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Fri, 24 Nov 2023 11:03:52 +0900 Subject: [PATCH 30/51] =?UTF-8?q?feat:=20RestDocs=20request/response=20fie?= =?UTF-8?q?lds=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- src/docs/asciidoc/index.adoc | 14 +- src/main/resources/static/docs/index.html | 598 +++++++++++++++++++++- 2 files changed, 596 insertions(+), 16 deletions(-) diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index a880899fe..a5a23c8d8 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -15,21 +15,33 @@ http-request include::{snippets}/getPostById/http-request.adoc[] http-response include::{snippets}/getPostById/http-response.adoc[] +response-fields +include::{snippets}/getPostById/response-fields.adoc[] === 모든 게시글 조회 http-request include::{snippets}/getAllPosts/http-request.adoc[] http-response include::{snippets}/getAllPosts/http-response.adoc[] +response-fields +include::{snippets}/getAllPosts/response-fields.adoc[] === 게시글 생성 http-request include::{snippets}/createPost/http-request.adoc[] +request-fields +include::{snippets}/createPost/request-fields.adoc[] http-response include::{snippets}/createPost/http-response.adoc[] +response-fields +include::{snippets}/createPost/response-fields.adoc[] === 게시글 수정 http-request include::{snippets}/updatePost/http-request.adoc[] +request-fields +include::{snippets}/updatePost/request-fields.adoc[] http-response -include::{snippets}/updatePost/http-response.adoc[] \ No newline at end of file +include::{snippets}/updatePost/http-response.adoc[] +response-fields +include::{snippets}/updatePost/response-fields.adoc[] \ No newline at end of file diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index aa68781db..aa4056cd4 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -461,7 +461,7 @@

-
GET /api/v1/posts/6031 HTTP/1.1
+
GET /api/v1/posts/6237 HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Host: localhost:8080
@@ -473,11 +473,101 @@

HTTP/1.1 200 OK
 Content-Type: application/json
-Content-Length: 300
+Content-Length: 399
 
-{"message":"성공적으로 게시글이 조회되었습니다.","data":{"id":6031,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:52.837993","updatedAt":"2023-11-24T02:02:52.8745915","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}}}
+{ + "message" : "성공적으로 게시글이 조회되었습니다.", + "data" : { + "id" : 6237, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:30.380548", + "updatedAt" : "2023-11-24T10:53:30.401951", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + } +}
+
+

response-fields

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

data.id

Number

게시글 아이디

data.title

String

게시글 제목

data.content

String

게시글 내용

data.postType

String

게시글 종류

data.createdAt

String

게시글 생성시간

data.updatedAt

String

게시글 수정시간

data.userResponse.id

Number

게시자 아이디

data.userResponse.name

String

게시자 이름

data.userResponse.age

Number

게시자 나이

data.userResponse.hobby

String

게시자 취미

모든 게시글 조회

@@ -498,11 +588,355 @@

HTTP/1.1 200 OK
 Content-Type: application/json
-Content-Length: 2692
+Content-Length: 4051
 
-{"message":"성공적으로 모든 게시글이 조회되었습니다.","data":{"content":[{"id":6464,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.4872727","updatedAt":"2023-11-24T02:02:58.4873521","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6465,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.4918942","updatedAt":"2023-11-24T02:02:58.4924472","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6466,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.4967948","updatedAt":"2023-11-24T02:02:58.4967948","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6467,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5019694","updatedAt":"2023-11-24T02:02:58.5019694","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6468,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5080055","updatedAt":"2023-11-24T02:02:58.5087324","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6469,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5162351","updatedAt":"2023-11-24T02:02:58.5162351","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6470,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5229304","updatedAt":"2023-11-24T02:02:58.5229304","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6471,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5291098","updatedAt":"2023-11-24T02:02:58.5291098","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6472,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5351837","updatedAt":"2023-11-24T02:02:58.5356945","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}},{"id":6473,"title":"Test Title","content":"Test Content","postType":"FREE","createdAt":"2023-11-24T02:02:58.5412776","updatedAt":"2023-11-24T02:02:58.5415318","userResponse":{"id":1,"name":"최정은","age":25,"hobby":"코딩"}}],"pageable":{"pageNumber":3,"pageSize":10,"sort":{"empty":true,"sorted":false,"unsorted":true},"offset":30,"unpaged":false,"paged":true},"last":false,"totalElements":100,"totalPages":10,"size":10,"number":3,"sort":{"empty":true,"sorted":false,"unsorted":true},"numberOfElements":10,"first":false,"empty":false}}
+{ + "message" : "성공적으로 모든 게시글이 조회되었습니다.", + "data" : { + "content" : [ { + "id" : 6670, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.579316", + "updatedAt" : "2023-11-24T10:53:33.579441", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6671, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.580615", + "updatedAt" : "2023-11-24T10:53:33.580661", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6672, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.581813", + "updatedAt" : "2023-11-24T10:53:33.581981", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6673, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.584492", + "updatedAt" : "2023-11-24T10:53:33.584636", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6674, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.58606", + "updatedAt" : "2023-11-24T10:53:33.586121", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6675, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.587548", + "updatedAt" : "2023-11-24T10:53:33.587629", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6676, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.588448", + "updatedAt" : "2023-11-24T10:53:33.588501", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6677, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.589113", + "updatedAt" : "2023-11-24T10:53:33.589152", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6678, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.589802", + "updatedAt" : "2023-11-24T10:53:33.58987", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + }, { + "id" : 6679, + "title" : "Test Title", + "content" : "Test Content", + "postType" : "FREE", + "createdAt" : "2023-11-24T10:53:33.59047", + "updatedAt" : "2023-11-24T10:53:33.59052", + "userResponse" : { + "id" : 1, + "name" : "ogu", + "age" : 59, + "hobby" : "platypus" + } + } ], + "pageable" : { + "pageNumber" : 3, + "pageSize" : 10, + "sort" : { + "empty" : true, + "unsorted" : true, + "sorted" : false + }, + "offset" : 30, + "paged" : true, + "unpaged" : false + }, + "totalPages" : 10, + "totalElements" : 100, + "last" : false, + "size" : 10, + "number" : 3, + "sort" : { + "empty" : true, + "unsorted" : true, + "sorted" : false + }, + "numberOfElements" : 10, + "first" : false, + "empty" : false + } +} +

+
+

response-fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

data.content[].id

Number

게시글 아이디

data.content[].title

String

게시글 제목

data.content[].content

String

게시글 내용

data.content[].postType

String

게시글 종류

data.content[].createdAt

String

게시글 생성시간

data.content[].updatedAt

String

게시글 수정시간

data.content[].userResponse.id

Number

게시자 아이디

data.content[].userResponse.name

String

게시자 이름

data.content[].userResponse.age

Number

게시자 나이

data.content[].userResponse.hobby

String

게시자 취미

data.pageable

Object

페이징 정보

data.pageable.pageNumber

Number

현재 페이지 번호

data.pageable.pageSize

Number

페이지 크기

data.pageable.sort

Object

정렬 정보

data.pageable.sort.empty

Boolean

정렬이 비어있는지 여부

data.pageable.sort.unsorted

Boolean

정렬되지 않았는지 여부

data.pageable.sort.sorted

Boolean

정렬되었는지 여부

data.pageable.offset

Number

현재 페이지의 시작 오프셋

data.pageable.unpaged

Boolean

페이징되지 않은지 여부

data.pageable.paged

Boolean

페이징된지 여부

data.last

Boolean

마지막 페이지 여부

data.totalPages

Number

전체 페이지 수

data.totalElements

Number

전체 엘리먼트 수

data.first

Boolean

첫 번째 페이지 여부

data.size

Number

현재 페이지의 엘리먼트 수

data.number

Number

현재 페이지 번호

data.sort

Object

정렬 정보

data.sort.empty

Boolean

정렬이 비어있는지 여부

data.sort.unsorted

Boolean

정렬되지 않았는지 여부

data.sort.sorted

Boolean

정렬되었는지 여부

data.numberOfElements

Number

현재 페이지의 엘리먼트 수

data.empty

Boolean

데이터가 비어있는지 여부

+
+

request-fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

title

String

게시글 제목

content

String

게시글 내용

userId

Number

게시자 아이디

postType

String

게시글 종류

http-response

@@ -526,11 +1004,37 @@

게시글
HTTP/1.1 201 Created
 Content-Type: application/json
-Content-Length: 65
+Content-Length: 71
 
-{"message":"성공적으로 게시글이 생성되었습니다."}
+{ + "message" : "성공적으로 게시글이 생성되었습니다." +}
+
+

response-fields

+
+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

게시글 수정

@@ -539,14 +1043,52 @@

게시글

-
PATCH /api/v1/posts/6032 HTTP/1.1
+
PATCH /api/v1/posts/6238 HTTP/1.1
 Content-Type: application/json;charset=UTF-8
-Content-Length: 64
+Content-Length: 80
 Host: localhost:8080
 
-{"title":"Updated Title","content":"Updated Content","userId":1}
+{ + "title" : "Updated Title", + "content" : "Updated Content", + "userId" : 1 +}
+
+
+

request-fields

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

title

String

게시글 제목

content

String

게시글 내용

userId

Number

게시자 아이디

http-response

@@ -554,17 +1096,43 @@

게시글
HTTP/1.1 200 OK
 Content-Type: application/json
-Content-Length: 65
+Content-Length: 71
 
-{"message":"성공적으로 게시글이 수정되었습니다."}
+{ + "message" : "성공적으로 게시글이 수정되었습니다." +} +
+
+

response-fields

+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

From 6aaabf5e1b295b7a7db2be2bc5ff4d00b3be8882 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Fri, 24 Nov 2023 11:22:29 +0900 Subject: [PATCH 31/51] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EC=8B=9C=20=EB=A1=9C=EA=B9=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../config/GlobalExceptionHandler.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java b/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java index a71a56522..f1338aaf8 100644 --- a/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java +++ b/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java @@ -2,21 +2,26 @@ import jehs.springbootboardjpa.exception.PostException; import jehs.springbootboardjpa.exception.UserException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(PostException.class) - public ResponseEntity handlePostException(PostException e){ + public ResponseEntity handlePostException(PostException e) { + log.warn(e.getMessage()); ExceptionMessage exceptionMessage = new ExceptionMessage(e.getClass().getSimpleName(), e.getMessage()); return new ResponseEntity<>(exceptionMessage, e.getHttpStatus()); } @ExceptionHandler(UserException.class) - public ResponseEntity handleUserException(UserException e){ + public ResponseEntity handleUserException(UserException e) { + log.warn(e.getMessage()); ExceptionMessage exceptionMessage = new ExceptionMessage(e.getClass().getSimpleName(), e.getMessage()); return new ResponseEntity<>(exceptionMessage, e.getHttpStatus()); } From e31ae9439db0243f095a4affa5f0c2f84ef9fd48 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Fri, 24 Nov 2023 11:30:05 +0900 Subject: [PATCH 32/51] =?UTF-8?q?feat:=20=EC=A0=84=EC=B2=B4=20Exception=20?= =?UTF-8?q?=EB=8C=80=EC=83=81=20Handler=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../springbootboardjpa/config/GlobalExceptionHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java b/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java index f1338aaf8..9d072f2ff 100644 --- a/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java +++ b/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java @@ -25,4 +25,11 @@ public ResponseEntity handleUserException(UserException e) { ExceptionMessage exceptionMessage = new ExceptionMessage(e.getClass().getSimpleName(), e.getMessage()); return new ResponseEntity<>(exceptionMessage, e.getHttpStatus()); } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + log.error(e.getMessage()); + ExceptionMessage exceptionMessage = new ExceptionMessage(e.getClass().getSimpleName(), e.getMessage()); + return new ResponseEntity<>(exceptionMessage, HttpStatus.INTERNAL_SERVER_ERROR); + } } From e1621edd968431791dd0f65a1c9bfa631c29b0d9 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Sun, 26 Nov 2023 15:00:56 +0900 Subject: [PATCH 33/51] =?UTF-8?q?feat:=20Cursor=20=EA=B8=B0=EB=B0=98=20Pos?= =?UTF-8?q?t=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../jehs/springbootboardjpa/controller/PostController.java | 6 ++++++ .../jehs/springbootboardjpa/repository/PostRepository.java | 3 +++ .../java/jehs/springbootboardjpa/service/PostService.java | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index dd07c4473..229926f35 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -31,6 +31,12 @@ public ResponseEntity getAllPosts(@PageableDefault Pageable page return new ResponseEntity<>(new SuccessMessage("성공적으로 모든 게시글이 조회되었습니다.", postResponses), HttpStatus.OK); } + @GetMapping("/cursor") + public ResponseEntity getAllPostsByCursor(@PageableDefault Pageable pageable, @RequestParam(name = "cursorId") Long cursorId) { + Page postResponses = postService.getAllPostsWithUserByCursor(pageable, cursorId); + return new ResponseEntity<>(new SuccessMessage("성공적으로 모든 게시글이 조회되었습니다.", postResponses), HttpStatus.OK); + } + @PostMapping public ResponseEntity createPost(@RequestBody PostCreateRequest postCreateRequest) { postService.createPost(postCreateRequest); diff --git a/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java b/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java index 79f3c6fde..6c60fc1fa 100644 --- a/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java +++ b/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java @@ -15,4 +15,7 @@ public interface PostRepository extends JpaRepository { @Query("select p from Post p join fetch p.user") Page findAllWithUser(Pageable pageable); + + @Query("select p from Post p join fetch p.user where p.id > :cursorId") + Page findAllWithUserByCursor(Long cursorId, Pageable pageable); } diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index e51d35699..b5073fd35 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -49,4 +49,10 @@ public Page getAllPostsWithUser(Pageable pageable) { return postRepository.findAllWithUser(pageable) .map(PostResponse::new); } + + @Transactional(readOnly = true) + public Page getAllPostsWithUserByCursor(Pageable pageable, Long cursorId) { + return postRepository.findAllWithUserByCursor(cursorId, pageable) + .map(PostResponse::new); + } } From 2781796a5b9b58c72c9143260b7169279a28d3e9 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Sun, 26 Nov 2023 18:18:55 +0900 Subject: [PATCH 34/51] =?UTF-8?q?mod:=20Cursor=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20Slice=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../jehs/springbootboardjpa/controller/PostController.java | 3 ++- .../jehs/springbootboardjpa/repository/PostRepository.java | 3 ++- src/main/java/jehs/springbootboardjpa/service/PostService.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index 229926f35..408576913 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -33,7 +34,7 @@ public ResponseEntity getAllPosts(@PageableDefault Pageable page @GetMapping("/cursor") public ResponseEntity getAllPostsByCursor(@PageableDefault Pageable pageable, @RequestParam(name = "cursorId") Long cursorId) { - Page postResponses = postService.getAllPostsWithUserByCursor(pageable, cursorId); + Slice postResponses = postService.getAllPostsWithUserByCursor(pageable, cursorId); return new ResponseEntity<>(new SuccessMessage("성공적으로 모든 게시글이 조회되었습니다.", postResponses), HttpStatus.OK); } diff --git a/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java b/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java index 6c60fc1fa..3467b6250 100644 --- a/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java +++ b/src/main/java/jehs/springbootboardjpa/repository/PostRepository.java @@ -3,6 +3,7 @@ import jehs.springbootboardjpa.entity.Post; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -17,5 +18,5 @@ public interface PostRepository extends JpaRepository { Page findAllWithUser(Pageable pageable); @Query("select p from Post p join fetch p.user where p.id > :cursorId") - Page findAllWithUserByCursor(Long cursorId, Pageable pageable); + Slice findAllWithUserByCursor(Long cursorId, Pageable pageable); } diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index b5073fd35..cf8a5e527 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -11,6 +11,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -51,7 +52,7 @@ public Page getAllPostsWithUser(Pageable pageable) { } @Transactional(readOnly = true) - public Page getAllPostsWithUserByCursor(Pageable pageable, Long cursorId) { + public Slice getAllPostsWithUserByCursor(Pageable pageable, Long cursorId) { return postRepository.findAllWithUserByCursor(cursorId, pageable) .map(PostResponse::new); } From 7226a2a5e47c8e0268945eafe1d995024c25d701 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:38:42 +0900 Subject: [PATCH 35/51] =?UTF-8?q?feat:=20User=20isSameName=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/entity/Post.java | 2 +- src/main/java/jehs/springbootboardjpa/entity/User.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index 2087fe33a..14da68ce9 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -41,7 +41,7 @@ public void updatePost(PostUpdateRequest postUpdateRequest, User user) { } private void validateUser(User updateUser) { - if (!updateUser.getId().equals(this.user.getId())) { + if (!updateUser.isSameName(this.getUser().getName())) { throw new PostException(PostErrorMessage.NOT_POST_BY_USER); } } diff --git a/src/main/java/jehs/springbootboardjpa/entity/User.java b/src/main/java/jehs/springbootboardjpa/entity/User.java index e871cc3e9..0d59a4c99 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/User.java +++ b/src/main/java/jehs/springbootboardjpa/entity/User.java @@ -25,4 +25,8 @@ public class User extends BaseEntity { @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) private List posts; + + public boolean isSameName(String name) { + return this.getName().equals(name); + } } From 6ba889bcc2893f18b836ed01854c6a6697cf4994 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:39:03 +0900 Subject: [PATCH 36/51] =?UTF-8?q?chore:=20code=20reformat=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../java/jehs/springbootboardjpa/exception/PostException.java | 4 ++-- .../java/jehs/springbootboardjpa/exception/UserException.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/exception/PostException.java b/src/main/java/jehs/springbootboardjpa/exception/PostException.java index 68b8fdfa5..5de6fa86a 100644 --- a/src/main/java/jehs/springbootboardjpa/exception/PostException.java +++ b/src/main/java/jehs/springbootboardjpa/exception/PostException.java @@ -2,7 +2,7 @@ import org.springframework.http.HttpStatus; -public class PostException extends RuntimeException{ +public class PostException extends RuntimeException { private final HttpStatus httpStatus; @@ -11,7 +11,7 @@ public PostException(PostErrorMessage postErrorMessage) { this.httpStatus = postErrorMessage.getHttpStatus(); } - public HttpStatus getHttpStatus(){ + public HttpStatus getHttpStatus() { return httpStatus; } } diff --git a/src/main/java/jehs/springbootboardjpa/exception/UserException.java b/src/main/java/jehs/springbootboardjpa/exception/UserException.java index 483011db9..5c1311791 100644 --- a/src/main/java/jehs/springbootboardjpa/exception/UserException.java +++ b/src/main/java/jehs/springbootboardjpa/exception/UserException.java @@ -2,7 +2,7 @@ import org.springframework.http.HttpStatus; -public class UserException extends RuntimeException{ +public class UserException extends RuntimeException { private final HttpStatus httpStatus; @@ -11,7 +11,7 @@ public UserException(UserErrorMessage userErrorMessage) { this.httpStatus = userErrorMessage.getHttpStatus(); } - public HttpStatus getHttpStatus(){ + public HttpStatus getHttpStatus() { return httpStatus; } } From 5be79c1e844362e21a9b313ab099d6e1eefed5e5 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:40:40 +0900 Subject: [PATCH 37/51] =?UTF-8?q?mod:=20RequestMapping=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../controller/PostController.java | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index 408576913..5f4002541 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -15,37 +15,25 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/posts") public class PostController { private final PostService postService; - @GetMapping("/{id}") - public ResponseEntity getPost(@PathVariable(name = "id") Long postId) { - PostResponse postResponse = postService.getPostResponseById(postId); - return new ResponseEntity<>(new SuccessMessage("성공적으로 게시글이 조회되었습니다.", postResponse), HttpStatus.OK); + @GetMapping("/api/v1/posts/{id}") } - @GetMapping - public ResponseEntity getAllPosts(@PageableDefault Pageable pageable) { - Page postResponses = postService.getAllPostsWithUser(pageable); - return new ResponseEntity<>(new SuccessMessage("성공적으로 모든 게시글이 조회되었습니다.", postResponses), HttpStatus.OK); + @GetMapping("/api/v1/posts") } - @GetMapping("/cursor") - public ResponseEntity getAllPostsByCursor(@PageableDefault Pageable pageable, @RequestParam(name = "cursorId") Long cursorId) { - Slice postResponses = postService.getAllPostsWithUserByCursor(pageable, cursorId); - return new ResponseEntity<>(new SuccessMessage("성공적으로 모든 게시글이 조회되었습니다.", postResponses), HttpStatus.OK); + @GetMapping("/api/v1/posts/cursor") } - @PostMapping - public ResponseEntity createPost(@RequestBody PostCreateRequest postCreateRequest) { + @PostMapping("/api/v1/posts") postService.createPost(postCreateRequest); return new ResponseEntity<>(new SuccessMessage("성공적으로 게시글이 생성되었습니다."), HttpStatus.CREATED); } - @PatchMapping("/{id}") - public ResponseEntity updatePost(@PathVariable(name = "id") Long postId, @RequestBody PostUpdateRequest postUpdateRequest) { + @PatchMapping("/api/v1/posts/{id}") postService.updatePost(postId, postUpdateRequest); return new ResponseEntity<>(new SuccessMessage("성공적으로 게시글이 수정되었습니다."), HttpStatus.OK); } From fb847a537d947088c867c232f423b9e0d2cf7168 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:42:47 +0900 Subject: [PATCH 38/51] =?UTF-8?q?mod:=20RequestMapping=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20SuccessMessage=20=EC=A0=9C=EB=84=A4?= =?UTF-8?q?=EB=A6=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../controller/PostController.java | 44 ++++++++++++++++--- .../controller/SuccessMessage.java | 8 ++-- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index 5f4002541..c78031af5 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -3,12 +3,10 @@ import jehs.springbootboardjpa.dto.PostCreateRequest; import jehs.springbootboardjpa.dto.PostResponse; import jehs.springbootboardjpa.dto.PostUpdateRequest; +import jehs.springbootboardjpa.dto.PostsResponse; import jehs.springbootboardjpa.service.PostService; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -20,21 +18,57 @@ public class PostController { private final PostService postService; @GetMapping("/api/v1/posts/{id}") + public ResponseEntity> getPost(@PathVariable(name = "id") Long postId) { + return new ResponseEntity<>( + new SuccessMessage<>( + "성공적으로 게시글이 조회되었습니다.", + postService.getPostResponseById(postId) + ), + HttpStatus.OK + ); } @GetMapping("/api/v1/posts") + public ResponseEntity> getAllPosts(@RequestParam(name = "page") int page, @RequestParam(name = "size") int size) { + return new ResponseEntity<>( + new SuccessMessage<>( + "성공적으로 모든 게시글이 조회되었습니다.", + postService.getAllPostsWithUser(page, size) + ), + HttpStatus.OK + ); } @GetMapping("/api/v1/posts/cursor") + public ResponseEntity>> getAllPostsByCursor(@RequestParam(name = "size") int size, @RequestParam(name = "cursorId") Long cursorId) { + return new ResponseEntity<>( + new SuccessMessage<>( + "성공적으로 모든 게시글이 조회되었습니다.", + postService.getAllPostsWithUserByCursor(size, cursorId) + ), + HttpStatus.OK + ); } @PostMapping("/api/v1/posts") + public ResponseEntity> createPost(@RequestBody PostCreateRequest postCreateRequest) { postService.createPost(postCreateRequest); - return new ResponseEntity<>(new SuccessMessage("성공적으로 게시글이 생성되었습니다."), HttpStatus.CREATED); + return new ResponseEntity<>( + new SuccessMessage<>( + "성공적으로 게시글이 생성되었습니다." + ), + HttpStatus.CREATED + ); } @PatchMapping("/api/v1/posts/{id}") + public ResponseEntity> updatePost(@PathVariable(name = "id") Long postId, @RequestBody PostUpdateRequest postUpdateRequest) { postService.updatePost(postId, postUpdateRequest); - return new ResponseEntity<>(new SuccessMessage("성공적으로 게시글이 수정되었습니다."), HttpStatus.OK); + return new ResponseEntity<>( + new SuccessMessage<>( + "성공적으로 게시글이 수정되었습니다." + ), + HttpStatus.OK + ); } } diff --git a/src/main/java/jehs/springbootboardjpa/controller/SuccessMessage.java b/src/main/java/jehs/springbootboardjpa/controller/SuccessMessage.java index e7dd2c090..40ddf11d7 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/SuccessMessage.java +++ b/src/main/java/jehs/springbootboardjpa/controller/SuccessMessage.java @@ -5,12 +5,12 @@ @Getter @JsonInclude(JsonInclude.Include.NON_NULL) -public class SuccessMessage { +public class SuccessMessage { - private String message; - private Object data; + private final String message; + private T data; - public SuccessMessage(String message, Object data) { + public SuccessMessage(String message, T data) { this.message = message; this.data = data; } From c4f934bddb4cd90d0aa0eb01df290474cfec6917 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:43:18 +0900 Subject: [PATCH 39/51] =?UTF-8?q?mod:=20=EC=95=8C=20=EC=88=98=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=97=90=EB=9F=AC=20=EB=B0=9C=EC=83=9D=20=EC=8B=9C?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20=EC=97=90=EB=9F=AC=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EB=B0=98=ED=99=98=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../jehs/springbootboardjpa/config/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java b/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java index 9d072f2ff..871956fcc 100644 --- a/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java +++ b/src/main/java/jehs/springbootboardjpa/config/GlobalExceptionHandler.java @@ -29,7 +29,7 @@ public ResponseEntity handleUserException(UserException e) { @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { log.error(e.getMessage()); - ExceptionMessage exceptionMessage = new ExceptionMessage(e.getClass().getSimpleName(), e.getMessage()); + ExceptionMessage exceptionMessage = new ExceptionMessage("Internal Server Error", "서버 내부 에러가 발생했습니다."); return new ResponseEntity<>(exceptionMessage, HttpStatus.INTERNAL_SERVER_ERROR); } } From eeb45ffe910b094664cd085c6812e4e376d96d6e Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:43:51 +0900 Subject: [PATCH 40/51] =?UTF-8?q?chore:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=84=9C=EB=93=9C=20Transational?= =?UTF-8?q?=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/service/PostService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index cf8a5e527..7c57e3b0d 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -22,6 +22,7 @@ public class PostService { private final PostRepository postRepository; private final UserService userService; + @Transactional public void createPost(PostCreateRequest postCreateRequest) { User user = userService.getUserById(postCreateRequest.getUserId()); postRepository.save(postCreateRequest.toEntity(user)); From 6232a38c3cacf27ad34381cb5428b893b26b60a6 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:46:41 +0900 Subject: [PATCH 41/51] =?UTF-8?q?mod:=20Post=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20Pageable=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EB=8C=80=EC=8B=A0=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EA=B0=92=EB=A7=8C=20=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20Post=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20Response=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../springbootboardjpa/dto/PostsResponse.java | 21 +++++++++++++++++++ .../service/PostService.java | 16 +++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java diff --git a/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java b/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java new file mode 100644 index 000000000..e50ebb8ab --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java @@ -0,0 +1,21 @@ +package jehs.springbootboardjpa.dto; + +import jehs.springbootboardjpa.entity.Post; +import lombok.Getter; +import org.springframework.data.domain.Page; + +import java.util.List; + +@Getter +public class PostsResponse { + + private final List content; + private final int totalPages; + private final long totalElements; + + public PostsResponse(Page postsPage) { + this.content = postsPage.getContent().stream().map(PostResponse::new).toList(); + this.totalPages = postsPage.getTotalPages(); + this.totalElements = postsPage.getTotalElements(); + } +} diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index 7c57e3b0d..b03fb6b84 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -3,15 +3,14 @@ import jehs.springbootboardjpa.dto.PostCreateRequest; import jehs.springbootboardjpa.dto.PostResponse; import jehs.springbootboardjpa.dto.PostUpdateRequest; +import jehs.springbootboardjpa.dto.PostsResponse; import jehs.springbootboardjpa.entity.Post; import jehs.springbootboardjpa.entity.User; import jehs.springbootboardjpa.exception.PostErrorMessage; import jehs.springbootboardjpa.exception.PostException; import jehs.springbootboardjpa.repository.PostRepository; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; +import org.springframework.data.domain.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -47,14 +46,15 @@ public Post getPostByIdWithUser(Long postId) { } @Transactional(readOnly = true) - public Page getAllPostsWithUser(Pageable pageable) { - return postRepository.findAllWithUser(pageable) - .map(PostResponse::new); + public PostsResponse getAllPostsWithUser(int page, int size) { + return new PostsResponse( + postRepository.findAllWithUser(PageRequest.of(page, size, Sort.by("id").descending())) + ); } @Transactional(readOnly = true) - public Slice getAllPostsWithUserByCursor(Pageable pageable, Long cursorId) { - return postRepository.findAllWithUserByCursor(cursorId, pageable) + public Slice getAllPostsWithUserByCursor(int size, Long cursorId) { + return postRepository.findAllWithUserByCursor(cursorId, PageRequest.of(0, size)) .map(PostResponse::new); } } From f1cbbe7c3afec9a9e60d6d2a021fbad7b452d217 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:48:33 +0900 Subject: [PATCH 42/51] =?UTF-8?q?mod:=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=9C=EA=B0=84=20=EC=95=A0=ED=94=8C=EB=A6=AC?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=85=98=20=EB=A0=88=EB=B2=A8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B4=80=EB=A6=AC=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../java/jehs/springbootboardjpa/entity/BaseEntity.java | 9 ++++----- src/main/java/jehs/springbootboardjpa/entity/Post.java | 3 +++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java b/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java index c393ade57..992ee7930 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java +++ b/src/main/java/jehs/springbootboardjpa/entity/BaseEntity.java @@ -1,27 +1,26 @@ package jehs.springbootboardjpa.entity; import jakarta.persistence.Column; -import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import lombok.Builder; import lombok.Getter; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; @Getter @MappedSuperclass -@EntityListeners(AuditingEntityListener.class) public abstract class BaseEntity { @Builder.Default @Column(name = "created_at", columnDefinition = "datetime") private LocalDateTime createdAt = LocalDateTime.now(); @Builder.Default - @LastModifiedDate @Column(name = "updated_at", columnDefinition = "datetime") private LocalDateTime updatedAt = LocalDateTime.now(); @Column(name = "deleted_at", columnDefinition = "datetime") private LocalDateTime deletedAt; + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } } diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index 14da68ce9..53384b597 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -9,6 +9,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + @Entity @Table(name = "posts") @Getter @@ -38,6 +40,7 @@ public void updatePost(PostUpdateRequest postUpdateRequest, User user) { validateUser(user); this.title = postUpdateRequest.getTitle(); this.content = postUpdateRequest.getContent(); + this.setUpdatedAt(LocalDateTime.now()); } private void validateUser(User updateUser) { From 6d5d2f571078dc66a1bbaa5a8859ab1fe29e85b5 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:49:04 +0900 Subject: [PATCH 43/51] =?UTF-8?q?feat:=20postType=20ColumnName=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/entity/Post.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/jehs/springbootboardjpa/entity/Post.java b/src/main/java/jehs/springbootboardjpa/entity/Post.java index 53384b597..6569c73c0 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/Post.java +++ b/src/main/java/jehs/springbootboardjpa/entity/Post.java @@ -34,6 +34,7 @@ public class Post extends BaseEntity { private User user; @Enumerated(EnumType.STRING) + @Column(name = "post_type") private PostType postType; public void updatePost(PostUpdateRequest postUpdateRequest, User user) { From de7046e011a1f34abff704114ed7593a5481b6ad Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:53:08 +0900 Subject: [PATCH 44/51] =?UTF-8?q?fix:=20=EC=A0=95=EB=A0=AC=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- src/main/java/jehs/springbootboardjpa/service/PostService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index b03fb6b84..6bda2ac82 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -48,7 +48,7 @@ public Post getPostByIdWithUser(Long postId) { @Transactional(readOnly = true) public PostsResponse getAllPostsWithUser(int page, int size) { return new PostsResponse( - postRepository.findAllWithUser(PageRequest.of(page, size, Sort.by("id").descending())) + postRepository.findAllWithUser(PageRequest.of(page, size)) ); } From c6a8683fa20677238629d8196c54a6079d10d355 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Mon, 4 Dec 2023 14:54:20 +0900 Subject: [PATCH 45/51] =?UTF-8?q?fix:=20PagesResponse=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../controller/PostControllerTest.java | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java b/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java index 5a91fe724..1ad1793db 100644 --- a/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java +++ b/src/test/java/jehs/springbootboardjpa/controller/PostControllerTest.java @@ -168,28 +168,8 @@ void testGetPostPagination(int page, int size) throws Exception { fieldWithPath("data.content[].userResponse.name").description("게시자 이름"), fieldWithPath("data.content[].userResponse.age").description("게시자 나이"), fieldWithPath("data.content[].userResponse.hobby").description("게시자 취미"), - fieldWithPath("data.pageable").description("페이징 정보").optional(), - fieldWithPath("data.pageable.pageNumber").description("현재 페이지 번호"), - fieldWithPath("data.pageable.pageSize").description("페이지 크기"), - fieldWithPath("data.pageable.sort").description("정렬 정보").optional(), - fieldWithPath("data.pageable.sort.empty").description("정렬이 비어있는지 여부"), - fieldWithPath("data.pageable.sort.unsorted").description("정렬되지 않았는지 여부"), - fieldWithPath("data.pageable.sort.sorted").description("정렬되었는지 여부"), - fieldWithPath("data.pageable.offset").description("현재 페이지의 시작 오프셋"), - fieldWithPath("data.pageable.unpaged").description("페이징되지 않은지 여부"), - fieldWithPath("data.pageable.paged").description("페이징된지 여부"), - fieldWithPath("data.last").description("마지막 페이지 여부"), fieldWithPath("data.totalPages").description("전체 페이지 수"), - fieldWithPath("data.totalElements").description("전체 엘리먼트 수"), - fieldWithPath("data.first").description("첫 번째 페이지 여부"), - fieldWithPath("data.size").description("현재 페이지의 엘리먼트 수"), - fieldWithPath("data.number").description("현재 페이지 번호"), - fieldWithPath("data.sort").description("정렬 정보").optional(), - fieldWithPath("data.sort.empty").description("정렬이 비어있는지 여부"), - fieldWithPath("data.sort.unsorted").description("정렬되지 않았는지 여부"), - fieldWithPath("data.sort.sorted").description("정렬되었는지 여부"), - fieldWithPath("data.numberOfElements").description("현재 페이지의 엘리먼트 수"), - fieldWithPath("data.empty").description("데이터가 비어있는지 여부") + fieldWithPath("data.totalElements").description("전체 엘리먼트 수") )) ); } From 88c16dd4fe51266adabd47d1453ffbb08e9ef31c Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Wed, 6 Dec 2023 11:13:04 +0900 Subject: [PATCH 46/51] =?UTF-8?q?mod:=20Post=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20Slice=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=20=EB=8C=80=EC=8B=A0=20=ED=95=84=EC=9A=94=ED=95=9C=20=EA=B0=92?= =?UTF-8?q?=EB=A7=8C=20=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20Post=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20Response=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PostController.java | 8 ++------ .../dto/PostsCursorResponse.java | 19 +++++++++++++++++++ .../service/PostService.java | 14 ++++++-------- 3 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 src/main/java/jehs/springbootboardjpa/dto/PostsCursorResponse.java diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index c78031af5..247f57516 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -1,12 +1,8 @@ package jehs.springbootboardjpa.controller; -import jehs.springbootboardjpa.dto.PostCreateRequest; -import jehs.springbootboardjpa.dto.PostResponse; -import jehs.springbootboardjpa.dto.PostUpdateRequest; -import jehs.springbootboardjpa.dto.PostsResponse; +import jehs.springbootboardjpa.dto.*; import jehs.springbootboardjpa.service.PostService; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -40,7 +36,7 @@ public ResponseEntity> getAllPosts(@RequestParam(n } @GetMapping("/api/v1/posts/cursor") - public ResponseEntity>> getAllPostsByCursor(@RequestParam(name = "size") int size, @RequestParam(name = "cursorId") Long cursorId) { + public ResponseEntity> getAllPostsByCursor(@RequestParam(name = "size") int size, @RequestParam(name = "cursorId") Long cursorId) { return new ResponseEntity<>( new SuccessMessage<>( "성공적으로 모든 게시글이 조회되었습니다.", diff --git a/src/main/java/jehs/springbootboardjpa/dto/PostsCursorResponse.java b/src/main/java/jehs/springbootboardjpa/dto/PostsCursorResponse.java new file mode 100644 index 000000000..24be6c9f5 --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/dto/PostsCursorResponse.java @@ -0,0 +1,19 @@ +package jehs.springbootboardjpa.dto; + +import jehs.springbootboardjpa.entity.Post; +import lombok.Getter; +import org.springframework.data.domain.Slice; + +import java.util.List; + +@Getter +public class PostsCursorResponse { + + private final List content; + private final boolean hasNext; + + public PostsCursorResponse(Slice postSlice) { + this.content = postSlice.getContent().stream().map(PostResponse::new).toList(); + this.hasNext = postSlice.hasNext(); + } +} diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index 6bda2ac82..83724de74 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -1,16 +1,13 @@ package jehs.springbootboardjpa.service; -import jehs.springbootboardjpa.dto.PostCreateRequest; -import jehs.springbootboardjpa.dto.PostResponse; -import jehs.springbootboardjpa.dto.PostUpdateRequest; -import jehs.springbootboardjpa.dto.PostsResponse; +import jehs.springbootboardjpa.dto.*; import jehs.springbootboardjpa.entity.Post; import jehs.springbootboardjpa.entity.User; import jehs.springbootboardjpa.exception.PostErrorMessage; import jehs.springbootboardjpa.exception.PostException; import jehs.springbootboardjpa.repository.PostRepository; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.*; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -53,8 +50,9 @@ public PostsResponse getAllPostsWithUser(int page, int size) { } @Transactional(readOnly = true) - public Slice getAllPostsWithUserByCursor(int size, Long cursorId) { - return postRepository.findAllWithUserByCursor(cursorId, PageRequest.of(0, size)) - .map(PostResponse::new); + public PostsCursorResponse getAllPostsWithUserByCursor(int size, Long cursorId) { + return new PostsCursorResponse( + postRepository.findAllWithUserByCursor(cursorId, PageRequest.of(0, size)) + ); } } From c4ab0e06a521c99dd594f4410d5a4338ec2ad1f2 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Wed, 6 Dec 2023 11:29:25 +0900 Subject: [PATCH 47/51] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8F=AC=EB=A9=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../springbootboardjpa/dto/PostsResponse.java | 2 +- .../service/UserService.java | 2 +- src/main/resources/static/docs/index.html | 3528 ++++++++++++----- 3 files changed, 2631 insertions(+), 901 deletions(-) diff --git a/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java b/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java index e50ebb8ab..254c50c54 100644 --- a/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java +++ b/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java @@ -8,7 +8,7 @@ @Getter public class PostsResponse { - + private final List content; private final int totalPages; private final long totalElements; diff --git a/src/main/java/jehs/springbootboardjpa/service/UserService.java b/src/main/java/jehs/springbootboardjpa/service/UserService.java index ea60b4f23..85c45623e 100644 --- a/src/main/java/jehs/springbootboardjpa/service/UserService.java +++ b/src/main/java/jehs/springbootboardjpa/service/UserService.java @@ -13,7 +13,7 @@ public class UserService { private final UserRepository userRepository; - public User getUserById(Long userId){ + public User getUserById(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new UserException(UserErrorMessage.NOT_FOUND)); } diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index aa4056cd4..0ff16253b 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -1,476 +1,2177 @@ - - - - -REST Docs - - - + + + + + REST Docs + + +
-
-

특정 게시글 조회

-
-

http-request

-
-
-
+
+

특정 게시글 조회

+
+

http-request

+
+
+
GET /api/v1/posts/6237 HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Host: localhost:8080
-
-
-
-

http-response

-
-
-
+
+
+
+

http-response

+
+ -
-

모든 게시글 조회

-
-

http-request

-
-
-
+
+
+
+

response-fields

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

data.id

Number

게시글 아이디

data.title

String

게시글 제목

data.content

String

게시글 내용

data.postType

String

게시글 종류

data.createdAt

String

게시글 생성시간

data.updatedAt

String

게시글 수정시간

data.userResponse.id +

Number

게시자 아이디

data.userResponse.name +

String

게시자 이름

data.userResponse.age +

Number

게시자 나이

data.userResponse.hobby +

String

게시자 취미

+
+
+

모든 게시글 조회

+
+

http-request

+
+
+
GET /api/v1/posts?page=3&size=10 HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Host: localhost:8080
-
-
-
-

http-response

-
-
-
+
+
+
+

http-response

+
+
+
HTTP/1.1 200 OK
 Content-Type: application/json
 Content-Length: 4051
@@ -751,200 +2456,225 @@ 

-
-
-
-

response-fields

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription

message

String

성공 메시지

data.content[].id

Number

게시글 아이디

data.content[].title

String

게시글 제목

data.content[].content

String

게시글 내용

data.content[].postType

String

게시글 종류

data.content[].createdAt

String

게시글 생성시간

data.content[].updatedAt

String

게시글 수정시간

data.content[].userResponse.id

Number

게시자 아이디

data.content[].userResponse.name

String

게시자 이름

data.content[].userResponse.age

Number

게시자 나이

data.content[].userResponse.hobby

String

게시자 취미

data.pageable

Object

페이징 정보

data.pageable.pageNumber

Number

현재 페이지 번호

data.pageable.pageSize

Number

페이지 크기

data.pageable.sort

Object

정렬 정보

data.pageable.sort.empty

Boolean

정렬이 비어있는지 여부

data.pageable.sort.unsorted

Boolean

정렬되지 않았는지 여부

data.pageable.sort.sorted

Boolean

정렬되었는지 여부

data.pageable.offset

Number

현재 페이지의 시작 오프셋

data.pageable.unpaged

Boolean

페이징되지 않은지 여부

data.pageable.paged

Boolean

페이징된지 여부

data.last

Boolean

마지막 페이지 여부

data.totalPages

Number

전체 페이지 수

data.totalElements

Number

전체 엘리먼트 수

data.first

Boolean

첫 번째 페이지 여부

data.size

Number

현재 페이지의 엘리먼트 수

data.number

Number

현재 페이지 번호

data.sort

Object

정렬 정보

data.sort.empty

Boolean

정렬이 비어있는지 여부

data.sort.unsorted

Boolean

정렬되지 않았는지 여부

data.sort.sorted

Boolean

정렬되었는지 여부

data.numberOfElements

Number

현재 페이지의 엘리먼트 수

data.empty

Boolean

데이터가 비어있는지 여부

-
-
-

게시글 생성

-
-

http-request

-
-
-
+
+
+
+

response-fields

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

data.content[].id

+

Number

게시글 아이디

data.content[].title +

String

게시글 제목

data.content[].content +

String

게시글 내용

data.content[].postType +

String

게시글 종류

+ data.content[].createdAt

String

게시글 생성시간

+ data.content[].updatedAt

String

게시글 수정시간

+ data.content[].userResponse.id

Number

게시자 아이디

data.content[].userResponse.name +

String

게시자 이름

data.content[].userResponse.age +

Number

게시자 나이

data.content[].userResponse.hobby +

String

게시자 취미

data.pageable

Object

페이징 정보

+ data.pageable.pageNumber

Number

현재 페이지 번호

data.pageable.pageSize +

Number

페이지 크기

data.pageable.sort

+

Object

정렬 정보

+ data.pageable.sort.empty

Boolean

정렬이 비어있는지 여부

+ data.pageable.sort.unsorted

Boolean

정렬되지 않았는지 여부

+ data.pageable.sort.sorted

Boolean

정렬되었는지 여부

data.pageable.offset +

Number

현재 페이지의 시작 오프셋

data.pageable.unpaged +

Boolean

페이징되지 않은지 여부

data.pageable.paged

+

Boolean

페이징된지 여부

data.last

Boolean

마지막 페이지 여부

data.totalPages

+

Number

전체 페이지 수

data.totalElements

+

Number

전체 엘리먼트 수

data.first

Boolean

첫 번째 페이지 여부

data.size

Number

현재 페이지의 엘리먼트 수

data.number

Number

현재 페이지 번호

data.sort

Object

정렬 정보

data.sort.empty

+

Boolean

정렬이 비어있는지 여부

data.sort.unsorted

+

Boolean

정렬되지 않았는지 여부

data.sort.sorted

+

Boolean

정렬되었는지 여부

data.numberOfElements +

Number

현재 페이지의 엘리먼트 수

data.empty

Boolean

데이터가 비어있는지 여부

+
+ -
-

게시글 수정

-
-

http-request

-
-
-
+
+
+
+

response-fields

+
+ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

+
+ +
+
+
+

response-fields

+
+ + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

+
From 245af977b6809848f72cb540309d9116bc8f7cd4 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:22:56 +0900 Subject: [PATCH 48/51] =?UTF-8?q?mod:=20@EnableJpaAuditing=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- .../jehs/springbootboardjpa/SpringbootBoardJpaApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java b/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java index 19b0f0cee..eb9d36555 100644 --- a/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java +++ b/src/main/java/jehs/springbootboardjpa/SpringbootBoardJpaApplication.java @@ -5,7 +5,6 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication -@EnableJpaAuditing public class SpringbootBoardJpaApplication { public static void main(String[] args) { From 680e343ec698b2c34a59ffd395bd93b5a43007ce Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Tue, 12 Dec 2023 13:56:39 +0900 Subject: [PATCH 49/51] =?UTF-8?q?refactor:=20List=20=EC=A1=B0=ED=9A=8C=20R?= =?UTF-8?q?esponse=20DTO=20=EC=A0=9C=EB=84=A4=EB=A6=AD=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../controller/PostController.java | 3 +- .../{PostsResponse.java => ListResponse.java} | 10 +- .../service/PostService.java | 6 +- src/main/resources/static/docs/index.html | 3528 +++++------------ 4 files changed, 909 insertions(+), 2638 deletions(-) rename src/main/java/jehs/springbootboardjpa/dto/{PostsResponse.java => ListResponse.java} (56%) diff --git a/src/main/java/jehs/springbootboardjpa/controller/PostController.java b/src/main/java/jehs/springbootboardjpa/controller/PostController.java index 247f57516..43e45314b 100644 --- a/src/main/java/jehs/springbootboardjpa/controller/PostController.java +++ b/src/main/java/jehs/springbootboardjpa/controller/PostController.java @@ -1,6 +1,7 @@ package jehs.springbootboardjpa.controller; import jehs.springbootboardjpa.dto.*; +import jehs.springbootboardjpa.entity.Post; import jehs.springbootboardjpa.service.PostService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -25,7 +26,7 @@ public ResponseEntity> getPost(@PathVariable(name = } @GetMapping("/api/v1/posts") - public ResponseEntity> getAllPosts(@RequestParam(name = "page") int page, @RequestParam(name = "size") int size) { + public ResponseEntity>> getAllPosts(@RequestParam(name = "page") int page, @RequestParam(name = "size") int size) { return new ResponseEntity<>( new SuccessMessage<>( "성공적으로 모든 게시글이 조회되었습니다.", diff --git a/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java b/src/main/java/jehs/springbootboardjpa/dto/ListResponse.java similarity index 56% rename from src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java rename to src/main/java/jehs/springbootboardjpa/dto/ListResponse.java index 254c50c54..cd074af28 100644 --- a/src/main/java/jehs/springbootboardjpa/dto/PostsResponse.java +++ b/src/main/java/jehs/springbootboardjpa/dto/ListResponse.java @@ -1,20 +1,20 @@ package jehs.springbootboardjpa.dto; -import jehs.springbootboardjpa.entity.Post; import lombok.Getter; import org.springframework.data.domain.Page; import java.util.List; +import java.util.function.Function; @Getter -public class PostsResponse { +public class ListResponse { - private final List content; + private final List content; private final int totalPages; private final long totalElements; - public PostsResponse(Page postsPage) { - this.content = postsPage.getContent().stream().map(PostResponse::new).toList(); + public ListResponse(Page postsPage, Function function) { + this.content = postsPage.getContent().stream().map(function).toList(); this.totalPages = postsPage.getTotalPages(); this.totalElements = postsPage.getTotalElements(); } diff --git a/src/main/java/jehs/springbootboardjpa/service/PostService.java b/src/main/java/jehs/springbootboardjpa/service/PostService.java index 83724de74..d07fa8d38 100644 --- a/src/main/java/jehs/springbootboardjpa/service/PostService.java +++ b/src/main/java/jehs/springbootboardjpa/service/PostService.java @@ -43,9 +43,9 @@ public Post getPostByIdWithUser(Long postId) { } @Transactional(readOnly = true) - public PostsResponse getAllPostsWithUser(int page, int size) { - return new PostsResponse( - postRepository.findAllWithUser(PageRequest.of(page, size)) + public ListResponse getAllPostsWithUser(int page, int size) { + return new ListResponse<>( + postRepository.findAllWithUser(PageRequest.of(page, size)), PostResponse::new ); } diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html index 0ff16253b..aa4056cd4 100644 --- a/src/main/resources/static/docs/index.html +++ b/src/main/resources/static/docs/index.html @@ -1,2177 +1,476 @@ - - - - - REST Docs - - - + + + + +REST Docs + + +
-
-

특정 게시글 조회

-
-

http-request

-
-
-
+
+

특정 게시글 조회

+
+

http-request

+
+
+
GET /api/v1/posts/6237 HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Host: localhost:8080
-
-
-
-

http-response

-
-
-
+
+
+
+

http-response

+
+ -
-

모든 게시글 조회

-
-

http-request

-
-
-
+
+
+
+

response-fields

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

data.id

Number

게시글 아이디

data.title

String

게시글 제목

data.content

String

게시글 내용

data.postType

String

게시글 종류

data.createdAt

String

게시글 생성시간

data.updatedAt

String

게시글 수정시간

data.userResponse.id

Number

게시자 아이디

data.userResponse.name

String

게시자 이름

data.userResponse.age

Number

게시자 나이

data.userResponse.hobby

String

게시자 취미

+
+
+

모든 게시글 조회

+
+

http-request

+
+
+
GET /api/v1/posts?page=3&size=10 HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Host: localhost:8080
-
-
-
-

http-response

-
-
-
+
+
+
+

http-response

+
+
+
HTTP/1.1 200 OK
 Content-Type: application/json
 Content-Length: 4051
@@ -2456,225 +751,200 @@ 

-
-
-
-

response-fields

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription

message

String

성공 메시지

data.content[].id

-

Number

게시글 아이디

data.content[].title -

String

게시글 제목

data.content[].content -

String

게시글 내용

data.content[].postType -

String

게시글 종류

- data.content[].createdAt

String

게시글 생성시간

- data.content[].updatedAt

String

게시글 수정시간

- data.content[].userResponse.id

Number

게시자 아이디

data.content[].userResponse.name -

String

게시자 이름

data.content[].userResponse.age -

Number

게시자 나이

data.content[].userResponse.hobby -

String

게시자 취미

data.pageable

Object

페이징 정보

- data.pageable.pageNumber

Number

현재 페이지 번호

data.pageable.pageSize -

Number

페이지 크기

data.pageable.sort

-

Object

정렬 정보

- data.pageable.sort.empty

Boolean

정렬이 비어있는지 여부

- data.pageable.sort.unsorted

Boolean

정렬되지 않았는지 여부

- data.pageable.sort.sorted

Boolean

정렬되었는지 여부

data.pageable.offset -

Number

현재 페이지의 시작 오프셋

data.pageable.unpaged -

Boolean

페이징되지 않은지 여부

data.pageable.paged

-

Boolean

페이징된지 여부

data.last

Boolean

마지막 페이지 여부

data.totalPages

-

Number

전체 페이지 수

data.totalElements

-

Number

전체 엘리먼트 수

data.first

Boolean

첫 번째 페이지 여부

data.size

Number

현재 페이지의 엘리먼트 수

data.number

Number

현재 페이지 번호

data.sort

Object

정렬 정보

data.sort.empty

-

Boolean

정렬이 비어있는지 여부

data.sort.unsorted

-

Boolean

정렬되지 않았는지 여부

data.sort.sorted

-

Boolean

정렬되었는지 여부

data.numberOfElements -

Number

현재 페이지의 엘리먼트 수

data.empty

Boolean

데이터가 비어있는지 여부

-
-
-

게시글 생성

-
-

http-request

-
-
-
+
+
+
+

response-fields

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

data.content[].id

Number

게시글 아이디

data.content[].title

String

게시글 제목

data.content[].content

String

게시글 내용

data.content[].postType

String

게시글 종류

data.content[].createdAt

String

게시글 생성시간

data.content[].updatedAt

String

게시글 수정시간

data.content[].userResponse.id

Number

게시자 아이디

data.content[].userResponse.name

String

게시자 이름

data.content[].userResponse.age

Number

게시자 나이

data.content[].userResponse.hobby

String

게시자 취미

data.pageable

Object

페이징 정보

data.pageable.pageNumber

Number

현재 페이지 번호

data.pageable.pageSize

Number

페이지 크기

data.pageable.sort

Object

정렬 정보

data.pageable.sort.empty

Boolean

정렬이 비어있는지 여부

data.pageable.sort.unsorted

Boolean

정렬되지 않았는지 여부

data.pageable.sort.sorted

Boolean

정렬되었는지 여부

data.pageable.offset

Number

현재 페이지의 시작 오프셋

data.pageable.unpaged

Boolean

페이징되지 않은지 여부

data.pageable.paged

Boolean

페이징된지 여부

data.last

Boolean

마지막 페이지 여부

data.totalPages

Number

전체 페이지 수

data.totalElements

Number

전체 엘리먼트 수

data.first

Boolean

첫 번째 페이지 여부

data.size

Number

현재 페이지의 엘리먼트 수

data.number

Number

현재 페이지 번호

data.sort

Object

정렬 정보

data.sort.empty

Boolean

정렬이 비어있는지 여부

data.sort.unsorted

Boolean

정렬되지 않았는지 여부

data.sort.sorted

Boolean

정렬되었는지 여부

data.numberOfElements

Number

현재 페이지의 엘리먼트 수

data.empty

Boolean

데이터가 비어있는지 여부

+
+ -
-

게시글 수정

-
-

http-request

-
-
-
+
+
+
+

response-fields

+
+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

+
+ +
+
+
+

response-fields

+
+ +++++ + + + + + + + + + + + + + + +
PathTypeDescription

message

String

성공 메시지

+
From dfc083b93078bd87d12e34d7729390cfebcb5aa1 Mon Sep 17 00:00:00 2001 From: hyoguoo Date: Tue, 12 Dec 2023 14:17:38 +0900 Subject: [PATCH 50/51] =?UTF-8?q?feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20age=20=EA=B0=92=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JeongeunChoi <77786996+jeongeunchoi@users.noreply.github.com> --- .../springbootboardjpa/dto/UserResponse.java | 2 +- .../jehs/springbootboardjpa/entity/User.java | 5 ++-- .../springbootboardjpa/entity/vo/Age.java | 27 +++++++++++++++++++ .../exception/UserErrorMessage.java | 3 ++- 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/main/java/jehs/springbootboardjpa/entity/vo/Age.java diff --git a/src/main/java/jehs/springbootboardjpa/dto/UserResponse.java b/src/main/java/jehs/springbootboardjpa/dto/UserResponse.java index 9c60b1c38..cf96e2d6b 100644 --- a/src/main/java/jehs/springbootboardjpa/dto/UserResponse.java +++ b/src/main/java/jehs/springbootboardjpa/dto/UserResponse.java @@ -14,7 +14,7 @@ public class UserResponse { public UserResponse(User user) { this.id = user.getId(); this.name = user.getName(); - this.age = user.getAge(); + this.age = user.getAge().getValue(); this.hobby = user.getHobby(); } } diff --git a/src/main/java/jehs/springbootboardjpa/entity/User.java b/src/main/java/jehs/springbootboardjpa/entity/User.java index 0d59a4c99..5202c72b3 100644 --- a/src/main/java/jehs/springbootboardjpa/entity/User.java +++ b/src/main/java/jehs/springbootboardjpa/entity/User.java @@ -1,6 +1,7 @@ package jehs.springbootboardjpa.entity; import jakarta.persistence.*; +import jehs.springbootboardjpa.entity.vo.Age; import lombok.Getter; import java.util.List; @@ -17,8 +18,8 @@ public class User extends BaseEntity { @Column(name = "name", nullable = false, length = 59) private String name; - @Column(name = "age") - private Long age; + @Embedded + private Age age; @Column(name = "hobby", length = 59) private String hobby; diff --git a/src/main/java/jehs/springbootboardjpa/entity/vo/Age.java b/src/main/java/jehs/springbootboardjpa/entity/vo/Age.java new file mode 100644 index 000000000..c027f93fe --- /dev/null +++ b/src/main/java/jehs/springbootboardjpa/entity/vo/Age.java @@ -0,0 +1,27 @@ +package jehs.springbootboardjpa.entity.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jehs.springbootboardjpa.exception.UserErrorMessage; +import jehs.springbootboardjpa.exception.UserException; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Age { + + private static final Long MIN_AGE = 0L; + + @Column(name = "age", nullable = false) + private Long value; + + public Age(Long value) { + if (value < MIN_AGE) { + throw new UserException(UserErrorMessage.INVALID_AGE); + } + this.value = value; + } +} diff --git a/src/main/java/jehs/springbootboardjpa/exception/UserErrorMessage.java b/src/main/java/jehs/springbootboardjpa/exception/UserErrorMessage.java index 25dcee52a..4c9da4141 100644 --- a/src/main/java/jehs/springbootboardjpa/exception/UserErrorMessage.java +++ b/src/main/java/jehs/springbootboardjpa/exception/UserErrorMessage.java @@ -7,7 +7,8 @@ @Getter @RequiredArgsConstructor public enum UserErrorMessage { - NOT_FOUND("존재하지 않는 회원입니다.", HttpStatus.NOT_FOUND); + NOT_FOUND("존재하지 않는 회원입니다.", HttpStatus.NOT_FOUND), + INVALID_AGE("나이는 0보다 작을 수 없습니다.", HttpStatus.BAD_REQUEST); private final String message; private final HttpStatus httpStatus; From 6d76b2ff237e69f843f72e01ad76a263a1af8211 Mon Sep 17 00:00:00 2001 From: JeongeunChoi <77786996+JeongeunChoi@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:02:51 +0900 Subject: [PATCH 51/51] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Rest=20Docs=20=EA=B4=80=EB=A0=A8=20=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=EB=93=A4=20=EC=84=A4=EC=A0=95=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: hyoguoo <113650170+hyoguoo@users.noreply.github.com> --- build.gradle | 9 --------- 1 file changed, 9 deletions(-) diff --git a/build.gradle b/build.gradle index e7842c4e9..7bbed62e1 100644 --- a/build.gradle +++ b/build.gradle @@ -45,20 +45,11 @@ tasks.named('test') { useJUnitPlatform() } -tasks.named('asciidoctor') { - inputs.dir snippetsDir - dependsOn test -} - asciidoctor { dependsOn test inputs.dir snippetsDir } -asciidoctor.doFirst { - delete file('src/main/resources/static/docs') -} - bootJar { enabled = true dependsOn asciidoctor